diff --git a/src/api/Areas/Admin/Controllers/GroupController.cs b/src/api/Areas/Admin/Controllers/GroupController.cs index d985028..c19e312 100644 --- a/src/api/Areas/Admin/Controllers/GroupController.cs +++ b/src/api/Areas/Admin/Controllers/GroupController.cs @@ -9,7 +9,7 @@ using HSB.Core.Exceptions; using Microsoft.AspNetCore.Http.Extensions; -namespace HSB.API.Areas.SystemAdmin.Controllers; +namespace HSB.API.Areas.Admin.Controllers; /// /// GroupController class, provides endpoints for groups. @@ -49,7 +49,7 @@ public GroupController(IGroupService service, ILogger logger) [HttpGet(Name = "GetGroups-SystemAdmin")] [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] - [SwaggerOperation(Tags = new[] { "Group" })] + [SwaggerOperation(Tags = ["Group"])] public IActionResult Find() { var uri = new Uri(this.Request.GetDisplayUrl()); @@ -60,7 +60,7 @@ public IActionResult Find() } /// - /// + /// Get the group for the specified 'id'. /// /// /// @@ -68,7 +68,7 @@ public IActionResult Find() [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(GroupModel), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NoContent)] - [SwaggerOperation(Tags = new[] { "Group" })] + [SwaggerOperation(Tags = ["Group"])] public IActionResult GetForId(int id) { var group = _service.FindForId(id); @@ -79,7 +79,7 @@ public IActionResult GetForId(int id) } /// - /// + /// Add a new group to the database. /// /// /// @@ -87,7 +87,7 @@ public IActionResult GetForId(int id) [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(GroupModel), (int)HttpStatusCode.Created)] [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] - [SwaggerOperation(Tags = new[] { "Group" })] + [SwaggerOperation(Tags = ["Group"])] public IActionResult Add(GroupModel model) { var entity = model.ToEntity(); @@ -97,7 +97,7 @@ public IActionResult Add(GroupModel model) } /// - /// + /// Update the group for the specified 'id'. /// /// /// @@ -105,7 +105,7 @@ public IActionResult Add(GroupModel model) [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(GroupModel), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] - [SwaggerOperation(Tags = new[] { "Group" })] + [SwaggerOperation(Tags = ["Group"])] public IActionResult Update(GroupModel model) { var entity = model.ToEntity(); @@ -115,7 +115,7 @@ public IActionResult Update(GroupModel model) } /// - /// + /// Delete the group from the database. /// /// /// @@ -123,7 +123,7 @@ public IActionResult Update(GroupModel model) [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(GroupModel), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] - [SwaggerOperation(Tags = new[] { "Group" })] + [SwaggerOperation(Tags = ["Group"])] public IActionResult Remove(GroupModel model) { var entity = model.ToEntity() ?? throw new NoContentException(); diff --git a/src/api/Areas/Admin/Controllers/OrganizationController.cs b/src/api/Areas/Admin/Controllers/OrganizationController.cs index 0683b90..15b6ea3 100644 --- a/src/api/Areas/Admin/Controllers/OrganizationController.cs +++ b/src/api/Areas/Admin/Controllers/OrganizationController.cs @@ -1,5 +1,4 @@ using System.Net.Mime; -using HSB.Models; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; using HSB.Core.Models; @@ -8,9 +7,9 @@ using HSB.Keycloak; using HSB.Core.Exceptions; using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.Extensions.Caching.Memory; +using HSB.Models.Admin; -namespace HSB.API.Areas.SystemAdmin.Controllers; +namespace HSB.API.Areas.Admin.Controllers; /// /// OrganizationController class, provides endpoints for organizations. @@ -52,7 +51,7 @@ public OrganizationController( [HttpGet(Name = "GetOrganizations-SystemAdmin")] [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] - [SwaggerOperation(Tags = new[] { "Organization" })] + [SwaggerOperation(Tags = ["Organization"])] public IActionResult Find() { var uri = new Uri(this.Request.GetDisplayUrl()); @@ -71,7 +70,7 @@ public IActionResult Find() [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(OrganizationModel), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NoContent)] - [SwaggerOperation(Tags = new[] { "Organization" })] + [SwaggerOperation(Tags = ["Organization"])] public IActionResult GetForId(int id) { var organization = _service.FindForId(id); @@ -90,7 +89,7 @@ public IActionResult GetForId(int id) [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(OrganizationModel), (int)HttpStatusCode.Created)] [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] - [SwaggerOperation(Tags = new[] { "Organization" })] + [SwaggerOperation(Tags = ["Organization"])] public IActionResult Add(OrganizationModel model) { var entity = model.ToEntity(); @@ -112,10 +111,12 @@ public IActionResult Add(OrganizationModel model) [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(OrganizationModel), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] - [SwaggerOperation(Tags = new[] { "Organization" })] + [SwaggerOperation(Tags = ["Organization"])] public IActionResult Update(OrganizationModel model) { + var original = _service.FindForIdAsNoTracking(model.Id) ?? throw new NoContentException(); var entity = model.ToEntity(); + entity.RawData = original.RawData; _service.Update(entity); _service.CommitTransaction(); @@ -133,7 +134,7 @@ public IActionResult Update(OrganizationModel model) [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(OrganizationModel), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] - [SwaggerOperation(Tags = new[] { "Organization" })] + [SwaggerOperation(Tags = ["Organization"])] public IActionResult Remove(OrganizationModel model) { var entity = model.ToEntity() ?? throw new NoContentException(); diff --git a/src/api/Areas/Admin/Controllers/RoleController.cs b/src/api/Areas/Admin/Controllers/RoleController.cs index 69be5f2..d0d4fd4 100644 --- a/src/api/Areas/Admin/Controllers/RoleController.cs +++ b/src/api/Areas/Admin/Controllers/RoleController.cs @@ -9,7 +9,7 @@ using HSB.Models; using HSB.Keycloak; -namespace HSB.API.Areas.SystemAdmin.Controllers; +namespace HSB.API.Areas.Admin.Controllers; /// /// RoleController class, provides endpoints for roles. @@ -49,7 +49,7 @@ public RoleController(IRoleService service, ILogger logger) [HttpGet(Name = "GetRoles-SystemAdmin")] [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] - [SwaggerOperation(Tags = new[] { "Role" })] + [SwaggerOperation(Tags = ["Role"])] public IActionResult Find() { var uri = new Uri(this.Request.GetDisplayUrl()); @@ -68,7 +68,7 @@ public IActionResult Find() [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(RoleModel), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NoContent)] - [SwaggerOperation(Tags = new[] { "Role" })] + [SwaggerOperation(Tags = ["Role"])] public IActionResult GetForId(int id) { var role = _service.FindForId(id); @@ -87,7 +87,7 @@ public IActionResult GetForId(int id) [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(RoleModel), (int)HttpStatusCode.Created)] [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] - [SwaggerOperation(Tags = new[] { "Role" })] + [SwaggerOperation(Tags = ["Role"])] public IActionResult Add(RoleModel model) { var entity = model.ToEntity(); @@ -105,7 +105,7 @@ public IActionResult Add(RoleModel model) [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(RoleModel), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] - [SwaggerOperation(Tags = new[] { "Role" })] + [SwaggerOperation(Tags = ["Role"])] public IActionResult Update(RoleModel model) { var entity = model.ToEntity(); @@ -123,7 +123,7 @@ public IActionResult Update(RoleModel model) [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(RoleModel), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] - [SwaggerOperation(Tags = new[] { "Role" })] + [SwaggerOperation(Tags = ["Role"])] public IActionResult Remove(RoleModel model) { var entity = model.ToEntity() ?? throw new NoContentException(); diff --git a/src/api/Areas/Admin/Controllers/TenantController.cs b/src/api/Areas/Admin/Controllers/TenantController.cs index 77be72f..93bcf8b 100644 --- a/src/api/Areas/Admin/Controllers/TenantController.cs +++ b/src/api/Areas/Admin/Controllers/TenantController.cs @@ -1,5 +1,4 @@ using System.Net.Mime; -using HSB.Models; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; using HSB.Core.Models; @@ -8,8 +7,9 @@ using HSB.Keycloak; using HSB.Core.Exceptions; using Microsoft.AspNetCore.Http.Extensions; +using HSB.Models.Admin; -namespace HSB.API.Areas.SystemAdmin.Controllers; +namespace HSB.API.Areas.Admin.Controllers; /// /// TenantController class, provides endpoints for tenants. @@ -49,7 +49,7 @@ public TenantController(ITenantService service, ILogger logger [HttpGet(Name = "GetTenants-SystemAdmin")] [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] - [SwaggerOperation(Tags = new[] { "Tenant" })] + [SwaggerOperation(Tags = ["Tenant"])] public IActionResult Find() { var uri = new Uri(this.Request.GetDisplayUrl()); @@ -68,7 +68,7 @@ public IActionResult Find() [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(TenantModel), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NoContent)] - [SwaggerOperation(Tags = new[] { "Tenant" })] + [SwaggerOperation(Tags = ["Tenant"])] public IActionResult GetForId(int id) { var tenant = _service.FindForId(id); @@ -87,7 +87,7 @@ public IActionResult GetForId(int id) [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(TenantModel), (int)HttpStatusCode.Created)] [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] - [SwaggerOperation(Tags = new[] { "Tenant" })] + [SwaggerOperation(Tags = ["Tenant"])] public IActionResult Add(TenantModel model) { var entity = model.ToEntity(); @@ -108,10 +108,12 @@ public IActionResult Add(TenantModel model) [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(TenantModel), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] - [SwaggerOperation(Tags = new[] { "Tenant" })] + [SwaggerOperation(Tags = ["Tenant"])] public IActionResult Update(TenantModel model) { + var original = _service.FindForIdAsNoTracking(model.Id) ?? throw new NoContentException(); var entity = model.ToEntity(); + entity.RawData = original.RawData; _service.Update(entity); _service.CommitTransaction(); @@ -129,7 +131,7 @@ public IActionResult Update(TenantModel model) [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(TenantModel), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] - [SwaggerOperation(Tags = new[] { "Tenant" })] + [SwaggerOperation(Tags = ["Tenant"])] public IActionResult Remove(TenantModel model) { var entity = model.ToEntity() ?? throw new NoContentException(); diff --git a/src/api/Areas/Admin/Controllers/UserController.cs b/src/api/Areas/Admin/Controllers/UserController.cs index 6fe6c28..e1b9b92 100644 --- a/src/api/Areas/Admin/Controllers/UserController.cs +++ b/src/api/Areas/Admin/Controllers/UserController.cs @@ -11,7 +11,7 @@ using Microsoft.Extensions.Options; using Swashbuckle.AspNetCore.Annotations; -namespace HSB.API.Areas.SystemAdmin.Controllers; +namespace HSB.API.Areas.Admin.Controllers; /// /// UserController class, provides User endpoints for the admin api. diff --git a/src/libs/dal/Services/IOrganizationService.cs b/src/libs/dal/Services/IOrganizationService.cs index 08f5431..fd6c626 100644 --- a/src/libs/dal/Services/IOrganizationService.cs +++ b/src/libs/dal/Services/IOrganizationService.cs @@ -5,6 +5,13 @@ namespace HSB.DAL.Services; public interface IOrganizationService : IBaseService { + /// + /// Find the entity for the specified `keyValues`. + /// + /// + /// + Organization? FindForIdAsNoTracking(int id); + IEnumerable Find( Models.Filters.OrganizationFilter filter); diff --git a/src/libs/dal/Services/ITenantService.cs b/src/libs/dal/Services/ITenantService.cs index ee4b172..cd7c2bd 100644 --- a/src/libs/dal/Services/ITenantService.cs +++ b/src/libs/dal/Services/ITenantService.cs @@ -5,6 +5,13 @@ namespace HSB.DAL.Services; public interface ITenantService : IBaseService { + /// + /// Find the entity for the specified `keyValues`. + /// + /// + /// + Tenant? FindForIdAsNoTracking(int id); + IEnumerable Find( Models.Filters.TenantFilter filter); diff --git a/src/libs/dal/Services/OrganizationService.cs b/src/libs/dal/Services/OrganizationService.cs index 8a63da8..da55fc1 100644 --- a/src/libs/dal/Services/OrganizationService.cs +++ b/src/libs/dal/Services/OrganizationService.cs @@ -18,6 +18,17 @@ public OrganizationService(HSBContext dbContext, ClaimsPrincipal principal, ISer #endregion #region Methods + /// + /// Find the entity for the specified `keyValues`. + /// + /// + /// + public Organization? FindForIdAsNoTracking(int id) + { + return this.Context.Organizations.AsNoTracking() + .FirstOrDefault(t => t.Id == id); + } + public IEnumerable Find(Models.Filters.OrganizationFilter filter) { var query = from org in this.Context.Organizations diff --git a/src/libs/dal/Services/TenantService.cs b/src/libs/dal/Services/TenantService.cs index 6976288..c1aeff4 100644 --- a/src/libs/dal/Services/TenantService.cs +++ b/src/libs/dal/Services/TenantService.cs @@ -8,9 +8,19 @@ namespace HSB.DAL.Services; +/// +/// +/// public class TenantService : BaseService, ITenantService { #region Constructors + /// + /// + /// + /// + /// + /// + /// public TenantService(HSBContext dbContext, ClaimsPrincipal principal, IServiceProvider serviceProvider, ILogger logger) : base(dbContext, principal, serviceProvider, logger) { @@ -18,6 +28,17 @@ public TenantService(HSBContext dbContext, ClaimsPrincipal principal, IServicePr #endregion #region Methods + /// + /// Find the entity for the specified `keyValues`. + /// + /// + /// + public Tenant? FindForIdAsNoTracking(int id) + { + return this.Context.Tenants.AsNoTracking() + .FirstOrDefault(t => t.Id == id); + } + public IEnumerable Find( Models.Filters.TenantFilter filter) { diff --git a/src/libs/models/Admin/OrganizationModel.cs b/src/libs/models/Admin/OrganizationModel.cs new file mode 100644 index 0000000..a265a8d --- /dev/null +++ b/src/libs/models/Admin/OrganizationModel.cs @@ -0,0 +1,77 @@ +using System.Text.Json; +using HSB.Entities; + +namespace HSB.Models.Admin; +public class OrganizationModel : SortableCodeAuditableModel +{ + #region Properties + public int? ParentId { get; set; } + public OrganizationModel? Parent { get; set; } + public JsonDocument? RawData { get; set; } + + /// + /// get/set - The ServiceNow tenant key. + /// + public string ServiceNowKey { get; set; } = ""; + public IEnumerable Children { get; set; } = Array.Empty(); + + public IEnumerable Tenants { get; set; } = Array.Empty(); + #endregion + + #region Constructors + public OrganizationModel() { } + + public OrganizationModel(Organization entity, bool includeTenants, bool includeChildren = false) : base(entity) + { + this.Id = entity.Id; + this.ParentId = entity.ParentId; + if (!includeChildren) this.Parent = entity.Parent != null ? new OrganizationModel(entity.Parent, false) : null; + if (includeChildren) this.Children = entity.Children.Select(c => new OrganizationModel(c, false)); + + if (includeTenants) + { + this.Tenants = entity.TenantsManyToMany.Any() ? entity.TenantsManyToMany.Where(t => t.Tenant != null).Select(t => new TenantModel(t.Tenant!, false)).ToArray() : this.Tenants; + this.Tenants = entity.Tenants.Any() ? entity.Tenants.Select(t => new TenantModel(t, false)).ToArray() : this.Tenants; + } + + this.ServiceNowKey = entity.ServiceNowKey; + } + + public OrganizationModel(ServiceNow.ResultModel model) + { + if (model.Data == null) throw new InvalidOperationException("Organization data cannot be null"); + + this.Name = model.Data.Name ?? ""; + this.Code = model.Data.OrganizationCode ?? Guid.NewGuid().ToString(); + this.ServiceNowKey = model.Data.Id; + } + #endregion + + #region Methods + public Organization ToEntity() + { + return (Organization)this; + } + + public static explicit operator Organization(OrganizationModel model) + { + if (model.RawData == null) throw new InvalidOperationException("Property 'RawData' is required."); + + var entity = new Organization(model.Name) + { + Id = model.Id, + Description = model.Description, + Code = model.Code, + ParentId = model.ParentId, + ServiceNowKey = model.ServiceNowKey, + RawData = model.RawData, + IsEnabled = model.IsEnabled, + SortOrder = model.SortOrder, + Version = model.Version, + }; + entity.TenantsManyToMany.AddRange(model.Tenants.Select(t => new TenantOrganization(t.Id, entity.Id))); + + return entity; + } + #endregion +} diff --git a/src/libs/models/Admin/TenantModel.cs b/src/libs/models/Admin/TenantModel.cs new file mode 100644 index 0000000..59d73f1 --- /dev/null +++ b/src/libs/models/Admin/TenantModel.cs @@ -0,0 +1,76 @@ +using System.Text.Json; +using HSB.Entities; + +namespace HSB.Models.Admin; +public class TenantModel : SortableCodeAuditableModel +{ + #region Properties + public JsonDocument? RawData { get; set; } + + /// + /// get/set - The ServiceNow tenant key. + /// + public string ServiceNowKey { get; set; } = ""; + + /// + /// get/set - An array of organizations. + /// + public IEnumerable Organizations { get; set; } = Array.Empty(); + #endregion + + #region Constructors + public TenantModel() { } + + public TenantModel(Tenant entity, bool includeOrganizations) : base(entity) + { + this.Id = entity.Id; + + this.ServiceNowKey = entity.ServiceNowKey; + + if (includeOrganizations) + { + this.Organizations = entity.OrganizationsManyToMany.Any() ? entity.OrganizationsManyToMany.Where(o => o.Organization != null).Select(o => new OrganizationModel(o.Organization!, false)) : this.Organizations; + this.Organizations = entity.Organizations.Any() ? entity.Organizations.Select(o => new OrganizationModel(o, false)) : this.Organizations; + } + } + + public TenantModel(ServiceNow.ResultModel model, IEnumerable organizations) + { + if (model.Data == null) throw new InvalidOperationException("Tenant data cannot be null"); + + this.Name = model.Data.Name ?? model.Data.SysName ?? ""; + this.Code = model.Data.SysName ?? Guid.NewGuid().ToString(); + this.ServiceNowKey = model.Data.Id; + + this.Organizations = organizations; + } + #endregion + + #region Methods + public Tenant ToEntity() + { + return (Tenant)this; + } + + public static explicit operator Tenant(TenantModel model) + { + if (model.RawData == null) throw new InvalidOperationException("Property 'RawData' is required."); + + var tenant = new Tenant(model.Name) + { + Id = model.Id, + Description = model.Description, + Code = model.Code, + ServiceNowKey = model.ServiceNowKey, + RawData = model.RawData, + IsEnabled = model.IsEnabled, + SortOrder = model.SortOrder, + Version = model.Version, + }; + + tenant.OrganizationsManyToMany.AddRange(model.Organizations.Select(o => new TenantOrganization(model.Id, o.Id))); + + return tenant; + } + #endregion +}