diff --git a/src/EventHub.Admin.Application.Contracts/Events/CountryLookupDto.cs b/src/EventHub.Admin.Application.Contracts/Events/CountryLookupDto.cs new file mode 100644 index 0000000..6056ea3 --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Events/CountryLookupDto.cs @@ -0,0 +1,10 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace EventHub.Admin.Events +{ + public class CountryLookupDto : EntityDto + { + public string Name { get; set; } + } +} diff --git a/src/EventHub.Admin.Application.Contracts/Events/EventDetailDto.cs b/src/EventHub.Admin.Application.Contracts/Events/EventDetailDto.cs index a6d5f32..a0bded1 100644 --- a/src/EventHub.Admin.Application.Contracts/Events/EventDetailDto.cs +++ b/src/EventHub.Admin.Application.Contracts/Events/EventDetailDto.cs @@ -7,6 +7,24 @@ namespace EventHub.Admin.Events { public string Title { get; set; } + public string Description { get; set; } + public DateTime StartTime { get; set; } + + public DateTime EndTime { get; set; } + + public byte[] CoverImageContent { get; set; } + + public bool IsOnline { get; set; } + + public string OnlineLink { get; set; } + + public Guid? CountryId { get; set; } + + public string City { get; set; } + + public string Language { get; set; } + + public int? Capacity { get; set; } } } diff --git a/src/EventHub.Admin.Application.Contracts/Events/IEventAppService.cs b/src/EventHub.Admin.Application.Contracts/Events/IEventAppService.cs index 70bd7d5..12c2a22 100644 --- a/src/EventHub.Admin.Application.Contracts/Events/IEventAppService.cs +++ b/src/EventHub.Admin.Application.Contracts/Events/IEventAppService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; @@ -12,5 +13,9 @@ namespace EventHub.Admin.Events Task GetAsync(Guid id); Task UpdateAsync(Guid id, UpdateEventDto input); + + Task> GetCountriesLookupAsync(); + + Task GetCoverImageAsync(Guid id); } } diff --git a/src/EventHub.Admin.Application.Contracts/Events/UpdateEventDto.cs b/src/EventHub.Admin.Application.Contracts/Events/UpdateEventDto.cs index 5b8f661..1b041bd 100644 --- a/src/EventHub.Admin.Application.Contracts/Events/UpdateEventDto.cs +++ b/src/EventHub.Admin.Application.Contracts/Events/UpdateEventDto.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; using EventHub.Events; +using JetBrains.Annotations; namespace EventHub.Admin.Events { @@ -10,8 +11,37 @@ namespace EventHub.Admin.Events [StringLength(EventConsts.MaxTitleLength, MinimumLength = EventConsts.MinTitleLength)] public string Title { get; set; } + [Required] + [StringLength(EventConsts.MaxDescriptionLength, MinimumLength = EventConsts.MinDescriptionLength)] + public string Description { get; set; } + [Required] [DataType(DataType.DateTime)] public DateTime StartTime { get; set; } + + [Required] + [DataType(DataType.DateTime)] + public DateTime EndTime { get; set; } + + [CanBeNull] + public byte[] CoverImageContent { get; set; } + + public bool IsOnline { get; set; } + + [CanBeNull] + [StringLength(EventConsts.MaxOnlineLinkLength, MinimumLength = EventConsts.MinOnlineLinkLength)] + public string OnlineLink { get; set; } + public Guid? CountryId { get; set; } + + [CanBeNull] + [StringLength(EventConsts.MaxCityLength, MinimumLength = EventConsts.MinCityLength)] + public string City { get; set; } + + [CanBeNull] + [StringLength(EventConsts.MaxLanguageLength, MinimumLength = EventConsts.MinLanguageLength)] + public string Language { get; set; } + + [Range(1, int.MaxValue)] + public int? Capacity { get; set; } } } diff --git a/src/EventHub.Admin.Application/EventHubApplicationAutoMapperProfile.cs b/src/EventHub.Admin.Application/EventHubApplicationAutoMapperProfile.cs index 9eddbdb..476c8d8 100644 --- a/src/EventHub.Admin.Application/EventHubApplicationAutoMapperProfile.cs +++ b/src/EventHub.Admin.Application/EventHubApplicationAutoMapperProfile.cs @@ -2,6 +2,7 @@ using AutoMapper; using EventHub.Admin.Events; using EventHub.Admin.Organizations; using EventHub.Admin.Organizations.Memberships; +using EventHub.Countries; using EventHub.Events; using EventHub.Organizations; using EventHub.Organizations.Memberships; @@ -22,7 +23,10 @@ namespace EventHub.Admin CreateMap(); - CreateMap(); + CreateMap() + .Ignore(x => x.CoverImageContent); + + CreateMap(); } } } diff --git a/src/EventHub.Admin.Application/Events/EventAppService.cs b/src/EventHub.Admin.Application/Events/EventAppService.cs index 331f4bc..e66ad0a 100644 --- a/src/EventHub.Admin.Application/Events/EventAppService.cs +++ b/src/EventHub.Admin.Application/Events/EventAppService.cs @@ -1,11 +1,14 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Dynamic.Core; using System.Threading.Tasks; +using EventHub.Countries; using EventHub.Events; using EventHub.Events.Registrations; using EventHub.Organizations; using Volo.Abp.Application.Dtos; +using Volo.Abp.BlobStoring; using Volo.Abp.Domain.Repositories; namespace EventHub.Admin.Events @@ -16,23 +19,35 @@ namespace EventHub.Admin.Events private readonly IRepository _eventRepository; private readonly IRepository _eventRegistrationRepository; private readonly IRepository _organizationRepository; + private readonly IBlobContainer _eventBlobContainer; + private readonly EventManager _eventManager; + private readonly IRepository _countryRepository; + public EventAppService( IRepository eventRepository, IRepository eventRegistrationRepository, - IRepository organizationRepository - ) + IRepository organizationRepository, + IBlobContainer eventBlobContainer, + EventManager eventManager, + IRepository countryRepository) { _eventRepository = eventRepository; _eventRegistrationRepository = eventRegistrationRepository; _organizationRepository = organizationRepository; + _eventBlobContainer = eventBlobContainer; + _eventManager = eventManager; + _countryRepository = countryRepository; } public async Task GetAsync(Guid id) { var @event = await _eventRepository.GetAsync(id); - return ObjectMapper.Map(@event); + var eventDetailDto = ObjectMapper.Map(@event); + eventDetailDto.CoverImageContent = await GetCoverImageAsync(id); + + return eventDetailDto; } public async Task> GetListAsync(EventListFilterDto input) @@ -74,10 +89,44 @@ namespace EventHub.Admin.Events { var @event = await _eventRepository.GetAsync(id); + await _eventManager.SetLocationAsync(@event, input.IsOnline, input.OnlineLink, input.CountryId, input.City); @event.SetTitle(input.Title); + @event.SetDescription(input.Description); + @event.Language = input.Language; @event.SetTime(input.StartTime, @event.EndTime); + await _eventManager.SetCapacityAsync(@event, input.Capacity); + + var blobName = id.ToString(); + if (input.CoverImageContent.IsNullOrEmpty()) + { + await _eventBlobContainer.DeleteAsync(blobName); + } + else + { + await _eventBlobContainer.SaveAsync(blobName, input.CoverImageContent, overrideExisting: true); + } await _eventRepository.UpdateAsync(@event); } + + public async Task GetCoverImageAsync(Guid id) + { + var blobName = id.ToString(); + + return await _eventBlobContainer.GetAllBytesOrNullAsync(blobName); + } + + public async Task> GetCountriesLookupAsync() + { + var countriesQueryable = await _countryRepository.GetQueryableAsync(); + + var query = from country in countriesQueryable + orderby country.Name + select country; + + var countries = await AsyncExecuter.ToListAsync(query); + + return ObjectMapper.Map, List>(countries); + } } } diff --git a/src/EventHub.Admin.HttpApi.Host/Controllers/Events/EventController.cs b/src/EventHub.Admin.HttpApi.Host/Controllers/Events/EventController.cs index bc8e4ab..f75aa86 100644 --- a/src/EventHub.Admin.HttpApi.Host/Controllers/Events/EventController.cs +++ b/src/EventHub.Admin.HttpApi.Host/Controllers/Events/EventController.cs @@ -30,6 +30,18 @@ namespace EventHub.Admin.Controllers.Events return _eventAppService.GetAsync(id); } + [HttpGet("countries")] + public Task> GetCountriesLookupAsync() + { + return _eventAppService.GetCountriesLookupAsync(); + } + + [HttpGet("cover-image/{id}")] + public Task GetCoverImageAsync(Guid id) + { + return _eventAppService.GetCoverImageAsync(id); + } + [HttpGet] public Task> GetListAsync(EventListFilterDto input) { diff --git a/src/EventHub.Admin.Web/Pages/EventManagement.razor b/src/EventHub.Admin.Web/Pages/EventManagement.razor index 472aaf1..6b79e4f 100644 --- a/src/EventHub.Admin.Web/Pages/EventManagement.razor +++ b/src/EventHub.Admin.Web/Pages/EventManagement.razor @@ -2,6 +2,7 @@ @using Microsoft.AspNetCore.Authorization @using EventHub.Admin.Permissions @using EventHub.Admin.Events +@using EventHub.Events @inherits EventHubComponentBase @attribute [Authorize(EventHubPermissions.Events.Default)] @inject IEventAppService EventAppService @@ -111,14 +112,160 @@ @L["CoverImage"] - - EventInfo + + + + + @L["Title"] * + + + + + + + + + + + @L["Description"] * + + + + + + + + + + + @L["IsOnline"] + + + + + + + @L["Language"] + + + + + @if (EditingEvent.IsOnline) + { + + + @L["OnlineLink"] + + + + + + + + } + else + { + + + @L["Country"] + + + + + + + @L["City"] + + + + + + + + } + + + + @L["Capacity"] + + + + + + + + + - - Timing + + + + + @L["StartTime"] * + + + + + + + + + + @L["EndTime"] * + + + + + + + + - - CoverImage + + @if (!string.IsNullOrEmpty(CoverImageUrl)) + { +
+ cover-image +
+ } + +
+ + @L["ChooseCoverImage"] + + + + + @if (!string.IsNullOrEmpty(CoverImageUrl)) + { + + } + else + { + + } + + +
diff --git a/src/EventHub.Admin.Web/Pages/EventManagement.razor.cs b/src/EventHub.Admin.Web/Pages/EventManagement.razor.cs index 0c919bb..c42e142 100644 --- a/src/EventHub.Admin.Web/Pages/EventManagement.razor.cs +++ b/src/EventHub.Admin.Web/Pages/EventManagement.razor.cs @@ -8,6 +8,9 @@ using Volo.Abp.Application.Dtos; using System; using System.ComponentModel; using Microsoft.AspNetCore.Components.Web; +using System.IO; +using System.Globalization; +using NUglify.Helpers; namespace EventHub.Admin.Web.Pages { @@ -23,18 +26,42 @@ namespace EventHub.Admin.Web.Pages private EventDetailDto Event { get; set; } private UpdateEventDto EditingEvent { get; set; } private Modal EditEventModal { get; set; } - private string SelectedTabInEditModal { get; set; } + private string SelectedTabInEditModal { get; set; } + private string CoverImageUrl { get; set; } + private IFileEntry FileEntry { get; set; } + private List Countries { get; set; } + private List Languages { get; set; } public EventManagement() { Filter = new EventListFilterDto(); PageSize = LimitedResultRequestDto.DefaultMaxResultCount; SelectedTabInEditModal = EventEditTabs.EventInfo.ToString(); + EditingEvent = new UpdateEventDto(); + Countries = new List(); + Languages = new List(); } protected override async Task OnInitializedAsync() { await GetEventsAsync(); + await FillCountriesAsync(); + FillLanguages(); + } + + private void FillLanguages() + { + var result = CultureInfo.GetCultures(CultureTypes.NeutralCultures) + .DistinctBy(x => x.EnglishName) + .OrderBy(x => x.EnglishName) + .ToList(); + result.Remove(result.Single(x => x.TwoLetterISOLanguageName == "iv")); // Invariant Language + + Languages = result.Select(cultureInfo => new Language + { + Value = cultureInfo.TwoLetterISOLanguageName, + Text = cultureInfo.EnglishName + }).ToList(); } private async Task GetEventsAsync() @@ -66,6 +93,8 @@ namespace EventHub.Admin.Web.Pages Event = await EventAppService.GetAsync(EditingEventId); EditingEvent = ObjectMapper.Map(Event); + FillCoverImageUrl(EditingEvent.CoverImageContent); + EditEventModal.Show(); } @@ -83,6 +112,8 @@ namespace EventHub.Admin.Web.Pages { await EventAppService.UpdateAsync(EditingEventId, EditingEvent); await GetEventsAsync(); + CoverImageUrl = string.Empty; + EditEventModal.Hide(); } @@ -99,6 +130,49 @@ namespace EventHub.Admin.Web.Pages Filter.StartTime = changedDate; await GetEventsAsync(); } + + private void FillCoverImageUrl(byte[] content) + { + if (content.IsNullOrEmpty()) + { + return; + } + + var imageBase64Data = Convert.ToBase64String(content); + var imageDataUrl = $"data:image/png;base64,{imageBase64Data}"; + CoverImageUrl = imageDataUrl; + } + + private async Task OnCoverImageFileChanged(FileChangedEventArgs e) + { + FileEntry = e.Files.FirstOrDefault(); + if (FileEntry is null) + { + return; + } + + using (var stream = new MemoryStream()) + { + await FileEntry.WriteToStreamAsync(stream); + + stream.Seek(0, SeekOrigin.Begin); + EditingEvent.CoverImageContent = stream.ToArray(); + FillCoverImageUrl(EditingEvent.CoverImageContent); + await InvokeAsync(StateHasChanged); + } + } + + private void OnDeleteCoverImageButtonClicked() + { + EditingEvent.CoverImageContent = null; + FileEntry = new FileEntry(); + CoverImageUrl = null; + } + + private async Task FillCountriesAsync() + { + Countries = await EventAppService.GetCountriesLookupAsync(); + } } public enum EventEditTabs : byte @@ -107,4 +181,10 @@ namespace EventHub.Admin.Web.Pages Timing, CoverImage } + + public class Language + { + public string Value { get; set; } + public string Text { get; set; } + } } diff --git a/src/EventHub.Application/Events/EventAppService.cs b/src/EventHub.Application/Events/EventAppService.cs index 2bf29cb..ff14e70 100644 --- a/src/EventHub.Application/Events/EventAppService.cs +++ b/src/EventHub.Application/Events/EventAppService.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using EventHub.Countries; using EventHub.Events.Registrations; using EventHub.Organizations; -using EventHub.Users; using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Dtos; using Volo.Abp.Authorization; diff --git a/src/EventHub.Domain.Shared/Events/EventConsts.cs b/src/EventHub.Domain.Shared/Events/EventConsts.cs index 506d9cf..6e27a39 100644 --- a/src/EventHub.Domain.Shared/Events/EventConsts.cs +++ b/src/EventHub.Domain.Shared/Events/EventConsts.cs @@ -26,5 +26,7 @@ public const int MaxTimingChangeCountForUser = 2; public const int MaxCoverImageFileSize = 5 * 1024 * 1024; + + public static string[] AllowedCoverImageExtensions = { ".jpg", ".png" }; } } diff --git a/src/EventHub.Domain.Shared/Localization/EventHub/en.json b/src/EventHub.Domain.Shared/Localization/EventHub/en.json index 43adc30..e3aa773 100644 --- a/src/EventHub.Domain.Shared/Localization/EventHub/en.json +++ b/src/EventHub.Domain.Shared/Localization/EventHub/en.json @@ -126,6 +126,14 @@ "MaxAttendeeCount": "Max Attendee Count", "UpdateEvent": "Update Event", "EventInfo": "Event Info", - "Timing": "Timing" + "Timing": "Timing", + "IsOnline": "Is Online?", + "OnlineLink": "Online Link", + "Country": "Country", + "City": "City", + "Capacity": "Capacity", + "EndTime": "End Time", + "ChooseCoverImage": "Choose a cover image", + } }