Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions exercise.wwwapi/Configuration/ConfigurationSettings.cs
Original file line number Diff line number Diff line change
@@ -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<string>(key)!;
}
}

8 changes: 8 additions & 0 deletions exercise.wwwapi/Configuration/IConfigurationSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

namespace exercise.wwwapi.Configuration;

public interface IConfigurationSettings
{
string GetValue(string key);
}
33 changes: 33 additions & 0 deletions exercise.wwwapi/DTOs/BlogDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;

namespace exercise.wwwapi.DTOs;



public class GetBlogDTO
{
public GetUserFromBlogBTO User { get; set; }
public int Id {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; }
}

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; }
}
33 changes: 33 additions & 0 deletions exercise.wwwapi/DTOs/UserDTOs.cs
Original file line number Diff line number Diff line change
@@ -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<GetBlogFromUserDTO> 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; }
}


55 changes: 55 additions & 0 deletions exercise.wwwapi/Data/DataContext.cs
Original file line number Diff line number Diff line change
@@ -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<DataContext> options) : base(options)
{
var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();

_connectionstring = configuration.GetValue<string>("ConnectionStrings:DefaultConnectionString")!;

this.Database.EnsureCreated();
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(_connectionstring);
optionsBuilder.UseLazyLoadingProxies();
}


public DbSet<User> Users { get; set; }
public DbSet<Blog> Blogs { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasKey(u => u.Id);
modelBuilder.Entity<Blog>()
.HasKey(b => b.Id);

modelBuilder.Entity<User>()
.HasMany(u => u.Blogs)
.WithOne(b => b.User)
.HasForeignKey(b => b.UserId);

modelBuilder.Entity<Blog>()
.HasOne(b => b.User)
.WithMany(u => u.Blogs)
.HasForeignKey(b => b.UserId);





}



}
147 changes: 147 additions & 0 deletions exercise.wwwapi/Endpoint/BlogEndpoints.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@

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.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;

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.MapGet("/all", GetBlogs);
blog.MapPost("/create", CreateBlog);
blog.MapPut("/{id}", UpdateBlog);

// Blog endpoints


}


#region User endpoints

[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public static async Task<IResult> Register(IRepository<User> repository, CreateUserDTO userDTO, IMapper mapper)
{
Func<User, bool> 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<User>(userDTO);
user.Password = passwordHash;
var newUser = await repository.CreateEntity(user);
Payload<GetUserDTO> payload = new Payload<GetUserDTO> {Data = mapper.Map<GetUserDTO>(newUser)};
return Results.Ok(payload);
}

[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public static async Task<IResult> LogIn(IRepository<User> 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<string> payload = new Payload<string> {Data = CreateToken(user, config)};
return Results.Ok(payload);
}

private static string CreateToken(User user, IConfigurationSettings config)
{
List<Claim> claims = new List<Claim>
{
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;
}

[Authorize]
private static async Task<IResult> GetUsers(IRepository<User> repository, ClaimsPrincipal user, IMapper mapper)
{
var users = await repository.GetAll();
Payload<IEnumerable<GetUserDTO>> payload = new Payload<IEnumerable<GetUserDTO>> {Data = mapper.Map<IEnumerable<GetUserDTO>>(users)};
return Results.Ok(payload);
}
#endregion

#region Blog endpoints
[Authorize]
public static async Task<IResult> GetBlogs(IRepository<Blog> Blogrepository, IRepository<User> Userrepository, ClaimsPrincipal user, IMapper mapper)
{
int userId = user.UserRealId() != null ? user.UserRealId()!.Value : 0;
User usr = await Userrepository.GetEntityById(userId);

Payload<IEnumerable<GetBlogDTO>> payload = new Payload<IEnumerable<GetBlogDTO>> {Data = mapper.Map<List<GetBlogDTO>>(usr.Blogs)};
return Results.Ok(payload);
}

public static async Task<IResult> CreateBlog(IRepository<Blog> 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<Blog>(blogDTO);
blog.UserId = userId;
Blog newBlog = await repository.CreateEntity(blog);
Payload<GetBlogDTO> payload = new Payload<GetBlogDTO> {Data = mapper.Map<GetBlogDTO>(newBlog)};
return Results.Ok(payload);
}

public static async Task<IResult> UpdateBlog(IRepository<Blog> blogrepository, IRepository<User> 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<Blog>(blogDTO));
Payload<GetBlogDTO> payload = new Payload<GetBlogDTO> {Data =mapper.Map<GetBlogDTO>(updatedBlog)};
return Results.Ok(payload);
}


#endregion
}
28 changes: 28 additions & 0 deletions exercise.wwwapi/Helpers/ClaimsPrincipleHelpers.cs
Original file line number Diff line number Diff line change
@@ -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<Claim> 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;
}

}
22 changes: 22 additions & 0 deletions exercise.wwwapi/Mappers/Mappers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using AutoMapper;
using exercise.wwwapi.DTOs;
using exercise.wwwapi.Models;

namespace exercise.wwwapi.Mappers;

public class Mappers : Profile
{
public Mappers()
{
CreateMap<CreateUserDTO, User>();
CreateMap<User, GetUserDTO>();
CreateMap<Blog, GetBlogDTO>();
CreateMap<User, GetUserFromBlogBTO>();
CreateMap<Blog, GetBlogFromUserDTO>();
CreateMap<CreateBlogDTO, Blog>();
CreateMap<UpdateBlogDTO, Blog>();


}
}
28 changes: 28 additions & 0 deletions exercise.wwwapi/Models/Blog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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 != "")
Title = blog.Title;
if (blog.Content != "")
Content = blog.Content;
}
}
}

13 changes: 13 additions & 0 deletions exercise.wwwapi/Models/IBlogEntity.cs
Original file line number Diff line number Diff line change
@@ -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);

}
Loading