Browse Source

Merge pull request #16 from volosoft/berkan/organization-membership

Add organization membership
pull/17/head
Halil İbrahim Kalkan 6 years ago
committed by GitHub
parent
commit
f46ab696a7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      src/EventHub.Application.Contracts/Organizations/Memberships/IOrganizationMembershipAppService.cs
  2. 15
      src/EventHub.Application.Contracts/Organizations/Memberships/OrganizationMemberDto.cs
  3. 3
      src/EventHub.Application/EventHubApplicationAutoMapperProfile.cs
  4. 78
      src/EventHub.Application/Organizations/Memberships/OrganizationMembershipAppService.cs
  5. 7
      src/EventHub.Domain.Shared/Localization/EventHub/en.json
  6. 8
      src/EventHub.Domain/Events/Registrations/EventRegistrationManager.cs
  7. 27
      src/EventHub.Domain/Organizations/Memberships/OrganizationMembership.cs
  8. 44
      src/EventHub.Domain/Organizations/Memberships/OrganizationMembershipManager.cs
  9. 2496
      src/EventHub.EntityFrameworkCore.DbMigrations/Migrations/20210202085532_Added_OrganizationMembership.Designer.cs
  10. 54
      src/EventHub.EntityFrameworkCore.DbMigrations/Migrations/20210202085532_Added_OrganizationMembership.cs
  11. 54
      src/EventHub.EntityFrameworkCore.DbMigrations/Migrations/EventHubMigrationsDbContextModelSnapshot.cs
  12. 2
      src/EventHub.EntityFrameworkCore/EntityFrameworkCore/EventHubDbContext.cs
  13. 17
      src/EventHub.EntityFrameworkCore/EntityFrameworkCore/EventHubDbContextModelCreatingExtensions.cs
  14. 42
      src/EventHub.Web/Controllers/OrganizationMembershipController.cs
  15. 10
      src/EventHub.Web/Controllers/WidgetsController.cs
  16. 34
      src/EventHub.Web/Pages/Organizations/Components/JoinArea/Default.cshtml
  17. 52
      src/EventHub.Web/Pages/Organizations/Components/JoinArea/JoinAreaViewComponent.cs
  18. 51
      src/EventHub.Web/Pages/Organizations/Components/JoinArea/join-area.js
  19. 14
      src/EventHub.Web/Pages/Organizations/Components/MembersArea/Default.cshtml
  20. 73
      src/EventHub.Web/Pages/Organizations/Components/MembersArea/MembersAreaViewComponent.cs
  21. 5
      src/EventHub.Web/Pages/Organizations/Components/MembersArea/members-area.css
  22. 18
      src/EventHub.Web/Pages/Organizations/Components/MembersArea/members-area.js
  23. 151
      src/EventHub.Web/Pages/Organizations/Profile.cshtml
  24. 101
      test/EventHub.Application.Tests/Organizations/Memberships/OrganizationMembershipAppServiceTests.cs
  25. 46
      test/EventHub.Domain.Tests/Organizations/Memberships/OrganizationMembershipManagerTests.cs

18
src/EventHub.Application.Contracts/Organizations/Memberships/IOrganizationMembershipAppService.cs

@ -0,0 +1,18 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace EventHub.Organizations.Memberships
{
public interface IOrganizationMembershipAppService : IApplicationService
{
Task JoinAsync(Guid organizationId);
Task LeaveAsync(Guid organizationId);
Task<bool> IsJoinedAsync(Guid organizationId);
Task<PagedResultDto<OrganizationMemberDto>> GetMembersAsync(Guid organizationId);
}
}

15
src/EventHub.Application.Contracts/Organizations/Memberships/OrganizationMemberDto.cs

@ -0,0 +1,15 @@
using System;
namespace EventHub.Organizations.Memberships
{
public class OrganizationMemberDto
{
public Guid Id { get; set; }
public string UserName { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
}

3
src/EventHub.Application/EventHubApplicationAutoMapperProfile.cs

@ -2,6 +2,7 @@
using EventHub.Events;
using EventHub.Events.Registrations;
using EventHub.Organizations;
using EventHub.Organizations.Memberships;
using EventHub.Users;
using Volo.Abp.AutoMapper;
@ -13,6 +14,8 @@ namespace EventHub
{
CreateMap<Organization, OrganizationInListDto>();
CreateMap<Organization, OrganizationProfileDto>();
CreateMap<AppUser, OrganizationMemberDto>();
CreateMap<Event, EventDto>();
CreateMap<Event, EventInListDto>()

78
src/EventHub.Application/Organizations/Memberships/OrganizationMembershipAppService.cs

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EventHub.Users;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Users;
namespace EventHub.Organizations.Memberships
{
public class OrganizationMembershipAppService : EventHubAppService, IOrganizationMembershipAppService
{
private readonly OrganizationMembershipManager _organizationMembershipManager;
private readonly IRepository<AppUser, Guid> _userRepository;
private readonly IRepository<Organization, Guid> _organizationRepository;
private readonly IRepository<OrganizationMembership, Guid> _organizationMembershipsRepository;
public OrganizationMembershipAppService(
OrganizationMembershipManager organizationMembershipManager,
IRepository<AppUser, Guid> userRepository,
IRepository<Organization, Guid> organizationRepository,
IRepository<OrganizationMembership, Guid> organizationMembershipsRepository)
{
_organizationMembershipManager = organizationMembershipManager;
_userRepository = userRepository;
_organizationRepository = organizationRepository;
_organizationMembershipsRepository = organizationMembershipsRepository;
}
[Authorize]
public async Task JoinAsync(Guid organizationId)
{
await _organizationMembershipManager.JoinAsync(
await _organizationRepository.GetAsync(organizationId),
await _userRepository.GetAsync(CurrentUser.GetId())
);
}
[Authorize]
public async Task LeaveAsync(Guid organizationId)
{
await _organizationMembershipsRepository.DeleteAsync(
x => x.OrganizationId == organizationId && x.UserId == CurrentUser.GetId()
);
}
[Authorize]
public async Task<bool> IsJoinedAsync(Guid organizationId)
{
return await _organizationMembershipManager.IsJoinedAsync(
await _organizationRepository.GetAsync(organizationId),
await _userRepository.GetAsync(CurrentUser.GetId())
);
}
public async Task<PagedResultDto<OrganizationMemberDto>> GetMembersAsync(Guid organizationId)
{
var organizationMembershipsQueryable = await _organizationMembershipsRepository.GetQueryableAsync();
var userQueryable = await _userRepository.GetQueryableAsync();
var query = from organizationMembership in organizationMembershipsQueryable
join user in userQueryable on organizationMembership.UserId equals user.Id
where organizationMembership.OrganizationId == organizationId
orderby organizationMembership.CreationTime descending
select user;
var totalCount = await AsyncExecuter.CountAsync(query);
var users = await AsyncExecuter.ToListAsync(query.Take(10));
return new PagedResultDto<OrganizationMemberDto>(
totalCount,
ObjectMapper.Map<List<AppUser>, List<OrganizationMemberDto>>(users)
);
}
}
}

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

@ -37,6 +37,11 @@
"Upcoming": "Upcoming",
"Past": "Past",
"NewEventYouHaveNoOrganizationYetMessage": "You have to create an organization first!",
"CreateAnOrganization": "Create an organization"
"CreateAnOrganization": "Create an organization",
"OrganizationJoinSuccessMessageTitle": "Thank you for your membership!",
"OrganizationJoinSuccessMessage": "See you at the events.",
"OrganizationMembershipLeaveMessage": "You left your organization membership.",
"LeaveOrganization": "Leave this organization",
"JoinOrganization": "Join the organization"
}
}

8
src/EventHub.Domain/Events/Registrations/EventRegistrationManager.cs

@ -11,14 +11,10 @@ namespace EventHub.Events.Registrations
public class EventRegistrationManager : DomainService
{
private readonly IRepository<EventRegistration, Guid> _eventRegistrationRepository;
private readonly IClock _clock;
public EventRegistrationManager(
IRepository<EventRegistration, Guid> eventRegistrationRepository,
IClock clock)
public EventRegistrationManager(IRepository<EventRegistration, Guid> eventRegistrationRepository)
{
_eventRegistrationRepository = eventRegistrationRepository;
_clock = clock;
}
public async Task RegisterAsync(
@ -62,7 +58,7 @@ namespace EventHub.Events.Registrations
private void CheckEventEndTime(Event @event)
{
if (_clock.Now > @event.EndTime)
if (Clock.Now > @event.EndTime)
{
throw new BusinessException(EventHubErrorCodes.CantRegisterOrUnregisterForAPastEvent);
}

27
src/EventHub.Domain/Organizations/Memberships/OrganizationMembership.cs

@ -0,0 +1,27 @@
using System;
using Volo.Abp.Domain.Entities.Auditing;
namespace EventHub.Organizations.Memberships
{
public class OrganizationMembership : CreationAuditedAggregateRoot<Guid>
{
public Guid OrganizationId { get; private set; }
public Guid UserId { get; private set; }
private OrganizationMembership()
{
}
internal OrganizationMembership(
Guid id,
Guid organizationId,
Guid userId)
: base(id)
{
OrganizationId = organizationId;
UserId = userId;
}
}
}

44
src/EventHub.Domain/Organizations/Memberships/OrganizationMembershipManager.cs

@ -0,0 +1,44 @@
using System;
using System.Threading.Tasks;
using EventHub.Users;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;
namespace EventHub.Organizations.Memberships
{
public class OrganizationMembershipManager : DomainService
{
private readonly IRepository<OrganizationMembership, Guid> _organizationMembershipsRepository;
public OrganizationMembershipManager(IRepository<OrganizationMembership, Guid> organizationMembershipsRepository)
{
_organizationMembershipsRepository = organizationMembershipsRepository;
}
public async Task JoinAsync(
Organization organization,
AppUser user)
{
if (await IsJoinedAsync(organization, user))
{
return;
}
await _organizationMembershipsRepository.InsertAsync(
new OrganizationMembership(
GuidGenerator.Create(),
organization.Id,
user.Id
)
);
}
public async Task<bool> IsJoinedAsync(
Organization organization,
AppUser user)
{
return await _organizationMembershipsRepository
.AnyAsync(x => x.OrganizationId == organization.Id && x.UserId == user.Id);
}
}
}

2496
src/EventHub.EntityFrameworkCore.DbMigrations/Migrations/20210202085532_Added_OrganizationMembership.Designer.cs

File diff suppressed because it is too large

54
src/EventHub.EntityFrameworkCore.DbMigrations/Migrations/20210202085532_Added_OrganizationMembership.cs

@ -0,0 +1,54 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace EventHub.Migrations
{
public partial class Added_OrganizationMembership : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AppOrganizationMemberships",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
OrganizationId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AppOrganizationMemberships", x => x.Id);
table.ForeignKey(
name: "FK_AppOrganizationMemberships_AbpUsers_UserId",
column: x => x.UserId,
principalTable: "AbpUsers",
principalColumn: "Id");
table.ForeignKey(
name: "FK_AppOrganizationMemberships_AppOrganizations_OrganizationId",
column: x => x.OrganizationId,
principalTable: "AppOrganizations",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_AppOrganizationMemberships_OrganizationId_UserId",
table: "AppOrganizationMemberships",
columns: new[] { "OrganizationId", "UserId" });
migrationBuilder.CreateIndex(
name: "IX_AppOrganizationMemberships_UserId",
table: "AppOrganizationMemberships",
column: "UserId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AppOrganizationMemberships");
}
}
}

54
src/EventHub.EntityFrameworkCore.DbMigrations/Migrations/EventHubMigrationsDbContextModelSnapshot.cs

@ -152,6 +152,45 @@ namespace EventHub.Migrations
b.ToTable("AppEventRegistrations");
});
modelBuilder.Entity("EventHub.Organizations.Memberships.OrganizationMembership", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasMaxLength(40)
.HasColumnType("nvarchar(40)")
.HasColumnName("ConcurrencyStamp");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
b.Property<Guid>("OrganizationId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("UserId");
b.HasIndex("OrganizationId", "UserId");
b.ToTable("AppOrganizationMemberships");
});
modelBuilder.Entity("EventHub.Organizations.Organization", b =>
{
b.Property<Guid>("Id")
@ -2077,6 +2116,21 @@ namespace EventHub.Migrations
.IsRequired();
});
modelBuilder.Entity("EventHub.Organizations.Memberships.OrganizationMembership", b =>
{
b.HasOne("EventHub.Organizations.Organization", null)
.WithMany()
.HasForeignKey("OrganizationId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.HasOne("Volo.Abp.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
});
modelBuilder.Entity("EventHub.Organizations.Organization", b =>
{
b.HasOne("Volo.Abp.Identity.IdentityUser", null)

2
src/EventHub.EntityFrameworkCore/EntityFrameworkCore/EventHubDbContext.cs

@ -1,6 +1,7 @@
using EventHub.Events;
using EventHub.Events.Registrations;
using EventHub.Organizations;
using EventHub.Organizations.Memberships;
using Microsoft.EntityFrameworkCore;
using EventHub.Users;
using Volo.Abp.Data;
@ -25,6 +26,7 @@ namespace EventHub.EntityFrameworkCore
{
public DbSet<AppUser> Users { get; set; }
public DbSet<Organization> Organizations { get; set; }
public DbSet<OrganizationMembership> OrganizationMemberships { get; set; }
public DbSet<Event> Events { get; set; }
public DbSet<EventRegistration> EventRegistrations { get; set; }

17
src/EventHub.EntityFrameworkCore/EntityFrameworkCore/EventHubDbContextModelCreatingExtensions.cs

@ -1,6 +1,7 @@
using EventHub.Events;
using EventHub.Events.Registrations;
using EventHub.Organizations;
using EventHub.Organizations.Memberships;
using Microsoft.EntityFrameworkCore;
using Volo.Abp;
using Volo.Abp.EntityFrameworkCore.Modeling;
@ -36,6 +37,22 @@ namespace EventHub.EntityFrameworkCore
b.HasIndex(x => x.Name);
b.HasIndex(x => x.DisplayName);
});
builder.Entity<OrganizationMembership>(b =>
{
b.ToTable(EventHubConsts.DbTablePrefix + "OrganizationMemberships", EventHubConsts.DbSchema);
b.ConfigureByConvention();
b.HasOne<Organization>().WithMany().HasForeignKey(x => x.OrganizationId).IsRequired().OnDelete(DeleteBehavior.NoAction);
if (isMigrationDbContext)
{
b.HasOne<IdentityUser>().WithMany().HasForeignKey(x => x.UserId).IsRequired().OnDelete(DeleteBehavior.NoAction);
}
b.HasIndex(x => new {x.OrganizationId, x.UserId});
});
builder.Entity<Event>(b =>
{

42
src/EventHub.Web/Controllers/OrganizationMembershipController.cs

@ -0,0 +1,42 @@
using System;
using System.Threading.Tasks;
using EventHub.Organizations.Memberships;
using EventHub.Web.Pages.Organizations.Components.JoinArea;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
namespace EventHub.Web.Controllers
{
public class OrganizationMembershipController : AbpController
{
private readonly IOrganizationMembershipAppService _organizationMembershipAppService;
public OrganizationMembershipController(IOrganizationMembershipAppService organizationMembershipAppService)
{
_organizationMembershipAppService = organizationMembershipAppService;
}
[HttpPost]
public async Task<NoContentResult> Join(Guid organizationId)
{
await _organizationMembershipAppService.JoinAsync(organizationId);
return NoContent();
}
[HttpPost]
public async Task<NoContentResult> Leave(Guid organizationId)
{
await _organizationMembershipAppService.LeaveAsync(organizationId);
return NoContent();
}
[HttpGet]
public IActionResult Widget(Guid organizationId)
{
return ViewComponent(
typeof(JoinAreaViewComponent),
new {organizationId}
);
}
}
}

10
src/EventHub.Web/Controllers/WidgetsController.cs

@ -1,5 +1,6 @@
using System;
using EventHub.Web.Pages.Events.Components.AttendeesArea;
using EventHub.Web.Pages.Organizations.Components.MembersArea;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
@ -15,5 +16,14 @@ namespace EventHub.Web.Controllers
new {eventId}
);
}
[HttpGet]
public IActionResult OrganizationMembersArea(Guid organizationId)
{
return ViewComponent(
typeof(MembersAreaViewComponent),
new {organizationId}
);
}
}
}

34
src/EventHub.Web/Pages/Organizations/Components/JoinArea/Default.cshtml

@ -0,0 +1,34 @@
@using EventHub.Localization
@using Microsoft.AspNetCore.Mvc.Localization
@inject IHtmlLocalizer<EventHubResource> L
@model EventHub.Web.Pages.Organizations.Components.JoinArea.JoinAreaViewComponent.MembershipAreaViewComponentModel
<abp-card class="mb-3" data-organization-id="@Model.OrganizationId">
<abp-card-body>
@if (Model.IsLoggedIn)
{
if (Model.IsJoined)
{
<abp-button id="OrganizationLeaveButton"
button-type="Secondary"
size="Block"
icon="fas fa-times"
text="@L["LeaveOrganization"].Value"
data-url="@Url.Action("Leave","OrganizationMembership", new { organizationId = Model.OrganizationId })" />
}
else
{
<abp-button id="OrganizationJoinButton"
button-type="Primary"
size="Block"
icon="fas fa-plus"
text="@L["JoinOrganization"].Value"
data-url="@Url.Action("Join","OrganizationMembership", new { organizationId = Model.OrganizationId })"/>
}
}
else
{
<a abp-button="Primary"
href="@Url.Action("Login", "Account", new { returnUrl = Context.Request.Path.Value })"> @L["LoginToRegister"]</a>
}
</abp-card-body>
</abp-card>

52
src/EventHub.Web/Pages/Organizations/Components/JoinArea/JoinAreaViewComponent.cs

@ -0,0 +1,52 @@
using System;
using System.Threading.Tasks;
using EventHub.Organizations.Memberships;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
using Volo.Abp.Users;
namespace EventHub.Web.Pages.Organizations.Components.JoinArea
{
[Widget(
AutoInitialize = true,
RefreshUrl = "/OrganizationMembership/Widget",
ScriptFiles = new[] {"/Pages/Organizations/Components/JoinArea/join-area.js"}
)]
public class JoinAreaViewComponent : AbpViewComponent
{
private readonly IOrganizationMembershipAppService _organizationMembershipAppService;
private readonly ICurrentUser _currentUser;
public JoinAreaViewComponent(
IOrganizationMembershipAppService organizationMembershipAppService,
ICurrentUser currentUser)
{
_organizationMembershipAppService = organizationMembershipAppService;
_currentUser = currentUser;
}
public async Task<IViewComponentResult> InvokeAsync(Guid organizationId)
{
var model = new MembershipAreaViewComponentModel
{
OrganizationId = organizationId,
IsLoggedIn = _currentUser.IsAuthenticated
};
if (model.IsLoggedIn)
{
model.IsJoined = await _organizationMembershipAppService.IsJoinedAsync(organizationId);
}
return View("~/Pages/Organizations/Components/JoinArea/Default.cshtml", model);
}
public class MembershipAreaViewComponentModel
{
public Guid OrganizationId { get; set; }
public bool IsLoggedIn { get; set; }
public bool IsJoined { get; set; }
}
}
}

51
src/EventHub.Web/Pages/Organizations/Components/JoinArea/join-area.js

@ -0,0 +1,51 @@
(function () {
var l = abp.localization.getResource('EventHub');
abp.widgets.JoinArea = function ($wrapper) {
var widgetManager = $wrapper.data('abp-widget-manager');
var organizationId = $wrapper.find('[data-organization-id]').attr('data-organization-id');
function getFilters() {
return {
organizationId: organizationId
};
}
function init() {
var joinButton = $wrapper.find('#OrganizationJoinButton');
joinButton.click(function (e) {
e.preventDefault();
joinButton.buttonBusy(true);
abp.ajax({
url: joinButton.attr('data-url')
}).then(function (){
widgetManager.refresh();
abp.event.trigger('EventHub.Organization.JoinStatusChanged');
abp.message.success(l('OrganizationJoinSuccessMessage'), l('OrganizationJoinSuccessMessageTitle'));
}).always(function (){
joinButton.buttonBusy(false);
});
});
var $leaveButton = $wrapper.find('#OrganizationLeaveButton');
$leaveButton.click(function (e) {
e.preventDefault();
$leaveButton.buttonBusy(true);
abp.ajax({
url: $leaveButton.attr('data-url')
}).then(function (){
widgetManager.refresh();
abp.event.trigger('EventHub.Organization.JoinStatusChanged');
abp.notify.info(l('OrganizationMembershipLeaveMessage'));
}).always(function (){
$leaveButton.buttonBusy(false);
});
});
}
return {
getFilters: getFilters,
init: init
};
};
})();

14
src/EventHub.Web/Pages/Organizations/Components/MembersArea/Default.cshtml

@ -0,0 +1,14 @@
@model EventHub.Web.Pages.Organizations.Components.MembersArea.MembersAreaViewComponent.MembersAreaViewComponentModel
<abp-card class="mb-3" data-organization-id="@Model.OrganizationId">
<abp-card-body>
<abp-card-title>Members (@Model.TotalCount)</abp-card-title>
<abp-card-text>
<ul id="OrganizationMemberList">
@foreach (var member in Model.Members)
{
<li class="border p-2 mb-2"><i class="fas fa-user"></i> @Model.GetMemberName(member)</li>
}
</ul>
</abp-card-text>
</abp-card-body>
</abp-card>

73
src/EventHub.Web/Pages/Organizations/Components/MembersArea/MembersAreaViewComponent.cs

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using EventHub.Organizations.Memberships;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
namespace EventHub.Web.Pages.Organizations.Components.MembersArea
{
[Widget(
AutoInitialize = true,
RefreshUrl = "/Widgets/OrganizationMembersArea",
ScriptFiles = new[] {"/Pages/Organizations/Components/MembersArea/members-area.js"},
StyleFiles = new[] {"/Pages/Organizations/Components/MembersArea/members-area.css"}
)]
public class MembersAreaViewComponent : AbpViewComponent
{
private readonly IOrganizationMembershipAppService _organizationMembershipAppService;
public MembersAreaViewComponent(IOrganizationMembershipAppService organizationMembershipAppService)
{
_organizationMembershipAppService = organizationMembershipAppService;
}
public async Task<IViewComponentResult> InvokeAsync(Guid organizationId)
{
var result = await _organizationMembershipAppService.GetMembersAsync(organizationId);
return View(
"~/Pages/Organizations/Components/MembersArea/Default.cshtml",
new MembersAreaViewComponentModel
{
OrganizationId = organizationId,
Members = result.Items,
TotalCount = result.TotalCount
}
);
}
public class MembersAreaViewComponentModel
{
public IReadOnlyList<OrganizationMemberDto> Members { get; set; }
public long TotalCount { get; set; }
public Guid OrganizationId { get; set; }
public string GetMemberName(OrganizationMemberDto member)
{
var nameBuilder = new StringBuilder();
if (!member.Name.IsNullOrEmpty())
{
nameBuilder.Append(member.Name);
}
if (!member.Surname.IsNullOrEmpty())
{
nameBuilder.Append(member.Surname);
}
if (nameBuilder.Length == 0)
{
nameBuilder.Append(member.UserName);
}
return nameBuilder.ToString();
}
}
}
}

5
src/EventHub.Web/Pages/Organizations/Components/MembersArea/members-area.css

@ -0,0 +1,5 @@
#OrganizationMemberList
{
list-style: none;
padding: 0;
}

18
src/EventHub.Web/Pages/Organizations/Components/MembersArea/members-area.js

@ -0,0 +1,18 @@
(function () {
abp.widgets.MembersArea = function ($wrapper) {
var organizationId = $wrapper.find('[data-organization-id]').attr('data-organization-id');
return {
getFilters: function () {
return {
organizationId: organizationId
};
}
};
};
abp.event.on("EventHub.Organization.JoinStatusChanged", function(){
$('[data-widget-name="MembersArea"]')
.data('abp-widget-manager')
.refresh();
});
})();

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

@ -2,78 +2,89 @@
@inject IHtmlLocalizer<EventHubResource> L
@using EventHub.Localization
@using EventHub.Web.Pages.Events
@using EventHub.Web.Pages.Organizations.Components.JoinArea
@using EventHub.Web.Pages.Organizations.Components.MembersArea
@using Microsoft.AspNetCore.Mvc.Localization
@model EventHub.Web.Pages.Organizations.ProfilePageModel
<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"/>
<abp-card-body>
<abp-card-title>
<h2>@Model.Organization.DisplayName</h2>
</abp-card-title>
<abp-card-text class="mt-3">
@Model.Organization.Description
</abp-card-text>
</abp-card-body>
</abp-card>
<abp-column size-lg="_8" size-md="_6">
<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"/>
<abp-card-body>
<abp-card-title>
<h2>@Model.Organization.DisplayName</h2>
</abp-card-title>
<abp-card-text class="mt-3">
@Model.Organization.Description
</abp-card-text>
</abp-card-body>
</abp-card>
</abp-column>
</abp-row>
@if (Model.UpcomingEvents.Any())
{
<h3>Events</h3>
<abp-tabs tab-style="Pill">
<abp-tab title="@($"{L["Upcoming"].Value} ({Model.UpcomingEventTotalCount})")">
<abp-row class="mt-3">
@foreach (var eventItem in Model.UpcomingEvents)
{
<abp-column size-lg="_4" size-md="_6">
<abp-card class="mb-3">
<img abp-card-image="Top" src="https://picsum.photos/seed/@eventItem.Id.ToString()/400/160?blur=8" alt="@eventItem.Title"/>
<abp-card-body>
<abp-card-title>@eventItem.Title</abp-card-title>
<abp-card-subtitle>
@EventDateHelper.GetDateRangeText(eventItem.StartTime, eventItem.EndTime)
</abp-card-subtitle>
<abp-card-text>
@eventItem.Description.TruncateWithPostfix(100)
</abp-card-text>
<a abp-button="Primary" href="@Url.Page("/Events/Detail", new {url = eventItem.Url})"> @L["SeeEvent"]</a>
</abp-card-body>
</abp-card>
</abp-column>
}
</abp-row>
</abp-tab>
<abp-tab title="@($"{L["Past"].Value} ({Model.PastEventTotalCount})")">
<abp-row class="mt-3">
@foreach (var eventItem in Model.PastEvents)
{
<abp-column size-lg="_4" size-md="_6">
<abp-card class="mb-3">
<img abp-card-image="Top" src="https://picsum.photos/seed/@eventItem.Id.ToString()/400/160?blur=8" alt="@eventItem.Title"/>
<abp-card-body>
<abp-card-title>@eventItem.Title</abp-card-title>
<abp-card-subtitle>
@EventDateHelper.GetDateRangeText(eventItem.StartTime, eventItem.EndTime)
</abp-card-subtitle>
<abp-card-text>
@eventItem.Description.TruncateWithPostfix(100)
</abp-card-text>
<a abp-button="Primary" href="@Url.Page("/Events/Detail", new {url = eventItem.Url})"> @L["SeeEvent"]</a>
</abp-card-body>
</abp-card>
</abp-column>
}
</abp-row>
</abp-tab>
</abp-tabs>
}
else
{
<abp-alert alert-type="Info">
@L["NoEventOrganizedByThisOrganizationYet"]
</abp-alert>
}
</abp-column>
<abp-column size-lg="_4" size-md="_6">
@await Component.InvokeAsync(typeof(JoinAreaViewComponent), new {organizationId = Model.Organization.Id})
@await Component.InvokeAsync(typeof(MembersAreaViewComponent), new {organizationId = Model.Organization.Id})
</abp-column>
</abp-row>
@if (Model.UpcomingEvents.Any())
{
<h3>Events</h3>
<abp-tabs tab-style="Pill">
<abp-tab title="@($"{L["Upcoming"].Value} ({Model.UpcomingEventTotalCount})")">
<abp-row class="mt-3">
@foreach (var eventItem in Model.UpcomingEvents)
{
<abp-column size-lg="_4" size-md="_6">
<abp-card class="mb-3">
<img abp-card-image="Top" src="https://picsum.photos/seed/@eventItem.Id.ToString()/400/160?blur=8" alt="@eventItem.Title"/>
<abp-card-body>
<abp-card-title>@eventItem.Title</abp-card-title>
<abp-card-subtitle>
@EventDateHelper.GetDateRangeText(eventItem.StartTime, eventItem.EndTime)
</abp-card-subtitle>
<abp-card-text>
@eventItem.Description.TruncateWithPostfix(100)
</abp-card-text>
<a abp-button="Primary" href="@Url.Page("/Events/Detail", new {url = eventItem.Url})"> @L["SeeEvent"]</a>
</abp-card-body>
</abp-card>
</abp-column>
}
</abp-row>
</abp-tab>
<abp-tab title="@($"{L["Past"].Value} ({Model.PastEventTotalCount})")">
<abp-row class="mt-3">
@foreach (var eventItem in Model.PastEvents)
{
<abp-column size-lg="_4" size-md="_6">
<abp-card class="mb-3">
<img abp-card-image="Top" src="https://picsum.photos/seed/@eventItem.Id.ToString()/400/160?blur=8" alt="@eventItem.Title"/>
<abp-card-body>
<abp-card-title>@eventItem.Title</abp-card-title>
<abp-card-subtitle>
@EventDateHelper.GetDateRangeText(eventItem.StartTime, eventItem.EndTime)
</abp-card-subtitle>
<abp-card-text>
@eventItem.Description.TruncateWithPostfix(100)
</abp-card-text>
<a abp-button="Primary" href="@Url.Page("/Events/Detail", new {url = eventItem.Url})"> @L["SeeEvent"]</a>
</abp-card-body>
</abp-card>
</abp-column>
}
</abp-row>
</abp-tab>
</abp-tabs>
}
else
{
<abp-alert alert-type="Info">
@L["NoEventOrganizedByThisOrganizationYet"]
</abp-alert>
}

101
test/EventHub.Application.Tests/Organizations/Memberships/OrganizationMembershipAppServiceTests.cs

@ -0,0 +1,101 @@
using System;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Users;
using Xunit;
namespace EventHub.Organizations.Memberships
{
public class OrganizationMembershipAppServiceTests : EventHubApplicationTestBase
{
private readonly IOrganizationMembershipAppService _organizationMembershipAppService;
private readonly IRepository<OrganizationMembership, Guid> _organizationMembershipRepository;
private readonly EventHubTestData _testData;
private readonly ICurrentUser _currentUser;
public OrganizationMembershipAppServiceTests()
{
_organizationMembershipAppService = GetRequiredService<IOrganizationMembershipAppService>();
_organizationMembershipRepository = GetRequiredService<IRepository<OrganizationMembership, Guid>>();
_testData = GetRequiredService<EventHubTestData>();
_currentUser = GetRequiredService<ICurrentUser>();
}
[Fact]
public async Task Should_Join_To_An_Organization()
{
await _organizationMembershipAppService.JoinAsync(
_testData.OrganizationVolosoftId
);
(await GetMembershipOrNull(
_testData.OrganizationVolosoftId,
_currentUser.GetId()
)).ShouldNotBeNull();
}
[Fact]
public async Task Should_Leave_From_An_Organization()
{
await WithUnitOfWorkAsync(async () =>
{
await _organizationMembershipRepository.InsertAsync(
new OrganizationMembership(
Guid.NewGuid(),
_testData.OrganizationDotnetEuropeId,
_currentUser.GetId()
)
);
});
await _organizationMembershipAppService.LeaveAsync(
_testData.OrganizationDotnetEuropeId
);
(await GetMembershipOrNull(
_testData.OrganizationDotnetEuropeId,
_currentUser.GetId())
).ShouldBeNull();
}
[Fact]
public async Task Should_Get_List_Of_Members()
{
await WithUnitOfWorkAsync(async () =>
{
await _organizationMembershipRepository.InsertAsync(
new OrganizationMembership(
Guid.NewGuid(),
_testData.OrganizationVolosoftId,
_testData.UserAdminId
)
);
await _organizationMembershipRepository.InsertAsync(
new OrganizationMembership(
Guid.NewGuid(),
_testData.OrganizationVolosoftId,
_testData.UserJohnId
)
);
});
var result = await _organizationMembershipAppService.GetMembersAsync(_testData.OrganizationVolosoftId);
result.TotalCount.ShouldBeGreaterThanOrEqualTo(2);
result.Items.ShouldContain(x => x.Id == _testData.UserAdminId);
result.Items.ShouldContain(x => x.Id == _testData.UserJohnId);
}
private async Task<OrganizationMembership> GetMembershipOrNull(Guid organizationId, Guid userId)
{
return await WithUnitOfWorkAsync(async () =>
{
return await _organizationMembershipRepository.FirstOrDefaultAsync(
x => x.OrganizationId == organizationId && x.UserId == userId
);
});
}
}
}

46
test/EventHub.Domain.Tests/Organizations/Memberships/OrganizationMembershipManagerTests.cs

@ -0,0 +1,46 @@
using System;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Domain.Repositories;
using Xunit;
namespace EventHub.Organizations.Memberships
{
public class OrganizationMembershipManagerTests : EventHubDomainTestBase
{
private readonly OrganizationMembershipManager _organizationMembershipManager;
private readonly IRepository<OrganizationMembership, Guid> _organizationMembershipRepository;
private readonly EventHubTestData _testData;
public OrganizationMembershipManagerTests()
{
_organizationMembershipManager = GetRequiredService<OrganizationMembershipManager>();
_organizationMembershipRepository = GetRequiredService<IRepository<OrganizationMembership, Guid>>();
_testData = GetRequiredService<EventHubTestData>();
}
[Fact]
public async Task Should_Join_To_An_Organization()
{
await WithUnitOfWorkAsync(async () =>
{
var user = await GetUserAsync(_testData.UserAdminId);
var organization = await GetOrganizationAsync(_testData.OrganizationVolosoftName);
await _organizationMembershipManager.JoinAsync(organization, user);
});
(await GetMembershipOrNull(_testData.OrganizationVolosoftId, _testData.UserAdminId))
.ShouldNotBeNull();
}
private async Task<OrganizationMembership> GetMembershipOrNull(Guid organizationId, Guid userId)
{
return await WithUnitOfWorkAsync(async () =>
{
return await _organizationMembershipRepository.FirstOrDefaultAsync(
x => x.OrganizationId == organizationId && x.UserId == userId
);
});
}
}
}
Loading…
Cancel
Save