diff --git a/src/EventHub.Admin.Application.Contracts/Organizations/IOrganizationAppService.cs b/src/EventHub.Admin.Application.Contracts/Organizations/IOrganizationAppService.cs new file mode 100644 index 0000000..e2ac1ce --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Organizations/IOrganizationAppService.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading.Tasks; +using EventHub.Admin.Application.Contracts.Organizations; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace EventHub.Admin.Organizations +{ + public interface IOrganizationAppService : IApplicationService + { + Task> GetListAsync(OrganizationListFilterDto input); + + Task GetAsync(Guid id); + + Task UpdateAsync(Guid id, UpdateOrganizationDto input); + + Task DeleteAsync(Guid id); + } +} diff --git a/src/EventHub.Admin.Application.Contracts/Organizations/OrganizationInListDto.cs b/src/EventHub.Admin.Application.Contracts/Organizations/OrganizationInListDto.cs new file mode 100644 index 0000000..796ed3d --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Organizations/OrganizationInListDto.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace EventHub.Admin.Organizations +{ + public class OrganizationInListDto : EntityDto + { + public string Name { get; set; } + + public string DisplayName { get; set; } + + public int MemberCount { get; set; } + } +} diff --git a/src/EventHub.Admin.Application.Contracts/Organizations/OrganizationListFilterDto.cs b/src/EventHub.Admin.Application.Contracts/Organizations/OrganizationListFilterDto.cs new file mode 100644 index 0000000..d9acaf1 --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Organizations/OrganizationListFilterDto.cs @@ -0,0 +1,16 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace EventHub.Admin.Application.Contracts.Organizations +{ + public class OrganizationListFilterDto : PagedAndSortedResultRequestDto + { + public string? Name { get; set; } + + public string? DisplayName { get; set; } + + public int? MinMemberCount { get; set; } + + public int? MaxMemberCount { get; set; } + } +} diff --git a/src/EventHub.Admin.Application.Contracts/Organizations/OrganizationProfileDto.cs b/src/EventHub.Admin.Application.Contracts/Organizations/OrganizationProfileDto.cs new file mode 100644 index 0000000..86b469c --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Organizations/OrganizationProfileDto.cs @@ -0,0 +1,32 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace EventHub.Admin.Organizations +{ + public class OrganizationProfileDto : EntityDto + { + public string Name { get; set; } + + public string DisplayName { get; set; } + + public byte[] ProfilePictureContent { get; set; } + + public string OwnerUserName { get; set; } + + public string OwnerEmail { get; set; } + + 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; } + } +} \ No newline at end of file diff --git a/src/EventHub.Admin.Application.Contracts/Organizations/UpdateOrganizationDto.cs b/src/EventHub.Admin.Application.Contracts/Organizations/UpdateOrganizationDto.cs new file mode 100644 index 0000000..fcc0ce9 --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Organizations/UpdateOrganizationDto.cs @@ -0,0 +1,45 @@ +using System; +using System.ComponentModel.DataAnnotations; +using EventHub.Organizations; +using JetBrains.Annotations; + +namespace EventHub.Admin.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; } + + [CanBeNull] + public byte[] ProfilePictureContent { get; set; } + + [CanBeNull] + [StringLength(OrganizationConsts.MaxWebsiteLength)] + public string Website { get; set; } + + [CanBeNull] + [StringLength(OrganizationConsts.MaxTwitterUsernameLength)] + public string TwitterUsername { get; set; } + + [CanBeNull] + [StringLength(OrganizationConsts.MaxGitHubUsernameLength)] + public string GitHubUsername { get; set; } + + [CanBeNull] + [StringLength(OrganizationConsts.MaxFacebookUsernameLength)] + public string FacebookUsername { get; set; } + + [CanBeNull] + [StringLength(OrganizationConsts.MaxInstagramUsernameLength)] + public string InstagramUsername { get; set; } + + [CanBeNull] + [StringLength(OrganizationConsts.MaxMediumUsernameLength)] + public string MediumUsername { get; set; } + } +} \ No newline at end of file diff --git a/src/EventHub.Admin.Application.Contracts/Permissions/EventHubPermissionDefinitionProvider.cs b/src/EventHub.Admin.Application.Contracts/Permissions/EventHubPermissionDefinitionProvider.cs index e0f7571..469f16e 100644 --- a/src/EventHub.Admin.Application.Contracts/Permissions/EventHubPermissionDefinitionProvider.cs +++ b/src/EventHub.Admin.Application.Contracts/Permissions/EventHubPermissionDefinitionProvider.cs @@ -8,10 +8,12 @@ namespace EventHub.Admin.Permissions { public override void Define(IPermissionDefinitionContext context) { - var myGroup = context.AddGroup(EventHubPermissions.GroupName); + var eventHubGroup = context.AddGroup(EventHubPermissions.GroupName); - //Define your own permissions here. Example: - //myGroup.AddPermission(EventHubPermissions.MyPermission1, L("Permission:MyPermission1")); + var tenantsPermission = eventHubGroup.AddPermission(EventHubPermissions.Organizations.Default, L("Permission:OrganizationManagement")); + tenantsPermission.AddChild(EventHubPermissions.Organizations.Create, L("Permission:Create")); + tenantsPermission.AddChild(EventHubPermissions.Organizations.Update, L("Permission:Edit")); + tenantsPermission.AddChild(EventHubPermissions.Organizations.Delete, L("Permission:Delete")); } private static LocalizableString L(string name) diff --git a/src/EventHub.Admin.Application.Contracts/Permissions/EventHubPermissions.cs b/src/EventHub.Admin.Application.Contracts/Permissions/EventHubPermissions.cs index b7fbe16..ecae825 100644 --- a/src/EventHub.Admin.Application.Contracts/Permissions/EventHubPermissions.cs +++ b/src/EventHub.Admin.Application.Contracts/Permissions/EventHubPermissions.cs @@ -4,7 +4,12 @@ { public const string GroupName = "EventHub"; - //Add your own permission names. Example: - //public const string MyPermission1 = GroupName + ".MyPermission1"; + public static class Organizations + { + public const string Default = GroupName + ".Organizations"; + public const string Create = Default + ".Create"; + public const string Update = Default + ".Update"; + public const string Delete = Default + ".Delete"; + } } } \ No newline at end of file diff --git a/src/EventHub.Admin.Application/EventHubAdminAppService.cs b/src/EventHub.Admin.Application/EventHubAdminAppService.cs new file mode 100644 index 0000000..3ef4f1b --- /dev/null +++ b/src/EventHub.Admin.Application/EventHubAdminAppService.cs @@ -0,0 +1,13 @@ +using EventHub.Localization; +using Volo.Abp.Application.Services; + +namespace EventHub.Admin.Application +{ + public class EventHubAdminAppService : ApplicationService + { + public EventHubAdminAppService() + { + LocalizationResource = typeof(EventHubResource); + } + } +} diff --git a/src/EventHub.Admin.Application/EventHubAdminApplicationModule.cs b/src/EventHub.Admin.Application/EventHubAdminApplicationModule.cs index b046665..64fc7dc 100644 --- a/src/EventHub.Admin.Application/EventHubAdminApplicationModule.cs +++ b/src/EventHub.Admin.Application/EventHubAdminApplicationModule.cs @@ -1,4 +1,5 @@ using Volo.Abp.Account; +using Volo.Abp.AutoMapper; using Volo.Abp.Identity; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement; @@ -10,9 +11,17 @@ namespace EventHub.Admin typeof(AbpAccountApplicationModule), typeof(EventHubAdminApplicationContractsModule), typeof(AbpIdentityApplicationModule), - typeof(AbpPermissionManagementApplicationModule) + typeof(AbpPermissionManagementApplicationModule), + typeof(AbpAutoMapperModule) )] public class EventHubAdminApplicationModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.AddProfile(validate: true); + }); + } } } \ No newline at end of file diff --git a/src/EventHub.Admin.Application/EventHubApplicationAutoMapperProfile.cs b/src/EventHub.Admin.Application/EventHubApplicationAutoMapperProfile.cs new file mode 100644 index 0000000..6b20518 --- /dev/null +++ b/src/EventHub.Admin.Application/EventHubApplicationAutoMapperProfile.cs @@ -0,0 +1,20 @@ +using AutoMapper; +using EventHub.Admin.Organizations; +using EventHub.Organizations; +using Volo.Abp.AutoMapper; + +namespace EventHub.Admin +{ + public class EventHubAdminApplicationAutoMapperProfile : Profile + { + public EventHubAdminApplicationAutoMapperProfile() + { + CreateMap(); + + CreateMap() + .Ignore(x => x.OwnerUserName) + .Ignore(x => x.OwnerEmail) + .Ignore(x => x.ProfilePictureContent); + } + } +} diff --git a/src/EventHub.Admin.Application/Organizations/OrganizationAppService.cs b/src/EventHub.Admin.Application/Organizations/OrganizationAppService.cs new file mode 100644 index 0000000..fc164c1 --- /dev/null +++ b/src/EventHub.Admin.Application/Organizations/OrganizationAppService.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Threading.Tasks; +using EventHub.Admin.Application; +using EventHub.Admin.Application.Contracts.Organizations; +using EventHub.Admin.Permissions; +using EventHub.Organizations; +using EventHub.Organizations.Memberships; +using EventHub.Users; +using Microsoft.AspNetCore.Authorization; +using Volo.Abp.Application.Dtos; +using Volo.Abp.BlobStoring; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Identity; +using Volo.Abp.Identity.Settings; + +namespace EventHub.Admin.Organizations +{ + [Authorize(EventHubPermissions.Organizations.Default)] + public class OrganizationAppService : EventHubAdminAppService, IOrganizationAppService + { + private readonly IRepository _organizationRepository; + private readonly IRepository _identityUserRepository; + private readonly IBlobContainer _organizationBlobContainer; + + public OrganizationAppService( + IRepository organizationRepository, + IRepository identityUserRepository, + IBlobContainer organizationBlobContainer) + { + _organizationRepository = organizationRepository; + _identityUserRepository = identityUserRepository; + _organizationBlobContainer = organizationBlobContainer; + } + + public async Task> GetListAsync(OrganizationListFilterDto input) + { + var query = await _organizationRepository.GetQueryableAsync(); + + if (!input.Name.IsNullOrWhiteSpace()) + { + query = query.Where(o => o.Name.Contains(input.Name)); + } + + if (!input.DisplayName.IsNullOrWhiteSpace()) + { + query = query.Where(o => o.DisplayName.Contains(input.DisplayName)); + } + + if (!input.DisplayName.IsNullOrWhiteSpace()) + { + query = query.Where(o => o.DisplayName.Contains(input.DisplayName)); + } + + if (input.MinMemberCount != null) + { + query = query.Where(o => o.MemberCount >= input.MinMemberCount); + } + + if (input.MaxMemberCount != null) + { + query = query.Where(o => o.MemberCount <= input.MaxMemberCount); + } + + var totalCount = await AsyncExecuter.CountAsync(query); + + query = query.OrderBy(string.IsNullOrWhiteSpace(input.Sorting) ? OrganizationConsts.DefaultSorting : input.Sorting); + + query = query.PageBy(input); + + var organizationListDto = ObjectMapper + .Map, List>(await AsyncExecuter.ToListAsync(query)); + + return new PagedResultDto(totalCount, organizationListDto); + } + + public async Task GetAsync(Guid id) + { + var organization = await _organizationRepository.GetAsync(id); + + var dto = ObjectMapper.Map(organization); + + var user = await _identityUserRepository.GetAsync(organization.OwnerUserId); + dto.OwnerUserName = user.UserName; + dto.OwnerEmail = user.Email; + + dto.ProfilePictureContent = await GetCoverImageAsync(id); + + return dto; + } + + public async Task UpdateAsync(Guid id, UpdateOrganizationDto input) + { + var organization = await _organizationRepository.GetAsync(id); + + organization.SetDisplayName(input.DisplayName); + organization.SetDescription(input.Description); + + organization.Website = input.Website; + organization.TwitterUsername = input.TwitterUsername; + organization.GitHubUsername = input.GitHubUsername; + organization.FacebookUsername = input.FacebookUsername; + organization.InstagramUsername = input.InstagramUsername; + organization.MediumUsername = input.MediumUsername; + + if (input.ProfilePictureContent?.Length > 0) + { + await SaveCoverImageAsync(organization.Id, input.ProfilePictureContent); + } + + await _organizationRepository.UpdateAsync(organization); + + return ObjectMapper.Map(organization); + } + + [Authorize(EventHubPermissions.Organizations.Delete)] + public async Task DeleteAsync(Guid id) + { + await _organizationRepository.DeleteAsync(id); + } + + private async Task GetCoverImageAsync(Guid id) + { + var blobName = id.ToString(); + + return await _organizationBlobContainer.GetAllBytesOrNullAsync(blobName); + } + + private async Task SaveCoverImageAsync(Guid id, byte[] coverImageContent) + { + var blobName = id.ToString(); + + await _organizationBlobContainer.SaveAsync(blobName, coverImageContent, overrideExisting: true); + } + } +} diff --git a/src/EventHub.Admin.HttpApi.Host/Controllers/OrganizationController.cs b/src/EventHub.Admin.HttpApi.Host/Controllers/OrganizationController.cs new file mode 100644 index 0000000..93984b7 --- /dev/null +++ b/src/EventHub.Admin.HttpApi.Host/Controllers/OrganizationController.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading.Tasks; +using EventHub.Admin.Application.Contracts.Organizations; +using EventHub.Admin.Organizations; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Application.Dtos; +using Volo.Abp.AspNetCore.Mvc; + +namespace EventHub.Admin.Controllers +{ + [Controller] + [Area("eventhub-admin")] + [ControllerName("Organization")] + [Route("/api/eventhub/organizations")] + public class OrganizationController : AbpController, IOrganizationAppService + { + private readonly IOrganizationAppService _organizationAppService; + + public OrganizationController(IOrganizationAppService organizationAppService) + { + _organizationAppService = organizationAppService; + } + + [HttpGet] + public Task> GetListAsync(OrganizationListFilterDto input) + { + return _organizationAppService.GetListAsync(input); + } + + [HttpGet] + [Route("{id}")] + public Task GetAsync(Guid id) + { + return _organizationAppService.GetAsync(id); + } + + [HttpPut] + public Task UpdateAsync(Guid id, UpdateOrganizationDto input) + { + return _organizationAppService.UpdateAsync(id, input); + } + + [HttpDelete] + [Route("{id}")] + public Task DeleteAsync(Guid id) + { + return _organizationAppService.DeleteAsync(id); + } + } +} \ No newline at end of file diff --git a/src/EventHub.Admin.Web/EventHubBlazorAutoMapperProfile.cs b/src/EventHub.Admin.Web/EventHubBlazorAutoMapperProfile.cs index 3cdc492..c0b5d01 100644 --- a/src/EventHub.Admin.Web/EventHubBlazorAutoMapperProfile.cs +++ b/src/EventHub.Admin.Web/EventHubBlazorAutoMapperProfile.cs @@ -1,4 +1,5 @@ using AutoMapper; +using EventHub.Admin.Organizations; namespace EventHub.Admin.Web { @@ -6,7 +7,7 @@ namespace EventHub.Admin.Web { public EventHubBlazorAutoMapperProfile() { - //Define your AutoMapper configuration here for the Blazor project. + CreateMap(); } } } diff --git a/src/EventHub.Domain.Shared/Organizations/OrganizationContst.cs b/src/EventHub.Domain.Shared/Organizations/OrganizationContst.cs index 7b9fedc..712dcf6 100644 --- a/src/EventHub.Domain.Shared/Organizations/OrganizationContst.cs +++ b/src/EventHub.Domain.Shared/Organizations/OrganizationContst.cs @@ -2,6 +2,8 @@ { public static class OrganizationConsts { + public const string DefaultSorting = "CreationTime desc"; + public const int MinNameLength = 2; public const int MaxNameLength = 32; @@ -24,6 +26,5 @@ public const int MaxInstagramUsernameLength = 24; public const int MaxMediumUsernameLength = 24; - } }