Browse Source

Add organization profile section into organization edit page

pull/19/head
EngincanV 6 years ago
parent
commit
5b619f370c
  1. 2
      src/EventHub.Application.Contracts/Organizations/IOrganizationAppService.cs
  2. 2
      src/EventHub.Application.Contracts/Organizations/OrganizationInListDto.cs
  3. 2
      src/EventHub.Application.Contracts/Organizations/OrganizationProfileDto.cs
  4. 56
      src/EventHub.Application/Organizations/OrganizationAppService.cs
  5. 2
      src/EventHub.Domain.Shared/Organizations/OrganizationContst.cs
  6. 10
      src/EventHub.Domain/Organizations/OrganizationProfilePictureContainer.cs
  7. 39
      src/EventHub.Web/Controllers/OrganizationController.cs
  8. 38
      src/EventHub.Web/Helpers/AllowedExtensionsAttribute.cs
  9. 36
      src/EventHub.Web/Helpers/MaxFileSizeAttribute.cs
  10. 53
      src/EventHub.Web/Pages/Organizations/Edit.cshtml
  11. 14
      src/EventHub.Web/Pages/Organizations/Edit.cshtml.cs
  12. 113
      src/EventHub.Web/Pages/Organizations/Edit.js
  13. 9
      src/EventHub.Web/Pages/Organizations/Index.cshtml
  14. 13
      src/EventHub.Web/Pages/Organizations/Profile.cshtml

2
src/EventHub.Application.Contracts/Organizations/IOrganizationAppService.cs

@ -18,5 +18,7 @@ namespace EventHub.Organizations
Task<bool> IsOrganizationOwnerAsync(Guid organizationId);
Task UpdateAsync(Guid id, UpdateOrganizationDto input);
Task SaveProfilePictureAsync(Guid id, byte[] bytes);
}
}

2
src/EventHub.Application.Contracts/Organizations/OrganizationInListDto.cs

@ -10,5 +10,7 @@ namespace EventHub.Organizations
public string DisplayName { get; set; }
public string Description { get; set; }
public byte[] ProfilePictureContent { get; set; }
}
}

2
src/EventHub.Application.Contracts/Organizations/OrganizationProfileDto.cs

@ -22,5 +22,7 @@ namespace EventHub.Organizations
public string InstagramUsername { get; set; }
public string MediumUsername { get; set; }
public byte[] ProfilePictureContent { get; set; }
}
}

56
src/EventHub.Application/Organizations/OrganizationAppService.cs

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.BlobStoring;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Users;
@ -14,13 +15,17 @@ namespace EventHub.Organizations
{
private readonly IRepository<Organization, Guid> _organizationRepository;
private readonly OrganizationManager _organizationManager;
private readonly IBlobContainer<OrganizationProfilePictureContainer> _organizationBlobContainer;
public OrganizationAppService(
IRepository<Organization, Guid> organizationRepository,
OrganizationManager organizationManager)
OrganizationManager organizationManager,
IBlobContainer<OrganizationProfilePictureContainer> organizationBlobContainer
)
{
_organizationRepository = organizationRepository;
_organizationManager = organizationManager;
_organizationBlobContainer = organizationBlobContainer;
}
[Authorize]
@ -45,16 +50,27 @@ namespace EventHub.Organizations
nameof(Organization.DisplayName)
);
var organizationDto = ObjectMapper.Map<List<Organization>, List<OrganizationInListDto>>(organizations);
foreach (var organization in organizationDto)
{
organization.ProfilePictureContent = await GetProfilePictureAsync(organization.Id);
}
return new PagedResultDto<OrganizationInListDto>(
organizationCount,
ObjectMapper.Map<List<Organization>, List<OrganizationInListDto>>(organizations)
organizationDto
);
}
public async Task<OrganizationProfileDto> GetProfileAsync(string name)
{
var organization = await _organizationRepository.GetAsync(o => o.Name == name);
return ObjectMapper.Map<Organization, OrganizationProfileDto>(organization);
var organizationProfileDto = ObjectMapper.Map<Organization, OrganizationProfileDto>(organization);
organizationProfileDto.ProfilePictureContent = await GetProfilePictureAsync(organizationProfileDto.Id);
return organizationProfileDto;
}
[Authorize]
@ -64,9 +80,14 @@ namespace EventHub.Organizations
var query = (await _organizationRepository.GetQueryableAsync()).Where(o => o.OwnerUserId == currentUserId);
var organizations = await AsyncExecuter.ToListAsync(query);
return new ListResultDto<OrganizationInListDto>(
ObjectMapper.Map<List<Organization>, List<OrganizationInListDto>>(organizations)
);
var organizationDto = ObjectMapper.Map<List<Organization>, List<OrganizationInListDto>>(organizations);
foreach (var organization in organizationDto)
{
organization.ProfilePictureContent = await GetProfilePictureAsync(organization.Id);
}
return new ListResultDto<OrganizationInListDto>(organizationDto);
}
public async Task<bool> IsOrganizationOwnerAsync(Guid organizationId)
@ -96,5 +117,28 @@ namespace EventHub.Organizations
await _organizationRepository.UpdateAsync(organization);
}
[Authorize]
public async Task SaveProfilePictureAsync(Guid id, byte[] bytes)
{
var organization = await _organizationRepository.GetAsync(x => x.Id == id);
if (organization.OwnerUserId != CurrentUser.GetId())
{
throw new BusinessException(EventHubErrorCodes.NotAuthorizedToUpdateOrganizationProfile)
.WithData("Name", organization.DisplayName);
}
var blobName = id.ToString();
await _organizationBlobContainer.SaveAsync(blobName, bytes, overrideExisting: true);
}
private async Task<byte[]> GetProfilePictureAsync(Guid id)
{
var blobName = id.ToString();
return await _organizationBlobContainer.GetAllBytesOrNullAsync(blobName);
}
}
}

2
src/EventHub.Domain.Shared/Organizations/OrganizationContst.cs

@ -10,5 +10,7 @@
public const int MinDescriptionNameLength = 50;
public const int MaxDescriptionNameLength = 1000;
public const int MaxProfilePictureFileSize = 1048576;
}
}

10
src/EventHub.Domain/Organizations/OrganizationProfilePictureContainer.cs

@ -0,0 +1,10 @@
using Volo.Abp.BlobStoring;
namespace EventHub.Organizations
{
[BlobContainerName("organization-profile-pictures")]
public class OrganizationProfilePictureContainer
{
}
}

39
src/EventHub.Web/Controllers/OrganizationController.cs

@ -0,0 +1,39 @@
using System.IO;
using System.Threading.Tasks;
using EventHub.Organizations;
using EventHub.Web.Pages.Organizations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
namespace EventHub.Web.Controllers
{
[Route("api/organization")]
public class OrganizationController : AbpController
{
private readonly IOrganizationAppService _organizationAppService;
public OrganizationController(IOrganizationAppService organizationAppService)
{
_organizationAppService = organizationAppService;
}
[HttpPost]
[Authorize]
public async Task SaveProfilePicture([FromForm] OrganizationProfilePictureInput input)
{
byte[] profilePictureContent = new byte[] {};
if (input.ProfilePictureFile != null && input.ProfilePictureFile.Length > 0)
{
using (var memoryStream = new MemoryStream())
{
await input.ProfilePictureFile.CopyToAsync(memoryStream);
profilePictureContent = memoryStream.ToArray();
}
}
await _organizationAppService.SaveProfilePictureAsync(input.OrganizationId, profilePictureContent);
}
}
}

38
src/EventHub.Web/Helpers/AllowedExtensionsAttribute.cs

@ -0,0 +1,38 @@
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Http;
namespace EventHub.Web.Helpers
{
public class AllowedExtensionsAttribute : ValidationAttribute
{
private readonly string[] _allowedExtensions;
public AllowedExtensionsAttribute(string[] allowedExtensions)
{
_allowedExtensions = allowedExtensions;
}
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
if (value == null)
{
return ValidationResult.Success;
}
var file = value as IFormFile;
var extension = Path.GetExtension(file.FileName);
if (file.Length > 0 && file != null)
{
if (!_allowedExtensions.Contains(extension.ToLower()))
{
return new ValidationResult("This file extension is not allowed. Allowed extensions: png, jpg, jpeg");
}
}
return ValidationResult.Success;
}
}
}

36
src/EventHub.Web/Helpers/MaxFileSizeAttribute.cs

@ -0,0 +1,36 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http;
namespace EventHub.Web.Helpers
{
public class MaxFileSizeAttribute : ValidationAttribute
{
private readonly int _maxFileSize;
public MaxFileSizeAttribute(int maxFileSize)
{
_maxFileSize = maxFileSize;
}
protected override ValidationResult IsValid(
object value, ValidationContext validationContext)
{
if (value == null)
{
return ValidationResult.Success;
}
var file = value as IFormFile;
if (file.Length > 0)
{
if (file.Length > _maxFileSize)
{
return new ValidationResult("Maximum file size: 1MB");
}
}
return ValidationResult.Success;
}
}
}

53
src/EventHub.Web/Pages/Organizations/Edit.cshtml

@ -4,7 +4,58 @@
@using Microsoft.AspNetCore.Mvc.Localization
@model EventHub.Web.Pages.Organizations.EditPageModel
@section scripts {
<abp-script-bundle>
<abp-script src="/Pages/Organizations/Edit.js"/>
</abp-script-bundle>
}
<div class="container mb-4">
<h1>@L["EditOrganization"]</h1>
<abp-dynamic-form abp-model="Organization" submit-button="true"/>
<ul class="nav nav-pills mt-3 mb-3" id="pills-tab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="profile-info-tab" data-toggle="pill" href="#profile-info" role="tab" aria-controls="profile-info" aria-selected="true">Profile Info</a>
</li>
<li class="nav-item">
<a class="nav-link" id="profile-picture-tab" data-toggle="pill" href="#profile-picture" role="tab" aria-controls="profile-picture" aria-selected="false">Profile Picture</a>
</li>
</ul>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="profile-info" role="tabpanel" aria-labelledby="profile-info-tab">
<abp-dynamic-form abp-model="Organization" submit-button="true"/>
</div>
<div class="tab-pane fade" id="profile-picture" role="tabpanel" aria-labelledby="profile-picture-tab">
<form method="post" id="EditOrganizationProfileForm" enctype="multipart/form-data">
<input type="hidden" id="my-id" value="@Model.Organization.Id"/>
<abp-row>
<abp-column size="_12" v-align="Center">
<div class="form-group">
<label>Profile Picture</label>
<div class="custom-file">
<input id="organization_id" type="file" class="custom-file-input form-control" required="required">
<label id="choose-cover-image" class="custom-file-label" for="customFile">Choose a profile picture...</label>
<small id="Organization_ProfilePictureInfoText" class="form-text text-muted mt-2">Profile picture info</small>
</div>
</div>
</abp-column>
</abp-row>
<button id="btnSubmit" type="submit" class="btn btn-primary btn-block">
@L["Submit"]
</button>
</form>
<div class="modal fade" id="picturePreviewModal" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<img class="img-fluid" id="img-upload" alt=""/>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-block" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

14
src/EventHub.Web/Pages/Organizations/Edit.cshtml.cs

@ -2,7 +2,9 @@ using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using EventHub.Organizations;
using EventHub.Web.Helpers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace EventHub.Web.Pages.Organizations
@ -78,4 +80,16 @@ namespace EventHub.Web.Pages.Organizations
public string MediumUsername { get; set; }
}
public class OrganizationProfilePictureInput
{
[Required]
public Guid OrganizationId { get; set; }
[Required]
[DataType(DataType.Upload)]
[MaxFileSize(OrganizationConsts.MaxProfilePictureFileSize)]
[AllowedExtensions(new string[] { ".jpg", ".png", ".jpeg" })]
public IFormFile ProfilePictureFile { get; set; }
}
}

113
src/EventHub.Web/Pages/Organizations/Edit.js

@ -0,0 +1,113 @@
$(function () {
var l = abp.localization.getResource('EventHub');
var fileInput = document.getElementById("organization_id");
var file;
fileInput.addEventListener('change', function () {
var showModal = true;
file = fileInput.files[0];
if(file == undefined) {
$("#choose-cover-image").html("Choose a cover image...");
return;
}
else {
$("#choose-cover-image").html(file.name);
}
var permittedExtensions = ["jpg", "jpeg", "png"]
var fileExtension = $(this).val().split('.').pop();
if (permittedExtensions.indexOf(fileExtension) === -1) {
showModal = false;
abp.message.error('This Extension Is Not Allowed')
.then(() => {
$("#choose-cover-image").html("Choose a cover image...");
file = null;
});
}
//1mb
else if(file.size > 1024 * 1024) {
showModal = false;
abp.message.error('The File Is Too Large')
.then(() => {
$("#choose-cover-image").html("Choose a cover image...");
file = null;
});
}
var img = new Image();
img.onload = function () {
var imageError = true;
var sizes = {
width: this.width,
height: this.height
};
URL.revokeObjectURL(this.src);
if(showModal && imageError) {
readURL(file);
$("#picturePreviewModal").modal('show');
}
}
var objectURL = URL.createObjectURL(file);
img.src = objectURL;
});
function readURL(input) {
if (input) {
var reader = new FileReader();
reader.onload = function (e) {
$('#img-upload').attr('src', e.target.result);
}
reader.readAsDataURL(input);
}
}
$("form#EditOrganizationProfileForm").submit(function (e) {
e.preventDefault();
var id = document.getElementById("my-id").value;
var formData = new FormData();
formData.append("OrganizationId", id);
formData.append("ProfilePictureFile", file);
$.ajax({
xhr: function() {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener("progress", function(evt) {
if (evt.lengthComputable) {
var percentComplete = evt.loaded / evt.total;
percentComplete = parseInt(percentComplete * 100);
if(percentComplete !== 100){
$('#btnSubmit').prop( "disabled", true );
}
}
}, false);
return xhr;
},
url: `/api/organization`,
data: formData,
type: 'POST',
contentType: false,
processData: false,
success: function(data){
abp.message
.success("Organization Profile Picture successfully added.")
.then(data => window.location.href = "/");
},
error: function (data) {
abp.message.error(data.responseJSON.error.message);
}
});
});
});

9
src/EventHub.Web/Pages/Organizations/Index.cshtml

@ -9,7 +9,14 @@
{
<abp-column size-lg="_4" size-md="_6">
<abp-card class="mb-3">
<img abp-card-image="Top" src="https://picsum.photos/seed/@organization.Name/400/160?blur=8" alt="@organization.DisplayName"/>
@if (organization.ProfilePictureContent == null)
{
<img abp-card-image="Top" src="https://picsum.photos/seed/@organization.Name/400/160?blur=8" alt="@organization.DisplayName"/>
}
else
{
<img abp-card-image="Top" src="@($"data:image/png;base64,{Convert.ToBase64String(organization.ProfilePictureContent)}")" alt="@organization.DisplayName"/>
}
<abp-card-body>
<abp-card-title>@organization.DisplayName</abp-card-title>
<abp-card-text>

13
src/EventHub.Web/Pages/Organizations/Profile.cshtml

@ -12,7 +12,14 @@
<abp-row>
<abp-column size-lg="_12">
<abp-card class="mb-3">
<img abp-card-image="Top" src="https://picsum.photos/seed/@Model.Name/1920/400?blur=8" alt="@Model.Organization.DisplayName"/>
@if (Model.Organization.ProfilePictureContent == null)
{
<img abp-card-image="Top" src="https://picsum.photos/seed/@Model.Name/1920/400?blur=8" alt="@Model.Organization.DisplayName"/>
}
else
{
<img abp-card-image="Top" src="@($"data:image/png;base64,{Convert.ToBase64String(Model.Organization.ProfilePictureContent)}")" alt="@Model.Organization.DisplayName"/>
}
<abp-card-body>
<abp-card-title>
@if (Model.IsOrganizationOwner)
@ -53,8 +60,8 @@
</h5>
</div>
}
@if (!Model.Organization.TwitterUsername.IsNullOrWhiteSpace() || !Model.Organization.GitHubUsername.IsNullOrWhiteSpace() ||
!Model.Organization.InstagramUsername.IsNullOrWhiteSpace() || !Model.Organization.FacebookUsername.IsNullOrWhiteSpace() ||
@if (!Model.Organization.TwitterUsername.IsNullOrWhiteSpace() || !Model.Organization.GitHubUsername.IsNullOrWhiteSpace() ||
!Model.Organization.InstagramUsername.IsNullOrWhiteSpace() || !Model.Organization.FacebookUsername.IsNullOrWhiteSpace() ||
!Model.Organization.MediumUsername.IsNullOrWhiteSpace())
{
<div class="profile-icons mt-4 text-center">

Loading…
Cancel
Save