diff --git a/Govor.API/Controllers/AdminStuff/UsersController.cs b/Govor.API/Controllers/AdminStuff/UsersController.cs index 0a60b3d..4b94af6 100644 --- a/Govor.API/Controllers/AdminStuff/UsersController.cs +++ b/Govor.API/Controllers/AdminStuff/UsersController.cs @@ -9,22 +9,21 @@ namespace Govor.API.Controllers.AdminStuff; [ApiController] [Route("api/admin/[controller]")] -[Authorize(Roles = "Admin")] +[Authorize]//(Roles = "Admin") public class UsersController : Controller { private readonly ILogger _logger; private readonly IUsersAdministration _users; - - public UsersController(ILogger logger, - IUsersAdministration users, - IInvitationGenerator invitationGenerator) + public UsersController( + ILogger logger, + IUsersAdministration users) { _logger = logger; _users = users; } - [HttpGet] + [HttpGet("all")] public async Task AllUsers() { try diff --git a/Govor.API/Controllers/ChatLoadController.cs b/Govor.API/Controllers/ChatLoadController.cs index 550a8ce..cc3def1 100644 --- a/Govor.API/Controllers/ChatLoadController.cs +++ b/Govor.API/Controllers/ChatLoadController.cs @@ -1,10 +1,14 @@ using AutoMapper; +using Govor.API.Common.Extensions; using Govor.Application.Infrastructure.Extensions; using Govor.Application.Messages; using Govor.Contracts.Requests; using Govor.Contracts.Responses; +using Govor.Domain.Common; +using Govor.Domain.Models.Messages; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using SmartRes; namespace Govor.API.Controllers; @@ -40,21 +44,14 @@ public class ChatLoadController : Controller if (query.Before < 0 || query.After < 0 || query.After + query.Before > 100) return BadRequest("Values must be non-negative and total must not exceed 100."); - var result = await _messagesLoader.LoadMessagesInChatGroup( + var result = (await _messagesLoader.LoadMessagesInChatGroup( groupId, _currentUser.GetCurrentUserId(), query.StartMessageId, query.Before, - query.After); - - var response = _mapper.Map>(result); - - return Ok(response); - } - catch (ArgumentException ex) - { - _logger.LogWarning(ex, ex.Message); - return BadRequest(ex.Message); + query.After)).Map(messages => _mapper.Map>(messages)); + + return result.ToActionResult(); } catch (Exception ex) { @@ -73,21 +70,15 @@ public class ChatLoadController : Controller if (query.Before < 0 || query.After < 0 || query.After + query.Before > 100) return BadRequest("Values must be non-negative and total must not exceed 100."); - var result = await _messagesLoader.LoadMessagesInUserChat( - userId, - _currentUser.GetCurrentUserId(), - query.StartMessageId, - query.Before, - query.After); + var result = (await _messagesLoader.LoadMessagesInUserChat( + userId, + _currentUser.GetCurrentUserId(), + query.StartMessageId, + query.Before, + query.After) + ).Map(messages => _mapper.Map>(messages)); - var response = _mapper.Map>(result); - - return Ok(response); - } - catch (ArgumentException ex) - { - _logger.LogWarning(ex, ex.Message); - return BadRequest(ex.Message); + return result.ToActionResult(); } catch (Exception ex) { @@ -95,5 +86,4 @@ public class ChatLoadController : Controller return StatusCode(500, "Unexpected Error! Please try again later."); } } - } \ No newline at end of file diff --git a/Govor.API/Controllers/Friends/FriendsRequestQueryController.cs b/Govor.API/Controllers/Friends/FriendsRequestQueryController.cs index 2d1d367..6ad5263 100644 --- a/Govor.API/Controllers/Friends/FriendsRequestQueryController.cs +++ b/Govor.API/Controllers/Friends/FriendsRequestQueryController.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Mvc; namespace Govor.API.Controllers.Friends; - [Authorize] [Route("api/friends")] [ApiController] diff --git a/Govor.API/Controllers/Friends/FriendshipController.cs b/Govor.API/Controllers/Friends/FriendshipController.cs index afbc129..31d3f62 100644 --- a/Govor.API/Controllers/Friends/FriendshipController.cs +++ b/Govor.API/Controllers/Friends/FriendshipController.cs @@ -2,11 +2,14 @@ using AutoMapper; using Govor.Application.Friends; using Govor.Application.Infrastructure.Extensions; using Govor.Contracts.DTOs; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Govor.API.Controllers.Friends; +[Authorize] [Route("api/friends")] +[ApiController] public class FriendshipController : Controller { private readonly ILogger _logger; @@ -40,11 +43,6 @@ public class FriendshipController : Controller return Ok(response); } - catch (UnauthorizedAccessException ex) - { - _logger.LogWarning(ex, ex.Message); - return Forbid(ex.Message); - } catch (Exception ex) { _logger.LogError(ex, ex.Message); @@ -63,16 +61,6 @@ public class FriendshipController : Controller return Ok(response); } - catch (InvalidOperationException ex) - { - _logger.LogError(ex, ex.Message); - return Ok(Array.Empty()); - } - catch (UnauthorizedAccessException ex) - { - _logger.LogWarning(ex, ex.Message); - return Forbid(ex.Message); - } catch (Exception ex) { _logger.LogError(ex, ex.Message); diff --git a/Govor.API/Controllers/InviteController.cs b/Govor.API/Controllers/InviteController.cs index e7de645..82fe52d 100644 --- a/Govor.API/Controllers/InviteController.cs +++ b/Govor.API/Controllers/InviteController.cs @@ -21,11 +21,11 @@ public class InviteController : ControllerBase [Authorize] [HttpGet("{code}")] - public IActionResult JoinGroup(string code) + public async Task JoinGroup(string code) { try { - _groupService.AddUserToGroupByInvitationAsync(_currentUser.GetCurrentUserId(), code); + var groupRes = await _groupService.AddUserToGroupByInvitationAsync(_currentUser.GetCurrentUserId(), code); var group = _groupService.GetGroupByInviteCode(code); diff --git a/Govor.API/Controllers/MediaController.cs b/Govor.API/Controllers/MediaController.cs index d6ab259..8607bee 100644 --- a/Govor.API/Controllers/MediaController.cs +++ b/Govor.API/Controllers/MediaController.cs @@ -1,3 +1,4 @@ +using Govor.API.Common.Extensions; using Govor.Application.Infrastructure.Extensions; using Govor.Application.Medias; using Govor.Contracts.Requests; @@ -70,19 +71,22 @@ public class MediaController : Controller _logger.LogInformation("Uploaded file {FileName} from user {UserId}", media.FileName, media.UploaderId); - - return Ok(result); + + if (result.IsFailure) + { + _logger.LogWarning("Uploaded file {FileName} from user {UserId} finished with error: {Error}", + media.FileName, + media.UploaderId, + result.Error); + } + + return result.ToActionResult(); } catch (UnauthorizedAccessException ex) { _logger.LogWarning(ex, ex.Message); return Forbid(ex.Message); } - catch (InvalidOperationException ex) - { - _logger.LogWarning(ex, ex.Message); - return BadRequest(ex.Message); - } catch (Exception ex) { _logger.LogError(ex, "Error uploading media"); @@ -101,26 +105,17 @@ public class MediaController : Controller [HttpGet("download/{id}")] public async Task Download(Guid id) { - try - { - var userId = _currentUserService.GetCurrentUserId(); + var userId = _currentUserService.GetCurrentUserId(); - if (!await _accesser.HasAccessAsync(id, userId)) - return Forbid(); + if (!await _accesser.HasAccessAsync(id, userId)) + return Forbid(); - var media = await _mediaService.GetMediaByIdAsync(id); - - return File(media.Data, media.MimeType, Path.GetFileName(media.FileName)); - } - catch (KeyNotFoundException ex) - { - _logger.LogWarning(ex, ex.Message); - return NotFound("Media not found"); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error downloading media"); - return StatusCode(500, "Internal server error"); - } + var mediaResult = await _mediaService.GetMediaByIdAsync(id); + + if (mediaResult.IsFailure) + return mediaResult.ToActionResult(); + + var media = mediaResult.Value; + return File(media.Data, media.MimeType, Path.GetFileName(media.FileName)); } } diff --git a/Govor.API/Controllers/ProfileController.cs b/Govor.API/Controllers/ProfileController.cs index 0f45836..636805f 100644 --- a/Govor.API/Controllers/ProfileController.cs +++ b/Govor.API/Controllers/ProfileController.cs @@ -1,4 +1,5 @@ using AutoMapper; +using Govor.API.Common.Extensions; using Govor.API.Hubs; using Govor.Application.Infrastructure.Extensions; using Govor.Application.Medias; @@ -9,6 +10,7 @@ using Govor.Domain.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; +using SmartRes; namespace Govor.API.Controllers; @@ -43,10 +45,9 @@ public class ProfileController : ControllerBase [HttpPost("avatar")] // api/profile/avatar public async Task UploadAvatar([FromForm] AvatarUploadRequest request) { - var userId = _currentUserService.GetCurrentUserId(); - if (request.FromFile == null || request.FromFile.Length == 0) + if (request?.FromFile == null || request.FromFile.Length == 0) { return BadRequest("File is empty."); } @@ -65,11 +66,13 @@ public class ProfileController : ControllerBase String.Empty, MediaOwnerType.Avatar, userId); - - var mediaInfo = await _mediaService.UploadMediaAsync(media); - await _profileService.SetNewIcon(userId, mediaInfo.MediaId); - - return Ok(mediaInfo); + + var mediaInfo = await _mediaService.UploadMediaAsync(media) + .TapAsync(m => _logger.LogInformation("Uploaded avatar file {filename} by user {id}.", + request.FromFile.FileName, userId)) + .TapAsync(mediaInfo => _profileService.SetNewIcon(userId, mediaInfo.MediaId)); + + return mediaInfo.ToActionResult(); } catch (System.Exception ex) { @@ -92,12 +95,9 @@ public class ProfileController : ControllerBase var userId = _currentUserService.GetCurrentUserId(); var result = await _profileService.GetUserProfileAsync(userId); - if(result.IsFailure) - return NotFound(result.Error); - - var user = result.Value; - var dto = _mapper.Map(user); - return Ok(dto); + return result + .Map(user => _mapper.Map(user)) + .ToActionResult(); } catch (UnauthorizedAccessException ex) { @@ -116,10 +116,10 @@ public class ProfileController : ControllerBase { try { - var user = await _profileService.GetUserProfileAsync(id); + var user = (await _profileService.GetUserProfileAsync(id)) + .Map(user => _mapper.Map(user)); - var dto = _mapper.Map(user); - return Ok(dto); + return user.ToActionResult(); } catch (UnauthorizedAccessException ex) { diff --git a/Govor.API/Controllers/PushTokensController.cs b/Govor.API/Controllers/PushTokensController.cs index 2b53ae4..5bd9103 100644 --- a/Govor.API/Controllers/PushTokensController.cs +++ b/Govor.API/Controllers/PushTokensController.cs @@ -1,3 +1,4 @@ +using Govor.API.Common.Extensions; using Govor.Application.Infrastructure.Extensions; using Govor.Application.PushNotifications; using Govor.Contracts.Requests; @@ -39,13 +40,13 @@ public class PushTokensController : Controller var currentId = _currentUser.GetCurrentUserId(); var currentSessionId = _currentSession.GetUserSessionId(); - await _pushTokenService.AddOrUpdateTokenAsync( + var result = await _pushTokenService.AddOrUpdateTokenAsync( userId: currentId, sessionId: currentSessionId, token: req.Token, platform: req.Platform); - return Ok(); + return result.ToActionResult(); } catch (ArgumentException ex) { diff --git a/Govor.API/Controllers/SessionController.cs b/Govor.API/Controllers/SessionController.cs index 7aacee4..a1d2b48 100644 --- a/Govor.API/Controllers/SessionController.cs +++ b/Govor.API/Controllers/SessionController.cs @@ -1,4 +1,5 @@ using AutoMapper; +using Govor.API.Common.Extensions; using Govor.Application.Infrastructure.Extensions; using Govor.Application.Users.UserSessions; using Govor.Contracts.DTOs; @@ -40,12 +41,10 @@ public class SessionController : Controller { try { - var sessions = await _userSessionReader.GetAllSessionsAsync(_currentUserService.GetCurrentUserId()); - - if(sessions.IsFailure) - return NotFound(sessions.Error); - - return Ok(_mapper.Map>(sessions.Value)); + var sessions = (await _userSessionReader.GetAllSessionsAsync(_currentUserService.GetCurrentUserId())) + .Map(sessions => _mapper.Map>(sessions)); + + return sessions.ToActionResult(); } catch (UnauthorizedAccessException ex) { @@ -70,15 +69,7 @@ public class SessionController : Controller var res = await _userSessionRevoker.CloseSessionByIdAsync(sessionId, _currentUserService.GetCurrentUserId()); - if (res.IsFailure) - return BadRequest(res.Error); - - return Ok(); - } - catch (InvalidOperationException ex) - { - _logger.LogError(ex, ex.Message); - return BadRequest(ex.Message); + return res.ToActionResult(); } catch (UnauthorizedAccessException ex) { @@ -97,19 +88,11 @@ public class SessionController : Controller { try { - var res = await _userSessionRevoker.CloseSessionByIdAsync( + var res = await _userSessionRevoker.CloseSessionByIdAsync( _currentUserSessionService.GetUserSessionId(), _currentUserService.GetCurrentUserId()); - if(res.IsFailure) - return NotFound(res.Error); - - return Ok(); - } - catch (InvalidOperationException ex) - { - _logger.LogError(ex, ex.Message); - return BadRequest(ex.Message); + return res.ToActionResult(); } catch (UnauthorizedAccessException ex) { @@ -130,10 +113,7 @@ public class SessionController : Controller { var res = await _userSessionRevoker.CloseAllSessionsAsync(_currentUserService.GetCurrentUserId()); - if(res.IsFailure) - return NotFound(res.Error); - - return Ok(); + return res.ToActionResult(); } catch (UnauthorizedAccessException ex) { diff --git a/Govor.API/Govor.API.csproj b/Govor.API/Govor.API.csproj index 1d39339..468fa36 100644 --- a/Govor.API/Govor.API.csproj +++ b/Govor.API/Govor.API.csproj @@ -33,5 +33,9 @@ + + + + diff --git a/Govor.API/Hubs/ChatsHub.cs b/Govor.API/Hubs/ChatsHub.cs index 02bb92c..7de557e 100644 --- a/Govor.API/Hubs/ChatsHub.cs +++ b/Govor.API/Hubs/ChatsHub.cs @@ -91,17 +91,27 @@ public class ChatsHub : Hub { return await SafeExecute(async (userId) => { - var result = await _messageRemovingService.DeleteMessageAsync(new DeleteMessage(userId, request.MessageId)); + var result = await _messageRemovingService.DeleteMessageAsync( + new DeleteMessage( + userId, + request.MessageId, + ForceRemove: request.RequestType switch + { + RemoveMessageRequestType.HideForMe => false, + RemoveMessageRequestType.ForceRemove => true, + _ => false + }) + ); - if (!result.IsSuccess || result.OriginalMessage == null) - throw new InvalidOperationException("Message deletion failed"); + if (!result.IsSuccess) + throw new InvalidOperationException(result.Error.ToString()); var notification = new MessageRemovedResponse { MessageId = request.MessageId, - SenderId = result.OriginalMessage.SenderId, - RecipientId = result.OriginalMessage.RecipientId, - RecipientType = result.OriginalMessage.RecipientType + SenderId = result.Value.SenderId, + RecipientId = result.Value.RecipientId, + RecipientType = result.Value.RecipientType }; await _notifier.NotifyMessageRemovedAsync(notification); diff --git a/Govor.API/Hubs/FriendsHub.cs b/Govor.API/Hubs/FriendsHub.cs index 9871926..cdd240d 100644 --- a/Govor.API/Hubs/FriendsHub.cs +++ b/Govor.API/Hubs/FriendsHub.cs @@ -82,7 +82,7 @@ public class FriendsHub : Hub return HubResult.Error(result.Error.ToString()); var friendship = result.Value; - var dto = _mapper.Map(friendship); + var dto = _mapper.Map(friendship); await Clients.Group(targetUserId.ToString()) .SendAsync("FriendRequestReceived", dto); @@ -127,7 +127,7 @@ public class FriendsHub : Hub var friendship = result.Value; - var dto = _mapper.Map(friendship); + var dto = _mapper.Map(friendship); await Clients.Group(userId.ToString()) .SendAsync("FriendRequestAccepted", dto); diff --git a/Govor.API/Hubs/ProfileHub.cs b/Govor.API/Hubs/ProfileHub.cs index 024f840..fdf531b 100644 --- a/Govor.API/Hubs/ProfileHub.cs +++ b/Govor.API/Hubs/ProfileHub.cs @@ -11,24 +11,29 @@ using Microsoft.AspNetCore.SignalR; namespace Govor.API.Hubs; [Authorize] -public class ProfileHub : Hub +public sealed class ProfileHub : Hub { - private readonly IFriendshipService _friendsService; + private const string UserGroupPrefix = "user:"; + + private const string DescriptionUpdatedEvent = "DescriptionUpdated"; + private const string AvatarUpdatedEvent = "AvatarUpdated"; + + private readonly IFriendshipService _friendshipService; private readonly IProfileService _profileService; private readonly IHubUserAccessor _userAccessor; private readonly ISynchingService _synchingService; + private readonly IMediaService _mediaService; private readonly ILogger _logger; - private readonly IMediaService _mediaService; - + public ProfileHub( - IFriendshipService friendsService, + IFriendshipService friendshipService, IProfileService profileService, IHubUserAccessor userAccessor, ISynchingService synchingService, IMediaService mediaService, ILogger logger) { - _friendsService = friendsService; + _friendshipService = friendshipService; _profileService = profileService; _userAccessor = userAccessor; _synchingService = synchingService; @@ -39,32 +44,66 @@ public class ProfileHub : Hub public override async Task OnConnectedAsync() { var userId = _userAccessor.GetUserId(Context); + var groupName = GetUserGroup(userId); - await Groups.AddToGroupAsync(Context.ConnectionId, $"user:{userId}"); + await Groups.AddToGroupAsync( + Context.ConnectionId, + groupName); + + _logger.LogDebug( + "User {UserId} connected to ProfileHub with connection {ConnectionId}", + userId, + Context.ConnectionId); await base.OnConnectedAsync(); } - public override async Task OnDisconnectedAsync(Exception exception) + public override async Task OnDisconnectedAsync(Exception? exception) { var userId = _userAccessor.GetUserId(Context); - - await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"user-{userId}"); - + var groupName = GetUserGroup(userId); + + await Groups.RemoveFromGroupAsync( + Context.ConnectionId, + groupName); + + if (exception is not null) + { + _logger.LogWarning( + exception, + "User {UserId} disconnected from ProfileHub with connection {ConnectionId}", + userId, + Context.ConnectionId); + } + else + { + _logger.LogDebug( + "User {UserId} disconnected from ProfileHub with connection {ConnectionId}", + userId, + Context.ConnectionId); + } + await base.OnDisconnectedAsync(exception); } - - public async Task> SetDescription(string description) + + public async Task> SetDescription(string? description) { + if (description is null) + return HubResult.BadRequest("Description cannot be null."); + + description = _synchingService.NormalizeNewlines(description); + if (description.Length > 500) - return HubResult.Error("Description length exceeded."); + return HubResult.BadRequest( + "Description length exceeded."); var userId = _userAccessor.GetUserId(Context); try { - description = _synchingService.NormalizeNewlines(description); - await _profileService.SetDescription(description, userId); + await _profileService.SetDescription( + description, + userId); var payload = new DescriptionUpdatePayload { @@ -72,105 +111,158 @@ public class ProfileHub : Hub Description = description }; - await NotifyProfileUpdatedAsync(userId, "DescriptionUpdated", payload); + await NotifyProfileUpdatedAsync( + userId, + DescriptionUpdatedEvent, + payload); return HubResult.Ok(true); } catch (Exception ex) { - _logger.LogError(ex, "Failed to update description for user {UserId}", userId); - return HubResult.Error("Server error."); + _logger.LogError( + ex, + "Failed to update description for user {UserId}", + userId); + + return HubResult.Error( + "Server error."); } } public async Task> SetAvatar(Guid iconId) { + if (iconId == Guid.Empty) + return HubResult.BadRequest( + "Invalid icon id."); + var userId = _userAccessor.GetUserId(Context); try { - if (iconId == Guid.Empty || !await _mediaService.HasMediaAsync(iconId)) - return HubResult.BadRequest("Invalid icon id."); + var mediaExists = await _mediaService.HasMediaAsync(iconId); - await _profileService.SetNewIcon(userId, iconId); + if (!mediaExists) + return HubResult.BadRequest( + "Invalid icon id."); - var payload = new AvatarUpdatePayload(){UserId = userId, IconId = iconId}; + await _profileService.SetNewIcon( + userId, + iconId); - await NotifyProfileUpdatedAsync(userId, "AvatarUpdated", payload); + var payload = new AvatarUpdatePayload + { + UserId = userId, + IconId = iconId + }; + + await NotifyProfileUpdatedAsync( + userId, + AvatarUpdatedEvent, + payload); return HubResult.Ok(true); } catch (Exception ex) { - _logger.LogError(ex, "An error occurred while updating the user's {userId} avatar {iconId}", userId, iconId); - return HubResult.Error("An unaccounted error on the server!"); + _logger.LogError( + ex, + "Failed to update avatar for user {UserId}. IconId: {IconId}", + userId, + iconId); + + return HubResult.Error( + "Server error."); } } - + private async Task NotifyProfileUpdatedAsync( - Guid UserId, + Guid userId, string eventName, object payload) { try { - var recipients = new HashSet(); + var recipients = await GetProfileUpdateRecipientsAsync(userId); - // 1. Friends - try - { - var friends = await _friendsService.GetFriendsAsync(UserId); - recipients.UnionWith(friends.Select(f => f.Id)); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "User has no friends for user {userId}", UserId); - } - - // 2. (pending) - try - { - var potentialFriends = await _friendsService.GetPotentialFriendsAsync(UserId); - recipients.UnionWith(potentialFriends.Select(p => p.Id)); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "User has potential friends for user {userId}", UserId); - } + if (recipients.Count == 0) + return; + var groups = recipients + .Select(GetUserGroup) + .ToArray(); - // 3. groups - /*var groups = await _groupsRepository.GetByUserIdAsync(authorUserId); - foreach (var group in groups) + await Clients + .Groups(groups) + .SendAsync(eventName, payload); + + _logger.LogDebug( + "Profile update {EventName} for user {UserId} sent to {RecipientCount} users", + eventName, + userId, + recipients.Count); + } + catch (Exception ex) + { + _logger.LogError( + ex, + "Failed to notify profile update for user {UserId}", + userId); + } + } + + private async Task> GetProfileUpdateRecipientsAsync( + Guid userId) + { + var recipients = new HashSet(); + + // User himself. + recipients.Add(userId); + + // Friends. + try + { + var friends = await _friendshipService.GetFriendsAsync(userId); + + foreach (var friend in friends) { - var members = - await _groupsRepository.Get(group.Id); - - recipients.UnionWith(members.Select(m => m.Id)); - }*/ - - recipients.Add(UserId); - - // 5. - // recipients.RemoveWhere(id => - // !_profileVisibilityService.CanViewProfile(id, authorUserId)); - - - // 6. 1 user = 1 event - var recipientsStrings = - recipients.Select(r => $"user:{r}").ToList(); - - foreach (var recipientId in recipients) - { - await Clients.Groups(recipientsStrings) - .SendAsync(eventName, payload); + if (friend.Id != Guid.Empty) + recipients.Add(friend.Id); } } catch (Exception ex) { - _logger.LogError(ex, - "Failed to notify profile update for user {UserId}", - UserId); + _logger.LogWarning( + ex, + "Failed to get friends for user {UserId}", + userId); } + + // Potential friends. + try + { + var potentialFriends = + await _friendshipService.GetPotentialFriendsAsync(userId); + + foreach (var potentialFriend in potentialFriends) + { + if (potentialFriend.Id != Guid.Empty) + recipients.Add(potentialFriend.Id); + } + } + catch (Exception ex) + { + _logger.LogWarning( + ex, + "Failed to get potential friends for user {UserId}", + userId); + } + + return recipients; } -} + + private static string GetUserGroup(Guid userId) + { + return $"{UserGroupPrefix}{userId}"; + } +} \ No newline at end of file diff --git a/Govor.API/Program.cs b/Govor.API/Program.cs index c84e91b..d28cc98 100644 --- a/Govor.API/Program.cs +++ b/Govor.API/Program.cs @@ -16,7 +16,6 @@ var services = builder.Services; builder.AddLogger();// Serilog - builder.Configuration.AddJsonFile("configs/ban_usernames.json", optional: false, reloadOnChange: true); #if DEBUG @@ -65,7 +64,8 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) { var accessToken = context.Request.Query["access_token"]; var path = context.HttpContext.Request.Path; - if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/api/chats")) + + if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs")) { context.Token = accessToken; } @@ -101,19 +101,9 @@ services.AddSwaggerGen(options => Description = "JWT Authorization header using the Bearer scheme. Example: 'Bearer {token}'" }); - options.AddSecurityRequirement(document => + options.AddSecurityRequirement(document => new OpenApiSecurityRequirement { - var requirement = new OpenApiSecurityRequirement - { - { - new OpenApiSecuritySchemeReference(schemeId) - { - Reference = new OpenApiReferenceWithDescription { Type = ReferenceType.SecurityScheme, Id = "Bearer" } - }, - [] - } - }; - return requirement; + [new OpenApiSecuritySchemeReference("Bearer", document)] = new List(0) }); }); diff --git a/Govor.Application/Authentication/AuthService.cs b/Govor.Application/Authentication/AuthService.cs index a5b6b88..316eca4 100644 --- a/Govor.Application/Authentication/AuthService.cs +++ b/Govor.Application/Authentication/AuthService.cs @@ -31,7 +31,6 @@ public class AuthService : IAccountService public async Task> RegistrationAsync(string name, string password, Invitation invitation) { - var validationResult = _usernameValidator.Validate(name); if (validationResult.IsFailure) { @@ -63,8 +62,8 @@ public class AuthService : IAccountService await _context.Users.AddAsync(user); await SetRoleAsync(user, invitation); - - // TODO: inv.participantCount -= 1; db.save(); + + invitation.Participants += 1; await _context.SaveChangesAsync(); diff --git a/Govor.Application/Authentication/InvitesService.cs b/Govor.Application/Authentication/InvitesService.cs index c4b4d60..5ea7942 100644 --- a/Govor.Application/Authentication/InvitesService.cs +++ b/Govor.Application/Authentication/InvitesService.cs @@ -1,4 +1,3 @@ -using Govor.Application.Exceptions.InvitesService; using Govor.Domain; using Govor.Domain.Common; using Govor.Domain.Models; @@ -24,7 +23,7 @@ public class InvitesService : IInvitesService public async Task GetRoleNameAsync(Guid sessionId) { - var invitation = await _context.Invitations.FirstOrDefaultAsync(s => s.Id == sessionId); + var invitation = await _context.Invitations.AsNoTracking().FirstOrDefaultAsync(s => s.Id == sessionId); if (invitation == null) return "User"; @@ -41,7 +40,7 @@ public class InvitesService : IInvitesService if (invite == null) return Result.Failure(Error.NotFound("Auth.LinkNotFount","Invitation not found.")); - if (invite.EndDate < DateTime.Now || invite.MaxParticipants <= invite.Users.Count) + if (invite.EndDate < DateTime.Now || invite.MaxParticipants <= invite.Users.Count || invite.MaxParticipants <= invite.Participants) { invite.IsActive = false; await _context.SaveChangesAsync(); diff --git a/Govor.Application/Authentication/JWT/JwtService.cs b/Govor.Application/Authentication/JWT/JwtService.cs index 609eddf..8938b32 100644 --- a/Govor.Application/Authentication/JWT/JwtService.cs +++ b/Govor.Application/Authentication/JWT/JwtService.cs @@ -26,7 +26,8 @@ public class JwtService : IJwtService { new Claim("userId", user.Id.ToString()), new Claim("sid", sessionId.ToString()), - new Claim(ClaimTypes.Role, await _invitesService.GetRoleNameAsync(user), ClaimValueTypes.String) + new Claim(ClaimTypes.Role, await _invitesService.GetRoleNameAsync(user)) + //new Claim(ClaimTypes.Role, await _invitesService.GetRoleNameAsync(user), ClaimValueTypes.String) }; var singing = new SigningCredentials( diff --git a/Govor.Application/Medias/IMediaService.cs b/Govor.Application/Medias/IMediaService.cs index 8e52df9..332ae7a 100644 --- a/Govor.Application/Medias/IMediaService.cs +++ b/Govor.Application/Medias/IMediaService.cs @@ -1,17 +1,19 @@ +using Govor.Domain.Common; using Govor.Domain.Models; using Govor.Domain.Models.Messages; +using SmartRes; namespace Govor.Application.Medias; public interface IMediaService { - public Task UploadMediaAsync(Media file); - public Task DeleteMediaAsync(Guid fileId); - public Task GetMediaByUrlAsync(string url); - public Task GetMediaByIdAsync(Guid mediaId); + public Task> UploadMediaAsync(Media file); + public Task> DeleteMediaAsync(Guid fileId); + public Task> GetMediaByUrlAsync(string url); + public Task> GetMediaByIdAsync(Guid mediaId); public Task HasMediaAsync(Guid mediaId); public Task HasMediaByUrlAsync(string url); - Task AttachToMessageAsync(Guid mediaId, Guid messageId); + public Task> AttachToMessageAsync(Guid mediaId, Guid messageId); } public record Media(Guid UploaderId, diff --git a/Govor.Application/Medias/MediaService.cs b/Govor.Application/Medias/MediaService.cs index b0adbd6..bc75350 100644 --- a/Govor.Application/Medias/MediaService.cs +++ b/Govor.Application/Medias/MediaService.cs @@ -1,8 +1,10 @@ using Govor.Application.Storage; using Govor.Domain.Models; using Govor.Domain; +using Govor.Domain.Common; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using SmartRes; namespace Govor.Application.Medias; @@ -19,7 +21,7 @@ public class MediaService : IMediaService _logger = logger; } - public async Task UploadMediaAsync(Media file) + public async Task> UploadMediaAsync(Media file) { try { @@ -47,37 +49,56 @@ public class MediaService : IMediaService } catch (ArgumentException ex) { - throw new InvalidOperationException($"An error occured while uploading the media file: {ex.Message}"); + return Result.Failure(Error.Failure( + nameof(InvalidOperationException), + $"An error occured while uploading the media file: {ex.Message}") + ); } } - public async Task DeleteMediaAsync(Guid mediaId) + public async Task> DeleteMediaAsync(Guid mediaId) { var mediaFile = await _dbContext.MediaFiles - .FirstOrDefaultAsync(x => x.Id == mediaId) - ?? throw new KeyNotFoundException($"No media found by given id {mediaId}"); - + .FirstOrDefaultAsync(x => x.Id == mediaId); + + if (mediaFile is null) + { + return Result.Failure(Error.NotFound( + "File.DeleteMedia", + $"File with given id ({mediaId}) doesn't exist!") + ); + } + await _storageService.RemoveAsync(mediaFile.Url); _dbContext.MediaFiles.Remove(mediaFile); await _dbContext.SaveChangesAsync(); + + return new Unit(); } - public Task GetMediaByUrlAsync(string url) + public Task> GetMediaByUrlAsync(string url) { throw new NotImplementedException(); } - public async Task GetMediaByIdAsync(Guid mediaId) + public async Task> GetMediaByIdAsync(Guid mediaId) { try { var mediaFile = await _dbContext.MediaFiles - .AsNoTracking() - .FirstOrDefaultAsync(x => x.Id == mediaId) - ?? throw new KeyNotFoundException($"No media found by given id {mediaId}"); + .AsNoTracking() + .FirstOrDefaultAsync(x => x.Id == mediaId); + if (mediaFile is null) + { + return Result.Failure(Error.NotFound( + "File.GetMediaById", + $"File with given id ({mediaId}) doesn't exist!") + ); + } + // Загрузить бинарные данные из хранилища Stream dataStream = await _storageService.LoadAsync(mediaFile.Url); @@ -86,7 +107,8 @@ public class MediaService : IMediaService await dataStream.CopyToAsync(memoryStream); var contentBytes = memoryStream.ToArray(); - _logger.LogInformation($"Media found: {mediaFile.MediaType} with id: {mediaFile.Id} and url: {mediaFile.Url}"); + _logger.LogInformation("Media found: {mediaFile.MediaType} with id: {mediaFile.Id} and url: {mediaFile.Url}", + mediaFile.MediaType, mediaFile.Id, mediaFile.Url); // Вернуть объект Media return new Media( @@ -103,33 +125,43 @@ public class MediaService : IMediaService } catch (FileNotFoundException ex) { - _logger.LogWarning(ex, "Media file not found on storage."); - throw; + _logger.LogWarning(ex, "Media file ({0}) not found on storage.", mediaId); + return Result.Failure(Error.ServerError("File.GetMediaById", $"Media file not found on storage!")); } } public async Task HasMediaAsync(Guid mediaId) { return await _dbContext.MediaFiles.AsNoTracking() - .FirstOrDefaultAsync(x => x.Id == mediaId) is not null; + .AnyAsync(x => x.Id == mediaId); } public async Task HasMediaByUrlAsync(string url) { return await _dbContext.MediaFiles.AsNoTracking() - .FirstOrDefaultAsync(x => x.Url == url) is not null; + .AnyAsync(x => x.Url == url); } - public async Task AttachToMessageAsync(Guid mediaId, Guid messageId) + public async Task> AttachToMessageAsync(Guid mediaId, Guid messageId) { var mediaFile = await _dbContext.MediaFiles - .FirstOrDefaultAsync(x => x.Id == mediaId) - ?? throw new KeyNotFoundException($"No media found by given id {mediaId}"); + .FirstOrDefaultAsync(x => x.Id == mediaId); + if (mediaFile is null) + { + return Result.Failure(Error.NotFound( + "File.AttachToMessage", + $"File with given id ({mediaId}) doesn't exist!") + ); + } + if (mediaFile.OwnerType != MediaOwnerType.Message) { _logger.LogWarning("Attempt to attach already owned media {MediaId}", mediaId); - throw new InvalidOperationException($"Media {mediaId} is already attached to {mediaFile.OwnerType}"); + return Result.Failure(Error.Failure( + "File.AttachToMessage", + $"Media {mediaId} is already attached to {mediaFile.OwnerType}") + ); } mediaFile.OwnerType = MediaOwnerType.Message; @@ -139,5 +171,7 @@ public class MediaService : IMediaService await _dbContext.SaveChangesAsync(); _logger.LogInformation("Media {MediaId} successfully attached to message {MessageId}", mediaId, messageId); + + return new Unit(); } } \ No newline at end of file diff --git a/Govor.Application/Messages/IMessageRemovingService.cs b/Govor.Application/Messages/IMessageRemovingService.cs index 041e71f..ea154c6 100644 --- a/Govor.Application/Messages/IMessageRemovingService.cs +++ b/Govor.Application/Messages/IMessageRemovingService.cs @@ -1,8 +1,11 @@ using Govor.Application.Messages.Parameters; +using Govor.Domain.Common; +using Govor.Domain.Models.Messages; +using SmartRes; namespace Govor.Application.Messages; public interface IMessageRemovingService { - Task DeleteMessageAsync(DeleteMessage deleteParams); + Task> DeleteMessageAsync(DeleteMessage deleteParams); } \ No newline at end of file diff --git a/Govor.Application/Messages/IMessagesLoader.cs b/Govor.Application/Messages/IMessagesLoader.cs index e6591a5..4648180 100644 --- a/Govor.Application/Messages/IMessagesLoader.cs +++ b/Govor.Application/Messages/IMessagesLoader.cs @@ -1,9 +1,11 @@ +using Govor.Domain.Common; using Govor.Domain.Models.Messages; +using SmartRes; namespace Govor.Application.Messages; public interface IMessagesLoader { - Task> LoadMessagesInUserChat(Guid privateChatId,Guid currentId, Guid? startMessageId, int before = 20, int after = 2); - Task> LoadMessagesInChatGroup(Guid chatId,Guid currentId, Guid? startMessageId, int before = 20, int after = 2); + Task,Error>> LoadMessagesInUserChat(Guid privateChatId,Guid currentId, Guid? startMessageId, int before = 20, int after = 2); + Task,Error>> LoadMessagesInChatGroup(Guid chatId,Guid currentId, Guid? startMessageId, int before = 20, int after = 2); } \ No newline at end of file diff --git a/Govor.Application/Messages/MessageRemovingService.cs b/Govor.Application/Messages/MessageRemovingService.cs index 51f6f59..32314a9 100644 --- a/Govor.Application/Messages/MessageRemovingService.cs +++ b/Govor.Application/Messages/MessageRemovingService.cs @@ -1,6 +1,10 @@ using Govor.Application.Messages.Parameters; using Govor.Domain; +using Govor.Domain.Common; +using Govor.Domain.Models.Messages; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using SmartRes; namespace Govor.Application.Messages; @@ -17,8 +21,67 @@ public class MessageRemovingService : IMessageRemovingService _logger = logger; } - public Task DeleteMessageAsync(DeleteMessage deleteParams) + public async Task> DeleteMessageAsync(DeleteMessage deleteParams) { - throw new NotImplementedException(); + var message = await _govorDbContext.Messages.FirstOrDefaultAsync(m => m.Id == deleteParams.MessageId); + + if (message == null) + return Result.Failure( + Error.NotFound( + "MessageRemoving", + "Message with given id doesn't exist") + ); + + var result = message.RecipientType switch + { + RecipientType.Group => await ValidateGroupRecipientAsync(message, deleteParams), + RecipientType.User => await ValidateUserRecipientAsync(message, deleteParams), + + _ => Result.Failure(Error.Failure( + "MessageRemoving.ArgumentOutOfRangeException", + $"Argument out of range: {nameof(message.RecipientType)}" + ) + ) + }; + + return result; + } + + private async Task> ValidateGroupRecipientAsync(Message message, DeleteMessage deleteParams) + { + if (deleteParams.DeleterId == message.RecipientId) + { + return await ForceRemoveAsync(message); + } + else + { + // TODO made admin rules + return Result.Failure(Error.Failure("MessageRemoving.HaveNoPermission", + $"You do not have permission to delete message {message.Id}" + )); + } + } + + private async Task> ValidateUserRecipientAsync(Message message, DeleteMessage deleteParams) + { + if (deleteParams.DeleterId == message.RecipientId) + { + return await ForceRemoveAsync(message); + } + else + { + // TODO made hide rules + return Result.Failure(Error.Failure("MessageRemoving.HaveNoPermission", + $"You do not have permission to delete message {message.Id}" + )); + } + } + + private async Task> ForceRemoveAsync(Message message) + { + _govorDbContext.Messages.Remove(message); + await _govorDbContext.SaveChangesAsync(); + + return message; } } \ No newline at end of file diff --git a/Govor.Application/Messages/MessagesLoader.cs b/Govor.Application/Messages/MessagesLoader.cs index ba11f1e..bf1a37a 100644 --- a/Govor.Application/Messages/MessagesLoader.cs +++ b/Govor.Application/Messages/MessagesLoader.cs @@ -1,7 +1,9 @@ using Govor.Application.Interfaces; using Govor.Domain.Models.Messages; using Govor.Domain; +using Govor.Domain.Common; using Microsoft.EntityFrameworkCore; +using SmartRes; namespace Govor.Application.Messages; @@ -14,7 +16,7 @@ public class MessagesLoader : IMessagesLoader _dbContext = dbContext; } - public async Task> LoadMessagesInUserChat( + public async Task,Error>> LoadMessagesInUserChat( Guid privateChatId, Guid currentUser, Guid? startMessageId, @@ -22,11 +24,11 @@ public class MessagesLoader : IMessagesLoader int after = 2) { if (privateChatId == Guid.Empty) - throw new ArgumentException("PrivateChatId id cannot be empty", nameof(privateChatId)); + return Result.Failure>(Error.Failure(nameof(ArgumentException),"PrivateChatId id cannot be empty.")); var chatExists = await _dbContext.PrivateChats.AnyAsync(c => c.Id == privateChatId); if (!chatExists) - return []; + return new List(0); var query = _dbContext.Messages .AsNoTracking() @@ -37,7 +39,7 @@ public class MessagesLoader : IMessagesLoader return await FetchPaginatedMessagesAsync(query, startMessageId, before, after); } - public async Task> LoadMessagesInChatGroup( + public async Task,Error>> LoadMessagesInChatGroup( Guid chatId, Guid currentUser, Guid? startMessageId, @@ -45,13 +47,13 @@ public class MessagesLoader : IMessagesLoader int after = 2) { if (chatId == Guid.Empty) - throw new ArgumentException("Chat id cannot be empty", nameof(chatId)); + return Result.Failure>(Error.Failure(nameof(ArgumentException),"Chat id cannot be empty.")); var isMember = await _dbContext.GroupMemberships .AnyAsync(gm => gm.UserId == currentUser && gm.GroupId == chatId); if (!isMember) - return []; + return new List(0); var query = _dbContext.Messages .AsNoTracking() diff --git a/Govor.Application/Messages/Parameters/DeleteMessage.cs b/Govor.Application/Messages/Parameters/DeleteMessage.cs index 409c848..86abda2 100644 --- a/Govor.Application/Messages/Parameters/DeleteMessage.cs +++ b/Govor.Application/Messages/Parameters/DeleteMessage.cs @@ -2,4 +2,5 @@ namespace Govor.Application.Messages.Parameters; public record DeleteMessage( Guid DeleterId, - Guid MessageId); \ No newline at end of file + Guid MessageId, + bool ForceRemove = false); diff --git a/Govor.Application/PushNotifications/PushTokenService.cs b/Govor.Application/PushNotifications/PushTokenService.cs index 11b1dea..1f6f66f 100644 --- a/Govor.Application/PushNotifications/PushTokenService.cs +++ b/Govor.Application/PushNotifications/PushTokenService.cs @@ -119,7 +119,9 @@ public class PushTokenService : IPushTokenService await _context.UserPushTokens .Where(t => tokens.Contains(t.Token)) .ExecuteDeleteAsync(); - + + await _context.SaveChangesAsync(); + return Result.Success(); } catch (Exception ex) diff --git a/Govor.Application/Users/UserSessions/UserSessionRefresher.cs b/Govor.Application/Users/UserSessions/UserSessionRefresher.cs index 3499d49..441c281 100644 --- a/Govor.Application/Users/UserSessions/UserSessionRefresher.cs +++ b/Govor.Application/Users/UserSessions/UserSessionRefresher.cs @@ -42,7 +42,6 @@ public class UserSessionRefresher : IUserSessionRefresher try { var session = await _context.UserSessions - .AsNoTracking() .Include(userSession => userSession.User) .FirstOrDefaultAsync(s => s.RefreshTokenHash == hashedToken); diff --git a/Govor.Contracts/Requests/SignalR/RemoveMessageRequest.cs b/Govor.Contracts/Requests/SignalR/RemoveMessageRequest.cs index 4948b75..91da80f 100644 --- a/Govor.Contracts/Requests/SignalR/RemoveMessageRequest.cs +++ b/Govor.Contracts/Requests/SignalR/RemoveMessageRequest.cs @@ -3,4 +3,5 @@ namespace Govor.Contracts.Requests.SignalR; public class RemoveMessageRequest { public Guid MessageId { get; set; } + public RemoveMessageRequestType RequestType { get; set; } } \ No newline at end of file diff --git a/Govor.Contracts/Requests/SignalR/RemoveMessageRequestType.cs b/Govor.Contracts/Requests/SignalR/RemoveMessageRequestType.cs new file mode 100644 index 0000000..13179a1 --- /dev/null +++ b/Govor.Contracts/Requests/SignalR/RemoveMessageRequestType.cs @@ -0,0 +1,7 @@ +namespace Govor.Contracts.Requests.SignalR; + +public enum RemoveMessageRequestType : int +{ + HideForMe = 0, + ForceRemove = 1 +} \ No newline at end of file diff --git a/Govor.Contracts/Responses/SignalR/MessageRemovedResponse.cs b/Govor.Contracts/Responses/SignalR/MessageRemovedResponse.cs index 07a72d4..7594d56 100644 --- a/Govor.Contracts/Responses/SignalR/MessageRemovedResponse.cs +++ b/Govor.Contracts/Responses/SignalR/MessageRemovedResponse.cs @@ -1,11 +1,13 @@ +using Govor.Contracts.Requests.SignalR; using Govor.Domain.Models.Messages; namespace Govor.Contracts.Responses.SignalR; public class MessageRemovedResponse { - public Guid MessageId { get; set; } - public Guid SenderId { get; set; } - public Guid RecipientId { get; set; } + public required Guid MessageId { get; set; } + public required Guid SenderId { get; set; } + public required Guid RecipientId { get; set; } + public RemoveMessageRequestType RequestType { get; set; } public RecipientType RecipientType { get; set; } } \ No newline at end of file diff --git a/Govor.Domain/Common/Error.cs b/Govor.Domain/Common/Error.cs index 242be7a..73f0a31 100644 --- a/Govor.Domain/Common/Error.cs +++ b/Govor.Domain/Common/Error.cs @@ -11,6 +11,6 @@ public record Error(string Code, string Message, ErrorType Type, Dictionary new(code, message, ErrorType.Unauthorized); public static Error Forbidden(string code, string message) => new(code, message, ErrorType.Forbidden); public static Error Failure(string code, string message) => new(code, message, ErrorType.Failure); - + public static Error ServerError(string code, string message) => new(code, message, ErrorType.ServerError); public override string ToString() => $"{Code}: {Message}"; } \ No newline at end of file diff --git a/Govor.Domain/Common/ErrorType.cs b/Govor.Domain/Common/ErrorType.cs index ac228b8..9b98ee9 100644 --- a/Govor.Domain/Common/ErrorType.cs +++ b/Govor.Domain/Common/ErrorType.cs @@ -7,5 +7,6 @@ public enum ErrorType NotFound = 2, // (404 Not Found) Conflict = 3, // (409 Conflict) Unauthorized = 4, // (401 Unauthorized) - Forbidden = 5 // (403 Forbidden) + Forbidden = 5, // (403 Forbidden) + ServerError = 6 // (500 Server Error) } \ No newline at end of file diff --git a/Govor.Domain/Migrations/20260716110338_InitialCreate.Designer.cs b/Govor.Domain/Migrations/20260727130055_InitialCreate.Designer.cs similarity index 99% rename from Govor.Domain/Migrations/20260716110338_InitialCreate.Designer.cs rename to Govor.Domain/Migrations/20260727130055_InitialCreate.Designer.cs index 6cfef7d..f81fad5 100644 --- a/Govor.Domain/Migrations/20260716110338_InitialCreate.Designer.cs +++ b/Govor.Domain/Migrations/20260727130055_InitialCreate.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Govor.Domain.Migrations { [DbContext(typeof(GovorDbContext))] - [Migration("20260716110338_InitialCreate")] + [Migration("20260727130055_InitialCreate")] partial class InitialCreate { /// @@ -20,7 +20,7 @@ namespace Govor.Domain.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("ProductVersion", "10.0.10") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -202,6 +202,9 @@ namespace Govor.Domain.Migrations b.Property("MaxParticipants") .HasColumnType("integer"); + b.Property("Participants") + .HasColumnType("integer"); + b.HasKey("Id"); b.ToTable("Invitations"); diff --git a/Govor.Domain/Migrations/20260716110338_InitialCreate.cs b/Govor.Domain/Migrations/20260727130055_InitialCreate.cs similarity index 99% rename from Govor.Domain/Migrations/20260716110338_InitialCreate.cs rename to Govor.Domain/Migrations/20260727130055_InitialCreate.cs index ead7690..f4fdd48 100644 --- a/Govor.Domain/Migrations/20260716110338_InitialCreate.cs +++ b/Govor.Domain/Migrations/20260727130055_InitialCreate.cs @@ -38,7 +38,8 @@ namespace Govor.Domain.Migrations Description = table.Column(type: "text", nullable: false), DateCreated = table.Column(type: "timestamp with time zone", nullable: false), EndDate = table.Column(type: "timestamp with time zone", nullable: false), - MaxParticipants = table.Column(type: "integer", nullable: false) + MaxParticipants = table.Column(type: "integer", nullable: false), + Participants = table.Column(type: "integer", nullable: false) }, constraints: table => { diff --git a/Govor.Domain/Migrations/GovorDbContextModelSnapshot.cs b/Govor.Domain/Migrations/GovorDbContextModelSnapshot.cs index 713c4e5..5e8d8c5 100644 --- a/Govor.Domain/Migrations/GovorDbContextModelSnapshot.cs +++ b/Govor.Domain/Migrations/GovorDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace Govor.Domain.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("ProductVersion", "10.0.10") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -199,6 +199,9 @@ namespace Govor.Domain.Migrations b.Property("MaxParticipants") .HasColumnType("integer"); + b.Property("Participants") + .HasColumnType("integer"); + b.HasKey("Id"); b.ToTable("Invitations"); diff --git a/Govor.Domain/Models/Invitation.cs b/Govor.Domain/Models/Invitation.cs index b2ebf48..8b5e015 100644 --- a/Govor.Domain/Models/Invitation.cs +++ b/Govor.Domain/Models/Invitation.cs @@ -12,6 +12,7 @@ public class Invitation public DateTime DateCreated { get; set; } public DateTime EndDate { get; set; } public int MaxParticipants { get; set; } + public int Participants { get; set; } = 0; public List Users { get; set; } = new List(); public override bool Equals(object? obj) diff --git a/libs/SmartRes.dll b/libs/SmartRes.dll index e2c071e..9787406 100644 Binary files a/libs/SmartRes.dll and b/libs/SmartRes.dll differ