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..e3bc1de 100644 --- a/src/EventHub.Admin.Application/EventHubApplicationAutoMapperProfile.cs +++ b/src/EventHub.Admin.Application/EventHubApplicationAutoMapperProfile.cs @@ -22,8 +22,7 @@ namespace EventHub.Admin CreateMap() .Ignore(x => x.OwnerUserName) - .Ignore(x => x.OwnerEmail) - .Ignore(x => x.ProfilePictureContent); + .Ignore(x => x.OwnerEmail); CreateMap(); diff --git a/src/EventHub.Admin.Application/Organizations/OrganizationAppService.cs b/src/EventHub.Admin.Application/Organizations/OrganizationAppService.cs index 4e21c4e..9c1a491 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 { @@ -106,13 +106,9 @@ 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); - } - else - { - await DeleteCoverImageAsync(organization.Id); + await SaveCoverImageAsync(organization.Id, input.ProfilePictureStreamContent); } await _organizationRepository.UpdateAsync(organization); @@ -134,29 +130,28 @@ 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) - { - var blobName = id.ToString(); - - return await _organizationBlobContainer.GetAllBytesOrNullAsync(blobName); - } - - private async Task SaveCoverImageAsync(Guid id, byte[] coverImageContent) + [AllowAnonymous] + public async Task GetCoverImageAsync(Guid id) { var blobName = id.ToString(); + var coverImageStream = await _organizationBlobContainer.GetOrNullAsync(blobName); + + if (coverImageStream is null) + { + return null; + } - await _organizationBlobContainer.SaveAsync(blobName, coverImageContent, overrideExisting: true); + return new RemoteStreamContent(coverImageStream, blobName); } - private async Task DeleteCoverImageAsync(Guid id) + private async Task SaveCoverImageAsync(Guid id, IRemoteStreamContent coverImageContent) { var blobName = id.ToString(); - await _organizationBlobContainer.DeleteAsync(blobName); + await _organizationBlobContainer.SaveAsync(blobName, coverImageContent.GetStream(), overrideExisting: true); } } } \ No newline at end of file diff --git a/src/EventHub.Admin.HttpApi.Host/Controllers/Organizations/OrganizationController.cs b/src/EventHub.Admin.HttpApi.Host/Controllers/Organizations/OrganizationController.cs index 82fd290..72f3794 100644 --- a/src/EventHub.Admin.HttpApi.Host/Controllers/Organizations/OrganizationController.cs +++ b/src/EventHub.Admin.HttpApi.Host/Controllers/Organizations/OrganizationController.cs @@ -1,10 +1,14 @@ using System; +using System.IO; using System.Threading.Tasks; using EventHub.Admin.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 +20,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] @@ -54,5 +60,30 @@ 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) + { + var stream = _virtualFileProvider + .GetFileInfo("/Images/eh-organization.png") + .CreateReadStream(); + + remoteStreamContent = new RemoteStreamContent(stream) + { + ContentType = "image/png" + }; + 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.Admin.HttpApi.Host/EventHub.Admin.HttpApi.Host.csproj b/src/EventHub.Admin.HttpApi.Host/EventHub.Admin.HttpApi.Host.csproj index f67494c..138285b 100644 --- a/src/EventHub.Admin.HttpApi.Host/EventHub.Admin.HttpApi.Host.csproj +++ b/src/EventHub.Admin.HttpApi.Host/EventHub.Admin.HttpApi.Host.csproj @@ -33,6 +33,7 @@ + diff --git a/src/EventHub.Admin.HttpApi.Host/EventHubAdminHttpApiHostModule.cs b/src/EventHub.Admin.HttpApi.Host/EventHubAdminHttpApiHostModule.cs index a2b47f8..b059e42 100644 --- a/src/EventHub.Admin.HttpApi.Host/EventHubAdminHttpApiHostModule.cs +++ b/src/EventHub.Admin.HttpApi.Host/EventHubAdminHttpApiHostModule.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using EventHub.Admin.Organizations; using EventHub.Admin.Utils; using EventHub.EntityFrameworkCore; using EventHub.Web; @@ -17,6 +18,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 +61,15 @@ namespace EventHub.Admin ConfigureCookies(context); ConfigureSwaggerServices(context, configuration); ConfigureBackgroundJobs(); + ConfigureAutoApiControllers(); + } + + private void ConfigureAutoApiControllers() + { + Configure(options => + { + options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(UpdateOrganizationDto)); + }); } private void ConfigureBackgroundJobs() @@ -78,9 +89,13 @@ namespace EventHub.Admin { var hostingEnvironment = context.Services.GetHostingEnvironment(); - if (hostingEnvironment.IsDevelopment()) + Configure(options => { - Configure(options => + options.FileSets.AddEmbedded( + baseNamespace: "EventHub.Admin", + baseFolder: "/Images"); + + if (hostingEnvironment.IsDevelopment()) { options.FileSets.ReplaceEmbeddedByPhysical( Path.Combine(hostingEnvironment.ContentRootPath, @@ -94,8 +109,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.HttpApi.Host/Images/eh-organization.png b/src/EventHub.Admin.HttpApi.Host/Images/eh-organization.png new file mode 100644 index 0000000..fcf5b0c Binary files /dev/null and b/src/EventHub.Admin.HttpApi.Host/Images/eh-organization.png differ diff --git a/src/EventHub.Admin.Web/Pages/OrganizationManagement.razor b/src/EventHub.Admin.Web/Pages/OrganizationManagement.razor index 6a81b63..3040ad8 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 diff --git a/src/EventHub.Admin.Web/Pages/OrganizationManagement.razor.cs b/src/EventHub.Admin.Web/Pages/OrganizationManagement.razor.cs index d441bae..2463493 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 { @@ -73,7 +74,9 @@ namespace EventHub.Admin.Web.Pages { EditingOrganizationId = input.Id; Organization = await OrganizationAppService.GetAsync(EditingOrganizationId); - FillProfileImageUrl(Organization.ProfilePictureContent); + + FileEntry = new FileEntry(); + ProfileImageUrl = UrlOptions.Value.AdminApi.EnsureEndsWith('/') + "api/eventhub/admin/organization/cover-image/" + EditingOrganizationId; EditingOrganization = ObjectMapper.Map(Organization); EditOrganizationModal.Show(); @@ -88,7 +91,7 @@ namespace EventHub.Admin.Web.Pages private void OnDeleteCoverImageButtonClicked() { - EditingOrganization.ProfilePictureContent = null; + EditingOrganization.ProfilePictureStreamContent = null; FileEntry = new FileEntry(); ProfileImageUrl = null; IsLoadingProfileImage = false; @@ -110,23 +113,26 @@ namespace EventHub.Admin.Web.Pages IsLoadingProfileImage = true; - using (var stream = new MemoryStream()) + var stream = new MemoryStream(); + await FileEntry.WriteToStreamAsync(stream); + stream.Seek(0, SeekOrigin.Begin); + + EditingOrganization.ProfilePictureStreamContent = new RemoteStreamContent(stream) { - await FileEntry.WriteToStreamAsync(stream); + ContentType = FileEntry.Type, + FileName = FileEntry.Name + }; - stream.Seek(0, SeekOrigin.Begin); - EditingOrganization.ProfilePictureContent = stream.ToArray(); - FillProfileImageUrl(EditingOrganization.ProfilePictureContent); - await InvokeAsync(StateHasChanged); - } + SetProfileImageUrl(FileEntry.Type, stream.ToArray()); + await InvokeAsync(StateHasChanged); } - private void FillProfileImageUrl(byte[] content) + private void SetProfileImageUrl(string contentType, byte[] content) { if (content != null) { - var imageBase64Data = Convert.ToBase64String(content); - var imageDataUrl = $"data:image/png;base64,{imageBase64Data}"; + contentType = string.IsNullOrWhiteSpace(contentType) ? "image/png" : contentType; + var imageDataUrl = $"data:{contentType};base64,{Convert.ToBase64String(content)}"; ProfileImageUrl = imageDataUrl; } } diff --git a/src/EventHub.Application/Events/EventAppService.cs b/src/EventHub.Application/Events/EventAppService.cs index fc5ed66..17bf152 100644 --- a/src/EventHub.Application/Events/EventAppService.cs +++ b/src/EventHub.Application/Events/EventAppService.cs @@ -5,6 +5,7 @@ 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.Application/Organizations/OrganizationAppService.cs b/src/EventHub.Application/Organizations/OrganizationAppService.cs index b5a49c4..f4dc8e7 100644 --- a/src/EventHub.Application/Organizations/OrganizationAppService.cs +++ b/src/EventHub.Application/Organizations/OrganizationAppService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using EventHub.Organizations.Memberships; +using EventHub.Users; using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Dtos; using Volo.Abp.Authorization;