Skip to content
Snippets Groups Projects
Commit a8287865 authored by Adiv's avatar Adiv
Browse files

added banner stuff

parent d175055f
No related branches found
No related tags found
No related merge requests found
Showing
with 260 additions and 157 deletions
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
......@@ -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";
......
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
{
......
using System.Security.Claims;
using Microsoft.VisualBasic;
namespace Group17profile.Controllers;
namespace Group17profile.Controllers;
using System.Security.Claims;
public class DefaultProfileController : DefaultController
{
......
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
<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>
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
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);
......
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)
......
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
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
{
......
......@@ -18,4 +18,4 @@ public class DefaultIntDTO
}
public int Id { get; set; }
}
}
\ No newline at end of file
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; }
}
......
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
......@@ -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
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();
......
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
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
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
namespace Group17profile.Settings;
public class ConnectionStrings
{
public string? DbConnectionString { get; set; }
public string? AzureBlobStorage { get; set; }
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment