diff --git a/src/EventHub.Admin.Application.Contracts/Events/EventDetailDto.cs b/src/EventHub.Admin.Application.Contracts/Events/EventDetailDto.cs index a0bded1..04b0c53 100644 --- a/src/EventHub.Admin.Application.Contracts/Events/EventDetailDto.cs +++ b/src/EventHub.Admin.Application.Contracts/Events/EventDetailDto.cs @@ -13,8 +13,6 @@ namespace EventHub.Admin.Events public DateTime EndTime { get; set; } - public byte[] CoverImageContent { get; set; } - public bool IsOnline { get; set; } public string OnlineLink { get; set; } diff --git a/src/EventHub.Admin.Application.Contracts/Events/IEventAppService.cs b/src/EventHub.Admin.Application.Contracts/Events/IEventAppService.cs index 12c2a22..da46c3b 100644 --- a/src/EventHub.Admin.Application.Contracts/Events/IEventAppService.cs +++ b/src/EventHub.Admin.Application.Contracts/Events/IEventAppService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; +using Volo.Abp.Content; namespace EventHub.Admin.Events { @@ -16,6 +17,6 @@ namespace EventHub.Admin.Events Task> GetCountriesLookupAsync(); - Task GetCoverImageAsync(Guid id); + 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 1b041bd..e6c9d54 100644 --- a/src/EventHub.Admin.Application.Contracts/Events/UpdateEventDto.cs +++ b/src/EventHub.Admin.Application.Contracts/Events/UpdateEventDto.cs @@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations; using EventHub.Events; using JetBrains.Annotations; +using Volo.Abp.Content; namespace EventHub.Admin.Events { @@ -24,7 +25,7 @@ namespace EventHub.Admin.Events public DateTime EndTime { get; set; } [CanBeNull] - public byte[] CoverImageContent { get; set; } + public RemoteStreamContent CoverImageStreamContent { get; set; } public bool IsOnline { get; set; } diff --git a/src/EventHub.Admin.Application.Contracts/Organizations/IOrganizationAppService.cs b/src/EventHub.Admin.Application.Contracts/Organizations/IOrganizationAppService.cs index b0850b8..fa26ed0 100644 --- a/src/EventHub.Admin.Application.Contracts/Organizations/IOrganizationAppService.cs +++ b/src/EventHub.Admin.Application.Contracts/Organizations/IOrganizationAppService.cs @@ -2,6 +2,7 @@ using System; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; +using Volo.Abp.Content; namespace EventHub.Admin.Organizations { @@ -16,5 +17,7 @@ namespace EventHub.Admin.Organizations Task UpdateAsync(Guid id, UpdateOrganizationDto input); Task DeleteAsync(Guid id); + + Task GetCoverImageAsync(Guid id); } } diff --git a/src/EventHub.Admin.Application.Contracts/Organizations/OrganizationProfileDto.cs b/src/EventHub.Admin.Application.Contracts/Organizations/OrganizationProfileDto.cs index 86b469c..bb7ad46 100644 --- a/src/EventHub.Admin.Application.Contracts/Organizations/OrganizationProfileDto.cs +++ b/src/EventHub.Admin.Application.Contracts/Organizations/OrganizationProfileDto.cs @@ -9,8 +9,6 @@ namespace EventHub.Admin.Organizations public string DisplayName { get; set; } - public byte[] ProfilePictureContent { get; set; } - public string OwnerUserName { get; set; } public string OwnerEmail { get; set; } diff --git a/src/EventHub.Admin.Application.Contracts/Organizations/UpdateOrganizationDto.cs b/src/EventHub.Admin.Application.Contracts/Organizations/UpdateOrganizationDto.cs index c90c59b..c2b018b 100644 --- a/src/EventHub.Admin.Application.Contracts/Organizations/UpdateOrganizationDto.cs +++ b/src/EventHub.Admin.Application.Contracts/Organizations/UpdateOrganizationDto.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using EventHub.Organizations; using JetBrains.Annotations; +using Volo.Abp.Content; namespace EventHub.Admin.Organizations { @@ -15,7 +16,7 @@ namespace EventHub.Admin.Organizations public string Description { get; set; } [CanBeNull] - public byte[] ProfilePictureContent { get; set; } + public RemoteStreamContent ProfilePictureStreamContent { get; set; } [CanBeNull] [StringLength(OrganizationConsts.MaxWebsiteLength)] diff --git a/src/EventHub.Admin.Application/EventHubApplicationAutoMapperProfile.cs b/src/EventHub.Admin.Application/EventHubApplicationAutoMapperProfile.cs index 0779f1e..f4a9ca2 100644 --- a/src/EventHub.Admin.Application/EventHubApplicationAutoMapperProfile.cs +++ b/src/EventHub.Admin.Application/EventHubApplicationAutoMapperProfile.cs @@ -22,13 +22,11 @@ namespace EventHub.Admin CreateMap() .Ignore(x => x.OwnerUserName) - .Ignore(x => x.OwnerEmail) - .Ignore(x => x.ProfilePictureContent); + .Ignore(x => x.OwnerEmail); CreateMap(); - CreateMap() - .Ignore(x => x.CoverImageContent); + CreateMap(); CreateMap(); diff --git a/src/EventHub.Admin.Application/Events/EventAppService.cs b/src/EventHub.Admin.Application/Events/EventAppService.cs index daf2b16..0f58e09 100644 --- a/src/EventHub.Admin.Application/Events/EventAppService.cs +++ b/src/EventHub.Admin.Application/Events/EventAppService.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Dynamic.Core; using System.Threading.Tasks; using EventHub.Admin.Permissions; using EventHub.Countries; @@ -9,6 +8,7 @@ using EventHub.Events; using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Dtos; using Volo.Abp.BlobStoring; +using Volo.Abp.Content; using Volo.Abp.Domain.Repositories; namespace EventHub.Admin.Events @@ -36,9 +36,7 @@ namespace EventHub.Admin.Events public async Task GetAsync(Guid id) { var @event = await _eventRepository.GetAsync(id); - var eventDetailDto = ObjectMapper.Map(@event); - eventDetailDto.CoverImageContent = await GetCoverImageAsync(id); return eventDetailDto; } @@ -69,16 +67,30 @@ namespace EventHub.Admin.Events @event.SetTime(input.StartTime, @event.EndTime); await _eventManager.SetCapacityAsync(@event, input.Capacity); - await SetCoverImageAsync(blobName: id.ToString(), input.CoverImageContent); + if (input.CoverImageStreamContent != null && input.CoverImageStreamContent.ContentLength > 0) + { + await SetCoverImageAsync(blobName: id.ToString(), input.CoverImageStreamContent); + } + else + { + await DeleteCoverImageAsync(blobName: id.ToString()); + } await _eventRepository.UpdateAsync(@event); } - public async Task GetCoverImageAsync(Guid id) + [AllowAnonymous] + public async Task GetCoverImageAsync(Guid id) { var blobName = id.ToString(); + var coverImageStream = await _eventBlobContainer.GetOrNullAsync(blobName); + + if (coverImageStream == null) + { + return null; + } - return await _eventBlobContainer.GetAllBytesOrNullAsync(blobName); + return new RemoteStreamContent(coverImageStream); } public async Task> GetCountriesLookupAsync() @@ -94,9 +106,14 @@ namespace EventHub.Admin.Events return ObjectMapper.Map, List>(countries); } - private async Task SetCoverImageAsync(string blobName, byte[] coverImageContent, bool overrideExisting = true) + private async Task SetCoverImageAsync(string blobName, IRemoteStreamContent streamContent, bool overrideExisting = true) + { + await _eventBlobContainer.SaveAsync(blobName, streamContent.GetStream(), overrideExisting); + } + + private async Task DeleteCoverImageAsync(string blobName) { - await _eventBlobContainer.SaveAsync(blobName, coverImageContent, overrideExisting); + await _eventBlobContainer.DeleteAsync(blobName); } } } diff --git a/src/EventHub.Admin.Application/Organizations/OrganizationAppService.cs b/src/EventHub.Admin.Application/Organizations/OrganizationAppService.cs index 4e21c4e..ba4049f 100644 --- a/src/EventHub.Admin.Application/Organizations/OrganizationAppService.cs +++ b/src/EventHub.Admin.Application/Organizations/OrganizationAppService.cs @@ -10,8 +10,8 @@ using Microsoft.AspNetCore.Authorization; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.BlobStoring; +using Volo.Abp.Content; using Volo.Abp.Domain.Repositories; -using Volo.Abp.Identity; namespace EventHub.Admin.Organizations { @@ -78,7 +78,7 @@ namespace EventHub.Admin.Organizations return await CreateOrganizationProfileDto(organization); } - + public async Task GetByNameAsync(string name) { var organization = await _organizationRepository.FindAsync(x => x.Name.ToLower() == name.ToLower()); @@ -106,13 +106,13 @@ namespace EventHub.Admin.Organizations organization.InstagramUsername = input.InstagramUsername; organization.MediumUsername = input.MediumUsername; - if (input.ProfilePictureContent?.Length > 0) + if (input.ProfilePictureStreamContent != null && input.ProfilePictureStreamContent.ContentLength > 0) { - await SaveCoverImageAsync(organization.Id, input.ProfilePictureContent); + await SaveCoverImageAsync(organization.Id, input.ProfilePictureStreamContent); } else { - await DeleteCoverImageAsync(organization.Id); + await DeleteCoverImageAsync(blobName: id.ToString()); } await _organizationRepository.UpdateAsync(organization); @@ -125,7 +125,7 @@ namespace EventHub.Admin.Organizations { await _organizationRepository.DeleteAsync(id); } - + private async Task CreateOrganizationProfileDto(Organization organization) { var dto = ObjectMapper.Map(organization); @@ -134,28 +134,32 @@ namespace EventHub.Admin.Organizations dto.OwnerUserName = user.UserName; dto.OwnerEmail = user.Email; - dto.ProfilePictureContent = await GetCoverImageAsync(organization.Id); return dto; } - private async Task GetCoverImageAsync(Guid id) + [AllowAnonymous] + public async Task GetCoverImageAsync(Guid id) { var blobName = id.ToString(); + var coverImageStream = await _organizationBlobContainer.GetOrNullAsync(blobName); - return await _organizationBlobContainer.GetAllBytesOrNullAsync(blobName); + if (coverImageStream is null) + { + return null; + } + + return new RemoteStreamContent(coverImageStream, blobName); } - private async Task SaveCoverImageAsync(Guid id, byte[] coverImageContent) + private async Task SaveCoverImageAsync(Guid id, IRemoteStreamContent coverImageContent) { var blobName = id.ToString(); - await _organizationBlobContainer.SaveAsync(blobName, coverImageContent, overrideExisting: true); + await _organizationBlobContainer.SaveAsync(blobName, coverImageContent.GetStream(), overrideExisting: true); } - private async Task DeleteCoverImageAsync(Guid id) + private async Task DeleteCoverImageAsync(string blobName) { - var blobName = id.ToString(); - await _organizationBlobContainer.DeleteAsync(blobName); } } diff --git a/src/EventHub.Admin.HttpApi.Host/Controllers/Events/EventController.cs b/src/EventHub.Admin.HttpApi.Host/Controllers/Events/EventController.cs index 5cc02a3..c99d897 100644 --- a/src/EventHub.Admin.HttpApi.Host/Controllers/Events/EventController.cs +++ b/src/EventHub.Admin.HttpApi.Host/Controllers/Events/EventController.cs @@ -2,10 +2,14 @@ using System.Collections.Generic; using System.Threading.Tasks; using EventHub.Admin.Events; +using EventHub.Events; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Content; +using Volo.Abp.VirtualFileSystem; namespace EventHub.Admin.Controllers.Events { @@ -17,10 +21,12 @@ namespace EventHub.Admin.Controllers.Events public class EventController : AbpController, IEventAppService { private readonly IEventAppService _eventAppService; - - public EventController(IEventAppService eventAppService) + private readonly IVirtualFileProvider _virtualFileProvider; + + public EventController(IEventAppService eventAppService, IVirtualFileProvider virtualFileProvider) { _eventAppService = eventAppService; + _virtualFileProvider = virtualFileProvider; } [HttpGet("{id}")] @@ -36,9 +42,19 @@ namespace EventHub.Admin.Controllers.Events } [HttpGet("cover-image/{id}")] - public Task GetCoverImageAsync(Guid id) + [AllowAnonymous] + public async Task GetCoverImageAsync(Guid id) { - return _eventAppService.GetCoverImageAsync(id); + var remoteStreamContent = await _eventAppService.GetCoverImageAsync(id); + if (remoteStreamContent is null) + { + return null; + } + + Response.Headers.Add("Accept-Ranges", "bytes"); + Response.ContentType = remoteStreamContent.ContentType; + + return remoteStreamContent; } [HttpGet] @@ -48,7 +64,7 @@ namespace EventHub.Admin.Controllers.Events } [HttpPut] - public Task UpdateAsync(Guid id, UpdateEventDto input) + public Task UpdateAsync(Guid id, [FromForm] UpdateEventDto input) { return _eventAppService.UpdateAsync(id, input); } diff --git a/src/EventHub.Admin.HttpApi.Host/Controllers/Organizations/OrganizationController.cs b/src/EventHub.Admin.HttpApi.Host/Controllers/Organizations/OrganizationController.cs index 82fd290..c30990f 100644 --- a/src/EventHub.Admin.HttpApi.Host/Controllers/Organizations/OrganizationController.cs +++ b/src/EventHub.Admin.HttpApi.Host/Controllers/Organizations/OrganizationController.cs @@ -1,10 +1,15 @@ using System; +using System.IO; using System.Threading.Tasks; using EventHub.Admin.Organizations; +using EventHub.Organizations; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Content; +using Volo.Abp.VirtualFileSystem; namespace EventHub.Admin.Controllers.Organizations { @@ -16,10 +21,12 @@ namespace EventHub.Admin.Controllers.Organizations public class OrganizationController : AbpController, IOrganizationAppService { private readonly IOrganizationAppService _organizationAppService; - - public OrganizationController(IOrganizationAppService organizationAppService) + private readonly IVirtualFileProvider _virtualFileProvider; + + public OrganizationController(IOrganizationAppService organizationAppService, IVirtualFileProvider virtualFileProvider) { _organizationAppService = organizationAppService; + _virtualFileProvider = virtualFileProvider; } [HttpGet] @@ -43,7 +50,7 @@ namespace EventHub.Admin.Controllers.Organizations } [HttpPut] - public Task UpdateAsync(Guid id, UpdateOrganizationDto input) + public Task UpdateAsync(Guid id, [FromForm] UpdateOrganizationDto input) { return _organizationAppService.UpdateAsync(id, input); } @@ -54,5 +61,22 @@ namespace EventHub.Admin.Controllers.Organizations { return _organizationAppService.DeleteAsync(id); } + + [HttpGet] + [AllowAnonymous] + [Route("cover-image/{id}")] + public async Task GetCoverImageAsync(Guid id) + { + var remoteStreamContent = await _organizationAppService.GetCoverImageAsync(id); + if (remoteStreamContent is null) + { + return null; + } + + Response.Headers.Add("Accept-Ranges", "bytes"); + Response.ContentType = remoteStreamContent.ContentType; + + return remoteStreamContent; + } } } \ No newline at end of file diff --git a/src/EventHub.Admin.HttpApi.Host/EventHubAdminHttpApiHostModule.cs b/src/EventHub.Admin.HttpApi.Host/EventHubAdminHttpApiHostModule.cs index a2b47f8..7bb8d9b 100644 --- a/src/EventHub.Admin.HttpApi.Host/EventHubAdminHttpApiHostModule.cs +++ b/src/EventHub.Admin.HttpApi.Host/EventHubAdminHttpApiHostModule.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using EventHub.Admin.Events; +using EventHub.Admin.Organizations; using EventHub.Admin.Utils; using EventHub.EntityFrameworkCore; using EventHub.Web; @@ -17,6 +19,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.OpenApi.Models; using StackExchange.Redis; using Volo.Abp; +using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Serilog; @@ -59,6 +62,16 @@ namespace EventHub.Admin ConfigureCookies(context); ConfigureSwaggerServices(context, configuration); ConfigureBackgroundJobs(); + ConfigureAutoApiControllers(); + } + + private void ConfigureAutoApiControllers() + { + Configure(options => + { + options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(UpdateOrganizationDto)); + options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(UpdateEventDto)); + }); } private void ConfigureBackgroundJobs() @@ -78,9 +91,9 @@ namespace EventHub.Admin { var hostingEnvironment = context.Services.GetHostingEnvironment(); - if (hostingEnvironment.IsDevelopment()) + Configure(options => { - Configure(options => + if (hostingEnvironment.IsDevelopment()) { options.FileSets.ReplaceEmbeddedByPhysical( Path.Combine(hostingEnvironment.ContentRootPath, @@ -94,8 +107,8 @@ namespace EventHub.Admin options.FileSets.ReplaceEmbeddedByPhysical( Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}EventHub.Admin.Application")); - }); - } + } + }); } private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) diff --git a/src/EventHub.Admin.Web/EventHub.Admin.Web.csproj b/src/EventHub.Admin.Web/EventHub.Admin.Web.csproj index 2517fdd..8988f54 100644 --- a/src/EventHub.Admin.Web/EventHub.Admin.Web.csproj +++ b/src/EventHub.Admin.Web/EventHub.Admin.Web.csproj @@ -22,4 +22,7 @@ + + + diff --git a/src/EventHub.Admin.Web/Pages/EventManagement.razor b/src/EventHub.Admin.Web/Pages/EventManagement.razor index 64435b2..78de89f 100644 --- a/src/EventHub.Admin.Web/Pages/EventManagement.razor +++ b/src/EventHub.Admin.Web/Pages/EventManagement.razor @@ -3,11 +3,14 @@ @using EventHub.Admin.Permissions @using EventHub.Admin.Events @using EventHub.Events +@using EventHub.Web +@using Microsoft.Extensions.Options @using Volo.Abp.AspNetCore.Components.Notifications @inherits EventHubComponentBase @attribute [Authorize(EventHubPermissions.Events.Default)] @inject IEventAppService EventAppService @inject NavigationManager NavigationManager +@inject IOptions UrlOptions @@ -263,13 +266,13 @@ - @if (!string.IsNullOrEmpty(CoverImageUrl)) + @if (DisabledCoverImageButton) { - + } else { - + } diff --git a/src/EventHub.Admin.Web/Pages/EventManagement.razor.cs b/src/EventHub.Admin.Web/Pages/EventManagement.razor.cs index ed46b9d..8240e2c 100644 --- a/src/EventHub.Admin.Web/Pages/EventManagement.razor.cs +++ b/src/EventHub.Admin.Web/Pages/EventManagement.razor.cs @@ -10,7 +10,9 @@ using System.ComponentModel; using Microsoft.AspNetCore.Components.Web; using System.IO; using System.Globalization; +using EventHub.Events; using Volo.Abp; +using Volo.Abp.Content; namespace EventHub.Admin.Web.Pages { @@ -31,6 +33,7 @@ namespace EventHub.Admin.Web.Pages private IFileEntry FileEntry { get; set; } private List Countries { get; set; } private List Languages { get; set; } + private bool DisabledCoverImageButton { get; set; } public EventManagement() { @@ -91,9 +94,10 @@ namespace EventHub.Admin.Web.Pages { EditingEventId = input.Id; Event = await EventAppService.GetAsync(EditingEventId); - EditingEvent = ObjectMapper.Map(Event); - FillCoverImageUrl(EditingEvent.CoverImageContent); + + FileEntry = new FileEntry(); + await SetCoverImageUrlAsync(); EditEventModal.Show(); } @@ -119,6 +123,7 @@ namespace EventHub.Admin.Web.Pages await GetEventsAsync(); CoverImageUrl = string.Empty; + DisabledCoverImageButton = false; EditEventModal.Hide(); } @@ -142,18 +147,6 @@ namespace EventHub.Admin.Web.Pages 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(); @@ -162,28 +155,57 @@ namespace EventHub.Admin.Web.Pages return; } - using (var stream = new MemoryStream()) + var stream = new MemoryStream(); + await FileEntry.WriteToStreamAsync(stream); + stream.Seek(0, SeekOrigin.Begin); + + EditingEvent.CoverImageStreamContent = new RemoteStreamContent(stream) { - await FileEntry.WriteToStreamAsync(stream); + ContentType = FileEntry.Type, + FileName = FileEntry.Name + }; - stream.Seek(0, SeekOrigin.Begin); - EditingEvent.CoverImageContent = stream.ToArray(); - FillCoverImageUrl(EditingEvent.CoverImageContent); - await InvokeAsync(StateHasChanged); + void SetCoverImageUrl(string contentType, byte[] content) + { + if (content.IsNullOrEmpty()) + { + return; + } + + contentType = string.IsNullOrWhiteSpace(contentType) ? "image/png" : contentType; + CoverImageUrl = $"data:{contentType};base64,{Convert.ToBase64String(content)}"; + DisabledCoverImageButton = false; } + + SetCoverImageUrl(FileEntry.Type, stream.ToArray()); + await InvokeAsync(StateHasChanged); } private void OnDeleteCoverImageButtonClicked() { - EditingEvent.CoverImageContent = null; + EditingEvent.CoverImageStreamContent = null; FileEntry = new FileEntry(); CoverImageUrl = null; + DisabledCoverImageButton = true; } private async Task FillCountriesAsync() { Countries = await EventAppService.GetCountriesLookupAsync(); } + + private async Task SetCoverImageUrlAsync() + { + var imageRemoteStreamContent = await EventAppService.GetCoverImageAsync(id: EditingEventId); + + if (imageRemoteStreamContent.ContentLength <= 0) + { + CoverImageUrl = "/assets/eh-event.png"; + DisabledCoverImageButton = true; + return; + } + CoverImageUrl = UrlOptions.Value.AdminApi.EnsureEndsWith('/') + "api/eventhub/admin/event/cover-image/" + EditingEventId; + } private enum EventEditTabs : byte { diff --git a/src/EventHub.Admin.Web/Pages/OrganizationManagement.razor b/src/EventHub.Admin.Web/Pages/OrganizationManagement.razor index 6a81b63..d9bd704 100644 --- a/src/EventHub.Admin.Web/Pages/OrganizationManagement.razor +++ b/src/EventHub.Admin.Web/Pages/OrganizationManagement.razor @@ -4,12 +4,15 @@ @using Microsoft.AspNetCore.Authorization @using EventHub.Admin.Organizations @using EventHub.Organizations +@using EventHub.Web +@using Microsoft.Extensions.Options @using Volo.Abp.AspNetCore.Components.Notifications @inherits EventHubComponentBase @inject NavigationManager NavigationManager @attribute [Authorize(EventHubPermissions.Organizations.Default)] @inject IOrganizationAppService OrganizationAppService @inject IUiNotificationService UiNotificationService +@inject IOptions UrlOptions @@ -235,16 +238,16 @@ @L["ChooseProfileImage"] - + - @if (!ProfileImageUrl.IsNullOrWhiteSpace()) + @if (DisabledProfileImageButton) { - + } else { - + } diff --git a/src/EventHub.Admin.Web/Pages/OrganizationManagement.razor.cs b/src/EventHub.Admin.Web/Pages/OrganizationManagement.razor.cs index d441bae..de5936a 100644 --- a/src/EventHub.Admin.Web/Pages/OrganizationManagement.razor.cs +++ b/src/EventHub.Admin.Web/Pages/OrganizationManagement.razor.cs @@ -9,6 +9,7 @@ using Blazorise.DataGrid; using EventHub.Admin.Organizations; using Microsoft.AspNetCore.Components.Web; using Volo.Abp.Application.Dtos; +using Volo.Abp.Content; namespace EventHub.Admin.Web.Pages { @@ -30,6 +31,7 @@ namespace EventHub.Admin.Web.Pages private IFileEntry FileEntry { get; set; } private bool IsLoadingProfileImage { get; set; } private string SelectedTabInEditModal { get; set; } + private bool DisabledProfileImageButton { get; set; } public OrganizationManagement() { @@ -73,7 +75,9 @@ namespace EventHub.Admin.Web.Pages { EditingOrganizationId = input.Id; Organization = await OrganizationAppService.GetAsync(EditingOrganizationId); - FillProfileImageUrl(Organization.ProfilePictureContent); + + FileEntry = new FileEntry(); + await SetProfileImageUrlAsync(); EditingOrganization = ObjectMapper.Map(Organization); EditOrganizationModal.Show(); @@ -84,13 +88,17 @@ namespace EventHub.Admin.Web.Pages await OrganizationAppService.UpdateAsync(EditingOrganizationId, EditingOrganization); await GetOrganizationsAsync(); EditOrganizationModal.Hide(); + + ProfileImageUrl = null; + DisabledProfileImageButton = false; } private void OnDeleteCoverImageButtonClicked() { - EditingOrganization.ProfilePictureContent = null; + EditingOrganization.ProfilePictureStreamContent = null; FileEntry = new FileEntry(); ProfileImageUrl = null; + DisabledProfileImageButton = true; IsLoadingProfileImage = false; } @@ -110,25 +118,29 @@ namespace EventHub.Admin.Web.Pages IsLoadingProfileImage = true; - using (var stream = new MemoryStream()) - { - await FileEntry.WriteToStreamAsync(stream); + var stream = new MemoryStream(); + await FileEntry.WriteToStreamAsync(stream); + stream.Seek(0, SeekOrigin.Begin); - stream.Seek(0, SeekOrigin.Begin); - EditingOrganization.ProfilePictureContent = stream.ToArray(); - FillProfileImageUrl(EditingOrganization.ProfilePictureContent); - await InvokeAsync(StateHasChanged); - } - } + EditingOrganization.ProfilePictureStreamContent = new RemoteStreamContent(stream) + { + ContentType = FileEntry.Type, + FileName = FileEntry.Name + }; - private void FillProfileImageUrl(byte[] content) - { - if (content != null) + void SetProfileImageUrl(string contentType, byte[] content) { - var imageBase64Data = Convert.ToBase64String(content); - var imageDataUrl = $"data:image/png;base64,{imageBase64Data}"; - ProfileImageUrl = imageDataUrl; + if (content != null) + { + contentType = string.IsNullOrWhiteSpace(contentType) ? "image/png" : contentType; + var imageDataUrl = $"data:{contentType};base64,{Convert.ToBase64String(content)}"; + ProfileImageUrl = imageDataUrl; + DisabledProfileImageButton = false; + } } + + SetProfileImageUrl(FileEntry.Type, stream.ToArray()); + await InvokeAsync(StateHasChanged); } private void OnProgressedForProfileImage(FileProgressedEventArgs e) @@ -157,6 +169,18 @@ namespace EventHub.Admin.Web.Pages await GetOrganizationsAsync(); } } + + private async Task SetProfileImageUrlAsync() + { + var imageRemoteStreamContent = await OrganizationAppService.GetCoverImageAsync(id: EditingOrganizationId); + if (imageRemoteStreamContent.ContentLength <= 0) + { + ProfileImageUrl = "assets/eh-organization.png"; + DisabledProfileImageButton = true; + return; + } + ProfileImageUrl = UrlOptions.Value.AdminApi.EnsureEndsWith('/') + "api/eventhub/admin/organization/cover-image/" + EditingOrganizationId; + } } public enum TabContentInEditModal : byte diff --git a/src/EventHub.Admin.Web/wwwroot/assets/eh-event.png b/src/EventHub.Admin.Web/wwwroot/assets/eh-event.png new file mode 100644 index 0000000..b49547e Binary files /dev/null and b/src/EventHub.Admin.Web/wwwroot/assets/eh-event.png differ diff --git a/src/EventHub.Admin.Web/wwwroot/assets/eh-organization.png b/src/EventHub.Admin.Web/wwwroot/assets/eh-organization.png new file mode 100644 index 0000000..fcf5b0c Binary files /dev/null and b/src/EventHub.Admin.Web/wwwroot/assets/eh-organization.png differ diff --git a/src/EventHub.Application.Contracts/Events/CreateEventDto.cs b/src/EventHub.Application.Contracts/Events/CreateEventDto.cs index 801c904..a508867 100644 --- a/src/EventHub.Application.Contracts/Events/CreateEventDto.cs +++ b/src/EventHub.Application.Contracts/Events/CreateEventDto.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; using JetBrains.Annotations; +using Volo.Abp.Content; namespace EventHub.Events { @@ -24,7 +25,7 @@ namespace EventHub.Events public string Description { get; set; } [CanBeNull] - public byte[] CoverImageContent { get; set; } + public IRemoteStreamContent CoverImageStreamContent { get; set; } public bool IsOnline { get; set; } diff --git a/src/EventHub.Application.Contracts/Events/EventDetailDto.cs b/src/EventHub.Application.Contracts/Events/EventDetailDto.cs index 6418a9e..ddb3eec 100644 --- a/src/EventHub.Application.Contracts/Events/EventDetailDto.cs +++ b/src/EventHub.Application.Contracts/Events/EventDetailDto.cs @@ -22,9 +22,7 @@ namespace EventHub.Events 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; } diff --git a/src/EventHub.Application.Contracts/Events/EventInListDto.cs b/src/EventHub.Application.Contracts/Events/EventInListDto.cs index d6208c1..a2235ac 100644 --- a/src/EventHub.Application.Contracts/Events/EventInListDto.cs +++ b/src/EventHub.Application.Contracts/Events/EventInListDto.cs @@ -16,8 +16,6 @@ namespace EventHub.Events public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } - - public byte[] CoverImageContent { get; set; } public bool IsOnline { get; set; } diff --git a/src/EventHub.Application.Contracts/Events/IEventAppService.cs b/src/EventHub.Application.Contracts/Events/IEventAppService.cs index dd253d3..bd1238f 100644 --- a/src/EventHub.Application.Contracts/Events/IEventAppService.cs +++ b/src/EventHub.Application.Contracts/Events/IEventAppService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; +using Volo.Abp.Content; namespace EventHub.Events { @@ -22,6 +23,6 @@ namespace EventHub.Events Task UpdateAsync(Guid id, UpdateEventDto input); - Task GetCoverImageAsync(Guid id); + Task GetCoverImageAsync(Guid id); } } diff --git a/src/EventHub.Application.Contracts/Events/UpdateEventDto.cs b/src/EventHub.Application.Contracts/Events/UpdateEventDto.cs index 82aae30..0e1edf6 100644 --- a/src/EventHub.Application.Contracts/Events/UpdateEventDto.cs +++ b/src/EventHub.Application.Contracts/Events/UpdateEventDto.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; using JetBrains.Annotations; +using Volo.Abp.Content; namespace EventHub.Events { @@ -15,7 +16,7 @@ namespace EventHub.Events public string Description { get; set; } [CanBeNull] - public byte[] CoverImageContent { get; set; } + public IRemoteStreamContent CoverImageStreamContent { get; set; } [Required] [DataType(DataType.DateTime)] diff --git a/src/EventHub.Application.Contracts/Organizations/CreateOrganizationDto.cs b/src/EventHub.Application.Contracts/Organizations/CreateOrganizationDto.cs index 08cb45a..32560d1 100644 --- a/src/EventHub.Application.Contracts/Organizations/CreateOrganizationDto.cs +++ b/src/EventHub.Application.Contracts/Organizations/CreateOrganizationDto.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using JetBrains.Annotations; +using Volo.Abp.Content; namespace EventHub.Organizations { @@ -18,7 +19,7 @@ namespace EventHub.Organizations public string Description { get; set; } [CanBeNull] - public byte[] ProfilePictureContent { get; set; } + public RemoteStreamContent ProfilePictureStreamContent { get; set; } [StringLength(OrganizationConsts.MaxWebsiteLength)] public string Website { get; set; } diff --git a/src/EventHub.Application.Contracts/Organizations/IOrganizationAppService.cs b/src/EventHub.Application.Contracts/Organizations/IOrganizationAppService.cs index 0516a30..55fa1a7 100644 --- a/src/EventHub.Application.Contracts/Organizations/IOrganizationAppService.cs +++ b/src/EventHub.Application.Contracts/Organizations/IOrganizationAppService.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; +using Volo.Abp.Content; namespace EventHub.Organizations { @@ -18,5 +19,7 @@ namespace EventHub.Organizations Task IsOrganizationOwnerAsync(Guid organizationId); Task UpdateAsync(Guid id, UpdateOrganizationDto input); + + Task GetProfilePictureAsync(Guid id); } } diff --git a/src/EventHub.Application.Contracts/Organizations/OrganizationInListDto.cs b/src/EventHub.Application.Contracts/Organizations/OrganizationInListDto.cs index f064065..16f6fd3 100644 --- a/src/EventHub.Application.Contracts/Organizations/OrganizationInListDto.cs +++ b/src/EventHub.Application.Contracts/Organizations/OrganizationInListDto.cs @@ -10,7 +10,5 @@ namespace EventHub.Organizations public string DisplayName { get; set; } public string Description { get; set; } - - public byte[] ProfilePictureContent { get; set; } } } diff --git a/src/EventHub.Application.Contracts/Organizations/OrganizationProfileDto.cs b/src/EventHub.Application.Contracts/Organizations/OrganizationProfileDto.cs index 1f5f89a..f7999ef 100644 --- a/src/EventHub.Application.Contracts/Organizations/OrganizationProfileDto.cs +++ b/src/EventHub.Application.Contracts/Organizations/OrganizationProfileDto.cs @@ -26,7 +26,5 @@ namespace EventHub.Organizations public string InstagramUsername { get; set; } public string MediumUsername { get; set; } - - public byte[] ProfilePictureContent { get; set; } } } diff --git a/src/EventHub.Application.Contracts/Organizations/UpdateOrganizationDto.cs b/src/EventHub.Application.Contracts/Organizations/UpdateOrganizationDto.cs index 8d43241..e5a2da4 100644 --- a/src/EventHub.Application.Contracts/Organizations/UpdateOrganizationDto.cs +++ b/src/EventHub.Application.Contracts/Organizations/UpdateOrganizationDto.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using JetBrains.Annotations; +using Volo.Abp.Content; namespace EventHub.Organizations { @@ -14,7 +15,7 @@ namespace EventHub.Organizations public string Description { get; set; } [CanBeNull] - public byte[] ProfilePictureContent { get; set; } + public RemoteStreamContent ProfilePictureStreamContent { get; set; } [CanBeNull] [StringLength(OrganizationConsts.MaxWebsiteLength)] diff --git a/src/EventHub.Application/Events/EventAppService.cs b/src/EventHub.Application/Events/EventAppService.cs index a116920..17bf152 100644 --- a/src/EventHub.Application/Events/EventAppService.cs +++ b/src/EventHub.Application/Events/EventAppService.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Dtos; using Volo.Abp.Authorization; using Volo.Abp.BlobStoring; +using Volo.Abp.Content; using Volo.Abp.Domain.Repositories; using Volo.Abp.Identity; using Volo.Abp.Users; @@ -73,9 +74,9 @@ namespace EventHub.Events await _eventManager.SetCapacityAsync(@event, input.Capacity); - if (input.CoverImageContent != null && input.CoverImageContent.Length > 0) + if (input.CoverImageStreamContent != null && input.CoverImageStreamContent.ContentLength > 0) { - await SaveCoverImageAsync(@event.Id, input.CoverImageContent); + await SaveCoverImageAsync(@event.Id, input.CoverImageStreamContent); } await _eventRepository.InsertAsync(@event); @@ -161,11 +162,6 @@ namespace EventHub.Events } ).ToList(); - foreach (var @event in events) - { - @event.CoverImageContent = await GetCoverImageAsync(@event.Id); - } - return new PagedResultDto(totalCount, events); } @@ -179,8 +175,7 @@ namespace EventHub.Events dto.OrganizationId = organization.Id; dto.OrganizationName = organization.Name; dto.OrganizationDisplayName = organization.DisplayName; - dto.CoverImageContent = await GetCoverImageAsync(dto.Id); - + var user = await _userRepository.GetAsync(organization.OwnerUserId); dto.OwnerUserName = user.UserName; dto.OwnerEmail = user.Email; @@ -254,26 +249,33 @@ namespace EventHub.Events @event.SetTime(input.StartTime, input.EndTime); await _eventManager.SetCapacityAsync(@event, input.Capacity); - if (input.CoverImageContent != null && input.CoverImageContent.Length > 0) + if (input.CoverImageStreamContent != null && input.CoverImageStreamContent.ContentLength > 0) { - await SaveCoverImageAsync(@event.Id, input.CoverImageContent); + await SaveCoverImageAsync(@event.Id, input.CoverImageStreamContent); } await _eventRepository.UpdateAsync(@event); } - public async Task GetCoverImageAsync(Guid id) + public async Task GetCoverImageAsync(Guid id) { var blobName = id.ToString(); - return await _eventBlobContainer.GetAllBytesOrNullAsync(blobName); + var coverImageStream = await _eventBlobContainer.GetOrNullAsync(blobName); + + if (coverImageStream is null) + { + return null; + } + + return new RemoteStreamContent(coverImageStream, blobName); } - private async Task SaveCoverImageAsync(Guid id, byte[] coverImageContent) + private async Task SaveCoverImageAsync(Guid id, IRemoteStreamContent streamContent) { var blobName = id.ToString(); - await _eventBlobContainer.SaveAsync(blobName, coverImageContent, overrideExisting: true); + await _eventBlobContainer.SaveAsync(blobName, streamContent.GetStream(), overrideExisting: true); } } } \ No newline at end of file diff --git a/src/EventHub.Application/Organizations/OrganizationAppService.cs b/src/EventHub.Application/Organizations/OrganizationAppService.cs index 7d36f4c..f4dc8e7 100644 --- a/src/EventHub.Application/Organizations/OrganizationAppService.cs +++ b/src/EventHub.Application/Organizations/OrganizationAppService.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Dtos; using Volo.Abp.Authorization; using Volo.Abp.BlobStoring; +using Volo.Abp.Content; using Volo.Abp.Domain.Repositories; using Volo.Abp.Identity; using Volo.Abp.Users; @@ -52,12 +53,12 @@ namespace EventHub.Organizations organization.FacebookUsername = input.FacebookUsername; organization.InstagramUsername = input.InstagramUsername; organization.MediumUsername = input.MediumUsername; - + await _organizationRepository.InsertAsync(organization, true); - - if (input.ProfilePictureContent != null && input.ProfilePictureContent.Length > 0) + + if (input.ProfilePictureStreamContent != null && input.ProfilePictureStreamContent.ContentLength > 0) { - await SaveProfilePictureAsync(organization.Id, input.ProfilePictureContent); + await SaveProfilePictureAsync(organization.Id, input.ProfilePictureStreamContent); } } @@ -67,13 +68,13 @@ namespace EventHub.Organizations var organizationMemberQueryable = await _organizationMembershipsRepository.GetQueryableAsync(); var query = organizationQueryable; - + if (input.RegisteredUserId.HasValue) { var registeredOrganization = organizationMemberQueryable .Where(x => x.UserId == input.RegisteredUserId) .Select(x => x.OrganizationId); - + var organizationIds = await AsyncExecuter.ToListAsync(registeredOrganization); query = query.Where(x => organizationIds.Contains(x.Id)); } @@ -84,11 +85,6 @@ namespace EventHub.Organizations var organizationDto = ObjectMapper .Map, List>(await AsyncExecuter.ToListAsync(query)); - - foreach (var organization in organizationDto) - { - organization.ProfilePictureContent = await GetProfilePictureAsync(organization.Id); - } return new PagedResultDto( totalCount, @@ -104,7 +100,6 @@ namespace EventHub.Organizations var owner = await _userRepository.GetAsync(u => u.Id == organization.OwnerUserId); organizationProfileDto.OwnerUserName = owner.UserName; organizationProfileDto.OwnerEmail = owner.Email; - organizationProfileDto.ProfilePictureContent = await GetProfilePictureAsync(organizationProfileDto.Id); return organizationProfileDto; } @@ -116,17 +111,13 @@ namespace EventHub.Organizations var organizationDto = ObjectMapper.Map, List>(organizations); - foreach (var organization in organizationDto) - { - organization.ProfilePictureContent = await GetProfilePictureAsync(organization.Id); - } - return new ListResultDto(organizationDto); } public async Task IsOrganizationOwnerAsync(Guid organizationId) { - return CurrentUser.Id.HasValue && await _organizationRepository.AnyAsync(x => x.Id == organizationId && x.OwnerUserId == CurrentUser.Id.Value); + return CurrentUser.Id.HasValue && await _organizationRepository + .AnyAsync(x => x.Id == organizationId && x.OwnerUserId == CurrentUser.Id.Value); } [Authorize] @@ -148,35 +139,34 @@ namespace EventHub.Organizations organization.InstagramUsername = input.InstagramUsername; organization.FacebookUsername = input.FacebookUsername; organization.MediumUsername = input.MediumUsername; - - if (input.ProfilePictureContent != null && input.ProfilePictureContent.Length > 0) + + if (input.ProfilePictureStreamContent != null && input.ProfilePictureStreamContent.ContentLength > 0) { - await SaveProfilePictureAsync(organization.Id, input.ProfilePictureContent); + await SaveProfilePictureAsync(organization.Id, input.ProfilePictureStreamContent); } - + await _organizationRepository.UpdateAsync(organization); } - - private async Task SaveProfilePictureAsync(Guid id, byte[] bytes) + + public async Task GetProfilePictureAsync(Guid id) { - var organization = await _organizationRepository.GetAsync(x => x.Id == id); + var blobName = id.ToString(); + + var pictureContent = await _organizationBlobContainer.GetOrNullAsync(blobName); - if (organization.OwnerUserId != CurrentUser.GetId()) + if (pictureContent is null) { - throw new AbpAuthorizationException(EventHubErrorCodes.NotAuthorizedToUpdateOrganizationProfile) - .WithData("Name", organization.DisplayName); + return null; } - - var blobName = id.ToString(); - - await _organizationBlobContainer.SaveAsync(blobName, bytes, overrideExisting: true); + + return new RemoteStreamContent(pictureContent, blobName); } - private async Task GetProfilePictureAsync(Guid id) + private async Task SaveProfilePictureAsync(Guid id, IRemoteStreamContent streamContent) { var blobName = id.ToString(); - return await _organizationBlobContainer.GetAllBytesOrNullAsync(blobName); + await _organizationBlobContainer.SaveAsync(blobName, streamContent.GetStream(), overrideExisting: true); } } -} +} \ No newline at end of file diff --git a/src/EventHub.Domain.Shared/Organizations/OrganizationContst.cs b/src/EventHub.Domain.Shared/Organizations/OrganizationContst.cs index 712dcf6..1097ad3 100644 --- a/src/EventHub.Domain.Shared/Organizations/OrganizationContst.cs +++ b/src/EventHub.Domain.Shared/Organizations/OrganizationContst.cs @@ -26,5 +26,7 @@ public const int MaxInstagramUsernameLength = 24; public const int MaxMediumUsernameLength = 24; + + public static string[] AllowedProfilePictureExtensions = { ".jpg", ".png" }; } } diff --git a/src/EventHub.HttpApi.Host/Controllers/Events/EventController.cs b/src/EventHub.HttpApi.Host/Controllers/Events/EventController.cs index b1f6e34..458f67c 100644 --- a/src/EventHub.HttpApi.Host/Controllers/Events/EventController.cs +++ b/src/EventHub.HttpApi.Host/Controllers/Events/EventController.cs @@ -6,6 +6,8 @@ using Microsoft.AspNetCore.Mvc; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Content; +using Volo.Abp.VirtualFileSystem; namespace EventHub.Controllers.Events { @@ -16,14 +18,18 @@ namespace EventHub.Controllers.Events public class EventController : AbpController, IEventAppService { private readonly IEventAppService _eventAppService; + private readonly IVirtualFileProvider _virtualFileProvider; - public EventController(IEventAppService eventAppService) + public EventController( + IEventAppService eventAppService, + IVirtualFileProvider virtualFileProvider) { _eventAppService = eventAppService; + _virtualFileProvider = virtualFileProvider; } [HttpPost] - public async Task CreateAsync(CreateEventDto input) + public async Task CreateAsync([FromForm] CreateEventDto input) { return await _eventAppService.CreateAsync(input); } @@ -64,16 +70,28 @@ namespace EventHub.Controllers.Events [HttpPut] [Route("{id}")] - public async Task UpdateAsync(Guid id, UpdateEventDto input) + public async Task UpdateAsync(Guid id, [FromForm] UpdateEventDto input) { await _eventAppService.UpdateAsync(id, input); } [HttpGet] [Route("cover-image/{id}")] - public async Task GetCoverImageAsync(Guid id) + public async Task GetCoverImageAsync(Guid id) { - return await _eventAppService.GetCoverImageAsync(id); + var remoteStreamContent = await _eventAppService.GetCoverImageAsync(id); + + if (remoteStreamContent is null) + { + var stream = _virtualFileProvider.GetFileInfo("/Images/eh-event.png").CreateReadStream(); + remoteStreamContent = new RemoteStreamContent(stream); + await stream.FlushAsync(); + } + + Response.Headers.Add("Accept-Ranges", "bytes"); + Response.ContentType = remoteStreamContent.ContentType; + + return remoteStreamContent; } } } \ No newline at end of file diff --git a/src/EventHub.HttpApi.Host/Controllers/Organizations/OrganizationController.cs b/src/EventHub.HttpApi.Host/Controllers/Organizations/OrganizationController.cs index 3f090b5..471657a 100644 --- a/src/EventHub.HttpApi.Host/Controllers/Organizations/OrganizationController.cs +++ b/src/EventHub.HttpApi.Host/Controllers/Organizations/OrganizationController.cs @@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Mvc; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Content; +using Volo.Abp.VirtualFileSystem; namespace EventHub.Controllers.Organizations { @@ -15,14 +17,18 @@ namespace EventHub.Controllers.Organizations public class OrganizationController : AbpController, IOrganizationAppService { private readonly IOrganizationAppService _organizationAppService; + private readonly IVirtualFileProvider _virtualFileProvider; - public OrganizationController(IOrganizationAppService organizationAppService) + public OrganizationController( + IOrganizationAppService organizationAppService, + IVirtualFileProvider virtualFileProvider) { _organizationAppService = organizationAppService; + _virtualFileProvider = virtualFileProvider; } [HttpPost] - public async Task CreateAsync(CreateOrganizationDto input) + public async Task CreateAsync([FromForm] CreateOrganizationDto input) { await _organizationAppService.CreateAsync(input); } @@ -56,9 +62,28 @@ namespace EventHub.Controllers.Organizations [HttpPut] [Route("{id}")] - public async Task UpdateAsync(Guid id, UpdateOrganizationDto input) + public async Task UpdateAsync(Guid id, [FromForm] UpdateOrganizationDto input) { await _organizationAppService.UpdateAsync(id, input); } + + [HttpGet] + [Route("profile-picture/{id}")] + public async Task GetProfilePictureAsync(Guid id) + { + var remoteStreamContent = await _organizationAppService.GetProfilePictureAsync(id); + + if (remoteStreamContent is null) + { + var stream = _virtualFileProvider.GetFileInfo("/Images/eh-organization.png").CreateReadStream(); + remoteStreamContent = new RemoteStreamContent(stream); + await stream.FlushAsync(); + } + + Response.Headers.Add("Accept-Ranges", "bytes"); + Response.ContentType = remoteStreamContent.ContentType; + + return remoteStreamContent; + } } } \ No newline at end of file diff --git a/src/EventHub.HttpApi.Host/EventHub.HttpApi.Host.csproj b/src/EventHub.HttpApi.Host/EventHub.HttpApi.Host.csproj index bc4101e..bb78248 100644 --- a/src/EventHub.HttpApi.Host/EventHub.HttpApi.Host.csproj +++ b/src/EventHub.HttpApi.Host/EventHub.HttpApi.Host.csproj @@ -33,6 +33,7 @@ + diff --git a/src/EventHub.HttpApi.Host/EventHubHttpApiHostModule.cs b/src/EventHub.HttpApi.Host/EventHubHttpApiHostModule.cs index b164b37..a9eceb1 100644 --- a/src/EventHub.HttpApi.Host/EventHubHttpApiHostModule.cs +++ b/src/EventHub.HttpApi.Host/EventHubHttpApiHostModule.cs @@ -11,6 +11,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using EventHub.EntityFrameworkCore; +using EventHub.Events; +using EventHub.Organizations; using EventHub.Utils; using EventHub.Web; using Microsoft.AspNetCore.Localization; @@ -61,6 +63,18 @@ namespace EventHub ConfigureCookies(context); ConfigureSwaggerServices(context, configuration); ConfigureBackgroundJobs(); + ConfigureAutoApiControllers(); + } + + private void ConfigureAutoApiControllers() + { + Configure(options => + { + options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(CreateOrganizationDto)); + options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(UpdateOrganizationDto)); + options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(CreateEventDto)); + options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(UpdateEventDto)); + }); } private void ConfigureBackgroundJobs() @@ -79,10 +93,14 @@ namespace EventHub private void ConfigureVirtualFileSystem(ServiceConfigurationContext context) { var hostingEnvironment = context.Services.GetHostingEnvironment(); - - if (hostingEnvironment.IsDevelopment()) + + Configure(options => { - Configure(options => + options.FileSets.AddEmbedded( + baseNamespace: "EventHub", + baseFolder: "/Images"); + + if (hostingEnvironment.IsDevelopment()) { options.FileSets.ReplaceEmbeddedByPhysical( Path.Combine(hostingEnvironment.ContentRootPath, @@ -96,8 +114,8 @@ namespace EventHub options.FileSets.ReplaceEmbeddedByPhysical( Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}EventHub.Application")); - }); - } + } + }); } private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) diff --git a/src/EventHub.HttpApi.Host/Images/eh-event.png b/src/EventHub.HttpApi.Host/Images/eh-event.png new file mode 100644 index 0000000..b49547e Binary files /dev/null and b/src/EventHub.HttpApi.Host/Images/eh-event.png differ diff --git a/src/EventHub.HttpApi.Host/Images/eh-organization.png b/src/EventHub.HttpApi.Host/Images/eh-organization.png new file mode 100644 index 0000000..fcf5b0c Binary files /dev/null and b/src/EventHub.HttpApi.Host/Images/eh-organization.png differ diff --git a/src/EventHub.Web/Controllers/EventController.cs b/src/EventHub.Web/Controllers/EventController.cs index f5497f3..fcc385b 100644 --- a/src/EventHub.Web/Controllers/EventController.cs +++ b/src/EventHub.Web/Controllers/EventController.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using System.Threading.Tasks; using EventHub.Events; @@ -29,19 +28,5 @@ namespace EventHub.Web.Controllers ViewData = ViewData }; } - - [HttpGet] - [Route("cover-picture-source/{eventId}")] - public async Task GetArticleCoverImageAsync(Guid eventId) - { - var coverImageContent = await _eventAppService.GetCoverImageAsync(eventId); - - if (coverImageContent == null) - { - return NotFound(); - } - - return File(coverImageContent, "image/jpeg"); - } } } \ No newline at end of file diff --git a/src/EventHub.Web/EventHubWebAutoMapperProfile.cs b/src/EventHub.Web/EventHubWebAutoMapperProfile.cs index 94e74fc..014ea33 100644 --- a/src/EventHub.Web/EventHubWebAutoMapperProfile.cs +++ b/src/EventHub.Web/EventHubWebAutoMapperProfile.cs @@ -13,7 +13,7 @@ namespace EventHub.Web { CreateMap(); CreateMap() - .Ignore(x => x.CoverImageContent); + .Ignore(x => x.CoverImageStreamContent); CreateMap(); CreateMap(); CreateMap(); diff --git a/src/EventHub.Web/Pages/Events/Components/EventsArea/_eventListSection.cshtml b/src/EventHub.Web/Pages/Events/Components/EventsArea/_eventListSection.cshtml index 20d23e6..e8e5ba0 100644 --- a/src/EventHub.Web/Pages/Events/Components/EventsArea/_eventListSection.cshtml +++ b/src/EventHub.Web/Pages/Events/Components/EventsArea/_eventListSection.cshtml @@ -1,22 +1,16 @@ @using System.Globalization -@using EventHub.Web.Helpers +@using EventHub.Web @using EventHub.Web.Pages.Events +@using Microsoft.Extensions.Options @model List +@inject IOptions UrlOptions @foreach (var eventDto in Model) {
- @if (eventDto.CoverImageContent is null) - { -
-
- } - else - { -
-
- } +
+
@@ -32,7 +26,7 @@ @eventDto.StartTime.ToString("hh tt", CultureInfo.InvariantCulture) - @eventDto.EndTime.ToString("hh tt", CultureInfo.InvariantCulture) | @EventDateHelper.GetLocationInfo(eventDto)

@eventDto.Description

- Learn More + Learn More
diff --git a/src/EventHub.Web/Pages/Events/Detail.cshtml b/src/EventHub.Web/Pages/Events/Detail.cshtml index 9c0c15e..67c825b 100644 --- a/src/EventHub.Web/Pages/Events/Detail.cshtml +++ b/src/EventHub.Web/Pages/Events/Detail.cshtml @@ -24,17 +24,7 @@ @{ string description = Model.Event.Description.Length >= 160 ? Model.Event.Description.TruncateWithPostfix(155) : Model.Event.Description; string title = Model.Event.Title; - string coverImageUrl; - - if (Model.Event.CoverImageContent is null) - { - // TODO(berkansasmaz): Get a default image from the design team to be used in social media posts - coverImageUrl = UrlOptions.Value.Www + "/assets/slide.jpg"; - } - else - { - coverImageUrl = $"{UrlOptions.Value.Www}/api/event/cover-picture-source/{Model.Event.Id}"; - } + string coverImageUrl = UrlOptions.Value.Api.EnsureEndsWith('/') + $"api/eventhub/event/cover-image/{Model.Event.Id}"; ViewBag.Title = title; ViewBag.Description = description; @@ -52,14 +42,7 @@
- @if (Model.Event.CoverImageContent == null) - { - @Model.Event.Title - } - else - { - @Model.Event.Title - } + @Model.Event.Title
diff --git a/src/EventHub.Web/Pages/Events/Edit.cshtml b/src/EventHub.Web/Pages/Events/Edit.cshtml index c5d3d40..dfe4c1d 100644 --- a/src/EventHub.Web/Pages/Events/Edit.cshtml +++ b/src/EventHub.Web/Pages/Events/Edit.cshtml @@ -1,8 +1,11 @@ @page "/event/edit/{url}" @inject IHtmlLocalizer L @using EventHub.Localization +@using EventHub.Web @using Microsoft.AspNetCore.Mvc.Localization +@using Microsoft.Extensions.Options @model EventHub.Web.Pages.Events.EditPageModel +@inject IOptions UrlOptions @section scripts { @@ -62,14 +65,7 @@
- @if (Model.CoverImageContent != null && Model.CoverImageContent.Length > 0) - { - - } - else - { - - } +
diff --git a/src/EventHub.Web/Pages/Events/Edit.cshtml.cs b/src/EventHub.Web/Pages/Events/Edit.cshtml.cs index 26758dd..53420dd 100644 --- a/src/EventHub.Web/Pages/Events/Edit.cshtml.cs +++ b/src/EventHub.Web/Pages/Events/Edit.cshtml.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; +using Volo.Abp.Content; using Volo.Abp.Users; namespace EventHub.Web.Pages.Events @@ -29,7 +30,6 @@ namespace EventHub.Web.Pages.Events public List Organizations { get; private set; } public List Countries { get; private set; } public List Languages { get; private set; } - public byte[] CoverImageContent { get; private set; } private readonly IEventAppService _eventAppService; private readonly IOrganizationAppService _organizationAppService; @@ -46,7 +46,7 @@ namespace EventHub.Web.Pages.Events { var urlCode = EventUrlCodeHelper.GetCodeFromUrl(Url); var eventDetailDto = await _eventAppService.GetByUrlCodeAsync(urlCode); - CoverImageContent = eventDetailDto.CoverImageContent; + Event = ObjectMapper.Map(eventDetailDto); await FillOrganizationsAsync(); @@ -60,19 +60,22 @@ namespace EventHub.Web.Pages.Events { ValidateModel(); - var input = ObjectMapper.Map(Event); + var updateEventDto = ObjectMapper.Map(Event); + await using var memoryStream = new MemoryStream(); if (Event.CoverImageFile != null && Event.CoverImageFile.Length > 0) { - using (var memoryStream = new MemoryStream()) + await Event.CoverImageFile.CopyToAsync(memoryStream); + updateEventDto.CoverImageStreamContent = new RemoteStreamContent(memoryStream) { - await Event.CoverImageFile.CopyToAsync(memoryStream); - input.CoverImageContent = memoryStream.ToArray(); - } + ContentType = Event.CoverImageFile.ContentType, + FileName = Event.CoverImageFile.FileName, + }; } - await _eventAppService.UpdateAsync(Event.Id, input); - + await _eventAppService.UpdateAsync(Event.Id, updateEventDto); + await memoryStream.DisposeAsync(); + return RedirectToPage("./Detail", new { url = Url }); } catch (Exception exception) diff --git a/src/EventHub.Web/Pages/Events/New.cshtml.cs b/src/EventHub.Web/Pages/Events/New.cshtml.cs index 73140d4..98e2e37 100644 --- a/src/EventHub.Web/Pages/Events/New.cshtml.cs +++ b/src/EventHub.Web/Pages/Events/New.cshtml.cs @@ -14,13 +14,15 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; +using Volo.Abp.Content; using Volo.Abp.Users; namespace EventHub.Web.Pages.Events { public class NewPageModel : EventHubPageModel { - [BindProperty] public NewEventViewModel Event { get; set; } + [BindProperty] + public NewEventViewModel Event { get; set; } public List Organizations { get; private set; } public List Countries { get; private set; } @@ -56,18 +58,22 @@ namespace EventHub.Web.Pages.Events { ValidateModel(); - var input = ObjectMapper.Map(Event); + var createEventDto = ObjectMapper.Map(Event); + await using var memoryStream = new MemoryStream(); if (Event.CoverImageFile != null && Event.CoverImageFile.Length > 0) { - using (var memoryStream = new MemoryStream()) + await Event.CoverImageFile.CopyToAsync(memoryStream); + + createEventDto.CoverImageStreamContent = new RemoteStreamContent(memoryStream) { - await Event.CoverImageFile.CopyToAsync(memoryStream); - input.CoverImageContent = memoryStream.ToArray(); - } + ContentType = Event.CoverImageFile.ContentType, + FileName = Event.CoverImageFile.FileName + }; } - var eventDto = await _eventAppService.CreateAsync(input); + var eventDto = await _eventAppService.CreateAsync(createEventDto); + await memoryStream.DisposeAsync(); return RedirectToPage("/Events/Detail", new {url = eventDto.UrlCode}); } diff --git a/src/EventHub.Web/Pages/Index.cshtml b/src/EventHub.Web/Pages/Index.cshtml index b5e6042..0bc2774 100644 --- a/src/EventHub.Web/Pages/Index.cshtml +++ b/src/EventHub.Web/Pages/Index.cshtml @@ -152,16 +152,8 @@
- @if (Model.OnlineEvents[i].CoverImageContent is null) - { -
-
- } - else - { -
-
- } +
+
@@ -171,7 +163,7 @@ @Model.OnlineEvents[i].StartTime.ToString("MMMM dd, yyyy dddd")
@Model.OnlineEvents[i].StartTime.ToString("hh tt", CultureInfo.InvariantCulture) - @Model.OnlineEvents[i].EndTime.ToString("hh tt", CultureInfo.InvariantCulture) | Online

@Model.OnlineEvents[i].Description

- Learn More + Learn More
@@ -182,16 +174,8 @@
- @if (Model.OnlineEvents[i + 1].CoverImageContent is null) - { -
-
- } - else - { -
-
- } +
+
diff --git a/src/EventHub.Web/Pages/Organizations/Components/OrganizationsArea/_organizationListSection.cshtml b/src/EventHub.Web/Pages/Organizations/Components/OrganizationsArea/_organizationListSection.cshtml index 07f9fdf..cec73b8 100644 --- a/src/EventHub.Web/Pages/Organizations/Components/OrganizationsArea/_organizationListSection.cshtml +++ b/src/EventHub.Web/Pages/Organizations/Components/OrganizationsArea/_organizationListSection.cshtml @@ -1,19 +1,14 @@ +@using EventHub.Web +@using Microsoft.Extensions.Options @model List +@inject IOptions UrlOptions @foreach (var organization in Model) {
- @if (organization.ProfilePictureContent == null) - { -
-
- } - else - { -
-
- } +
+
diff --git a/src/EventHub.Web/Pages/Organizations/Edit.cshtml b/src/EventHub.Web/Pages/Organizations/Edit.cshtml index 091efaa..708f518 100644 --- a/src/EventHub.Web/Pages/Organizations/Edit.cshtml +++ b/src/EventHub.Web/Pages/Organizations/Edit.cshtml @@ -2,9 +2,12 @@ @inject IHtmlLocalizer L @using EventHub.Localization @using EventHub.Organizations +@using EventHub.Web @using Microsoft.AspNetCore.Mvc.Localization +@using Microsoft.Extensions.Options @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @model EventHub.Web.Pages.Organizations.EditPageModel +@inject IOptions UrlOptions @section scripts { @@ -26,14 +29,7 @@
- @if (Model.ProfilePictureContent != null && Model.ProfilePictureContent.Length > 0) - { - - } - else - { - - } +
diff --git a/src/EventHub.Web/Pages/Organizations/Edit.cshtml.cs b/src/EventHub.Web/Pages/Organizations/Edit.cshtml.cs index 221eddc..b2f7c36 100644 --- a/src/EventHub.Web/Pages/Organizations/Edit.cshtml.cs +++ b/src/EventHub.Web/Pages/Organizations/Edit.cshtml.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; +using Volo.Abp.Content; namespace EventHub.Web.Pages.Organizations { @@ -20,7 +21,6 @@ namespace EventHub.Web.Pages.Organizations [BindProperty] public EditOrganizationViewModel Organization { get; set; } - public byte[] ProfilePictureContent { get; private set; } private readonly IOrganizationAppService _organizationAppService; @@ -32,7 +32,6 @@ namespace EventHub.Web.Pages.Organizations public async Task OnGetAsync() { var organizationProfileDto = await _organizationAppService.GetProfileAsync(Name); - ProfilePictureContent = organizationProfileDto.ProfilePictureContent; Organization = ObjectMapper.Map(organizationProfileDto); } @@ -43,18 +42,21 @@ namespace EventHub.Web.Pages.Organizations { ValidateModel(); - var input = ObjectMapper.Map(Organization); - + var updateOrganizationDto = ObjectMapper.Map(Organization); + + await using var memoryStream = new MemoryStream(); if (Organization.ProfilePictureFile != null && Organization.ProfilePictureFile.Length > 0) { - using (var memoryStream = new MemoryStream()) + await Organization.ProfilePictureFile.CopyToAsync(memoryStream); + updateOrganizationDto.ProfilePictureStreamContent = new RemoteStreamContent(memoryStream) { - await Organization.ProfilePictureFile.CopyToAsync(memoryStream); - input.ProfilePictureContent = memoryStream.ToArray(); - } + ContentType = Organization.ProfilePictureFile.ContentType, + FileName = Organization.ProfilePictureFile.FileName, + }; } - await _organizationAppService.UpdateAsync(Organization.Id, input); + await _organizationAppService.UpdateAsync(Organization.Id, updateOrganizationDto); + await memoryStream.DisposeAsync(); return RedirectToPage("./Profile", new { name = Name }); } diff --git a/src/EventHub.Web/Pages/Organizations/New.cshtml.cs b/src/EventHub.Web/Pages/Organizations/New.cshtml.cs index 4789a5b..e7edcf6 100644 --- a/src/EventHub.Web/Pages/Organizations/New.cshtml.cs +++ b/src/EventHub.Web/Pages/Organizations/New.cshtml.cs @@ -9,13 +9,15 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; +using Volo.Abp.Content; namespace EventHub.Web.Pages.Organizations { [Authorize] public class NewPageModel : EventHubPageModel { - [BindProperty] public CreateOrganizationViewModel Organization { get; set; } + [BindProperty] + public CreateOrganizationViewModel Organization { get; set; } private readonly IOrganizationAppService _organizationAppService; @@ -35,18 +37,22 @@ namespace EventHub.Web.Pages.Organizations { ValidateModel(); - var input = ObjectMapper.Map(Organization); - + var createOrganizationDto = ObjectMapper.Map(Organization); + + await using var memoryStream = new MemoryStream(); if (Organization.ProfilePictureFile != null && Organization.ProfilePictureFile.Length > 0) { - using (var memoryStream = new MemoryStream()) + await Organization.ProfilePictureFile.CopyToAsync(memoryStream); + + createOrganizationDto.ProfilePictureStreamContent = new RemoteStreamContent(memoryStream) { - await Organization.ProfilePictureFile.CopyToAsync(memoryStream); - input.ProfilePictureContent = memoryStream.ToArray(); - } + ContentType = Organization.ProfilePictureFile.ContentType, + FileName = Organization.ProfilePictureFile.FileName + }; } - await _organizationAppService.CreateAsync(input); + await _organizationAppService.CreateAsync(createOrganizationDto); + await memoryStream.DisposeAsync(); return RedirectToPage("./Profile", new {name = Organization.Name}); } diff --git a/src/EventHub.Web/Pages/Organizations/Profile.cshtml b/src/EventHub.Web/Pages/Organizations/Profile.cshtml index c8e8e0f..6d0e148 100644 --- a/src/EventHub.Web/Pages/Organizations/Profile.cshtml +++ b/src/EventHub.Web/Pages/Organizations/Profile.cshtml @@ -1,13 +1,16 @@ @page "/organizations/{name}" @inject IHtmlLocalizer L @using EventHub.Localization +@using EventHub.Web @using EventHub.Web.Pages.Events.Components.EventsArea @using EventHub.Web.Pages.Organizations.Components.JoinArea @using EventHub.Web.Pages.Organizations.Components.MembersArea @using Microsoft.AspNetCore.Mvc.Localization +@using Microsoft.Extensions.Options @using Volo.Abp.Timing @model EventHub.Web.Pages.Organizations.ProfilePageModel @inject IClock Clock +@inject IOptions UrlOptions @section scripts { @@ -18,14 +21,7 @@
- @if (Model.Organization.ProfilePictureContent == null) - { - @Model.Organization.Name - } - else - { - @Model.Organization.Name - } + @Model.Organization.Name