diff --git a/src/AssemblyInfo.cs b/src/AssemblyInfo.cs index ec13ee64b..86f714527 100644 --- a/src/AssemblyInfo.cs +++ b/src/AssemblyInfo.cs @@ -6,6 +6,7 @@ [assembly: System.CLSCompliant(false)] [assembly: InternalsVisibleTo("AzureIoTHub.Portal.Shared")] +[assembly: InternalsVisibleTo("AzureIoTHub.Portal.Infrastructure")] [assembly: InternalsVisibleTo("AzureIoTHub.Portal.Server")] [assembly: InternalsVisibleTo("AzureIoTHub.Portal.Client")] [assembly: InternalsVisibleTo("AzureIoTHub.Portal.Tests.Unit")] diff --git a/src/AzureIoTHub.Portal.Application/AzureIoTHub.Portal.Application.csproj b/src/AzureIoTHub.Portal.Application/AzureIoTHub.Portal.Application.csproj index 36d5cb127..3cf8623b6 100644 --- a/src/AzureIoTHub.Portal.Application/AzureIoTHub.Portal.Application.csproj +++ b/src/AzureIoTHub.Portal.Application/AzureIoTHub.Portal.Application.csproj @@ -8,6 +8,7 @@ + diff --git a/src/AzureIoTHub.Portal.Application/Mappers/AWS/ThingTypeProfile.cs b/src/AzureIoTHub.Portal.Application/Mappers/AWS/ThingTypeProfile.cs new file mode 100644 index 000000000..b8035b8ce --- /dev/null +++ b/src/AzureIoTHub.Portal.Application/Mappers/AWS/ThingTypeProfile.cs @@ -0,0 +1,51 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Application.Mappers.AWS +{ + using AutoMapper; + using AzureIoTHub.Portal.Domain.Entities.AWS; + using AzureIoTHub.Portal.Models.v10.AWS; + using Amazon.IoT.Model; + public class ThingTypeProfile : Profile + { + public ThingTypeProfile() + { + _ = CreateMap() + .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.ThingTypeID)) + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.ThingTypeName)) + .ForMember(dest => dest.Description, opts => opts.MapFrom(src => src.ThingTypeDescription)) + .ForMember(dest => dest.ThingTypeSearchableAttributes, opts => opts.MapFrom(src => src.ThingTypeSearchableAttDtos.Select(pair => new ThingTypeSearchableAtt + { + Name = pair.Name + }))) + .ForMember(dest => dest.Tags, opts => opts.MapFrom(src => src.Tags.Select(pair => new ThingTypeTag + { + Key = pair.Key, + Value = pair.Value + }))); + + _ = CreateMap() + .ForMember(dest => dest.ThingTypeID, opts => opts.MapFrom(src => src.Id)) + .ForMember(dest => dest.ThingTypeName, opts => opts.MapFrom(src => src.Name)) + .ForMember(dest => dest.ThingTypeDescription, opts => opts.MapFrom(src => src.Description)) + .ForMember(dest => dest.Tags, opts => opts.MapFrom(src => src.Tags != null ? src.Tags.ToList() : null)) + .ForMember(dest => dest.ThingTypeSearchableAttDtos, opts => opts.MapFrom( + src => src.ThingTypeSearchableAttributes != null ? src.ThingTypeSearchableAttributes.ToList() : null)); + + _ = CreateMap() + .ForMember(dest => dest.ThingTypeName, opts => opts.MapFrom(src => src.ThingTypeName)) + .ForMember(dest => dest.ThingTypeProperties, opts => opts.MapFrom(src => new ThingTypeProperties + { + ThingTypeDescription = src.ThingTypeDescription, + SearchableAttributes = src.ThingTypeSearchableAttDtos.Select(pair => pair.Name).ToList() ?? new List() + })) + .ForMember(dest => dest.Tags, opts => opts.MapFrom(src => src.Tags.Select(pair => new Tag + { + Key = pair.Key, + Value = pair.Value + }).ToList() ?? new List())); + + } + } +} diff --git a/src/AzureIoTHub.Portal.Application/Mappers/AWS/ThingTypeSearchableAttProfile.cs b/src/AzureIoTHub.Portal.Application/Mappers/AWS/ThingTypeSearchableAttProfile.cs new file mode 100644 index 000000000..1b2845ca0 --- /dev/null +++ b/src/AzureIoTHub.Portal.Application/Mappers/AWS/ThingTypeSearchableAttProfile.cs @@ -0,0 +1,20 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Application.Mappers.AWS +{ + using AutoMapper; + using AzureIoTHub.Portal.Domain.Entities.AWS; + using AzureIoTHub.Portal.Models.v10.AWS; + + public class ThingTypeSearchableAttProfile : Profile + { + public ThingTypeSearchableAttProfile() + { + _ = CreateMap() + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Name)) + .ReverseMap(); + + } + } +} diff --git a/src/AzureIoTHub.Portal.Application/Services/AWS/IThingTypeService.cs b/src/AzureIoTHub.Portal.Application/Services/AWS/IThingTypeService.cs new file mode 100644 index 000000000..60cdc0290 --- /dev/null +++ b/src/AzureIoTHub.Portal.Application/Services/AWS/IThingTypeService.cs @@ -0,0 +1,20 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Application.Services.AWS +{ + using AzureIoTHub.Portal.Models.v10.AWS; + using Microsoft.AspNetCore.Http; + + public interface IThingTypeService + { + //Create a thing type + Task CreateThingType(ThingTypeDto thingType); + + Task GetThingTypeAvatar(string thingTypeId); + + Task UpdateThingTypeAvatar(string thingTypeId, IFormFile file); + + Task DeleteThingTypeAvatar(string thingTypeId); + } +} diff --git a/src/AzureIoTHub.Portal.Client/AzureIoTHub.Portal.Client.csproj b/src/AzureIoTHub.Portal.Client/AzureIoTHub.Portal.Client.csproj index 4a68e8824..2fc035073 100644 --- a/src/AzureIoTHub.Portal.Client/AzureIoTHub.Portal.Client.csproj +++ b/src/AzureIoTHub.Portal.Client/AzureIoTHub.Portal.Client.csproj @@ -39,5 +39,9 @@ + + + + diff --git a/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/CreateDeviceModelPage.razor b/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/CreateDeviceModelPage.razor index ca55dead3..50340a98e 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/CreateDeviceModelPage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/CreateDeviceModelPage.razor @@ -1,11 +1,15 @@ @page "/device-models/new" + @using System.Net.Http.Headers @using AzureIoTHub.Portal.Client.Pages.DeviceModels @using AzureIoTHub.Portal.Client.Validators +@using AzureIoTHub.Portal.Shared.Models @using AzureIoTHub.Portal.Models @using AzureIoTHub.Portal.Models.v10 @using AzureIoTHub.Portal.Models.v10.LoRaWAN -@using AzureIoTHub.Portal.Shared.Models +@using AzureIoTHub.Portal.Models.v10.AWS +@using AzureIoTHub.Portal.Shared.Constants +@using AzureIoTHub.Portal.Client.Services.AWS @attribute [Authorize] @inject NavigationManager NavigationManager @@ -13,8 +17,19 @@ @inject ISnackbar Snackbar @inject IDeviceModelsClientService DeviceModelsClientService @inject ILoRaWanDeviceModelsClientService LoRaWanDeviceModelsClientService +@inject ILoRaWanDeviceModelsClientService LoRaWanDeviceModelsClientService +@inject IThingTypeClientService ThingTypeClientService -Create Device Model +@if (Portal.CloudProvider.Equals(CloudProviders.AWS)) +{ + Create Thing Type + +} +else +{ + Create Device Model + +} @@ -37,6 +52,7 @@ } + Save Changes @@ -52,26 +68,44 @@ - + @if (Portal.CloudProvider.Equals(CloudProviders.AWS)) + { + + + } + else + { + + + } - - - @if (Portal.IsLoRaSupported) - { - - - LoRa Device - - - - @if (IsLoRa) + @if (Portal.CloudProvider.Equals(CloudProviders.AWS)) + { + + + } + else + { + + @if (Portal.IsLoRaSupported) { - The device is a LoRa Device. - + + + LoRa Device + + + + @if (IsLoRa) + { + The device is a LoRa Device. + + } + } - - } + } + + @@ -79,83 +113,164 @@ @if (!IsLoRa) { - - - - Properties - - - - Add property - - - @foreach (var item in this.Properties.OrderBy(x => x.Order)) - { - - - + + + Tags + + + Add tag + + @foreach (var tag in this.ThingTypeTags) + { + + + + + + + + + + Remove + + + } + + + + + + + } + else + { + + + + Properties + + + + Add property + + + @foreach (var item in this.Properties.OrderBy(x => x.Order)) + { + + + - - - + + - - - + + - - - + + - @foreach (DevicePropertyType item in Enum.GetValues(typeof(DevicePropertyType))) - { - @item - } - - - - + @foreach (DevicePropertyType item in Enum.GetValues(typeof(DevicePropertyType))) + { + @item + } + + + + + + + + Remove + + + } + + + + + + + } + } + @if (Portal.CloudProvider.Equals(CloudProviders.AWS)) + { + + + + Searchable Attribute + + + + Add Attribute + + + @foreach (var search in this.searchableAtt) + { + + + + - Remove + Remove } - + + + + + + } + else + { + + + + Labels + + } - - - - - Labels - - - - - - @@ -178,12 +293,15 @@ private MudForm form = default!; private DeviceModelValidator standardValidator = new DeviceModelValidator(); + private ThingTypeValidator thingTypeValidator = new ThingTypeValidator(); private LoRaDeviceModelValidator loraValidator = new LoRaDeviceModelValidator(); private DevicePropertyValidator propertiesValidator = new DevicePropertyValidator(); private List Commands { get; set; } = new(); private List Properties { get; set; } = new List(); - + private List searchableAtt { get; set; } = new List(); + private List ThingTypeTags { get; set; } = new List(); private LoRaDeviceModelCommandValidator CommandValidator = new LoRaDeviceModelCommandValidator(); + private ThingTypeDto ThingType { get; set; } = new ThingTypeDto(); private bool isProcessing; @@ -243,11 +361,22 @@ private bool CheckGeneralValidation() { - if (!IsLoRa && this.Model is DeviceModelDto deviceModel) + if (Portal.CloudProvider.Equals(CloudProviders.AWS)) { - return !this.standardValidator.Validate(deviceModel).IsValid; + if(this.ThingType != null) + { + return !this.thingTypeValidator.Validate(this.ThingType).IsValid; + } + } + else + { + if (!IsLoRa && this.Model is DeviceModelDto deviceModel) + { + return !this.standardValidator.Validate(deviceModel).IsValid; + } } + return CheckLoRaValidation(); } @@ -277,6 +406,19 @@ } private async Task Save() + { + + if(Portal.CloudProvider.Equals(CloudProviders.AWS)) + { + await this.SaveThingType(); + } + else + { + await this.SaveDeviceModel(); + } + } + + private async Task SaveDeviceModel() { isProcessing = true; @@ -330,9 +472,12 @@ } else { + await DeviceModelsClientService.CreateDeviceModel((Model as DeviceModelDto)!); await DeviceModelsClientService.SetDeviceModelModelProperties(Model.ModelId, Properties); + + } if (content is not null) @@ -349,6 +494,53 @@ Snackbar.Add("Device model successfully created.", Severity.Success); + + // Go back to the list of devices + NavigationManager.NavigateTo("device-models"); + } + + catch (ProblemDetailsException exception) + { + Error?.ProcessProblemDetails(exception); + } + finally + { + isProcessing = false; + } + } + + private async Task SaveThingType() + { + isProcessing = true; + + // Displays validation error message for each field + await form.Validate(); + + + if (!thingTypeValidator.Validate(ThingType).IsValid) + { + Snackbar.Add("One or more validation errors occurred", Severity.Error); + + isProcessing = false; + + return; + } + + try + { + + ThingType.Tags = this.ThingTypeTags; + ThingType.ThingTypeSearchableAttDtos = searchableAtt; + + var GetThingTypeId = await ThingTypeClientService.CreateThingType(ThingType); + + if (content is not null) + { + _ = this.ThingTypeClientService.ChangeAvatar(GetThingTypeId, content); + } + + Snackbar.Add("Thing Type successfully created.", Severity.Success); + // Go back to the list of devices NavigationManager.NavigateTo("device-models"); } diff --git a/src/AzureIoTHub.Portal.Client/Pages/Devices/CreateDevicePage.razor b/src/AzureIoTHub.Portal.Client/Pages/Devices/CreateDevicePage.razor index 7674f9b22..dd7757230 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/Devices/CreateDevicePage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/Devices/CreateDevicePage.razor @@ -8,7 +8,7 @@ @using AzureIoTHub.Portal.Shared.Models @using AzureIoTHub.Portal.Shared.Models.v10; @using AzureIoTHub.Portal.Shared.Models.v10.Filters; -@using Microsoft.AspNetCore.Components +@using AzureIoTHub.Portal.Shared.Constants; @attribute [Authorize] @inject ISnackbar Snackbar @@ -22,6 +22,7 @@ @implements IDisposable + Create Device diff --git a/src/AzureIoTHub.Portal.Client/Program.cs b/src/AzureIoTHub.Portal.Client/Program.cs index c3db5e4a5..6dd47c209 100644 --- a/src/AzureIoTHub.Portal.Client/Program.cs +++ b/src/AzureIoTHub.Portal.Client/Program.cs @@ -5,6 +5,7 @@ using AzureIoTHub.Portal.Client; using AzureIoTHub.Portal.Client.Handlers; using AzureIoTHub.Portal.Client.Services; +using AzureIoTHub.Portal.Client.Services.AWS; using AzureIoTHub.Portal.Models.v10; using AzureIoTHub.Portal.Settings; using Blazored.LocalStorage; @@ -87,6 +88,10 @@ _ = builder.Services.AddScoped(); _ = builder.Services.AddScoped(); +//AWS Dependency injection +_ = builder.Services.AddScoped(); + + await ConfigurePortalSettings(builder); // Enable loading bar diff --git a/src/AzureIoTHub.Portal.Client/Services/AWS/IThingTypeClientService.cs b/src/AzureIoTHub.Portal.Client/Services/AWS/IThingTypeClientService.cs new file mode 100644 index 000000000..35bbc234d --- /dev/null +++ b/src/AzureIoTHub.Portal.Client/Services/AWS/IThingTypeClientService.cs @@ -0,0 +1,17 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Client.Services.AWS +{ + using AzureIoTHub.Portal.Models.v10.AWS; + + public interface IThingTypeClientService + { + Task CreateThingType(ThingTypeDto thingType); + + Task GetAvatarUrl(string thingTypeId); + + Task ChangeAvatar(string thingTypeId, MultipartFormDataContent avatar); + + } +} diff --git a/src/AzureIoTHub.Portal.Client/Services/AWS/ThingTypeClientService.cs b/src/AzureIoTHub.Portal.Client/Services/AWS/ThingTypeClientService.cs new file mode 100644 index 000000000..9a36037c5 --- /dev/null +++ b/src/AzureIoTHub.Portal.Client/Services/AWS/ThingTypeClientService.cs @@ -0,0 +1,39 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Client.Services.AWS +{ + using System.Threading.Tasks; + using AzureIoTHub.Portal.Models.v10.AWS; + + public class ThingTypeClientService : IThingTypeClientService + { + private readonly HttpClient http; + + public ThingTypeClientService(HttpClient http) + { + this.http = http; + } + + public async Task CreateThingType(ThingTypeDto thingType) + { + var response = await this.http.PostAsJsonAsync("api/aws/thingtypes", thingType); + _ = response.EnsureSuccessStatusCode(); + + return await response.Content.ReadAsStringAsync(); + } + + public Task GetAvatarUrl(string thingTypeId) + { + return this.http.GetStringAsync($"api/aws/thingtypes/{thingTypeId}/avatar"); + } + + public async Task ChangeAvatar(string thingTypeId, MultipartFormDataContent avatar) + { + var result = await this.http.PostAsync($"api/aws/thingtypes/{thingTypeId}/avatar", avatar); + + _ = result.EnsureSuccessStatusCode(); + } + + } +} diff --git a/src/AzureIoTHub.Portal.Client/Validators/AWS/ThingTypeValidator.cs b/src/AzureIoTHub.Portal.Client/Validators/AWS/ThingTypeValidator.cs new file mode 100644 index 000000000..42d1b7725 --- /dev/null +++ b/src/AzureIoTHub.Portal.Client/Validators/AWS/ThingTypeValidator.cs @@ -0,0 +1,18 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Client.Validators +{ + using AzureIoTHub.Portal.Models.v10.AWS; + using FluentValidation; + + public class ThingTypeValidator : AbstractValidator + { + public ThingTypeValidator() + { + _ = RuleFor(x => x.ThingTypeName) + .NotEmpty() + .WithMessage("Model name is required."); + } + } +} diff --git a/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingType.cs b/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingType.cs new file mode 100644 index 000000000..92b64e511 --- /dev/null +++ b/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingType.cs @@ -0,0 +1,17 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Domain.Entities.AWS +{ + using System.ComponentModel.DataAnnotations; + using AzureIoTHub.Portal.Domain.Base; + + public class ThingType : EntityBase + { + [Required] + public string Name { get; set; } = default!; + public string? Description { get; set; } = default!; + public ICollection? Tags { get; set; } = default!; + public ICollection? ThingTypeSearchableAttributes { get; set; } = default!; + } +} diff --git a/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingTypeSearchableAtt.cs b/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingTypeSearchableAtt.cs new file mode 100644 index 000000000..bdb36c649 --- /dev/null +++ b/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingTypeSearchableAtt.cs @@ -0,0 +1,13 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Domain.Entities.AWS +{ + using AzureIoTHub.Portal.Domain.Base; + + public class ThingTypeSearchableAtt : EntityBase + { + public string Name { get; set; } = default!; + + } +} diff --git a/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingTypeTag.cs b/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingTypeTag.cs new file mode 100644 index 000000000..568a4fa70 --- /dev/null +++ b/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingTypeTag.cs @@ -0,0 +1,14 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Domain.Entities.AWS +{ + using AzureIoTHub.Portal.Domain.Base; + + public class ThingTypeTag : EntityBase + { + public string Key { get; set; } = default!; + public string Value { get; set; } = default!; + + } +} diff --git a/src/AzureIoTHub.Portal.Domain/IRepository.cs b/src/AzureIoTHub.Portal.Domain/IRepository.cs index b80607aae..6d559624c 100644 --- a/src/AzureIoTHub.Portal.Domain/IRepository.cs +++ b/src/AzureIoTHub.Portal.Domain/IRepository.cs @@ -18,6 +18,7 @@ public interface IRepository where T : EntityBase Task GetByIdAsync(object id, params Expression>[] includes); Task InsertAsync(T obj); + Task InsertAndGetIdAsync(T obj); void Update(T obj); diff --git a/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeRepository.cs b/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeRepository.cs new file mode 100644 index 000000000..b711e2698 --- /dev/null +++ b/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeRepository.cs @@ -0,0 +1,11 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Domain.Repositories +{ + using AzureIoTHub.Portal.Domain.Entities.AWS; + + public interface IThingTypeRepository : IRepository + { + } +} diff --git a/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeSearchableAttRepository.cs b/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeSearchableAttRepository.cs new file mode 100644 index 000000000..d689e8bab --- /dev/null +++ b/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeSearchableAttRepository.cs @@ -0,0 +1,11 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Domain.Repositories.AWS +{ + using AzureIoTHub.Portal.Domain.Entities.AWS; + + internal interface IThingTypeSearchableAttRepository : IRepository + { + } +} diff --git a/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeTagRepository.cs b/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeTagRepository.cs new file mode 100644 index 000000000..4fc67abba --- /dev/null +++ b/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeTagRepository.cs @@ -0,0 +1,11 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Domain.Repositories.AWS +{ + using AzureIoTHub.Portal.Domain.Entities.AWS; + + internal interface IThingTypeTagRepository : IRepository + { + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/AzureIoTHub.Portal.Infrastructure.csproj b/src/AzureIoTHub.Portal.Infrastructure/AzureIoTHub.Portal.Infrastructure.csproj index a5e13a1c0..08f55962c 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/AzureIoTHub.Portal.Infrastructure.csproj +++ b/src/AzureIoTHub.Portal.Infrastructure/AzureIoTHub.Portal.Infrastructure.csproj @@ -106,9 +106,9 @@ - + - + @@ -143,6 +143,7 @@ + diff --git a/src/AzureIoTHub.Portal.Infrastructure/ConfigHandlerFactory.cs b/src/AzureIoTHub.Portal.Infrastructure/ConfigHandlerFactory.cs index 07bb00b9b..c6357e8ae 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/ConfigHandlerFactory.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/ConfigHandlerFactory.cs @@ -7,6 +7,7 @@ namespace AzureIoTHub.Portal.Infrastructure using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Domain.Shared.Constants; + using AzureIoTHub.Portal.Shared.Constants; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; diff --git a/src/AzureIoTHub.Portal.Infrastructure/Managers/AwsDeviceModelImageManager.cs b/src/AzureIoTHub.Portal.Infrastructure/Managers/AwsDeviceModelImageManager.cs index 663e7c294..756c03cf0 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Managers/AwsDeviceModelImageManager.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Managers/AwsDeviceModelImageManager.cs @@ -4,8 +4,8 @@ namespace AzureIoTHub.Portal.Infrastructure.Managers { using System; + using System.Reflection; using System.Threading.Tasks; - using Amazon; using Amazon.S3; using Amazon.S3.Model; using Azure; @@ -40,6 +40,7 @@ public async Task ChangeDeviceModelImageAsync(string deviceModelId, Stre { this.logger.LogInformation($"Uploading Image to AWS S3 storage"); + //Portal must be able to upload images to Amazon S3 var putObjectRequest = new PutObjectRequest { @@ -49,7 +50,6 @@ public async Task ChangeDeviceModelImageAsync(string deviceModelId, Stre ContentType = "image/*", Headers = {CacheControl = $"max-age={this.configHandler.StorageAccountDeviceModelImageMaxAge}, must-revalidate" } }; - var putObjectResponse = await this.s3Client.PutObjectAsync(putObjectRequest); if (putObjectResponse.HttpStatusCode == System.Net.HttpStatusCode.OK) @@ -64,25 +64,21 @@ public async Task ChangeDeviceModelImageAsync(string deviceModelId, Stre var putACLResponse = await this.s3Client.PutACLAsync(putAclRequest); return putACLResponse.HttpStatusCode == System.Net.HttpStatusCode.OK - ? ComputeImageUrl(deviceModelId) + ? ComputeImageUri(deviceModelId).ToString() : throw new InternalServerErrorException("Error by setting the image access to public and read-only"); } else { throw new InternalServerErrorException("Error by uploading the image in S3 Storage"); } - } public Uri ComputeImageUri(string deviceModelId) { - throw new NotImplementedException(); + var url = $"https://{this.configHandler.AWSBucketName}.s3.{this.configHandler.AWSRegion}.amazonaws.com/{deviceModelId}"; + return new Uri(url); } - private string ComputeImageUrl(string deviceModelId) - { - return $"https://{this.configHandler.AWSBucketName}.s3.{RegionEndpoint.GetBySystemName(this.configHandler.AWSRegion)}.amazonaws.com/{deviceModelId}"; - } public async Task DeleteDeviceModelImageAsync(string deviceModelId) { @@ -107,13 +103,17 @@ public async Task DeleteDeviceModelImageAsync(string deviceModelId) public async Task SetDefaultImageToModel(string deviceModelId) { this.logger.LogInformation($"Uploading Default Image to AWS S3 storage"); + var currentAssembly = Assembly.GetExecutingAssembly(); + var defaultImageStream = currentAssembly + .GetManifestResourceStream($"{currentAssembly.GetName().Name}.Resources.{this.imageOptions.Value.DefaultImageName}"); + //Portal must be able to upload images to Amazon S3 var putObjectRequest = new PutObjectRequest { BucketName = this.configHandler.AWSBucketName, Key = deviceModelId, - FilePath = $"../Resources/{this.imageOptions.Value.DefaultImageName}", + InputStream = defaultImageStream, ContentType = "image/*", // image content type Headers = {CacheControl = $"max-age={this.configHandler.StorageAccountDeviceModelImageMaxAge}, must-revalidate" } @@ -133,7 +133,7 @@ public async Task SetDefaultImageToModel(string deviceModelId) var putACLResponse = await this.s3Client.PutACLAsync(putAclRequest); return putACLResponse.HttpStatusCode == System.Net.HttpStatusCode.OK - ? ComputeImageUrl(deviceModelId) + ? ComputeImageUri(deviceModelId).ToString() : throw new InternalServerErrorException("Error by setting the image access to public and read-only"); } else @@ -148,11 +148,15 @@ public async Task InitializeDefaultImageBlob() this.logger.LogInformation($"Initializing default Image to AWS S3 storage"); + var currentAssembly = Assembly.GetExecutingAssembly(); + + var defaultImageStream = currentAssembly + .GetManifestResourceStream($"{currentAssembly.GetName().Name}.Resources.{this.imageOptions.Value.DefaultImageName}"); var putObjectRequest = new PutObjectRequest { BucketName = this.configHandler.AWSBucketName, Key = this.imageOptions.Value.DefaultImageName, - FilePath = $"../Resources/{this.imageOptions.Value.DefaultImageName}", + InputStream = defaultImageStream, ContentType = "image/*", // image content type Headers = {CacheControl = $"max-age={this.configHandler.StorageAccountDeviceModelImageMaxAge}, must-revalidate" } diff --git a/src/AzureIoTHub.Portal.Infrastructure/Managers/DeviceModelImageManager.cs b/src/AzureIoTHub.Portal.Infrastructure/Managers/DeviceModelImageManager.cs index 00ef9f75f..db405d39b 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Managers/DeviceModelImageManager.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Managers/DeviceModelImageManager.cs @@ -121,5 +121,6 @@ public async Task SyncImagesCacheControl() _ = await blobClient.SetHttpHeadersAsync(new BlobHttpHeaders { CacheControl = $"max-age={this.configHandler.StorageAccountDeviceModelImageMaxAge}, must-revalidate" }); } } + } } diff --git a/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs b/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs index 2cd8457bb..12a2bcea8 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs @@ -3,6 +3,7 @@ namespace AzureIoTHub.Portal.Infrastructure { + using AzureIoTHub.Portal.Domain.Entities.AWS; using Domain.Entities; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; @@ -22,6 +23,9 @@ public class PortalDbContext : DbContext public DbSet Concentrators { get; set; } public DbSet LoRaDeviceTelemetry { get; set; } public DbSet