Skip to content

Commit

Permalink
feat: passenger
Browse files Browse the repository at this point in the history
  • Loading branch information
kirinnee committed Nov 27, 2023
1 parent 0bf5a63 commit 6397cca
Show file tree
Hide file tree
Showing 37 changed files with 1,021 additions and 76 deletions.
2 changes: 2 additions & 0 deletions App.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=atomicloud/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Sentral/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=v_0027VVV/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=OTEL/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
57 changes: 48 additions & 9 deletions App/Modules/Common/BaseController.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
using System.Net;
using System.Runtime.CompilerServices;
using App.Error;
using App.Error.V1;
using App.StartUp.Registry;
using App.StartUp.Services.Auth;
using App.Utility;
using CSharp_Result;
using Microsoft.AspNetCore.Mvc;

namespace App.Modules.Common;

public class AtomiControllerBase : ControllerBase
public class AtomiControllerBase(AuthHelper h) : ControllerBase
{
protected ActionResult<T> Error<T>(HttpStatusCode code, IDomainProblem problem)
{
Expand Down Expand Up @@ -85,21 +87,58 @@ protected ActionResult<T> ReturnResult<T>(Result<T> entity)
: this.MapException<T>(entity.FailureOrDefault());
}

protected Result<Unit> Guard(string target)
protected Result<Unit> Guard(string? target)
{
if (this.Sub() == target) return new Unit();
if (target != null && this.Sub() == target) return new Unit();
return new Unauthorized("You are not authorized to access this resource").ToException();
}

protected Task<Result<Unit>> GuardAsync(string target)
protected Task<Result<Unit>> GuardAsync(string? target)
{
if (this.Sub() == target) return new Unit().ToResult().ToAsyncResult();
Result<Unit> r = new Unauthorized("You are not authorized to access this resource").ToException();
return Task.FromResult(r);
return Task.FromResult(this.Guard(target));
}

protected string? Sub()
protected Result<Unit> GuardOrAll(string? target, string field, params string[] value)
{
return this.HttpContext.User?.Identity?.Name;
if (
(target != null && this.Sub() == target)
||
h.HasAll(this.HttpContext.User, field, value)
) return new Unit().ToResult();
return new Unauthorized("You are not authorized to access this resource").ToException();
}

protected Task<Result<Unit>> GuardOrAllAsync(string? target, string field, params string[] value)
{
return Task.FromResult(this.GuardOrAll(target, field, value));
}

protected Result<Unit> GuardOrAny(string? target, string field, params string[] value)
{
if (
(target != null && this.Sub() == target)
||
h.HasAny(this.HttpContext.User, field, value)
) return new Unit().ToResult();
return new Unauthorized("You are not authorized to access this resource").ToException();
}

protected Task<Result<Unit>> GuardOrAnyAsync(string? target, string field, params string[] value)
{
return Task.FromResult(this.GuardOrAny(target, field, value));
}


protected ActionResult<T> BlockAuth<T>()
{
Result<T> r = new Unauthorized("You are not authorized to access this resource").ToException();
return this.ReturnResult(r);
}

protected Task<ActionResult<T>> BlockAuthAsync<T>()
{
return Task.FromResult(this.BlockAuth<T>());
}

protected string? Sub() => this.HttpContext.User?.Identity?.Name;
}
8 changes: 8 additions & 0 deletions App/Modules/DomainServices.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using App.Modules.Passengers.Data;
using App.Modules.Users.Data;
using App.StartUp.Services;
using Domain.Passenger;
using Domain.User;

namespace App.Modules;
Expand All @@ -15,6 +17,12 @@ public static IServiceCollection AddDomainServices(this IServiceCollection s)
s.AddScoped<IUserRepository, UserRepository>()
.AutoTrace<IUserRepository>();

// Passenger
s.AddScoped<IPassengerService, PassengerService>()
.AutoTrace<IPassengerService>();

s.AddScoped<IPassengerRepository, PassengerRepository>()
.AutoTrace<IPassengerRepository>();


return s;
Expand Down
81 changes: 81 additions & 0 deletions App/Modules/Passengers/API/V1/PassengerController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System.Net.Mime;
using App.Error.V1;
using App.Modules.Common;
using App.StartUp.Registry;
using App.StartUp.Services.Auth;
using App.Utility;
using Asp.Versioning;
using CSharp_Result;
using Domain.Passenger;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace App.Modules.Passengers.API.V1;

[ApiVersion(1.0)]
[ApiController]
[Consumes(MediaTypeNames.Application.Json)]
[Route("api/v{version:apiVersion}/[controller]")]
public class PassengerController(
IPassengerService service,
CreatePassengerReqValidator createPassengerReqValidator,
UpdatePassengerReqValidator updatePassengerReqValidator,
PassengerSearchQueryValidator passengerSearchQueryValidator,
AuthHelper authHelper
) : AtomiControllerBase(authHelper)
{
[Authorize, HttpGet]
public async Task<ActionResult<IEnumerable<PassengerPrincipalRes>>> Search([FromQuery] SearchPassengerQuery query)
{
var x = await this
.GuardOrAnyAsync(query.UserId, AuthRoles.Field, AuthRoles.Admin)
.ThenAwait(_ => passengerSearchQueryValidator.ValidateAsyncResult(query, "Invalid SearchPassengerQuery"))
.ThenAwait(q => service.Search(q.ToDomain()))
.Then(x => x.Select(u => u.ToRes()), Errors.MapAll);
return this.ReturnResult(x);
}

[Authorize, HttpGet("{userId}/{id:guid}")]
public async Task<ActionResult<PassengerRes>> Get(string userId, Guid id)
{
var r = await this
.GuardOrAnyAsync(userId, AuthRoles.Field, AuthRoles.Admin)
.ThenAwait(_ => service.Get(userId, id))
.Then(x => x.ToRes(), Errors.MapAll);
return this.ReturnNullableResult(r, new EntityNotFound(
"Passenger Not Found", typeof(Passenger), id.ToString()));
}

[Authorize, HttpPost("{userId}")]
public async Task<ActionResult<PassengerPrincipalRes>> Create(string userId, [FromBody] CreatePassengerReq req)
{
var user = await this.GuardOrAnyAsync(userId, AuthRoles.Field, AuthRoles.Admin)
.ThenAwait(_ => createPassengerReqValidator.ValidateAsyncResult(req, "Invalid CreatePassengerReq"))
.ThenAwait(x => service.Create(userId, x.ToRecord()))
.Then(x => x.ToRes(), Errors.MapAll);
return this.ReturnResult(user);
}

[Authorize, HttpPut("{userId}/{id:guid}")]
public async Task<ActionResult<PassengerPrincipalRes>> Update(string userId, Guid id,
[FromBody] UpdatePassengerReq req)
{
var user = await this.GuardOrAnyAsync(userId, AuthRoles.Field, AuthRoles.Admin)
.ThenAwait(_ => updatePassengerReqValidator.ValidateAsyncResult(req, "Invalid UpdatePassengerReq"))
.ThenAwait(x => service.Update(userId, id, x.ToRecord()))
.Then(x => x.ToRes(), Errors.MapAll);
return this.ReturnNullableResult(user, new EntityNotFound(
"Passenger Not Found", typeof(PassengerPrincipal), id.ToString()));
}

[Authorize, HttpDelete("{userId}/{id:guid}")]
public async Task<ActionResult> Delete(string userId, Guid id)
{
var user = await this
.GuardOrAnyAsync(userId, AuthRoles.Field, AuthRoles.Admin)
.ThenAwait(_ => service.Delete(userId, id));

return this.ReturnUnitNullableResult(user, new EntityNotFound(
"Passenger Not Found", typeof(PassengerPrincipal), id.ToString()));
}
}
66 changes: 66 additions & 0 deletions App/Modules/Passengers/API/V1/PassengerMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using App.Modules.Passengers.API.V1;
using App.Modules.Users.API.V1;
using App.Utility;
using Domain.Passenger;

namespace App.Modules.Passengers.API.V1;

public static class PassengerMapper
{
// RES
public static PassengerPrincipalRes ToRes(this PassengerPrincipal p)
=> new(p.Id,
p.Record.FullName,
p.Record.Gender.ToRes(),
p.Record.PassportExpiry.ToStandardDateFormat(),
p.Record.PassportNumber);

public static PassengerRes ToRes(this Passenger p)
=> new(p.Principal.ToRes(), p.User.ToRes());

public static string ToRes(this PassengerGender gender) =>
gender switch
{
PassengerGender.M => "M",
PassengerGender.F => "F",
_ => throw new ArgumentOutOfRangeException(nameof(gender), gender, "Failed to convert gender to response model")
};


// REQ
public static PassengerGender GenderToDomain(this string gender) =>
gender switch
{
"M" => PassengerGender.M,
"F" => PassengerGender.F,
_ => throw new ArgumentOutOfRangeException(nameof(gender), gender, "Failed to convert gender from request model")
};


public static PassengerRecord ToRecord(this CreatePassengerReq req) =>
new()
{
Gender = req.Gender.GenderToDomain(),
FullName = req.FullName,
PassportExpiry = req.PassportExpiry.ToDate(),
PassportNumber = req.PassportNumber,
};

public static PassengerRecord ToRecord(this UpdatePassengerReq req) =>
new()
{
Gender = req.Gender.GenderToDomain(),
FullName = req.FullName,
PassportExpiry = req.PassportExpiry.ToDate(),
PassportNumber = req.PassportNumber,
};

public static PassengerSearch ToDomain(this SearchPassengerQuery query) =>
new()
{
UserId = query.UserId,
Name = query.Name,
Limit = query.Limit ?? 20,
Skip = query.Skip ?? 0,
};
}
20 changes: 20 additions & 0 deletions App/Modules/Passengers/API/V1/PassengerModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using App.Modules.Users.API.V1;

namespace App.Modules.Passengers.API.V1;

public record SearchPassengerQuery(string? UserId, string? Name, int? Limit, int? Skip);

// REQ
public record CreatePassengerReq(string FullName, string Gender, string PassportExpiry, string PassportNumber);

public record UpdatePassengerReq(string FullName, string Gender, string PassportExpiry, string PassportNumber);

// RESP
public record PassengerPrincipalRes(
Guid Id,
string FullName,
string Gender,
string PassportExpiry,
string PassportNumber);

public record PassengerRes(PassengerPrincipalRes Principal, UserPrincipalRes User);
49 changes: 49 additions & 0 deletions App/Modules/Passengers/API/V1/PassengerValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using App.Utility;
using FluentValidation;

namespace App.Modules.Passengers.API.V1;

public class CreatePassengerReqValidator : AbstractValidator<CreatePassengerReq>
{
public CreatePassengerReqValidator()
{
this.RuleFor(x => x.FullName)
.NotNull();
this.RuleFor(x => x.PassportNumber)
.NotNull();
this.RuleFor(x => x.PassportExpiry)
.NotNull()
.DateValid();
this.RuleFor(x => x.Gender)
.NotNull()
.GenderValid();
}
}

public class UpdatePassengerReqValidator : AbstractValidator<UpdatePassengerReq>
{
public UpdatePassengerReqValidator()
{
this.RuleFor(x => x.FullName)
.NotNull();
this.RuleFor(x => x.PassportNumber)
.NotNull();
this.RuleFor(x => x.PassportExpiry)
.NotNull()
.DateValid();
this.RuleFor(x => x.Gender)
.NotNull()
.GenderValid();
}
}

public class PassengerSearchQueryValidator : AbstractValidator<SearchPassengerQuery>
{
public PassengerSearchQueryValidator()
{
this.RuleFor(x => x.Limit)
.Limit();
this.RuleFor(x => x.Skip)
.Skip();
}
}
21 changes: 21 additions & 0 deletions App/Modules/Passengers/Data/PassengerData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using App.Modules.Users.Data;

namespace App.Modules.Passengers.Data;

public class PassengerData
{
public Guid Id { get; set; }

public string FullName { get; set; } = string.Empty;

public int Gender { get; set; } = 0;

public DateOnly PassportExpiry { get; set; }

public string PassportNumber { get; set; } = string.Empty;

// FK
public string UserId { get; set; } = string.Empty;

public UserData User { get; set; }
}
40 changes: 40 additions & 0 deletions App/Modules/Passengers/Data/PassengerMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using App.Modules.Users.Data;
using Domain.Passenger;

namespace App.Modules.Passengers.Data;

public static class PassengerMapper
{
// to Domain
public static PassengerRecord ToRecord(this PassengerData data) => new()
{
Gender = (PassengerGender)data.Gender,
FullName = data.FullName,
PassportExpiry = data.PassportExpiry,
PassportNumber = data.PassportNumber,
};

public static PassengerPrincipal ToPrincipal(this PassengerData data) => new()
{
Id = data.Id,
Record = data.ToRecord(),
UserId = data.UserId,
};


public static Passenger ToDomain(this PassengerData data) => new()
{
User = data.User.ToPrincipal(),
Principal = data.ToPrincipal(),
};

// To Data
public static PassengerData EnrichData(this PassengerData data, PassengerRecord record)
{
data.Gender = (int)record.Gender;
data.FullName = record.FullName;
data.PassportExpiry = record.PassportExpiry;
data.PassportNumber = record.PassportNumber;
return data;
}
}
Loading

0 comments on commit 6397cca

Please sign in to comment.