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);
}
8 changes: 8 additions & 0 deletions exercise.wwwapi/Data/BlogData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

namespace exercise.wwwapi.Data;

public class BlogData
{

}
25 changes: 25 additions & 0 deletions exercise.wwwapi/Data/DataContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using exercise.wwwapi.Model;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace exercise.wwwapi.Data;

public class DataContext : DbContext
{
private readonly IConfiguration _configuration;

public DataContext(DbContextOptions<DataContext> options, IConfiguration configuration) : base(options)
{
_configuration = configuration;
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
}

public DbSet<User> Users { get; set; }
public DbSet<Blogpost> Blogposts { get; set; } // Add this line
}


9 changes: 9 additions & 0 deletions exercise.wwwapi/Dto/BlogPostDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace wwwapi.Dto
{
public class BlogPostDto
{
public int PostId { get; set; }
public string Author { get; set; }
public string Text { get; set; }
}
}
8 changes: 8 additions & 0 deletions exercise.wwwapi/Dto/BlogPostPost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace exercise.wwwapi.Dto
{
public class BlogPostPost
{
public int UserId { get; set; }
public string Text { get; set; }
}
}
8 changes: 8 additions & 0 deletions exercise.wwwapi/Dto/BlogpostPut.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace exercise.wwwapi.Dto
{
public class BlogPostPut
{
public int PostId { get; set; }
public string Text { get; set; }
}
}
8 changes: 8 additions & 0 deletions exercise.wwwapi/Dto/Payload.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace exercise.wwwapi.Dto
{
public class Payload<T> where T : class
{
public string status { get; set; } = "success";
public T data { get; set; }
}
}
10 changes: 10 additions & 0 deletions exercise.wwwapi/Dto/UserRequestDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

namespace exercise.wwwapi.Dto
{
public class UserRequestDto
{
public required string Username { get; set; }
public required string Password { get; set; }
public required string Email { get; set; }
}
}
8 changes: 8 additions & 0 deletions exercise.wwwapi/Dto/UserResponseDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace exercise.wwwapi.Dto
{
public class UserResponseDto
{
public string Username { get; set; }
public string Email { get; set; }
}
}
94 changes: 94 additions & 0 deletions exercise.wwwapi/Endpoints/AuthApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using exercise.wwwapi.Model;
using exercise.wwwapi.Data;
using exercise.wwwapi.Helpers;
using exercise.wwwapi.Dto;
using exercise.wwwapi.Repository;
using exercise.wwwapi.Configuration;


namespace exercise.wwwapi.Endpoints;

public static class AuthApi
{
public static void ConfigureAuthApi(this WebApplication app)
{
app.MapPost("register", Register);
app.MapPost("login", Login);
app.MapGet("users", GetUsers);

}
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
private static async Task<IResult> GetUsers(IRepository<User> service, ClaimsPrincipal user)
{
return TypedResults.Ok(service.GetAll());
}
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
private static async Task<IResult> Register(UserRequestDto request, IRepository<User> service)
{

//user exists
if (service.GetAll().Where(u => u.Username == request.Username).Any()) return Results.Conflict(new Payload<UserRequestDto>() { status = "Username already exists!", data = request });

string passwordHash = BCrypt.Net.BCrypt.HashPassword(request.Password);

var user = new User();

user.Username = request.Username;
user.PasswordHash = passwordHash;
user.Email = request.Email;

service.Insert(user);
service.Save();

return Results.Ok(new Payload<string>() { data = "Created Account" });
}

[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
private static async Task<IResult> Login(UserRequestDto request, IRepository<User> service, IConfigurationSettings config)
{
//user doesn't exist
if (!service.GetAll().Where(u => u.Username == request.Username).Any()) return Results.BadRequest(new Payload<UserRequestDto>() { status = "User does not exist", data = request });

User user = service.GetAll().FirstOrDefault(u => u.Username == request.Username)!;


if (!BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
{
return Results.BadRequest(new Payload<UserRequestDto>() { status = "Wrong Password", data = request });
}
string token = CreateToken(user, config);
return Results.Ok(new Payload<string>() { data = token });

}
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.Username),
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;
}
}
89 changes: 89 additions & 0 deletions exercise.wwwapi/Endpoints/BlogApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System.Security.Claims;
using exercise.wwwapi.Dto;
using exercise.wwwapi.Helpers;
using exercise.wwwapi.Model;
using exercise.wwwapi.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using wwwapi.Dto;

namespace exercise.wwwapi.Endpoints;

public static class BlogApi
{
public static void ConfigureBlogApi(this WebApplication app)
{
var blog = app.MapGroup("/posts");

blog.MapGet("/", GetAllBlogPosts);
blog.MapPost("/", CreateNewPost);
blog.MapPut("/", UpdatePost);
}

[Authorize]
private static async Task<IResult> UpdatePost([FromServices] IRepository<Blogpost> repo, ClaimsPrincipal user, [FromBody] BlogPostPut postPut)
{
try
{
var userRealId = user.UserRealId();
var post = repo.GetById(postPut.PostId);
if (post == null)
{
return Results.NotFound("Post not found");
}
if (userRealId.Value != post.AuthorId)
{
return Results.Unauthorized();
}

post.Text = postPut.Text;
repo.Update(post);
repo.Save(); // Save changes to the database
return TypedResults.Ok(post);
}
catch (Exception ex)
{
return Results.InternalServerError(ex.Message);
}
}

[Authorize]
private static async Task<IResult> CreateNewPost([FromServices] IRepository<Blogpost> repo, [FromBody] BlogPostPost postPost)
{
try
{
var newPost = new Blogpost()
{
Text = postPost.Text,
AuthorId = postPost.UserId
};
repo.Insert(newPost);
repo.Save(); // Save changes to the database
return TypedResults.Ok(newPost);
}
catch (Exception ex)
{
return Results.InternalServerError(ex.Message);
}
}

[Authorize]
private static async Task<IResult> GetAllBlogPosts([FromServices] IRepository<Blogpost> repo)
{
try
{
var posts = repo.GetAll(bp => bp.User); // Include the user property
var postsDto = posts.Select(p => new BlogPostDto()
{
PostId = p.Id,
Text = p.Text,
Author = p.User.Username
});
return TypedResults.Ok(postsDto);
}
catch (Exception ex)
{
return Results.InternalServerError(ex.Message);
}
}
}
30 changes: 30 additions & 0 deletions exercise.wwwapi/Helpers/ClaimsPrincipalHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Security.Claims;

namespace exercise.wwwapi.Helpers;

public static class ClaimsPrincipalHelper
{
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;
}
public static string? Role(this ClaimsPrincipal user)
{
Claim? claim = user.FindFirst(ClaimTypes.Role);
return claim?.Value;
}
}
21 changes: 21 additions & 0 deletions exercise.wwwapi/Model/Blogpost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;

namespace exercise.wwwapi.Model;


/*
Recommended BlogPost model: id, text, authorId (note: this should be a string, to accommodate the UUID)
*/
[Table("blogposts")]
public class Blogpost
{
[Column("id")]
public int Id { get; set; }
[Column("text")]
public string Text { get; set; }
[Column("authorId")]
public int AuthorId { get; set; }
[ForeignKey("AuthorId")]
public User User { get; set; } // Rename to follow C# naming conventions
}
17 changes: 17 additions & 0 deletions exercise.wwwapi/Model/User.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;

namespace exercise.wwwapi.Model;

[Table("users")]
public class User
{
[Column("id")]
public int Id { get; set; }
[Column("username")]
public string Username { get; set; }
[Column("passwordhash")]
public string PasswordHash { get; set; }
[Column("email")]
public string Email { get; set; }
}
Loading