From a82878656fa0bd91c7c39f52bac73166d68880dd Mon Sep 17 00:00:00 2001
From: Adiv <asifadiv@gmail.com>
Date: Tue, 11 Apr 2023 14:03:16 +0100
Subject: [PATCH] added banner stuff

---
 AutoMapperProfile.cs                    | 14 ++---
 Constants.cs                            |  5 ++
 Controllers/DefaultController.cs        |  8 +--
 Controllers/DefaultProfileController.cs |  5 +-
 Controllers/ProfileController.cs        | 39 ++++++++----
 Group17profile.csproj                   | 55 +++++++++--------
 Helpers/ResponseOperationFilter.cs      |  8 +--
 Middleware/ExceptionMiddleware.cs       | 18 +++---
 Middleware/SwaggerProfileMiddleware.cs  |  8 +--
 Models/DTOs/ProfileDTO.cs               | 22 +++----
 Models/DTOs/UserDTO.cs                  |  8 +--
 Models/DefaultObjects/DefaultDTO.cs     |  2 +-
 Models/DefaultObjects/DefaultEntity.cs  | 18 +++---
 Models/Entities/Profile.cs              | 15 +++--
 Models/Entities/User.cs                 |  2 +-
 Models/ProfileDbContext.cs              | 20 ++++---
 Program.cs                              | 15 ++++-
 Services/ProfileService.cs              | 79 +++++++++++++------------
 Services/StorageService.cs              | 69 +++++++++++++++++++++
 Settings/ConnectionStrings.cs           |  7 +++
 appsettings.json                        | 12 ++--
 21 files changed, 265 insertions(+), 164 deletions(-)
 create mode 100644 Services/StorageService.cs
 create mode 100644 Settings/ConnectionStrings.cs

diff --git a/AutoMapperProfile.cs b/AutoMapperProfile.cs
index b875bfc..dfec55e 100644
--- a/AutoMapperProfile.cs
+++ b/AutoMapperProfile.cs
@@ -1,16 +1,16 @@
-using Group17profile.Models.DTOs;
-using Group17profile.Models.Entities;
+namespace Group17profile;
 
-namespace Group17profile;
+using Models.DTOs;
+using Models.Entities;
+using Profile = AutoMapper.Profile;
 
-public class AutoMapperProfile : AutoMapper.Profile
+public class AutoMapperProfile : Profile
 {
     public AutoMapperProfile()
     {
         CreateMap<User, UserDTO>();
         CreateMap<UserDTO, User>();
-        CreateMap<Profile, ProfileDTO>().ForMember(opt => opt.FavouriteShows, src => src.Ignore());
-        CreateMap<ProfileDTO, Profile>().ForMember(opt => opt.FavouriteShows, src => src.Ignore());
-        
+        CreateMap<Models.Entities.Profile, ProfileDTO>().ForMember(opt => opt.FavouriteShows, src => src.Ignore());
+        CreateMap<ProfileDTO, Models.Entities.Profile>().ForMember(opt => opt.FavouriteShows, src => src.Ignore());
     }
 }
\ No newline at end of file
diff --git a/Constants.cs b/Constants.cs
index 5f08ef3..76bfeff 100644
--- a/Constants.cs
+++ b/Constants.cs
@@ -19,6 +19,11 @@ public class Constants
         public const string ProfileSasToken = "profileSasToken";
     }
 
+    public static class AzureBlobContainer
+    {
+        public const string BannerPictures = "banner-pictures";
+    }
+
     public static class AuthorizationPolicies
     {
         public const string HasUserId = "HasUserId";
diff --git a/Controllers/DefaultController.cs b/Controllers/DefaultController.cs
index e19bfea..2dce994 100644
--- a/Controllers/DefaultController.cs
+++ b/Controllers/DefaultController.cs
@@ -1,8 +1,8 @@
-using System.Reflection;
-using Group17profile.Middleware;
-using Microsoft.AspNetCore.Mvc;
+namespace Group17profile.Controllers;
 
-namespace Group17profile.Controllers;
+using System.Reflection;
+using Microsoft.AspNetCore.Mvc;
+using Middleware;
 
 public class DefaultController : Controller
 {
diff --git a/Controllers/DefaultProfileController.cs b/Controllers/DefaultProfileController.cs
index d681747..ec3012e 100644
--- a/Controllers/DefaultProfileController.cs
+++ b/Controllers/DefaultProfileController.cs
@@ -1,7 +1,6 @@
-using System.Security.Claims;
-using Microsoft.VisualBasic;
+namespace Group17profile.Controllers;
 
-namespace Group17profile.Controllers;
+using System.Security.Claims;
 
 public class DefaultProfileController : DefaultController
 {
diff --git a/Controllers/ProfileController.cs b/Controllers/ProfileController.cs
index 56bb2e2..b4c8898 100644
--- a/Controllers/ProfileController.cs
+++ b/Controllers/ProfileController.cs
@@ -1,14 +1,11 @@
-using Group17profile.helpers;
-using Group17profile.Middleware;
-using Group17profile.Models.DTOs;
-using Group17profile.Models.Entities;
-using Group17profile.Services;
-using Microsoft.AspNetCore.Authorization;
+namespace Group17profile.Controllers;
+
 using Microsoft.AspNetCore.Mvc;
+using Middleware;
+using Models.DTOs;
+using Services;
 using Swashbuckle.AspNetCore.Annotations;
 
-namespace Group17profile.Controllers;
-
 [Route("api/[controller]")]
 [ApiController]
 public class ProfileController : DefaultProfileController
@@ -20,6 +17,21 @@ public class ProfileController : DefaultProfileController
         _profileService = profileService;
     }
 
+    [HttpGet("GetProfileForUser")]
+    [SwaggerResponse(200, Type = typeof(ResponseEnvelope<UserProfileDTO>))]
+    [SwaggerResponse(400, Type = typeof(ResponseEnvelope<BadRequestObjectResult>))]
+    public async Task<ActionResult<ResponseEnvelope<UserProfileDTO>>> GetProfileForUser()
+    {
+        try
+        {
+            return Ok(await _profileService.GetProfileForUser(1));
+        }
+        catch (Exception ex)
+        {
+            return BadRequest(ex.Message);
+        }
+    }
+
     [HttpPost("CreateOrUpdateProfile")]
     [SwaggerResponse(200, Type = typeof(ResponseEnvelope<ProfileDTO>))]
     [SwaggerResponse(400, Type = typeof(ResponseEnvelope<BadRequestObjectResult>))]
@@ -27,14 +39,21 @@ public class ProfileController : DefaultProfileController
     {
         try
         {
-            var newProfile = await _profileService.CreateOrUpdateProfile(profile, UserId);
-                    
+            var newProfile = await _profileService.CreateOrUpdateProfile(profile, 1);
             return Ok(newProfile);
         }
         catch (Exception ex)
         {
             return BadRequest(ex.Message);
         }
+    }
 
+    [HttpPost("UploadBannerPicture")]
+    [Consumes("multipart/form-data")]
+    public async Task<ActionResult<ResponseEnvelope<string>>> UploadBannerPicture(IFormFile? profilePicture)
+    {
+        if (profilePicture == null)
+            return BadRequest("Please upload a file.");
+        return Ok(await _profileService.UploadBannerPicture(1, profilePicture));
     }
 }
\ No newline at end of file
diff --git a/Group17profile.csproj b/Group17profile.csproj
index dfa4874..f2bec7e 100644
--- a/Group17profile.csproj
+++ b/Group17profile.csproj
@@ -1,33 +1,32 @@
 <Project Sdk="Microsoft.NET.Sdk.Web">
 
-  <PropertyGroup>
-    <TargetFramework>net8.0</TargetFramework>
-    <Nullable>enable</Nullable>
-    <ImplicitUsings>enable</ImplicitUsings>
-  </PropertyGroup>
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <Nullable>enable</Nullable>
+        <ImplicitUsings>enable</ImplicitUsings>
+    </PropertyGroup>
 
-  <ItemGroup>
-    <PackageReference Include="AuthorizeNet" Version="2.0.3" />
-    <PackageReference Include="AutoMapper" Version="12.0.1" />
-    <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.0-preview.2.23153.2" />
-    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.4" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0-preview.2.23128.3" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0-preview.2.23128.3">
-      <PrivateAssets>all</PrivateAssets>
-      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
-    </PackageReference>
-    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0-preview.2.23128.3" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0-preview.2.23128.3">
-      <PrivateAssets>all</PrivateAssets>
-      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
-    </PackageReference>
-    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
-    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
-    <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <Folder Include="Settings" />
-  </ItemGroup>
+    <ItemGroup>
+        <PackageReference Include="AuthorizeNet" Version="2.0.3"/>
+        <PackageReference Include="AutoMapper" Version="12.0.1"/>
+        <PackageReference Include="Azure.Storage.Blobs" Version="12.16.0-beta.1"/>
+        <PackageReference Include="Azure.Storage.Common" Version="12.15.0-beta.1"/>
+        <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.0-preview.2.23153.2"/>
+        <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.4"/>
+        <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0-preview.2.23128.3"/>
+        <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0-preview.2.23128.3">
+            <PrivateAssets>all</PrivateAssets>
+            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+        </PackageReference>
+        <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0-preview.2.23128.3"/>
+        <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0-preview.2.23128.3">
+            <PrivateAssets>all</PrivateAssets>
+            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+        </PackageReference>
+        <PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
+        <PackageReference Include="SixLabors.ImageSharp" Version="3.0.1"/>
+        <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
+        <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0"/>
+    </ItemGroup>
 
 </Project>
diff --git a/Helpers/ResponseOperationFilter.cs b/Helpers/ResponseOperationFilter.cs
index 40ee5d6..5ef82ce 100644
--- a/Helpers/ResponseOperationFilter.cs
+++ b/Helpers/ResponseOperationFilter.cs
@@ -1,11 +1,5 @@
-using System.Reflection;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.OpenApi.Models;
-using Swashbuckle.AspNetCore.SwaggerGen;
-
-namespace Group17profile.helpers;
+namespace Group17profile.helpers;
 
 public class ResponseOperationFilter
 {
-    
 }
\ No newline at end of file
diff --git a/Middleware/ExceptionMiddleware.cs b/Middleware/ExceptionMiddleware.cs
index e13ec9d..76f8fe5 100644
--- a/Middleware/ExceptionMiddleware.cs
+++ b/Middleware/ExceptionMiddleware.cs
@@ -1,9 +1,9 @@
-using System.Net;
-using System.Security.Authentication;
-using Group17profile.Exceptions;
-using Newtonsoft.Json;
+namespace Group17profile.Middleware;
 
-namespace Group17profile.Middleware;
+using System.Net;
+using Exceptions;
+using Microsoft.EntityFrameworkCore;
+using Newtonsoft.Json;
 
 public class ExceptionMiddleware
 {
@@ -49,7 +49,7 @@ public class ExceptionMiddleware
                 logger.LogWarning(ex.Message);
                 break;
 
-            case Microsoft.EntityFrameworkCore.DbUpdateException dbex:
+            case DbUpdateException dbex:
                 envelope = new ResponseEnvelope<object>(dbex,
                     userMessage: "Database Error has occurred. Please try again or report the issue.");
                 context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
@@ -86,8 +86,6 @@ public class ResponseEnvelope
 
 public class ResponseEnvelope<T> : ResponseEnvelope
 {
-    public new T? Data { get; set; }
-
     public ResponseEnvelope()
     {
     }
@@ -95,7 +93,7 @@ public class ResponseEnvelope<T> : ResponseEnvelope
     public ResponseEnvelope(Exception ex, int statusCode = 400, string? userMessage = null) : this(
         new List<ErrorDetails>
         {
-            new ErrorDetails
+            new()
             {
                 Message = ex.Message,
                 InnerMessage = ex.InnerException?.Message,
@@ -126,6 +124,8 @@ public class ResponseEnvelope<T> : ResponseEnvelope
         StatusCode = statusCode;
     }
 
+    public new T? Data { get; set; }
+
     public override string ToString()
     {
         return JsonConvert.SerializeObject(this);
diff --git a/Middleware/SwaggerProfileMiddleware.cs b/Middleware/SwaggerProfileMiddleware.cs
index 79ed1a4..ed687a5 100644
--- a/Middleware/SwaggerProfileMiddleware.cs
+++ b/Middleware/SwaggerProfileMiddleware.cs
@@ -1,7 +1,7 @@
-using System.Net;
-using System.Text;
+namespace Group17profile.Middleware;
 
-namespace Group17profile.Middleware;
+using System.Net;
+using System.Text;
 
 public class SwaggerProfileMiddleware
 {
@@ -9,7 +9,7 @@ public class SwaggerProfileMiddleware
 
     public SwaggerProfileMiddleware(RequestDelegate next)
     {
-        this._next = next;
+        _next = next;
     }
 
     public async Task InvokeAsync(HttpContext context)
diff --git a/Models/DTOs/ProfileDTO.cs b/Models/DTOs/ProfileDTO.cs
index a6727f0..ff8dfec 100644
--- a/Models/DTOs/ProfileDTO.cs
+++ b/Models/DTOs/ProfileDTO.cs
@@ -1,24 +1,18 @@
-using System.ComponentModel.DataAnnotations;
-using Group17profile.Models.DefaultObjects;
+namespace Group17profile.Models.DTOs;
 
-namespace Group17profile.Models.DTOs;
+using DefaultObjects;
 
 public class ProfileDTO : DefaultGuidDTO
 {
     public string? Biography { get; set; }
-    
-    public string? BannerUrl { get; set; }
-    
-    public string? Gender { get; set; }
-    
-    public DateTimeOffset? DoB { get; set; }
-    
-    public List<string>? FavouriteShows { get; set; }
-    
-    public string? Pronoun { get; set; }
-}
 
+    public string? BannerUrl { get; set; }
 
+    public string? Gender { get; set; }
 
+    public DateTimeOffset? DoB { get; set; }
 
+    public List<string>? FavouriteShows { get; set; }
 
+    public string? Pronoun { get; set; }
+}
\ No newline at end of file
diff --git a/Models/DTOs/UserDTO.cs b/Models/DTOs/UserDTO.cs
index bca9073..d8e2941 100644
--- a/Models/DTOs/UserDTO.cs
+++ b/Models/DTOs/UserDTO.cs
@@ -1,7 +1,7 @@
-using System.ComponentModel.DataAnnotations;
-using Group17profile.Models.DefaultObjects;
+namespace Group17profile.Models.DTOs;
 
-namespace Group17profile.Models.DTOs;
+using System.ComponentModel.DataAnnotations;
+using DefaultObjects;
 
 public class UserDTO : DefaultIntDTO
 {
@@ -25,7 +25,7 @@ public class UserProfileDTO
 {
     public UserDTO? User { get; set; }
     public ProfileDTO? Profile { get; set; }
-} 
+}
 
 public class RefreshTokenRequestDTO
 {
diff --git a/Models/DefaultObjects/DefaultDTO.cs b/Models/DefaultObjects/DefaultDTO.cs
index 119ed79..e00c64b 100644
--- a/Models/DefaultObjects/DefaultDTO.cs
+++ b/Models/DefaultObjects/DefaultDTO.cs
@@ -18,4 +18,4 @@ public class DefaultIntDTO
     }
 
     public int Id { get; set; }
-}
+}
\ No newline at end of file
diff --git a/Models/DefaultObjects/DefaultEntity.cs b/Models/DefaultObjects/DefaultEntity.cs
index 1841717..eed48b4 100644
--- a/Models/DefaultObjects/DefaultEntity.cs
+++ b/Models/DefaultObjects/DefaultEntity.cs
@@ -1,29 +1,29 @@
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace Group17profile.Models.DefaultObjects;
+namespace Group17profile.Models.DefaultObjects;
 
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
 
 public class DefaultGuidEntity : IDefaultEntity, IGuidId, ITrackable
 {
+    public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
+
+    public DateTimeOffset? DeletedAt { get; set; }
+
     [Key]
     [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
     public Guid Id { get; set; }
 
-    public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
-    
-    public DateTimeOffset? DeletedAt { get; set; }
-    
     public DateTimeOffset? UpdatedAt { get; set; }
 }
 
-
 public class DefaultIntEntity : IDefaultEntity, IIntId, ITrackable
 {
     public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
     public DateTimeOffset? DeletedAt { get; set; }
+
     [Key]
     public int Id { get; set; }
+
     public DateTimeOffset? UpdatedAt { get; set; }
 }
 
diff --git a/Models/Entities/Profile.cs b/Models/Entities/Profile.cs
index 38232d1..7dfbad3 100644
--- a/Models/Entities/Profile.cs
+++ b/Models/Entities/Profile.cs
@@ -1,19 +1,18 @@
-using System.Runtime.InteropServices.JavaScript;
-using Group17profile.Models.DefaultObjects;
+namespace Group17profile.Models.Entities;
 
-namespace Group17profile.Models.Entities;
+using DefaultObjects;
 
 public class Profile : DefaultGuidEntity
 {
     public string? Biography { get; set; }
-    
+
     public string? BannerUrl { get; set; }
-    
+
     public string? Gender { get; set; }
-    
+
     public DateTimeOffset? DoB { get; set; }
-    
+
     public string? FavouriteShows { get; set; }
-    
+
     public string? Pronoun { get; set; }
 }
\ No newline at end of file
diff --git a/Models/Entities/User.cs b/Models/Entities/User.cs
index e7fdb17..586be24 100644
--- a/Models/Entities/User.cs
+++ b/Models/Entities/User.cs
@@ -28,6 +28,6 @@ public class User : DefaultIntEntity
 
     [ForeignKey("ProfileId")] public virtual Profile? Profile { get; set; }
 
-    
+
     [NotMapped] public string Username => FirstName + " " + Surname;
 }
\ No newline at end of file
diff --git a/Models/ProfileDbContext.cs b/Models/ProfileDbContext.cs
index bb87b98..2af10fc 100644
--- a/Models/ProfileDbContext.cs
+++ b/Models/ProfileDbContext.cs
@@ -1,16 +1,19 @@
-using System.ComponentModel.DataAnnotations.Schema;
-using Group17profile.Models.DefaultObjects;
-using Group17profile.Models.Entities;
-using Microsoft.EntityFrameworkCore;
+namespace Group17profile.Models;
 
-namespace Group17profile.Models;
+using System.ComponentModel.DataAnnotations.Schema;
+using DefaultObjects;
+using Entities;
+using Microsoft.EntityFrameworkCore;
 
 public class ProfileDbContext : DbContext
 {
     public static string ConnectionStringName = "";
 
-    
-    public ProfileDbContext(DbContextOptions<ProfileDbContext> options) : base(options){}
+
+    public ProfileDbContext(DbContextOptions<ProfileDbContext> options) : base(options)
+    {
+    }
+
     public DbSet<Profile> Profile { get; set; } = null!;
     public DbSet<User> User { get; set; } = null!;
 
@@ -33,9 +36,8 @@ public class ProfileDbContext : DbContext
                          p.CustomAttributes.Any(a => a.AttributeType == typeof(DatabaseGeneratedAttribute))))
                 modelBuilder.Entity(entity.ClrType).Property(property.Name).HasDefaultValueSql("SYSDATETIMEOFFSET()");
         }
-    
     }
-    
+
     public override int SaveChanges(bool acceptAllChangesOnSuccess)
     {
         UpdateTimestamps();
diff --git a/Program.cs b/Program.cs
index 078397e..3e1a4af 100644
--- a/Program.cs
+++ b/Program.cs
@@ -1,6 +1,9 @@
 using AutoMapper;
 using Group17profile;
 using Group17profile.Models;
+using Group17profile.Repositories;
+using Group17profile.Services;
+using Group17profile.Settings;
 using Microsoft.EntityFrameworkCore;
 
 var builder = WebApplication.CreateBuilder(args);
@@ -10,7 +13,8 @@ var builder = WebApplication.CreateBuilder(args);
 builder.Services.AddControllers();
 builder.Configuration.AddJsonFile("appsettings.Development.json", true, true);
 builder.Services.AddDbContext<ProfileDbContext>(
-    opt => opt.UseSqlServer(builder.Configuration.GetConnectionString("ProfileDb")));
+    opt => opt.UseSqlServer(builder.Configuration.GetConnectionString("DbConnectionString")));
+builder.Services.Configure<ConnectionStrings>(builder.Configuration.GetSection("ConnectionStrings"));
 
 // Automapper
 var mapperConfig = new MapperConfiguration(mc => { mc.AddProfile(new AutoMapperProfile()); });
@@ -21,6 +25,13 @@ builder.Services.AddSingleton(mapper);
 builder.Services.AddEndpointsApiExplorer();
 builder.Services.AddSwaggerGen();
 
+// Add repositories in /Repositories
+builder.Services.AddTransient(typeof(IBaseRepository<>), typeof(BaseRepository<>));
+
+// Add services in /Services
+builder.Services.AddTransient<IProfileService, ProfileService>();
+builder.Services.AddTransient<IStorageService, StorageService>();
+
 var app = builder.Build();
 
 // Configure the HTTP request pipeline.
@@ -36,4 +47,4 @@ app.UseAuthorization();
 
 app.MapControllers();
 
-app.Run();
+app.Run();
\ No newline at end of file
diff --git a/Services/ProfileService.cs b/Services/ProfileService.cs
index c60f5f9..04b6766 100644
--- a/Services/ProfileService.cs
+++ b/Services/ProfileService.cs
@@ -1,34 +1,35 @@
-using System.Text;
+namespace Group17profile.Services;
+
+using System.Text;
 using AutoMapper;
-using Group17profile.Exceptions;
-using Group17profile.Models.DTOs;
-using Group17profile.Models.Entities;
-using Group17profile.Repositories;
+using Exceptions;
 using Microsoft.EntityFrameworkCore;
-using Profile = Group17profile.Models.Entities.Profile;
-
-namespace Group17profile.Services;
+using Models.DTOs;
+using Models.Entities;
+using Repositories;
+using Profile = Models.Entities.Profile;
 
 public interface IProfileService
 {
     Task<ProfileDTO> CreateOrUpdateProfile(ProfileDTO profile, int userId);
-    Task<UserProfileDTO> GetProfileForUser(ProfileDTO profile, int userId);
-    Task<string> UploadBannerPicture(ProfileDTO profile, int userId, IFormFile bannerPicture);
-    Task<string> EditBannerPicture(ProfileDTO profile, int userId, IFormFile? bannerPicture = null);
+    Task<UserProfileDTO> GetProfileForUser(int userId);
+    Task<string> UploadBannerPicture(int userId, IFormFile bannerPicture);
 }
 
 public class ProfileService : IProfileService
 {
     private readonly IMapper _mapper;
     private readonly IBaseRepository<Profile> _profileRepository;
+    private readonly IStorageService _storageService;
     private readonly IBaseRepository<User> _userRepository;
 
     public ProfileService(IMapper mapper, IBaseRepository<Profile> profileRepository,
-        IBaseRepository<User> userRepository)
+        IBaseRepository<User> userRepository, IStorageService storageService)
     {
         _mapper = mapper;
         _profileRepository = profileRepository;
         _userRepository = userRepository;
+        _storageService = storageService;
     }
 
     public async Task<ProfileDTO> CreateOrUpdateProfile(ProfileDTO profile, int userId)
@@ -37,14 +38,12 @@ public class ProfileService : IProfileService
         var age = DateTimeOffset.Now.Year - profile.DoB.GetValueOrDefault().Year;
 
         if (age is <= 16 or >= 100)
-        {
-            throw new ProfileException("Please enter valid age");
-        }
+            throw new ProfileException("Please enter valid age.");
 
         var newDetails = _mapper.Map<Profile>(profile);
         var record = _mapper.Map<Profile>(profile);
 
-        if (newDetails.FavouriteShows.Any())
+        if (!string.IsNullOrWhiteSpace(newDetails.FavouriteShows))
         {
             var builder = new StringBuilder();
             foreach (var show in newDetails.FavouriteShows)
@@ -67,35 +66,41 @@ public class ProfileService : IProfileService
     {
         var user = _userRepository.GetByIdThrowIfNull(userId);
         if (user.ProfileId == null)
-            throw new ProfileException("Failed to retrieve profile for user");
+            throw new ProfileException("Failed to retrieve profile for user.");
 
-        var record = _mapper.Map<Profile>(profile);
-        if (!string.IsNullOrWhiteSpace(record.FavouriteShows))
-        {
-            var s = record.FavouriteShows.Split(',');
-            var list = s.ToList();
-            profile.FavouriteShows = list;
-        }
+        var profile = await _profileRepository.GetAll().AsNoTracking().FirstOrDefaultAsync(p => p.Id == user.ProfileId);
+        var record = _mapper.Map<ProfileDTO>(profile);
+        if (string.IsNullOrWhiteSpace(profile?.FavouriteShows))
+            return new UserProfileDTO {User = _mapper.Map<UserDTO>(user), Profile = record};
+        var s = profile.FavouriteShows.Split(",");
+        record.FavouriteShows = s.ToList();
 
-        var profile = _mapper.Map<ProfileDTO>(_profileRepository.GetAll().AsNoTracking()
-            .FirstOrDefault(p => p.Id == user.ProfileId));
-        return new UserProfileDTO() {User = _mapper.Map<UserDTO>(user), Profile = profile};
+        return new UserProfileDTO {User = _mapper.Map<UserDTO>(user), Profile = record};
     }
 
-    public Task<string> UploadBannerPicture(ProfileDTO profile, int userId, IFormFile bannerPicture)
+    public async Task<string> UploadBannerPicture(int userId, IFormFile bannerPicture)
     {
         var user = _userRepository.GetByIdThrowIfNull(userId);
-        if (profile.BannerUrl == null)
-        {
-        }
 
-        throw new NotImplementedException();
-    }
+        using var ms = new MemoryStream();
+        await bannerPicture.CopyToAsync(ms);
+        ms.Position = 0;
+        var fileName = $"{user.Id}/{user.FirstName}banner";
+        var image = await _storageService.SaveImageAsJpgBlob(Constants.AzureBlobContainer.BannerPictures, fileName, ms);
 
-    public Task<string> EditBannerPicture(ProfileDTO profile, int userId, IFormFile? bannerPicture = null)
-    {
-        var user = _userRepository.GetByIdThrowIfNull(userId);
+        var profile = await _profileRepository.GetAll().AsNoTracking().FirstOrDefaultAsync(p => p.Id == user.ProfileId);
+        if (profile == null)
+        {
+            var newProfile = new Profile
+            {
+                BannerUrl = image.ToString()
+            };
+            await _profileRepository.CreateAndSaveAsync(newProfile);
+            return _storageService.GetSasForFile(Constants.AzureBlobContainer.BannerPictures, image.ToString());
+        }
 
-        throw new NotImplementedException();
+        profile.BannerUrl = image.ToString();
+        await _profileRepository.UpdateAndSaveAsync(profile);
+        return _storageService.GetSasForFile(Constants.AzureBlobContainer.BannerPictures, image.ToString());
     }
 }
\ No newline at end of file
diff --git a/Services/StorageService.cs b/Services/StorageService.cs
new file mode 100644
index 0000000..3386e33
--- /dev/null
+++ b/Services/StorageService.cs
@@ -0,0 +1,69 @@
+namespace Group17profile.Services;
+
+using System.Web;
+using Azure.Storage.Blobs;
+using Azure.Storage.Sas;
+using Microsoft.Extensions.Options;
+using Settings;
+
+public interface IStorageService
+{
+    string GetSasForFile(string containerName, string url, DateTimeOffset? expires = null);
+
+    Task<Uri> SaveImageAsJpgBlob(string containerName, string fileName, Stream input,
+        Dictionary<string, string>? metadata = null);
+}
+
+public class StorageService : IStorageService
+{
+    private readonly ConnectionStrings _connectionStrings;
+
+    public StorageService(IOptions<ConnectionStrings> connectionStrings)
+    {
+        _connectionStrings = connectionStrings.Value;
+    }
+
+    public string GetSasForFile(string containerName, string url, DateTimeOffset? expires = null)
+    {
+        url = GetFileFromUrl(url, containerName) ?? string.Empty;
+        if (string.IsNullOrWhiteSpace(url))
+            return string.Empty;
+        var container = new BlobContainerClient(_connectionStrings.AzureBlobStorage, containerName);
+        var blob = container.GetBlobClient(url);
+        var urlWithSas = blob.GenerateSasUri(BlobSasPermissions.Read, DateTimeOffset.Now.AddHours(3)).ToString();
+        return urlWithSas;
+    }
+
+    public async Task<Uri> SaveImageAsJpgBlob(string containerName, string fileName, Stream input,
+        Dictionary<string, string>? metadata = null)
+    {
+        using var image = await Image.LoadAsync(input);
+        using var croppedImage = new MemoryStream();
+        var clone = image.Clone(context =>
+            context.Resize(new ResizeOptions {Mode = ResizeMode.Max, Size = new Size(1920, 1080)}));
+        await clone.SaveAsJpegAsync(croppedImage);
+        croppedImage.Position = 0;
+        var blob = await GetBlobReference(containerName, fileName);
+        await blob.UploadAsync(croppedImage);
+        return blob.Uri;
+    }
+
+    private async Task<BlobClient> GetBlobReference(string? containerName, string blobName)
+    {
+        var container = new BlobContainerClient(_connectionStrings.AzureBlobStorage, containerName);
+        await container.CreateIfNotExistsAsync();
+        var blob = container.GetBlobClient(blobName);
+        return blob;
+    }
+
+    private static string? GetFileFromUrl(string url, string container)
+    {
+        if (string.IsNullOrWhiteSpace(url))
+            return null;
+
+        var userDataContainerString = container + "/";
+        var fileUrl =
+            url[(url.IndexOf(userDataContainerString, StringComparison.Ordinal) + userDataContainerString.Length)..];
+        return HttpUtility.UrlDecode(fileUrl);
+    }
+}
\ No newline at end of file
diff --git a/Settings/ConnectionStrings.cs b/Settings/ConnectionStrings.cs
new file mode 100644
index 0000000..0a688ac
--- /dev/null
+++ b/Settings/ConnectionStrings.cs
@@ -0,0 +1,7 @@
+namespace Group17profile.Settings;
+
+public class ConnectionStrings
+{
+    public string? DbConnectionString { get; set; }
+    public string? AzureBlobStorage { get; set; }
+}
\ No newline at end of file
diff --git a/appsettings.json b/appsettings.json
index 7cae4b1..a837c29 100644
--- a/appsettings.json
+++ b/appsettings.json
@@ -1,14 +1,12 @@
 {
-  "ProfileDatabase": {
-    "ConnectionString": "",
-    "DatabaseName": "Profile-db"
-  },
   "Logging": {
     "LogLevel": {
       "Default": "Information",
       "Microsoft.AspNetCore": "Warning"
     }
   },
-  "AllowedHosts": "*"
-}
-
+  "ConnectionStrings": {
+    "DbConnectionString": "",
+    "AzureBlobStorage": ""
+  }
+}
\ No newline at end of file
-- 
GitLab