Browse Source

Merge pull request #17 from volosoft/EngincanV/edit-organization

Edit Organization Profile
pull/20/head
Halil İbrahim Kalkan 6 years ago
committed by GitHub
parent
commit
5bf48070c0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      src/EventHub.Application.Contracts/Organizations/IOrganizationAppService.cs
  2. 27
      src/EventHub.Application.Contracts/Organizations/UpdateOrganizationDto.cs
  3. 29
      src/EventHub.Application/Organizations/OrganizationAppService.cs
  4. 1
      src/EventHub.Domain.Shared/EventHubErrorCodes.cs
  5. 7
      src/EventHub.Domain.Shared/Localization/EventHub/en.json
  6. 7
      src/EventHub.Web/EventHubWebAutoMapperProfile.cs
  7. 10
      src/EventHub.Web/Pages/Organizations/Edit.cshtml
  8. 81
      src/EventHub.Web/Pages/Organizations/Edit.cshtml.cs
  9. 75
      src/EventHub.Web/Pages/Organizations/Profile.cshtml
  10. 4
      src/EventHub.Web/Pages/Organizations/Profile.cshtml.cs
  11. 66
      test/EventHub.Application.Tests/Organizations/OrganizationAppServiceTests.cs

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

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
@ -13,5 +14,9 @@ namespace EventHub.Organizations
Task<OrganizationProfileDto> GetProfileAsync(string name);
Task<ListResultDto<OrganizationInListDto>> GetMyOrganizationsAsync();
Task<bool> IsOrganizationOwnerAsync(Guid organizationId);
Task UpdateAsync(Guid id, UpdateOrganizationDto input);
}
}

27
src/EventHub.Application.Contracts/Organizations/UpdateOrganizationDto.cs

@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations;
namespace EventHub.Organizations
{
public class UpdateOrganizationDto
{
[Required]
[StringLength(OrganizationConsts.MaxDisplayNameLength, MinimumLength = OrganizationConsts.MinDisplayNameLength)]
public string DisplayName { get; set; }
[Required]
[StringLength(OrganizationConsts.MaxDescriptionNameLength, MinimumLength = OrganizationConsts.MinDescriptionNameLength)]
public string Description { get; set; }
public string Website { get; set; }
public string TwitterUsername { get; set; }
public string GitHubUsername { get; set; }
public string FacebookUsername { get; set; }
public string InstagramUsername { get; set; }
public string MediumUsername { get; set; }
}
}

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

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Users;
@ -67,5 +68,33 @@ namespace EventHub.Organizations
ObjectMapper.Map<List<Organization>, List<OrganizationInListDto>>(organizations)
);
}
public async Task<bool> IsOrganizationOwnerAsync(Guid organizationId)
{
return CurrentUser.Id.HasValue && await _organizationRepository.AnyAsync(x => x.Id == organizationId && x.OwnerUserId == CurrentUser.Id.Value);
}
[Authorize]
public async Task UpdateAsync(Guid id, UpdateOrganizationDto input)
{
var organization = await _organizationRepository.GetAsync(id);
if (organization.OwnerUserId != CurrentUser.GetId())
{
throw new BusinessException(EventHubErrorCodes.NotAuthorizedToUpdateOrganizationProfile)
.WithData("OrganizationName", organization.DisplayName);
}
organization.SetDisplayName(input.DisplayName);
organization.SetDescription(input.Description);
organization.Website = input.Website;
organization.TwitterUsername = input.TwitterUsername;
organization.GitHubUsername = input.GitHubUsername;
organization.InstagramUsername = input.InstagramUsername;
organization.FacebookUsername = input.FacebookUsername;
organization.MediumUsername = input.MediumUsername;
await _organizationRepository.UpdateAsync(organization);
}
}
}

1
src/EventHub.Domain.Shared/EventHubErrorCodes.cs

@ -6,5 +6,6 @@
public const string NotAuthorizedToCreateEventInThisOrganization = "EventHub:NotAuthorizedToCreateEventInThisOrganization";
public const string EventEndTimeCantBeEarlierThanStartTime = "EventHub:EventEndTimeCantBeEarlierThanStartTime";
public const string CantRegisterOrUnregisterForAPastEvent = "EventHub:CantRegisterOrUnregisterForAPastEvent";
public const string NotAuthorizedToUpdateOrganizationProfile = "EventHub:NotAuthorizedToUpdateOrganizationProfile";
}
}

7
src/EventHub.Domain.Shared/Localization/EventHub/en.json

@ -42,6 +42,11 @@
"OrganizationJoinSuccessMessage": "See you at the events.",
"OrganizationMembershipLeaveMessage": "You left your organization membership.",
"LeaveOrganization": "Leave this organization",
"JoinOrganization": "Join the organization"
"JoinOrganization": "Join the organization",
"Edit": "Edit",
"EditOrganization": "Edit Organization",
"PersonalWebsite": "Personal Website",
"SocialMedia": "Social Media",
"EventHub:NotAuthorizedToUpdateOrganizationProfile": "You are not authorized to update the \"{OrganizationName}\" organization."
}
}

7
src/EventHub.Web/EventHubWebAutoMapperProfile.cs

@ -1,6 +1,7 @@
using AutoMapper;
using EventHub.Events;
using EventHub.Organizations;
using EventHub.Web.Pages.Organizations;
namespace EventHub.Web
{
@ -8,8 +9,10 @@ namespace EventHub.Web
{
public EventHubWebAutoMapperProfile()
{
CreateMap<EventHub.Web.Pages.Organizations.NewPageModel.CreateOrganizationViewModel, CreateOrganizationDto>();
CreateMap<EventHub.Web.Pages.Events.NewPageModel.NewEventViewModel, CreateEventDto>();
CreateMap<NewPageModel.CreateOrganizationViewModel, CreateOrganizationDto>();
CreateMap<Pages.Events.NewPageModel.NewEventViewModel, CreateEventDto>();
CreateMap<OrganizationProfileDto, EditOrganizationViewModel>();
CreateMap<EditOrganizationViewModel, UpdateOrganizationDto>();
}
}
}

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

@ -0,0 +1,10 @@
@page "/organization/edit/{name}"
@inject IHtmlLocalizer<EventHubResource> L
@using EventHub.Localization
@using Microsoft.AspNetCore.Mvc.Localization
@model EventHub.Web.Pages.Organizations.EditPageModel
<div class="container mb-4">
<h1>@L["EditOrganization"]</h1>
<abp-dynamic-form abp-model="Organization" submit-button="true"/>
</div>

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

@ -0,0 +1,81 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using EventHub.Organizations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace EventHub.Web.Pages.Organizations
{
[Authorize]
public class EditPageModel : EventHubPageModel
{
[BindProperty(SupportsGet = true)]
public string Name { get; set; }
[BindProperty]
public EditOrganizationViewModel Organization { get; set; }
private readonly IOrganizationAppService _organizationAppService;
public EditPageModel(IOrganizationAppService organizationAppService)
{
_organizationAppService = organizationAppService;
}
public async Task OnGetAsync()
{
await GetOrganizationAsync();
}
public async Task<IActionResult> OnPostAsync()
{
try
{
ValidateModel();
var input = ObjectMapper.Map<EditOrganizationViewModel, UpdateOrganizationDto>(Organization);
await _organizationAppService.UpdateAsync(Organization.Id, input);
return RedirectToPage("./Profile", new { name = Name });
}
catch (Exception exception)
{
ShowAlert(exception);
return Page();
}
}
private async Task GetOrganizationAsync()
{
var organizationProfileDto = await _organizationAppService.GetProfileAsync(Name);
Organization = ObjectMapper.Map<OrganizationProfileDto, EditOrganizationViewModel>(organizationProfileDto);
}
}
public class EditOrganizationViewModel
{
[HiddenInput]
public Guid Id { get; set; }
[Required]
[StringLength(OrganizationConsts.MaxDisplayNameLength, MinimumLength = OrganizationConsts.MinDisplayNameLength)]
public string DisplayName { get; set; }
[Required]
[StringLength(OrganizationConsts.MaxDescriptionNameLength, MinimumLength = OrganizationConsts.MinDescriptionNameLength)]
public string Description { get; set; }
public string Website { get; set; }
public string TwitterUsername { get; set; }
public string GitHubUsername { get; set; }
public string FacebookUsername { get; set; }
public string InstagramUsername { get; set; }
public string MediumUsername { get; set; }
}
}

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

@ -15,10 +15,83 @@
<img abp-card-image="Top" src="https://picsum.photos/seed/@Model.Name/1920/400?blur=8" alt="@Model.Organization.DisplayName"/>
<abp-card-body>
<abp-card-title>
<h2>@Model.Organization.DisplayName</h2>
@if (Model.IsOrganizationOwner)
{
<div class="row">
<div class="col-9">
<h2>@Model.Organization.DisplayName</h2>
</div>
<div class="col-3 text-right">
<a href="/organization/edit/@Model.Organization.Name" class="btn btn-primary text-white">@L["Edit"] <i class="far fa-edit"></i></a>
</div>
</div>
}
else
{
<h2>@Model.Organization.DisplayName</h2>
}
</abp-card-title>
<abp-card-text class="mt-3">
@Model.Organization.Description
@if (!Model.Organization.Website.IsNullOrWhiteSpace() || !Model.Organization.TwitterUsername.IsNullOrWhiteSpace() ||
!Model.Organization.GitHubUsername.IsNullOrWhiteSpace() || !Model.Organization.InstagramUsername.IsNullOrWhiteSpace() ||
!Model.Organization.FacebookUsername.IsNullOrWhiteSpace() || !Model.Organization.MediumUsername.IsNullOrWhiteSpace())
{
<hr/>
@if (!Model.Organization.Website.IsNullOrWhiteSpace())
{
<div class="mt-4 text-center">
<small class="text-muted text-uppercase">@L["PersonalWebsite"]</small>
<h5 class="m-0">
@if (Model.Organization.Website != null)
{
<a href="@Model.Organization.Website" class="text-personel-web-site text-decoration-none" target="_blank" style="font-size: 1rem">
@Model.Organization.Website
</a>
}
</h5>
</div>
}
@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">
<small class="text-muted text-uppercase">@L["SocialMedia"]</small><br/>
@if (!Model.Organization.TwitterUsername.IsNullOrWhiteSpace())
{
<a href="https://twitter.com/@Model.Organization.TwitterUsername" class="text-twitter text-decoration-none" target="_blank" style="font-size: 1.5rem">
<i class="fab fa-twitter"></i>
</a>
}
@if (!Model.Organization.GitHubUsername.IsNullOrWhiteSpace())
{
<a href="https://github.com/@Model.Organization.GitHubUsername" class="text-github text-decoration-none" target="_blank" style="font-size: 1.5rem">
<i class="fab fa-github"></i>
</a>
}
@if (!Model.Organization.InstagramUsername.IsNullOrWhiteSpace())
{
<a href="https://www.instagram.com/@Model.Organization.InstagramUsername" class="text-instagram text-decoration-none" target="_blank" style="font-size: 1.5rem">
<i class="fab fa-instagram"></i>
</a>
}
@if (!Model.Organization.FacebookUsername.IsNullOrWhiteSpace())
{
<a href="https://www.facebook.com/@Model.Organization.FacebookUsername" class="text-facebook text-decoration-none" target="_blank" style="font-size: 1.5rem">
<i class="fab fa-facebook"></i>
</a>
}
@if (!Model.Organization.MediumUsername.IsNullOrWhiteSpace())
{
<a href="https://medium.com/@Html.Raw("@")@Model.Organization.MediumUsername" class="text-medium text-decoration-none" target="_blank" style="font-size: 1.5rem">
<i class="fab fa-medium"></i>
</a>
}
</div>
}
}
</abp-card-text>
</abp-card-body>
</abp-card>

4
src/EventHub.Web/Pages/Organizations/Profile.cshtml.cs

@ -18,6 +18,8 @@ namespace EventHub.Web.Pages.Organizations
public IReadOnlyList<EventInListDto> PastEvents { get; private set; }
public long PastEventTotalCount { get; private set; }
public bool IsOrganizationOwner { get; private set; }
private readonly IEventAppService _eventAppService;
private readonly IOrganizationAppService _organizationAppService;
@ -34,6 +36,8 @@ namespace EventHub.Web.Pages.Organizations
await GetProfileAsync();
await GetUpcomingEventsAsync();
await GetPastEventsAsync();
IsOrganizationOwner = await _organizationAppService.IsOrganizationOwnerAsync(Organization.Id);
}
private async Task GetProfileAsync()

66
test/EventHub.Application.Tests/Organizations/OrganizationAppServiceTests.cs

@ -1,7 +1,12 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using Shouldly;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Authorization;
using Volo.Abp.Users;
using Xunit;
namespace EventHub.Organizations
@ -10,16 +15,25 @@ namespace EventHub.Organizations
{
private readonly IOrganizationAppService _organizationAppService;
private readonly EventHubTestData _testData;
private ICurrentUser _currentUser;
public OrganizationAppServiceTests()
{
_organizationAppService = GetRequiredService<IOrganizationAppService>();
_testData = GetRequiredService<EventHubTestData>();
}
protected override void AfterAddApplication(IServiceCollection services)
{
_currentUser = Substitute.For<ICurrentUser>();
services.AddSingleton(_currentUser);
}
[Fact]
public async Task Should_Create_A_Valid_Organization()
{
Login(_testData.UserAdminId);
await _organizationAppService.CreateAsync(
new CreateOrganizationDto
{
@ -38,6 +52,8 @@ namespace EventHub.Organizations
[Fact]
public async Task Should_Not_Create_Organization_With_Existing_Name()
{
Login(_testData.UserAdminId);
var exception = await Assert.ThrowsAsync<BusinessException>(() =>
_organizationAppService.CreateAsync(
new CreateOrganizationDto
@ -68,5 +84,51 @@ namespace EventHub.Organizations
result.Id.ShouldBe(_testData.OrganizationVolosoftId);
result.Name.ShouldBe(_testData.OrganizationVolosoftName);
}
[Fact]
public async Task Should_Update_Organization_If_User_Is_Owner()
{
Login(_testData.UserAdminId);
await _organizationAppService.UpdateAsync(
_testData.OrganizationVolosoftId,
new UpdateOrganizationDto
{
DisplayName = "VOLOSOFT",
Description = "Test description text that is valid and long enough for updating!",
Website = "https://volosoft.com/",
MediumUsername = "volosoft"
});
var organization = await GetOrganizationOrNullAsync(_testData.OrganizationVolosoftName);
organization.ShouldNotBeNull();
organization.DisplayName.ShouldBe("VOLOSOFT");
organization.Description.ShouldBe("Test description text that is valid and long enough for updating!");
}
[Fact]
public async Task Should_Not_Update_Organization_If_User_Not_Owner()
{
Login(_testData.UserAdminId);
var exception = await Assert.ThrowsAsync<AbpAuthorizationException>(() =>
_organizationAppService.UpdateAsync(
_testData.OrganizationDotnetEuropeId,
new UpdateOrganizationDto
{
DisplayName = "Dotnet Europe (DE)",
Description = "Test description text that is valid and long enough for updating!",
Website = "https://dotnet-europe.com/"
})
);
exception.ShouldNotBeNull();
}
private void Login(Guid userId)
{
_currentUser.Id.Returns(userId);
_currentUser.IsAuthenticated.Returns(true);
}
}
}

Loading…
Cancel
Save