From eb47ce0cad084bfc869470914220da8fce7cbf45 Mon Sep 17 00:00:00 2001 From: Erlend Skutlaberg Date: Thu, 30 Jan 2025 16:51:15 +0100 Subject: [PATCH 1/2] Done for the day --- .../Configuration/ConfigurationSettings.cs | 16 +++ .../Configuration/IConfigurationSettings.cs | 8 ++ exercise.wwwapi/DTOs/BlogDTO.cs | 21 ++++ exercise.wwwapi/DTOs/UserDTOs.cs | 33 +++++ exercise.wwwapi/Data/DataContext.cs | 55 ++++++++ exercise.wwwapi/Endpoint/BlogEndpoints.cs | 101 +++++++++++++++ exercise.wwwapi/Mappers/Mappers.cs | 18 +++ exercise.wwwapi/Models/Blog.cs | 32 +++++ exercise.wwwapi/Models/IBlogEntity.cs | 13 ++ exercise.wwwapi/Models/User.cs | 30 +++++ exercise.wwwapi/Payload/Payload.cs | 10 ++ exercise.wwwapi/Program.cs | 119 +++++++++++++++++- exercise.wwwapi/Repository/IRepository.cs | 13 ++ exercise.wwwapi/Repository/Repository.cs | 78 ++++++++++++ exercise.wwwapi/appsettings.Developer.json | 16 +++ exercise.wwwapi/exercise.wwwapi.csproj | 14 +++ 16 files changed, 576 insertions(+), 1 deletion(-) create mode 100644 exercise.wwwapi/Configuration/ConfigurationSettings.cs create mode 100644 exercise.wwwapi/Configuration/IConfigurationSettings.cs create mode 100644 exercise.wwwapi/DTOs/BlogDTO.cs create mode 100644 exercise.wwwapi/DTOs/UserDTOs.cs create mode 100644 exercise.wwwapi/Data/DataContext.cs create mode 100644 exercise.wwwapi/Endpoint/BlogEndpoints.cs create mode 100644 exercise.wwwapi/Mappers/Mappers.cs create mode 100644 exercise.wwwapi/Models/Blog.cs create mode 100644 exercise.wwwapi/Models/IBlogEntity.cs create mode 100644 exercise.wwwapi/Models/User.cs create mode 100644 exercise.wwwapi/Payload/Payload.cs create mode 100644 exercise.wwwapi/Repository/IRepository.cs create mode 100644 exercise.wwwapi/Repository/Repository.cs create mode 100644 exercise.wwwapi/appsettings.Developer.json diff --git a/exercise.wwwapi/Configuration/ConfigurationSettings.cs b/exercise.wwwapi/Configuration/ConfigurationSettings.cs new file mode 100644 index 0000000..39a27c9 --- /dev/null +++ b/exercise.wwwapi/Configuration/ConfigurationSettings.cs @@ -0,0 +1,16 @@ +using System; + +namespace exercise.wwwapi.Configuration; +public class ConfigurationSettings : IConfigurationSettings + { + IConfiguration _configuration; + public ConfigurationSettings() + { + _configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + } + public string GetValue(string key) + { + return _configuration.GetValue(key)!; + } + } + diff --git a/exercise.wwwapi/Configuration/IConfigurationSettings.cs b/exercise.wwwapi/Configuration/IConfigurationSettings.cs new file mode 100644 index 0000000..410098b --- /dev/null +++ b/exercise.wwwapi/Configuration/IConfigurationSettings.cs @@ -0,0 +1,8 @@ +using System; + +namespace exercise.wwwapi.Configuration; + +public interface IConfigurationSettings +{ + string GetValue(string key); +} diff --git a/exercise.wwwapi/DTOs/BlogDTO.cs b/exercise.wwwapi/DTOs/BlogDTO.cs new file mode 100644 index 0000000..85d2349 --- /dev/null +++ b/exercise.wwwapi/DTOs/BlogDTO.cs @@ -0,0 +1,21 @@ +using System; + +namespace exercise.wwwapi.DTOs; + + + + +public class GetBlogDTO +{ + public GetUserFromBlogBTO Author { get; set; } + public string Title { get; set; } + public string Content { get; set; } +} + + +public class GetBlogFromUserDTO +{ + public string Title { get; set; } + public string Content { get; set; } + public string Username { get; set; } +} \ No newline at end of file diff --git a/exercise.wwwapi/DTOs/UserDTOs.cs b/exercise.wwwapi/DTOs/UserDTOs.cs new file mode 100644 index 0000000..3c7502c --- /dev/null +++ b/exercise.wwwapi/DTOs/UserDTOs.cs @@ -0,0 +1,33 @@ +using System; + +namespace exercise.wwwapi.DTOs; + + + +public class CreateUserDTO +{ + public string Name { get; set; } + public string Password { get; set; } + public string Email { get; set; } +} + +public class GetUserDTO +{ + public string Name { get; set; } + public string Email { get; set; } + public List Blogs { get; set; } +} + +public class GetUserFromBlogBTO +{ + public string Name { get; set; } + public string Email { get; set; } +} + +public class LogInDTO +{ + public string Email { get; set; } + public string Password { get; set; } +} + + diff --git a/exercise.wwwapi/Data/DataContext.cs b/exercise.wwwapi/Data/DataContext.cs new file mode 100644 index 0000000..f95679c --- /dev/null +++ b/exercise.wwwapi/Data/DataContext.cs @@ -0,0 +1,55 @@ +using System; +using exercise.wwwapi.Models; +using Microsoft.EntityFrameworkCore; + + +namespace exercise.wwwapi.Data; + +public class DataContext : DbContext +{ + private string _connectionstring; + public DataContext(DbContextOptions options) : base(options) + { + var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + + _connectionstring = configuration.GetValue("ConnectionStrings:DefaultConnectionString")!; + + this.Database.EnsureCreated(); + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseNpgsql(_connectionstring); + optionsBuilder.UseLazyLoadingProxies(); + } + + + public DbSet Users { get; set; } + public DbSet Blogs { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasKey(u => u.Id); + modelBuilder.Entity() + .HasKey(b => b.Id); + + modelBuilder.Entity() + .HasMany(u => u.Blogs) + .WithOne(b => b.User) + .HasForeignKey(b => b.UserId); + + modelBuilder.Entity() + .HasOne(b => b.User) + .WithMany(u => u.Blogs) + .HasForeignKey(b => b.UserId); + + + + + + } + + + +} diff --git a/exercise.wwwapi/Endpoint/BlogEndpoints.cs b/exercise.wwwapi/Endpoint/BlogEndpoints.cs new file mode 100644 index 0000000..1bc4201 --- /dev/null +++ b/exercise.wwwapi/Endpoint/BlogEndpoints.cs @@ -0,0 +1,101 @@ + +using System; +using System.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using AutoMapper; +using exercise.wwwapi.Configuration; +using exercise.wwwapi.DTOs; +using exercise.wwwapi.Models; +using exercise.wwwapi.Payload; +using exercise.wwwapi.Repository; +using Microsoft.IdentityModel.Tokens; + +namespace exercise.wwwapi.Endpoint; + +public static class BlogEndpoints +{ + + public static void ConfigureBlogEndpoints(this WebApplication app) + { + var blog = app.MapGroup("/blogs"); + + // User endpoints + blog.MapPost("/register", Register); + blog.MapPost("/login", LogIn); + blog.MapGet("/users", GetUsers); + + // Blog endpoints + + + } + + + #region User endpoints + + public static async Task Register(IRepository repository, CreateUserDTO userDTO, IMapper mapper) + { + Func email_exists = u => u.Email == userDTO.Email; + + if (repository.Exists(email_exists)) + return Results.Conflict("Email already exists"); + + string passwordHash = BCrypt.Net.BCrypt.HashPassword(userDTO.Password); + User user = mapper.Map(userDTO); + user.Password = passwordHash; + var newUser = await repository.CreateEntity(user); + Payload payload = new Payload {Data = mapper.Map(newUser)}; + return Results.Ok(payload); + } + + + public static async Task LogIn(IRepository repository, IConfigurationSettings config, LogInDTO logInDTO, IMapper mapper) + { + User user = (await repository.GetAll()).ToList().FirstOrDefault(u => u.Email == logInDTO.Email)!; + + if (user == null) + return Results.NotFound("User not found"); + + if (!BCrypt.Net.BCrypt.Verify(logInDTO.Password, user.Password)) + return Results.Unauthorized(); + + Payload payload = new Payload {Data = CreateToken(user, config)}; + return Results.Ok(payload); + } + + private static string CreateToken(User user, IConfigurationSettings config) + { + List claims = new List + { + new Claim(ClaimTypes.Sid, user.Id.ToString()), + new Claim(ClaimTypes.Name, user.Name), + new Claim(ClaimTypes.Email, user.Email), + + }; + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.GetValue("AppSettings:Token"))); + var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature); + var token = new JwtSecurityToken( + claims: claims, + expires: DateTime.Now.AddDays(1), + signingCredentials: credentials + ); + var jwt = new JwtSecurityTokenHandler().WriteToken(token); + return jwt; + } + + + private static async Task GetUsers(IRepository repository, IMapper mapper) + { + var users = await repository.GetAll(); + Payload> payload = new Payload> {Data = mapper.Map>(users)}; + return Results.Ok(payload); + } + #endregion + + #region Blog endpoints + + + #endregion +} diff --git a/exercise.wwwapi/Mappers/Mappers.cs b/exercise.wwwapi/Mappers/Mappers.cs new file mode 100644 index 0000000..11eccb6 --- /dev/null +++ b/exercise.wwwapi/Mappers/Mappers.cs @@ -0,0 +1,18 @@ +using System; +using AutoMapper; +using exercise.wwwapi.DTOs; +using exercise.wwwapi.Models; + +namespace exercise.wwwapi.Mappers; + +public class Mappers : Profile +{ + public Mappers() + { + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); + } +} diff --git a/exercise.wwwapi/Models/Blog.cs b/exercise.wwwapi/Models/Blog.cs new file mode 100644 index 0000000..6e5c6e1 --- /dev/null +++ b/exercise.wwwapi/Models/Blog.cs @@ -0,0 +1,32 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace exercise.wwwapi.Models; + +public class Blog : IBlogEntity +{ + public int Id { get;set; } + public int UserId {get;set;} + public string Title {get;set;} + public string Content {get;set;} + public DateTime CreatedAt {get;set;} + public DateTime UpdatedAt {get;set;} + [NotMapped] + public virtual User User {get;set;} + + public void Update(IBlogEntity entity) + { + if (entity is Blog blog) + { + if (blog.Title != null) + Title = blog.Title; + if (blog.Content != null) + Content = blog.Content; + if (blog.CreatedAt != null) + CreatedAt = blog.CreatedAt; + if (blog.UpdatedAt != null) + UpdatedAt = blog.UpdatedAt; + } + } +} + diff --git a/exercise.wwwapi/Models/IBlogEntity.cs b/exercise.wwwapi/Models/IBlogEntity.cs new file mode 100644 index 0000000..4aa6818 --- /dev/null +++ b/exercise.wwwapi/Models/IBlogEntity.cs @@ -0,0 +1,13 @@ +using System; + +namespace exercise.wwwapi.Models; + +public interface IBlogEntity +{ + public int Id {get;set;} + public DateTime CreatedAt {get;set;} + public DateTime UpdatedAt {get;set;} + + public void Update(IBlogEntity entity); + +} diff --git a/exercise.wwwapi/Models/User.cs b/exercise.wwwapi/Models/User.cs new file mode 100644 index 0000000..37408ce --- /dev/null +++ b/exercise.wwwapi/Models/User.cs @@ -0,0 +1,30 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace exercise.wwwapi.Models; + +public class User : IBlogEntity +{ + public int Id { get;set; } + public string Name {get;set;} + public string Email {get;set;} + public string Password {get;set;} + public DateTime CreatedAt {get;set;} + public DateTime UpdatedAt {get;set;} + + [NotMapped] + public virtual List Blogs {get;set;} + + public void Update(IBlogEntity entity) + { + if (entity is User user) + { + if (user.Name != null) + Name = user.Name; + if (user.Email != null) + Email = user.Email; + if (user.Password != null) + Password = user.Password; + } + } +} diff --git a/exercise.wwwapi/Payload/Payload.cs b/exercise.wwwapi/Payload/Payload.cs new file mode 100644 index 0000000..e741ae9 --- /dev/null +++ b/exercise.wwwapi/Payload/Payload.cs @@ -0,0 +1,10 @@ +using System; + +namespace exercise.wwwapi.Payload; + +public class Payload +{ + public string Status {get;set;} = "Success"; + public T Data {get;set;} + +} diff --git a/exercise.wwwapi/Program.cs b/exercise.wwwapi/Program.cs index 47f22ef..0cdf170 100644 --- a/exercise.wwwapi/Program.cs +++ b/exercise.wwwapi/Program.cs @@ -1,6 +1,105 @@ +using exercise.wwwapi.Configuration; +using exercise.wwwapi.Data; +using exercise.wwwapi.Endpoint; +using exercise.wwwapi.Models; +using exercise.wwwapi.Repository; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Scalar.AspNetCore; +using System.Diagnostics; +using System.Text; + + var builder = WebApplication.CreateBuilder(args); +builder.Logging.ClearProviders(); +builder.Logging.AddConsole(); +var config = new ConfigurationSettings(); // Add services to the container. +builder.Services.AddAutoMapper(typeof(Program)); +builder.Services.AddScoped(); +builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped>(); +builder.Services.AddDbContext(options => { + + options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnectionString")); + options.LogTo(message => Debug.WriteLine(message)); + +}); +//authentication verifying who they say they are +//authorization verifying what they have access to +builder.Services.AddAuthentication(x => +{ + x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + +}).AddJwtBearer(x => +{ + x.TokenValidationParameters = new TokenValidationParameters + { + + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.GetValue("AppSettings:Token"))), + ValidateIssuer = false, + ValidateAudience = false, + ValidateLifetime = false, + ValidateIssuerSigningKey = false + + }; +}); +builder.Services.AddSwaggerGen(s => +{ + s.SwaggerDoc("v1", new OpenApiInfo + { + Version = "v1", + Title = "C# API Authentication", + Description = "Demo of an API using JWT as an authentication method", + Contact = new OpenApiContact + { + Name = "Nigel", + Email = "nigel@nigel.nigel", + Url = new Uri("https://www.boolean.co.uk") + }, + License = new OpenApiLicense + { + Name = "Boolean", + Url = new Uri("https://github.com/boolean-uk/csharp-api-auth") + } + + }); + + s.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = "Add an Authorization header with a JWT token using the Bearer scheme see the app.http file for an example.)", + Name = "Authorization", + Type = SecuritySchemeType.ApiKey, + In = ParameterLocation.Header, + Scheme = "Bearer" + }); + + s.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + Array.Empty() + } + }); + +}); +builder.Services.AddAuthorization(); + +builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); @@ -12,9 +111,27 @@ { app.UseSwagger(); app.UseSwaggerUI(); + app.UseSwaggerUI(options => + { + options.SwaggerEndpoint("/openapi/v1.json", "Demo API"); + }); + app.MapScalarApiReference(); } +app.UseCors(x => x + .AllowAnyMethod() + .AllowAnyHeader() + .SetIsOriginAllowed(origin => true) // allow any origin + .AllowCredentials()); // allow credentials + app.UseHttpsRedirection(); +app.UseAuthentication(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.ConfigureBlogEndpoints(); -app.Run(); \ No newline at end of file +app.Run(); diff --git a/exercise.wwwapi/Repository/IRepository.cs b/exercise.wwwapi/Repository/IRepository.cs new file mode 100644 index 0000000..5a7af26 --- /dev/null +++ b/exercise.wwwapi/Repository/IRepository.cs @@ -0,0 +1,13 @@ +using System; + +namespace exercise.wwwapi.Repository; + +public interface IRepository +{ + Task> GetAll(); + Task GetEntityById(int id); + Task UpdateEntityById(int id, T entity); + Task CreateEntity(T entity); + Task DeleteEntityById(int id); + public bool Exists(Func exist); +} diff --git a/exercise.wwwapi/Repository/Repository.cs b/exercise.wwwapi/Repository/Repository.cs new file mode 100644 index 0000000..dc56489 --- /dev/null +++ b/exercise.wwwapi/Repository/Repository.cs @@ -0,0 +1,78 @@ +using System; +using exercise.wwwapi.Data; +using exercise.wwwapi.Models; +using Microsoft.EntityFrameworkCore; + +namespace exercise.wwwapi.Repository; + +public class Repository : IRepository where T : class, IBlogEntity +{ + private DataContext _db; + private DbSet _table; + + public Repository(DataContext context) + { + _db = context; + _table = _db.Set(); + } + + + public async Task CreateEntity(T entity) + { + if (_table.Count() == 0)entity.Id = 1; + else entity.Id = _table.Max(e => e.Id) + 1; + + entity.CreatedAt = DateTime.Now.ToUniversalTime(); + entity.UpdatedAt = DateTime.Now.ToUniversalTime(); + await _table.AddAsync(entity); + await _db.SaveChangesAsync(); + return await GetEntityById(entity.Id); //Ensures related objects are returned aswell + + } + + public async Task DeleteEntityById(int id) + { + T entity = await _table.FindAsync(id); + if (entity != null) + { + _table.Remove(entity); + await _db.SaveChangesAsync(); + } + + return entity; + } + + public async Task> GetAll() + { + return await _table.ToListAsync(); + } + + //The use of "lazy loading" enables us to get all relationed entities without include + public async Task GetEntityById(int id) + { + return await _table.FindAsync(id); + } + + public async Task UpdateEntityById(int id, T entity) + { + T entityToUpdate = await _table.FindAsync(id); + + try + { + entityToUpdate.Update(entity); + entityToUpdate.UpdatedAt = DateTime.Now.ToUniversalTime(); + await _db.SaveChangesAsync(); + + return entityToUpdate; + } + catch (Exception) + { + return entityToUpdate; + } + } + + public bool Exists(Func exist) + { + return _table.Any(exist); + } +} diff --git a/exercise.wwwapi/appsettings.Developer.json b/exercise.wwwapi/appsettings.Developer.json new file mode 100644 index 0000000..8d0ee88 --- /dev/null +++ b/exercise.wwwapi/appsettings.Developer.json @@ -0,0 +1,16 @@ +{ + "AppSettings": { + "Token": "any secret passphrase goes here but make sure that the passphrase is really big in size" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnectionString": "Host=ep-nameless-hall-a84v1lwd-pooler.eastus2.azure.neon.tech;Database=neondb;Username=neondb_owner;Password=npg_WosE8inuvDd3;SSL Mode=Require;Trust Server Certificate=true" + } + } + \ No newline at end of file diff --git a/exercise.wwwapi/exercise.wwwapi.csproj b/exercise.wwwapi/exercise.wwwapi.csproj index 56929a8..776ce05 100644 --- a/exercise.wwwapi/exercise.wwwapi.csproj +++ b/exercise.wwwapi/exercise.wwwapi.csproj @@ -8,8 +8,22 @@ + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + From 87928bd74e510ee74257297ae1ecf60f6df9e621 Mon Sep 17 00:00:00 2001 From: Erlend Skutlaberg Date: Sun, 2 Feb 2025 19:58:53 +0100 Subject: [PATCH 2/2] Core done --- exercise.wwwapi/DTOs/BlogDTO.cs | 16 +++++- exercise.wwwapi/Endpoint/BlogEndpoints.cs | 52 +++++++++++++++++-- .../Helpers/ClaimsPrincipleHelpers.cs | 28 ++++++++++ exercise.wwwapi/Mappers/Mappers.cs | 4 ++ exercise.wwwapi/Models/Blog.cs | 8 +-- exercise.wwwapi/Program.cs | 1 + exercise.wwwapi/Repository/Repository.cs | 14 +++-- 7 files changed, 108 insertions(+), 15 deletions(-) create mode 100644 exercise.wwwapi/Helpers/ClaimsPrincipleHelpers.cs diff --git a/exercise.wwwapi/DTOs/BlogDTO.cs b/exercise.wwwapi/DTOs/BlogDTO.cs index 85d2349..c1c0074 100644 --- a/exercise.wwwapi/DTOs/BlogDTO.cs +++ b/exercise.wwwapi/DTOs/BlogDTO.cs @@ -4,10 +4,10 @@ namespace exercise.wwwapi.DTOs; - public class GetBlogDTO { - public GetUserFromBlogBTO Author { get; set; } + public GetUserFromBlogBTO User { get; set; } + public int Id {get;set;} public string Title { get; set; } public string Content { get; set; } } @@ -18,4 +18,16 @@ public class GetBlogFromUserDTO public string Title { get; set; } public string Content { get; set; } public string Username { get; set; } +} + +public class CreateBlogDTO +{ + public string Title { get; set; } + public string Content { get; set; } +} + +public class UpdateBlogDTO +{ + public string Title { get; set; } + public string Content { get; set; } } \ No newline at end of file diff --git a/exercise.wwwapi/Endpoint/BlogEndpoints.cs b/exercise.wwwapi/Endpoint/BlogEndpoints.cs index 1bc4201..3dfddc7 100644 --- a/exercise.wwwapi/Endpoint/BlogEndpoints.cs +++ b/exercise.wwwapi/Endpoint/BlogEndpoints.cs @@ -7,9 +7,12 @@ using AutoMapper; using exercise.wwwapi.Configuration; using exercise.wwwapi.DTOs; +using exercise.wwwapi.Helpers; using exercise.wwwapi.Models; using exercise.wwwapi.Payload; using exercise.wwwapi.Repository; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; namespace exercise.wwwapi.Endpoint; @@ -25,6 +28,9 @@ public static void ConfigureBlogEndpoints(this WebApplication app) blog.MapPost("/register", Register); blog.MapPost("/login", LogIn); blog.MapGet("/users", GetUsers); + blog.MapGet("/all", GetBlogs); + blog.MapPost("/create", CreateBlog); + blog.MapPut("/{id}", UpdateBlog); // Blog endpoints @@ -34,6 +40,8 @@ public static void ConfigureBlogEndpoints(this WebApplication app) #region User endpoints + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status409Conflict)] public static async Task Register(IRepository repository, CreateUserDTO userDTO, IMapper mapper) { Func email_exists = u => u.Email == userDTO.Email; @@ -49,7 +57,9 @@ public static async Task Register(IRepository repository, CreateU return Results.Ok(payload); } - + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] public static async Task LogIn(IRepository repository, IConfigurationSettings config, LogInDTO logInDTO, IMapper mapper) { User user = (await repository.GetAll()).ToList().FirstOrDefault(u => u.Email == logInDTO.Email)!; @@ -85,8 +95,8 @@ private static string CreateToken(User user, IConfigurationSettings config) return jwt; } - - private static async Task GetUsers(IRepository repository, IMapper mapper) + [Authorize] + private static async Task GetUsers(IRepository repository, ClaimsPrincipal user, IMapper mapper) { var users = await repository.GetAll(); Payload> payload = new Payload> {Data = mapper.Map>(users)}; @@ -95,7 +105,43 @@ private static async Task GetUsers(IRepository repository, IMappe #endregion #region Blog endpoints + [Authorize] + public static async Task GetBlogs(IRepository Blogrepository, IRepository Userrepository, ClaimsPrincipal user, IMapper mapper) + { + int userId = user.UserRealId() != null ? user.UserRealId()!.Value : 0; + User usr = await Userrepository.GetEntityById(userId); + Payload> payload = new Payload> {Data = mapper.Map>(usr.Blogs)}; + return Results.Ok(payload); + } + + public static async Task CreateBlog(IRepository repository, ClaimsPrincipal user, IMapper mapper, CreateBlogDTO blogDTO) + { + int userId = user.UserRealId() != null ? user.UserRealId()!.Value : 0; + if (userId == 0) return Results.Unauthorized(); + + Blog blog = mapper.Map(blogDTO); + blog.UserId = userId; + Blog newBlog = await repository.CreateEntity(blog); + Payload payload = new Payload {Data = mapper.Map(newBlog)}; + return Results.Ok(payload); + } + + public static async Task UpdateBlog(IRepository blogrepository, IRepository userrepository, ClaimsPrincipal user, IMapper mapper, int id, UpdateBlogDTO blogDTO) + { + int userId = user.UserRealId() != null ? user.UserRealId()!.Value : 0; + if (userId == 0) return Results.Unauthorized(); + + User usr = await userrepository.GetEntityById(userId); + Blog currentBlog = await blogrepository.GetEntityById(id); + if (currentBlog.UserId != userId) return Results.Unauthorized(); + + + Blog updatedBlog = await blogrepository.UpdateEntityById(id, mapper.Map(blogDTO)); + Payload payload = new Payload {Data =mapper.Map(updatedBlog)}; + return Results.Ok(payload); + } + #endregion } diff --git a/exercise.wwwapi/Helpers/ClaimsPrincipleHelpers.cs b/exercise.wwwapi/Helpers/ClaimsPrincipleHelpers.cs new file mode 100644 index 0000000..b6de06d --- /dev/null +++ b/exercise.wwwapi/Helpers/ClaimsPrincipleHelpers.cs @@ -0,0 +1,28 @@ +using System; +using System.Security.Claims; + +namespace exercise.wwwapi.Helpers; + +public static class ClaimsPrincipleHelpers +{ + + public static int? UserRealId(this ClaimsPrincipal user) + { + Claim? claim = user.FindFirst(ClaimTypes.Sid); + return int.Parse(claim?.Value); + } + + public static string UserId(this ClaimsPrincipal user) + { + IEnumerable claims = user.Claims.Where(c => c.Type == ClaimTypes.NameIdentifier); + return claims.Count() >= 2 ? claims.ElementAt(1).Value : null; + + } + + public static string? Email(this ClaimsPrincipal user) + { + Claim? claim = user.FindFirst(ClaimTypes.Email); + return claim?.Value; + } + +} diff --git a/exercise.wwwapi/Mappers/Mappers.cs b/exercise.wwwapi/Mappers/Mappers.cs index 11eccb6..3aa405f 100644 --- a/exercise.wwwapi/Mappers/Mappers.cs +++ b/exercise.wwwapi/Mappers/Mappers.cs @@ -14,5 +14,9 @@ public Mappers() CreateMap(); CreateMap(); CreateMap(); + CreateMap(); + CreateMap(); + + } } diff --git a/exercise.wwwapi/Models/Blog.cs b/exercise.wwwapi/Models/Blog.cs index 6e5c6e1..72340f0 100644 --- a/exercise.wwwapi/Models/Blog.cs +++ b/exercise.wwwapi/Models/Blog.cs @@ -18,14 +18,10 @@ public void Update(IBlogEntity entity) { if (entity is Blog blog) { - if (blog.Title != null) + if (blog.Title != "") Title = blog.Title; - if (blog.Content != null) + if (blog.Content != "") Content = blog.Content; - if (blog.CreatedAt != null) - CreatedAt = blog.CreatedAt; - if (blog.UpdatedAt != null) - UpdatedAt = blog.UpdatedAt; } } } diff --git a/exercise.wwwapi/Program.cs b/exercise.wwwapi/Program.cs index 0cdf170..b5667cb 100644 --- a/exercise.wwwapi/Program.cs +++ b/exercise.wwwapi/Program.cs @@ -23,6 +23,7 @@ builder.Services.AddAutoMapper(typeof(Program)); builder.Services.AddScoped(); builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped, Repository>(); builder.Services.AddScoped>(); builder.Services.AddDbContext(options => { diff --git a/exercise.wwwapi/Repository/Repository.cs b/exercise.wwwapi/Repository/Repository.cs index dc56489..c32338c 100644 --- a/exercise.wwwapi/Repository/Repository.cs +++ b/exercise.wwwapi/Repository/Repository.cs @@ -19,14 +19,20 @@ public Repository(DataContext context) public async Task CreateEntity(T entity) { - if (_table.Count() == 0)entity.Id = 1; - else entity.Id = _table.Max(e => e.Id) + 1; + if (_table.Count() == 0) + { + entity.Id = 1; + } + else + { + entity.Id = _table.Max(e => e.Id) + 1; + } entity.CreatedAt = DateTime.Now.ToUniversalTime(); entity.UpdatedAt = DateTime.Now.ToUniversalTime(); await _table.AddAsync(entity); - await _db.SaveChangesAsync(); - return await GetEntityById(entity.Id); //Ensures related objects are returned aswell + await _db.SaveChangesAsync(); + return entity; }