diff --git a/server/StrDss.Api/Controllers/OrganizationsController.cs b/server/StrDss.Api/Controllers/OrganizationsController.cs index 8b672451..837c0043 100644 --- a/server/StrDss.Api/Controllers/OrganizationsController.cs +++ b/server/StrDss.Api/Controllers/OrganizationsController.cs @@ -79,7 +79,7 @@ public async Task> GetStrRequirements(double lo } - [ApiAuthorize()] + [ApiAuthorize] [HttpGet("platforms")] public async Task>> GetPlatforms(int pageSize = 10, int pageNumber = 1, string orderBy = "OrganizationNm", string direction = "asc") { @@ -87,7 +87,7 @@ public async Task>> GetPlatforms(int pageSize return Ok(platforms); } - [ApiAuthorize()] + [ApiAuthorize] [HttpGet("platforms/{id}")] public async Task> GetPlatform(long id) { @@ -100,5 +100,19 @@ public async Task> GetPlatform(long id) return Ok(platform); } + + [ApiAuthorize(Permissions.UserWrite)] //todo: use platform_write permission when it's ready in the database + [HttpPost("platforms", Name = "CreatePlatform")] + public async Task CreatePlatform(PlatformUpdateDto dto) + { + var (errors, id) = await _orgService.CreatePlatformAsync(dto); + + if (errors.Any()) + { + return ValidationUtils.GetValidationErrorResult(errors, ControllerContext); + } + + return Ok(id); + } } } diff --git a/server/StrDss.Api/SwaggerDocumentFilter.cs b/server/StrDss.Api/SwaggerDocumentFilter.cs index 1a9a3f72..89821a4c 100644 --- a/server/StrDss.Api/SwaggerDocumentFilter.cs +++ b/server/StrDss.Api/SwaggerDocumentFilter.cs @@ -7,98 +7,35 @@ public class SwaggerDocumentFilter : IDocumentFilter { public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { - var filteredPaths = new OpenApiPaths(); + // Key is read-only so make a copy of the Paths property + var pathsPerConsumer = new OpenApiPaths(); var referencedSchemas = new HashSet(); if (swaggerDoc.Info.Version == Common.ApiTags.Aps) { foreach (var path in swaggerDoc.Paths) { - var operation = path.Value?.Operations?.Values?.FirstOrDefault(); - - if (operation != null && operation.Tags.Any(t => Common.ApiTags.ApsTagList.Contains(t.Name))) + // If there are any tags (all methods are decorated with "SwaggerOperation(Tags = new[]...") with the current consumer name + var p = path.Value?.Operations?.Values?.FirstOrDefault(); + if (p != null && p.Tags + .Where(t => Common.ApiTags.ApsTagList.Contains(t.Name)).Any()) { - filteredPaths.Add(path.Key.ToLowerInvariant(), path.Value); - TrackSchemasInOperations(operation, referencedSchemas); + // Add the path to the collection of paths for current consumer + pathsPerConsumer.Add(path.Key, path.Value); } } - //swaggerDoc.Servers = new List - //{ - // new OpenApiServer { Url = "https://strdata.dev.api.gov.bc.ca" } - //}; + } else { foreach (var path in swaggerDoc.Paths) { - if (path.Key != null && path.Value != null) - { - filteredPaths.Add(path.Key, path.Value); - - foreach (var operation in path.Value.Operations.Values) - { - TrackSchemasInOperations(operation, referencedSchemas); - } - } - } - } - - swaggerDoc.Paths = filteredPaths; - - // Filter schemas in components to only include those that are referenced - var filteredSchemas = new Dictionary(); - foreach (var schemaKey in swaggerDoc.Components.Schemas.Keys) - { - if (referencedSchemas.Contains(schemaKey)) - { - filteredSchemas.Add(schemaKey, swaggerDoc.Components.Schemas[schemaKey]); - } - } - - // Update the document's components with filtered schemas - swaggerDoc.Components.Schemas = filteredSchemas; - } - - /// - /// Tracks the schemas used in the operation's parameters, request bodies, and responses. - /// - private void TrackSchemasInOperations(OpenApiOperation operation, HashSet referencedSchemas) - { - // Track schemas from parameters - foreach (var parameter in operation.Parameters) - { - if (parameter.Schema?.Reference != null) - { - referencedSchemas.Add(parameter.Schema.Reference.Id); - } - } - - // Track schemas from request bodies - if (operation.RequestBody?.Content != null) - { - foreach (var content in operation.RequestBody.Content.Values) - { - if (content.Schema?.Reference != null) - { - referencedSchemas.Add(content.Schema.Reference.Id); - } + if (path.Key != null && path.Value != null) pathsPerConsumer.Add(path.Key, path.Value); } } - // Track schemas from responses - foreach (var response in operation.Responses.Values) - { - foreach (var content in response.Content.Values) - { - if (content.Schema?.Reference != null) - { - referencedSchemas.Add(content.Schema.Reference.Id); - } - } - } + swaggerDoc.Paths = pathsPerConsumer; } - - } } diff --git a/server/StrDss.Common/Constants.cs b/server/StrDss.Common/Constants.cs index 8c524ae3..af273f18 100644 --- a/server/StrDss.Common/Constants.cs +++ b/server/StrDss.Common/Constants.cs @@ -12,26 +12,16 @@ public static class Entities public const string RentalListingRowUntyped = "RentalListingRowUntyped"; public const string Role = "Role"; public const string BizLicenceRowUntyped = "BizLicenceRowUntyped"; + public const string Platform = "Platform"; } public static class Fields { - public const string StreetAddress = "StreetAddress"; - public const string City = "City"; - public const string Province = "Province"; - public const string PostalCode = "PostalCode"; - public const string ZoningTypeId = "ZoningTypeId"; - public const string SquareFootage = "SquareFootage"; - public const string StrAffiliateId = "StrAffiliateId"; - public const string IsOwnerPrimaryResidence = "IsOwnerPrimaryResidence"; - public const string ComplianceStatusId = "ComplianceStatusId"; - public const string Username = "Username"; public const string Passwrod = "Passwrod"; public const string LastName = "LastName"; public const string PhoneNumber = "PhoneNumber"; public const string RoleId = "RoleId"; - } public static class RentalListingReportFields @@ -117,6 +107,16 @@ public static class BizLicenceRowFields public const string PropertyLegalDescriptionTxt = "PropertyLegalDescriptionTxt"; } + public static class PlatformFields + { + public const string OrganizationId = "OrganizationId"; + public const string OrganizationCd = "OrganizationCd"; + public const string OrganizationNm = "OrganizationNm"; + public const string NoticeOfTakedownContactEmail1 = "NoticeOfTakedownContactEmail1"; + public const string TakedownRequestContactEmail1 = "TakedownRequestContactEmail1"; + public const string NoticeOfTakedownContactEmail2 = "NoticeOfTakedownContactEmail2"; + public const string TakedownRequestContactEmail2 = "TakedownRequestContactEmail2"; + } public static class RentalListingExport { diff --git a/server/StrDss.Data/Mappings/EntityToModelProfile.cs b/server/StrDss.Data/Mappings/EntityToModelProfile.cs index e0480a73..88d012ba 100644 --- a/server/StrDss.Data/Mappings/EntityToModelProfile.cs +++ b/server/StrDss.Data/Mappings/EntityToModelProfile.cs @@ -51,7 +51,7 @@ public EntityToModelProfile() .ForMember(o => o.UpdDtm, opt => opt.MapFrom(i => DateUtils.ConvertUtcToPacificTime(i.UpdDtm))); CreateMap(); - CreateMap(); + CreateMap(); CreateMap(); } } diff --git a/server/StrDss.Data/Mappings/ModelToEntityProfile.cs b/server/StrDss.Data/Mappings/ModelToEntityProfile.cs index d7ac0c66..e54ef5ef 100644 --- a/server/StrDss.Data/Mappings/ModelToEntityProfile.cs +++ b/server/StrDss.Data/Mappings/ModelToEntityProfile.cs @@ -30,7 +30,7 @@ public ModelToEntityProfile() .ForMember(dest => dest.SeparateReservationsQty, opt => opt.MapFrom(src => CommonUtils.StringToShort(src.ReservationsQty))); CreateMap(); - CreateMap(); + CreateMap(); } } } diff --git a/server/StrDss.Data/Repositories/OrganizationRepository.cs b/server/StrDss.Data/Repositories/OrganizationRepository.cs index e779f035..23f9e7f5 100644 --- a/server/StrDss.Data/Repositories/OrganizationRepository.cs +++ b/server/StrDss.Data/Repositories/OrganizationRepository.cs @@ -10,6 +10,7 @@ using StrDss.Model; using StrDss.Model.OrganizationDtos; using StrDss.Model.RentalReportDtos; +using System.Data; namespace StrDss.Data.Repositories { @@ -25,6 +26,8 @@ public interface IOrganizationRepository Task GetStrRequirements(double longitude, double latitude); Task> GetPlatforms(int pageSize, int pageNumber, string orderBy, string direction); Task GetPlatform(long id); + Task CreatePlatformAsync(PlatformUpdateDto dto); + Task DoesOrgCdExist(string orgCd); } public class OrganizationRepository : RepositoryBase, IOrganizationRepository { @@ -175,5 +178,38 @@ public async Task> GetPlatforms(int pageSize, int page return platform; } + public async Task CreatePlatformAsync(PlatformUpdateDto dto) + { + var entity = _mapper.Map(dto); + + entity.OrganizationType = OrganizationTypes.Platform; + + await _dbSet.AddAsync(entity); + + CreateContact(entity, EmailMessageTypes.NoticeOfTakedown, dto.NoticeOfTakedownContactEmail1, true); + CreateContact(entity, EmailMessageTypes.NoticeOfTakedown, dto.NoticeOfTakedownContactEmail2, false); + CreateContact(entity, EmailMessageTypes.TakedownRequest, dto.TakedownRequestContactEmail1, true); + CreateContact(entity, EmailMessageTypes.TakedownRequest, dto.TakedownRequestContactEmail2, false); + + return entity; + } + + private void CreateContact(DssOrganization entity, string messageType, string? emailAddress, bool isPrimary) + { + if (!string.IsNullOrEmpty(emailAddress)) + { + entity.DssOrganizationContactPeople.Add(new DssOrganizationContactPerson + { + EmailAddressDsc = emailAddress, + IsPrimary = isPrimary, + EmailMessageType = messageType + }); + } + } + + public async Task DoesOrgCdExist(string orgCd) + { + return await _dbSet.AnyAsync(x => x.OrganizationCd == orgCd); + } } } diff --git a/server/StrDss.Model/OrganizationDtos/PlatformCreateDto.cs b/server/StrDss.Model/OrganizationDtos/PlatformCreateDto.cs deleted file mode 100644 index bf1b0bd0..00000000 --- a/server/StrDss.Model/OrganizationDtos/PlatformCreateDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace StrDss.Model.OrganizationDtos -{ - public class PlatformCreateDto - { - public long OrganizationId { get; set; } - public string OrganizationCd { get; set; } = null!; - public string OrganizationNm { get; set; } = null!; - public DateTime UpdDtm { get; set; } - public Guid? UpdUserGuid { get; set; } - //public virtual ICollection ContactPeople { get; set; } = new List(); - } -} diff --git a/server/StrDss.Model/OrganizationDtos/PlatformUpdateDto.cs b/server/StrDss.Model/OrganizationDtos/PlatformUpdateDto.cs new file mode 100644 index 00000000..eb329220 --- /dev/null +++ b/server/StrDss.Model/OrganizationDtos/PlatformUpdateDto.cs @@ -0,0 +1,14 @@ +namespace StrDss.Model.OrganizationDtos +{ + public class PlatformUpdateDto + { + public long OrganizationId { get; set; } + public string OrganizationCd { get; set; } = null!; + public string OrganizationNm { get; set; } = null!; + public DateTime UpdDtm { get; set; } + public string? NoticeOfTakedownContactEmail1 { get; set; } + public string? TakedownRequestContactEmail1 { get; set; } + public string? NoticeOfTakedownContactEmail2 { get; set; } + public string? TakedownRequestContactEmail2 { get; set; } + } +} diff --git a/server/StrDss.Service/FieldValidatorService.cs b/server/StrDss.Service/FieldValidatorService.cs index de517bf2..8747c885 100644 --- a/server/StrDss.Service/FieldValidatorService.cs +++ b/server/StrDss.Service/FieldValidatorService.cs @@ -21,6 +21,7 @@ public FieldValidatorService() RentalListingReportValidationRule.LoadReportValidationRules(_rules); RoleValidationRule.LoadReportValidationRules(_rules); BizLicenceValidationRule.LoadBizLicenceValidationRules(_rules); + PlatformValidationRule.LoadPlatformUpdateValidationRules(_rules); } public IEnumerable GetFieldValidationRules(string entityName) diff --git a/server/StrDss.Service/OrganizationService.cs b/server/StrDss.Service/OrganizationService.cs index b3825e16..121bda36 100644 --- a/server/StrDss.Service/OrganizationService.cs +++ b/server/StrDss.Service/OrganizationService.cs @@ -7,6 +7,8 @@ using StrDss.Data.Repositories; using StrDss.Model; using StrDss.Model.OrganizationDtos; +using StrDss.Model.UserDtos; +using System.Threading.Tasks; namespace StrDss.Service { @@ -20,6 +22,7 @@ public interface IOrganizationService Task GetStrRequirements(double longitude, double latitude); Task> GetPlatforms(int pageSize, int pageNumber, string orderBy, string direction); Task GetPlatform(long id); + Task<(Dictionary>, long)> CreatePlatformAsync(PlatformUpdateDto dto); } public class OrganizationService : ServiceBase, IOrganizationService { @@ -71,5 +74,40 @@ public async Task> GetPlatforms(int pageSize, int page { return await _orgRepo.GetPlatform(id); } + + public async Task<(Dictionary>, long)> CreatePlatformAsync(PlatformUpdateDto dto) + { + var errors = new Dictionary>(); + + await ValidatePlatformUpdateDto(dto, errors); + + if (errors.Any()) + { + return (errors, 0); + } + + var entity = await _orgRepo.CreatePlatformAsync(dto); + + _unitOfWork.Commit(); + + return (errors, entity.OrganizationId); + } + + private async Task>> ValidatePlatformUpdateDto(PlatformUpdateDto dto, Dictionary> errors) + { + _validator.Validate(Entities.Platform, dto, errors); + + if (errors.Any()) + { + return errors; + } + + if (await _orgRepo.DoesOrgCdExist(dto.OrganizationCd)) + { + errors.AddItem("OrganizationCd", $"Organization Code {dto.OrganizationCd} already exists"); + } + + return errors; + } } } diff --git a/server/StrDss.Service/PlatformValidationRule.cs b/server/StrDss.Service/PlatformValidationRule.cs new file mode 100644 index 00000000..6a003bb6 --- /dev/null +++ b/server/StrDss.Service/PlatformValidationRule.cs @@ -0,0 +1,71 @@ +using StrDss.Common; + +namespace StrDss.Service +{ + public static class PlatformValidationRule + { + public static void LoadPlatformUpdateValidationRules(List rules) + { + rules.Add(new FieldValidationRule + { + EntityName = Entities.Platform, + FieldName = PlatformFields.OrganizationCd, + FieldType = FieldTypes.String, + Required = true, + MaxLength = 25, + MinLength = 1, + }); + + rules.Add(new FieldValidationRule + { + EntityName = Entities.Platform, + FieldName = PlatformFields.OrganizationNm, + FieldType = FieldTypes.String, + Required = true, + MaxLength = 250, + MinLength = 1, + }); + + rules.Add(new FieldValidationRule + { + EntityName = Entities.Platform, + FieldName = PlatformFields.NoticeOfTakedownContactEmail1, + FieldType = FieldTypes.String, + Required = true, + MaxLength = 320, + RegexInfo = RegexDefs.GetRegexInfo(RegexDefs.Email) + }); + + rules.Add(new FieldValidationRule + { + EntityName = Entities.Platform, + FieldName = PlatformFields.TakedownRequestContactEmail1, + FieldType = FieldTypes.String, + Required = true, + MaxLength = 320, + RegexInfo = RegexDefs.GetRegexInfo(RegexDefs.Email) + }); + + rules.Add(new FieldValidationRule + { + EntityName = Entities.Platform, + FieldName = PlatformFields.NoticeOfTakedownContactEmail2, + FieldType = FieldTypes.String, + Required = false, + MaxLength = 320, + RegexInfo = RegexDefs.GetRegexInfo(RegexDefs.Email) + }); + + rules.Add(new FieldValidationRule + { + EntityName = Entities.Platform, + FieldName = PlatformFields.TakedownRequestContactEmail2, + FieldType = FieldTypes.String, + Required = false, + MaxLength = 320, + RegexInfo = RegexDefs.GetRegexInfo(RegexDefs.Email) + }); + } + } + +}