Publish from private repository

This commit is contained in:
Gitea Actions
2026-08-01 11:53:53 +00:00
commit ac7c7bc8ea
694 changed files with 69367 additions and 0 deletions
@@ -0,0 +1,187 @@
namespace MyOffice.Web.Controllers;
using AutoMapper;
using Core;
using Core.Extensions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Models.Account;
using Models.Item;
using Models.Motion;
using MyOffice.Web.Infrastructure.Attributes;
using Services.Account;
using Services.Account.Domain;
using Services.Item;
[Authorize]
[ApiController]
[Route("api/accounts")]
[DefaultFromBody]
public class AccountController : BaseApiController
{
private readonly ILogger<AccountController> _logger;
private readonly IMapper _mapper;
private readonly AccountService _accountService;
private readonly ItemService _itemService;
public AccountController(
ILogger<AccountController> logger,
IMapper mapper,
AccountService accountService,
ItemService itemService
)
{
_logger = logger;
_mapper = mapper;
_accountService = accountService;
_itemService = itemService;
}
[HttpGet]
public async Task<ObjectResult> AccountsGet([FromQuery] string category)
{
var list = await _accountService.GetByCategoryDetailedAsync(UserId, category!.AsGuid());
return OkResponse(_mapper.Map<List<AccountDetailedViewModel>>(list.OrderBy(x => x.Account.Name).ToList()));
}
[HttpGet("{id}")]
public async Task<ObjectResult> AccountGet(string id)
{
var exec = await _accountService.GetByIdDetailedAsync(UserId, id!.AsGuid());
return MapGeneralExec(
exec,
result => OkResponse(_mapper.Map<AccountDetailedViewModel>(result)),
notFoundDetail: "Account not found.",
failureDetail: "Failed to load account.");
}
[HttpGet("{id}/motions")]
public async Task<ObjectResult> MotionsGet(string id, [FromQuery] MotionsGetRequest request)
{
var exec = await _accountService.GetMotionsAsync(
UserId,
id!.AsGuid(),
request.From.StartOfDay(),
request.To.EndOfDay());
return MapGeneralExec(
exec,
result => OkResponse(_mapper.Map<List<MotionViewModel>>(result)),
notFoundDetail: "Account not found.",
failureDetail: "Failed to load motions.");
}
[HttpPost("{id}/motions")]
public async Task<ObjectResult> MotionsPost(string id, MotionRequest motion)
{
var exec = await _accountService.MotionAddAsync(
UserId,
id.AsGuid(),
_mapper.Map<MotionAddUpdate>(motion));
switch (exec.Status)
{
case MotionAddStatus.account_not_found:
return ProblemNotFoundResponse("Account not found.");
case MotionAddStatus.forbidden:
return ProblemForbiddenResponse("Write access required.");
case MotionAddStatus.failure:
return ProblemBadResponse("Adding motion failed.");
case MotionAddStatus.success:
return Ok(_mapper.Map<MotionViewModel[]>(exec.Result!));
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpPut("{id}/motions/{motionId}")]
public async Task<ObjectResult> MotionsPut(string id, string motionId, MotionRequest motion)
{
var exec = await _accountService.MotionUpdateAsync(
UserId,
id.AsGuid(),
motionId.AsGuid(),
_mapper.Map<MotionAddUpdate>(motion));
switch (exec.Status)
{
case MotionUpdateStatus.not_found:
return ProblemNotFoundResponse("Motion not found.");
case MotionUpdateStatus.forbidden:
return ProblemForbiddenResponse("Write access required.");
case MotionUpdateStatus.failure:
return ProblemBadResponse("Updating motion failed.");
case MotionUpdateStatus.success:
return OkResponse(_mapper.Map<List<MotionViewModel>>(exec.Result!));
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpDelete("{id}/motions/{motionId}")]
public async Task<ObjectResult> MotionsDelete(string id, string motionId)
{
var exec = await _accountService.MotionRemoveAsync(
UserId,
id.AsGuid(),
motionId.AsGuid());
switch (exec.Status)
{
case MotionDeleteStatus.not_found:
return ProblemNotFoundResponse("Motion not found.");
case MotionDeleteStatus.forbidden:
return ProblemForbiddenResponse("Write access required.");
case MotionDeleteStatus.failure:
return ProblemBadResponse("Deliting motion failed.");
case MotionDeleteStatus.success:
return OkResponse(_mapper.Map<MotionViewModel>(exec.Result!));
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpGet("~/api/items")]
public async Task<ObjectResult> FindItems([FromQuery] string term, CancellationToken cancellationToken)
{
var itemsDto = await _itemService.FindItemsAsync(UserId, term, cancellationToken: cancellationToken);
var items = _mapper.Map<List<ItemViewModel>>(itemsDto);
if (term.StartsWith("+") && term.Length > 1)
{
var foundAccounts = await _accountService.FindAccountsAsync(UserId, term.Substring(1), cancellationToken);
var accounts = foundAccounts.OrderByDescending(x => x.Name);
foreach (var account in accounts)
{
var accountName = $"+{account.Name}";
var item = items.FirstOrDefault(x => x.Name.IsPresent() && x.Name!.Length > 1 && x.Name.Substring(1) == accountName);
if (item == null)
{
items.Add(new ItemViewModel
{
Name = accountName,
AccountId = account.Id.ToShort(),
});
}
else
{
item.AccountId = account.Id.ToShort();
}
}
}
return OkResponse(_mapper
.Map<List<ItemViewModel>>(items)
.OrderBy(x => x.AccountId)
.ThenBy(x => x.Name)
);
}
}
@@ -0,0 +1,71 @@
namespace MyOffice.Web.Controllers;
using System.Security.Claims;
using Data.Repositories.Users;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Server.AspNetCore;
using OpenIddict.Validation.AspNetCore;
using static OpenIddict.Abstractions.OpenIddictConstants;
[ApiController]
public sealed class AuthorizationController : ControllerBase
{
private readonly IUserRepository _userRepository;
public AuthorizationController(IUserRepository userRepository)
{
_userRepository = userRepository;
}
[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
[HttpGet("~/connect/authorize")]
[HttpPost("~/connect/authorize")]
public async Task<IActionResult> Authorize()
{
var result = await HttpContext.AuthenticateAsync(
OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
if (!result.Succeeded)
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
"The user is not authenticated."
}));
}
return SignIn(result.Principal!, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
[Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)]
[HttpGet("~/connect/userinfo")]
[HttpPost("~/connect/userinfo")]
[Produces("application/json")]
public async Task<IActionResult> Userinfo()
{
var userIdValue = User.FindFirstValue(Claims.Subject);
if (!Guid.TryParse(userIdValue, out var userId))
return Challenge(authenticationSchemes: OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
var user = await _userRepository.GetUserAsync(userId);
if (user is null)
return Challenge(authenticationSchemes: OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
return Ok(new
{
sub = userIdValue,
id = userIdValue,
email = user.Email,
name = user.FullName ?? user.UserName,
firstName = user.FirstName,
lastName = user.LastName,
fullName = user.FullName,
phone = user.Phone
});
}
}
@@ -0,0 +1,80 @@
namespace MyOffice.Web.Controllers;
using System.Security.Authentication;
using Microsoft.AspNetCore.Mvc;
using Core;
using Core.Extensions;
using IdentityModel;
using MyOffice.Web.Infrastructure.Attributes;
using MyOffice.Web.Models;
[DefaultFromBody]
public class BaseApiController : ControllerBase
{
public Guid UserId
{
get
{
var subject = User.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Subject)?.Value;
if (subject.IsPresent() && Guid.TryParse(subject, out var guid)) return guid;
throw new AuthenticationException("Get UserId failed");
}
}
[NonAction]
public ObjectResult ProblemBadResponse(string? detail = null)
{
return Problem(detail, statusCode: StatusCodes.Status400BadRequest);
}
[NonAction]
public ObjectResult ProblemNotFoundResponse(string? detail = null)
{
return Problem(detail ?? "Not found.", statusCode: StatusCodes.Status404NotFound);
}
[NonAction]
public ObjectResult ProblemForbiddenResponse(string? detail = null)
{
return Problem(detail ?? "Forbidden.", statusCode: StatusCodes.Status403Forbidden);
}
[NonAction]
public ObjectResult OkResponse(IResponseModel response)
{
return Ok(response);
}
[NonAction]
public ObjectResult OkResponse<T>(IEnumerable<T> response) where T : IResponseModel
{
return Ok(response);
}
/// <summary>
/// Maps <see cref="GeneralExecStatus"/> to ProblemDetails status codes:
/// not_found → 404, forbidden → 403, failure → 400.
/// </summary>
protected ObjectResult MapGeneralExec<T>(
Exec<T, GeneralExecStatus> exec,
Func<T, ObjectResult> onSuccess,
string notFoundDetail,
string? failureDetail = null,
string? forbiddenDetail = null) where T : class
{
switch (exec.Status)
{
case GeneralExecStatus.success:
return onSuccess(exec.Result!);
case GeneralExecStatus.not_found:
return ProblemNotFoundResponse(notFoundDetail);
case GeneralExecStatus.forbidden:
return ProblemForbiddenResponse(forbiddenDetail);
case GeneralExecStatus.failure:
return ProblemBadResponse(failureDetail ?? "Operation failed.");
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
}
@@ -0,0 +1,75 @@
namespace MyOffice.Web.Controllers;
using AutoMapper;
using Core.Extensions;
using Infrastructure.Attributes;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MyOffice.Web.Models.Dashboard;
using Services.Dashboard;
[Authorize]
[ApiController]
[Route("api/dashboard")]
[DefaultFromBody]
public class DashboardController : BaseApiController
{
private readonly ILogger<DashboardController> _logger;
private readonly DashboardService _dashboardService;
private readonly IMapper _mapper;
public DashboardController(
ILogger<DashboardController> logger,
DashboardService dashboardService,
IMapper mapper
)
{
_logger = logger;
_dashboardService = dashboardService;
_mapper = mapper;
}
[HttpGet]
public async Task<ObjectResult> Index(CancellationToken cancellationToken)
{
var data = await _dashboardService.GetDashboardRestDataAsync(UserId, cancellationToken);
return OkResponse(_mapper.Map<DashboardViewModel>(data));
}
[HttpGet("income")]
public async Task<ObjectResult> Income(
DateTime from,
DateTime to,
[AsGuid(true)] string? category,
CancellationToken cancellationToken
)
{
var data = await _dashboardService.GetDashboardIncomeDataAsync(
UserId,
from.StartOfDay(),
to.EndOfDay(),
category?.AsGuidNull(),
cancellationToken);
return OkResponse(_mapper.Map<DashboardIncomeDataViewModel>(data));
}
[HttpGet("outcome")]
public async Task<ObjectResult> Outcome(
DateTime from,
DateTime to,
[AsGuid(true)] string? category,
CancellationToken cancellationToken
)
{
var data = await _dashboardService.GetDashboardOutcomeDataAsync(
UserId,
from.StartOfDay(),
to.EndOfDay(),
category?.AsGuidNull(),
cancellationToken);
return OkResponse(_mapper.Map<DashboardIncomeDataViewModel>(data));
}
}
@@ -0,0 +1,35 @@
namespace MyOffice.Web.Controllers;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Models.Currency;
using Services.Currency;
using MyOffice.Web.Infrastructure.Attributes;
[Authorize]
[ApiController]
[Route("api/general")]
[DefaultFromBody]
public class GeneralController : BaseApiController
{
private readonly CurrencyService _currencyService;
private readonly IMapper _mapper;
public GeneralController(
CurrencyService currencyService,
IMapper mapper
)
{
_currencyService = currencyService;
_mapper = mapper;
}
[HttpGet("currencies")]
public async Task<ObjectResult> GetGlobalCurrencies(CancellationToken cancellationToken)
{
var list = await _currencyService.GetGlobalAllAsync(cancellationToken);
return Ok(_mapper.Map<List<CurrencyGlobalViewModel>>(list));
}
}
@@ -0,0 +1,122 @@
namespace MyOffice.Web.Controllers;
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Core;
using Core.Extensions;
using Models.Account;
using Services.Account;
using Services.Account.Domain;
using Infrastructure.Attributes;
using Services.Identity;
using Microsoft.AspNetCore.Mvc;
[Authorize]
[ApiController]
[Route("api/settings/account-categories")]
[DefaultFromBody]
public class SettingsAccountCategoryController : BaseApiController
{
private readonly ILogger<SettingsAccountController> _logger;
private readonly IMapper _mapper;
private readonly AccountService _accountService;
private readonly IContextProvider _contextProvider;
public SettingsAccountCategoryController(
ILogger<SettingsAccountController> logger,
IMapper mapper,
AccountService accountService,
IContextProvider contextProvider
)
{
_logger = logger;
_mapper = mapper;
_accountService = accountService;
_contextProvider = contextProvider;
}
[HttpGet]
public async Task<ObjectResult> AccountCategories(CancellationToken cancellationToken)
{
var list = await _accountService.GetAllCategoriesAsync(UserId, cancellationToken);
return OkResponse(_mapper.Map<List<AccountCategoryViewModel>>(list).OrderBy(x => x.Name));
}
[HttpGet("{id}")]
public async Task<ObjectResult> AccountCategory([AsGuid] string id, CancellationToken cancellationToken)
{
var exec = await _accountService.GetCategoryAsync(UserId, id.AsGuid(), cancellationToken);
switch (exec.Status)
{
case GeneralExecStatus.not_found:
case GeneralExecStatus.failure:
return ProblemNotFoundResponse("Account category not found.");
case GeneralExecStatus.success:
return OkResponse(_mapper.Map<AccountCategoryViewModel>(exec.Result!));
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpPost]
public async Task<ObjectResult> AccountCategoriesAdd(AccountCategoryViewModel request, CancellationToken cancellationToken)
{
var exec = await _accountService.CategoryAddAsync(UserId, new AccountCategoryDto { Name = request.Name! }, cancellationToken);
switch (exec.Status)
{
case GeneralExecStatus.success:
return OkResponse(_mapper.Map<AccountCategoryViewModel>(exec.Result!));
case GeneralExecStatus.failure:
case GeneralExecStatus.not_found:
return ProblemBadResponse("Adding account category failed.");
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpPut("{id}")]
public async Task<ObjectResult> AccountCategoriesUpdate([AsGuid] string id, AccountCategoryViewModel request, CancellationToken cancellationToken)
{
var exec = await _accountService.CategoryUpdateAsync(UserId, id.AsGuid(), new AccountCategoryDto { Name = request.Name! }, cancellationToken);
switch (exec.Status)
{
case GeneralExecStatus.success:
return OkResponse(_mapper.Map<AccountCategoryViewModel>(exec.Result!));
case GeneralExecStatus.failure:
case GeneralExecStatus.not_found:
return ProblemBadResponse("Update account category failed.");
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpDelete("{id}")]
public async Task<ObjectResult> AccountCategoriesDelete([AsGuid] string id, CancellationToken cancellationToken)
{
var exec = await _accountService.CategoryRemoveAsync(UserId, id.AsGuid(), cancellationToken);
switch (exec.Status)
{
case AccountCategoryRemoveResult.success:
return OkResponse(_mapper.Map<AccountCategoryViewModel>(exec.Result!));
case AccountCategoryRemoveResult.failure:
return ProblemBadResponse("Remove account category failed.");
case AccountCategoryRemoveResult.not_found:
return ProblemNotFoundResponse("Account category not found.");
case AccountCategoryRemoveResult.accounts_exists:
return ProblemBadResponse("Account category have accounts.");
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
}
@@ -0,0 +1,226 @@
namespace MyOffice.Web.Controllers;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Core;
using Core.Extensions;
using Models.Account;
using Services.Account;
using Services.Account.Domain;
using Infrastructure.Attributes;
using Services.Identity;
[Authorize]
[ApiController]
[Route("api/settings/accounts")]
[DefaultFromBody]
public class SettingsAccountController : BaseApiController
{
private readonly ILogger<SettingsAccountController> _logger;
private readonly IMapper _mapper;
private readonly AccountService _accountService;
private readonly IContextProvider _contextProvider;
public SettingsAccountController(
ILogger<SettingsAccountController> logger,
IMapper mapper,
AccountService accountService,
IContextProvider contextProvider
)
{
_logger = logger;
_mapper = mapper;
_accountService = accountService;
_contextProvider = contextProvider;
}
[HttpGet("invites")]
public async Task<ObjectResult> AccountInvites(CancellationToken cancellationToken)
{
var invites = await _accountService.InvitesGetAsync(_contextProvider.User.Email, cancellationToken);
return OkResponse(_mapper.Map<List<AccountAccessInviteViewModel>>(invites));
}
[HttpPost("invites/{id}/accept")]
public async Task<ObjectResult> AccountInviteAccept([AsGuid] string id, AccountInviteAcceptRequest request, CancellationToken cancellationToken)
{
var exec = await _accountService.InviteAcceptAsync(UserId, id.AsGuid(), request.Name, cancellationToken);
switch (exec.Status)
{
case InviteAcceptStatus.invite_not_found:
return ProblemNotFoundResponse("Invite not found.");
case InviteAcceptStatus.account_not_found:
return ProblemNotFoundResponse("Account not found.");
case InviteAcceptStatus.already_accepted:
case InviteAcceptStatus.success:
return OkResponse(_mapper.Map<AccountViewModel>(exec.Result!));
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpPost("invites/{id}/reject")]
public async Task<ObjectResult> AccountInviteReject([AsGuid] string id, CancellationToken cancellationToken)
{
var exec = await _accountService.InviteRejectAsync(id.AsGuid(), cancellationToken);
return MapGeneralExec(
exec,
result => OkResponse(_mapper.Map<AccountAccessInviteViewModel>(result)),
notFoundDetail: "Invite not found.",
failureDetail: "Invite reject failed.");
}
[HttpGet]
public async Task<ObjectResult> AccountsGet([FromQuery][AsGuid(true)] string? category, CancellationToken cancellationToken)
{
var list = category.IsPresent()
? await _accountService.GetByCategoryAsync(UserId, category!.AsGuid(), cancellationToken)
: await _accountService.GetAllAccountsAsync(UserId, cancellationToken);
return OkResponse(_mapper.Map<List<AccountViewModel>>(list.OrderBy(x => x.Name)));
}
[HttpPost]
public async Task<ObjectResult> AccountsAdd(AccountViewModel request, CancellationToken cancellationToken)
{
var exec = await _accountService.AccountAddAsync(UserId, new AccountAdd
{
Name = request.Name,
CurrencyId = request.CurrencyId,
CategoryId = request.CategoryId.AsGuid(),
Type = request.Type,
}, cancellationToken);
switch (exec.Status)
{
case AccountAddStatus.category_not_found:
return ProblemNotFoundResponse("Category not found.");
case AccountAddStatus.currency_not_found:
return ProblemNotFoundResponse("Currency not found.");
case AccountAddStatus.failure:
return ProblemBadResponse("Adding account failed.");
case AccountAddStatus.success:
return OkResponse(_mapper.Map<AccountViewModel>(exec.Result!));
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpPut("{id}")]
public async Task<ObjectResult> AccountsUpdate([AsGuid] string id, AccountEditRequestModel request, CancellationToken cancellationToken)
{
var exec = await _accountService.AccountUpdateAsync(UserId, id.AsGuid(), new AccountEdit
{
Name = request.Name,
CurrencyId = request.CurrencyId,
CategoryId = request.CategoryId?.AsGuidNull(),
UserId = request.UserId?.AsGuidNull(),
Type = request.Type,
}, cancellationToken);
switch (exec.Status)
{
case AccountEditStatus.not_found:
return ProblemNotFoundResponse("Account not found.");
case AccountEditStatus.forbidden:
return ProblemForbiddenResponse("Manage access required.");
case AccountEditStatus.category_not_found:
return ProblemNotFoundResponse("Category not found.");
case AccountEditStatus.currency_not_found:
return ProblemNotFoundResponse("Currency not found.");
case AccountEditStatus.failure:
return ProblemBadResponse("Adding account failed.");
case AccountEditStatus.success:
return OkResponse(_mapper.Map<AccountViewModel>(exec.Result!));
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpDelete("{id}")]
public async Task<ObjectResult> AccountsDelete([AsGuid] string id, CancellationToken cancellationToken)
{
var exec = await _accountService.AccountDeleteAsync(UserId, id, cancellationToken);
return MapGeneralExec(
exec,
result => OkResponse(_mapper.Map<AccountViewModel>(result)),
notFoundDetail: "Account not found.",
forbiddenDetail: "Manage access required.");
}
[HttpDelete("{id}/category/{categoryId}")]
public async Task<ObjectResult> AccountsCategoryDelete([AsGuid] string id, [AsGuid] string categoryId, CancellationToken cancellationToken)
{
var exec = await _accountService.AccountCategoryRemoveAsync(UserId, id.AsGuid(), categoryId.AsGuid(), cancellationToken);
return MapGeneralExec(
exec,
result => OkResponse(_mapper.Map<AccountViewModel>(result)),
notFoundDetail: "Category not found.",
failureDetail: "Category remove failed.",
forbiddenDetail: "Manage access required.");
}
[HttpPost("{id}/access")]
public async Task<ObjectResult> AccountsAccessAdd([AsGuid] string id, AccountAccessViewModel request, CancellationToken cancellationToken)
{
var model = _mapper.Map<List<AccountAccessDto>>(request.Accesses);
var exec = await _accountService.AccessUpdateAsync(UserId, id.AsGuid(), model, cancellationToken);
if (exec.Status != GeneralExecStatus.success)
{
return MapGeneralExec(
exec,
_ => OkResponse(_mapper.Map<AccountViewModel>(exec.Result!)),
notFoundDetail: "Account not found.",
failureDetail: "Access update failed.",
forbiddenDetail: "Manage access required.");
}
if (!request.Email.IsPresent())
{
return OkResponse(_mapper.Map<AccountViewModel>(exec.Result!));
}
var inviteExec = await _accountService.AccessInviteAsync(UserId, id.AsGuid(), request.Email!, request.AllowWrite, cancellationToken);
switch (inviteExec.Status)
{
case AccessInviteStatus.account_not_found:
return ProblemNotFoundResponse("Account not found.");
case AccessInviteStatus.forbidden:
return ProblemForbiddenResponse("Manage access required.");
case AccessInviteStatus.access_exists:
case AccessInviteStatus.invite_exists:
case AccessInviteStatus.success:
return OkResponse(_mapper.Map<AccountViewModel>(exec.Result!));
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpDelete("{id}/access/{userId}")]
public async Task<ObjectResult> AccountsAccessDelete([AsGuid] string id, [AsGuid] string userId, CancellationToken cancellationToken)
{
var exec = await _accountService.AccessDeleteAsync(UserId, id.AsGuid(), userId.AsGuid(), cancellationToken);
return MapGeneralExec(
exec,
result => OkResponse(_mapper.Map<AccountViewModel>(result)),
notFoundDetail: "Account not found.",
failureDetail: "Access delete failed.",
forbiddenDetail: "Manage access required.");
}
}
@@ -0,0 +1,142 @@
namespace MyOffice.Web.Controllers;
using AutoMapper;
using Core;
using Core.Extensions;
using Infrastructure.Attributes;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Models.Currency;
using Services.Currency;
using Services.Currency.Domain;
[Authorize]
[ApiController]
[Route("api/settings/currencies")]
[DefaultFromBody]
public class SettingCurrencyController : BaseApiController
{
private readonly CurrencyService _currencyService;
private readonly ILogger<SettingCurrencyController> _logger;
private readonly IMapper _mapper;
public SettingCurrencyController(
CurrencyService currencyService,
ILogger<SettingCurrencyController> logger,
IMapper mapper
)
{
_currencyService = currencyService;
_logger = logger;
_mapper = mapper;
}
[HttpGet]
public async Task<ObjectResult> Get(CancellationToken cancellationToken)
{
var list = await _currencyService.GetAllWithRatesAsync(UserId, cancellationToken);
return OkResponse(_mapper.Map<List<CurrencyViewModel>>(list).OrderBy(x => x.Id));
}
[HttpPost]
public async Task<ObjectResult> Add(CurrencyAddModel currency, CancellationToken cancellationToken)
{
var exec = await _currencyService.CurrencyAddAsync(UserId, new CurrencyDto
{
UserId = UserId,
CurrencyGlobalId = currency.Id,
Name = currency.Name,
ShortName = currency.ShortName,
}, cancellationToken);
switch (exec.Status)
{
case CurrencyAddStatus.success:
case CurrencyAddStatus.exists:
await _currencyService.CurrencyRateAddAsync(UserId, exec.Result!.Id, new CurrencyRateDto
{
CurrencyId = exec.Result!.Id,
Rate = currency.Rate,
Quantity = currency.Quantity,
DateTime = currency.RateDate.Date,
}, cancellationToken);
return OkResponse(_mapper.Map<CurrencyViewModel>(exec.Result!));
case CurrencyAddStatus.failed:
return ProblemBadResponse("Adding currency failed.");
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpPut("{id}")]
public async Task<ObjectResult> Update(string id, CurrencyEditModel currency, CancellationToken cancellationToken)
{
var exec = await _currencyService.CurrencyUpdateAsync(
UserId,
id.AsGuid(),
new CurrencyEdit { Name = currency.Name, ShortName = currency.ShortName, IsPrimary = currency.IsPrimary },
cancellationToken
);
switch (exec.Status)
{
case CurrencyEditStatus.success:
return OkResponse(_mapper.Map<CurrencyViewModel>(exec.Result!));
case CurrencyEditStatus.not_found:
case CurrencyEditStatus.failed:
return ProblemNotFoundResponse("Currency not found.");
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpDelete("{id}")]
public async Task<ObjectResult> Delete([AsGuid] string id, CancellationToken cancellationToken)
{
var exec = await _currencyService.RemoveAsync(UserId, id.AsGuid(), cancellationToken);
switch (exec.Status)
{
case GeneralExecStatus.success:
return OkResponse(_mapper.Map<CurrencyViewModel>(exec.Result!));
case GeneralExecStatus.not_found:
case GeneralExecStatus.failure:
return ProblemNotFoundResponse("Currency not found.");
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpPost("{id}/rate")]
public async Task<ObjectResult> AddRate(string id, CurrencyRateModel currencyRate, CancellationToken cancellationToken)
{
var exec = await _currencyService.CurrencyRateAddAsync(UserId, id.AsGuid(), new CurrencyRateDto
{
CurrencyId = id.AsGuid(),
Quantity = currencyRate.Quantity,
Rate = currencyRate.Rate,
DateTime = currencyRate.RateDate.Date,
}, cancellationToken);
switch (exec.Status)
{
case CurrencyAddRateStatus.failed:
return ProblemBadResponse("Adding currency rate failed.");
case CurrencyAddRateStatus.not_found:
return ProblemNotFoundResponse("Currency not found.");
case CurrencyAddRateStatus.success:
return OkResponse(_mapper.Map<CurrencyRateViewModel>(exec.Result!));
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
}
@@ -0,0 +1,163 @@
namespace MyOffice.Web.Controllers;
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Models.Item;
using Core.Extensions;
using Core;
using Infrastructure.Attributes;
using Services.Item;
using Services.Item.Domain;
[Authorize]
[ApiController]
[Route("api/settings")]
[DefaultFromBody]
public class SettingsItemController : BaseApiController
{
private ILogger<SettingsItemController> _logger;
private IMapper _mapper;
private ItemService _itemService;
public SettingsItemController(
ILogger<SettingsItemController> logger,
ItemService itemService,
IMapper mapper
)
{
_itemService = itemService;
_logger = logger;
_mapper = mapper;
}
[HttpGet("item-categories")]
public async Task<ObjectResult> ItemsCategories(CancellationToken cancellationToken)
{
var list = await _itemService.GetAllCategoriesAsync(UserId, cancellationToken);
return Ok(_mapper.Map<List<ItemCategoryViewModel>>(list)
.OrderByDescending(x => x.SortOrder)
.ThenBy(x => x.Name));
}
[HttpPost("item-categories")]
public async Task<ObjectResult> ItemCategoriesAdd(ItemCategoryViewModel request, CancellationToken cancellationToken)
{
var exec = await _itemService.CategoryAddAsync(UserId, request.FromModel(), cancellationToken);
switch (exec.Status)
{
case GeneralExecStatus.success:
return Ok(_mapper.Map<ItemCategoryViewModel>(exec.Result!));
case GeneralExecStatus.failure:
case GeneralExecStatus.not_found:
return ProblemBadResponse("Adding item category failed.");
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpPut("item-categories/{id}")]
public async Task<ObjectResult> ItemCategoriesUpdate(
[AsGuid] string id,
ItemCategoryViewModel request,
CancellationToken cancellationToken)
{
var exec = await _itemService.CategoryUpdateAsync(UserId, id.AsGuid(), request.FromModel(), cancellationToken);
switch (exec.Status)
{
case GeneralExecStatus.success:
return Ok(_mapper.Map<ItemCategoryViewModel>(exec.Result!));
case GeneralExecStatus.failure:
case GeneralExecStatus.not_found:
return ProblemBadResponse("Update item category failed.");
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpDelete("item-categories/{id}")]
public async Task<ObjectResult> ItemCategoriesDelete([AsGuid] string id, CancellationToken cancellationToken)
{
var exec = await _itemService.CategoryRemoveAsync(UserId, id.AsGuid(), cancellationToken);
switch (exec.Status)
{
case ItemCategoryRemoveResult.success:
return Ok(_mapper.Map<ItemCategoryViewModel>(exec.Result!));
case ItemCategoryRemoveResult.failure:
return ProblemBadResponse("Remove item category failed.");
case ItemCategoryRemoveResult.not_found:
return ProblemNotFoundResponse("Account item not found.");
case ItemCategoryRemoveResult.accounts_exists:
return ProblemBadResponse("Account motion have accounts.");
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpGet("items")]
public async Task<ObjectResult> Items([FromQuery][AsGuid] string category, CancellationToken cancellationToken)
{
var categoryId = category.AsGuid();
var list = categoryId != UserId
? await _itemService.GetByCategoryAsync(UserId, categoryId, cancellationToken)
: await _itemService.GetByUncategorizedAsync(UserId, cancellationToken);
return Ok(_mapper.Map<List<ItemViewModel>>(list.OrderBy(x => x.Name)));
}
[HttpPut("items/{id}")]
public async Task<ObjectResult> ItemsUpdate(
[AsGuid] string id,
ItemEditModel request,
CancellationToken cancellationToken)
{
var exec = await _itemService.UpdateAsync(UserId, id.AsGuid(), request.Category.AsGuid(), cancellationToken);
switch (exec.Status)
{
case GeneralExecStatus.not_found:
return ProblemNotFoundResponse("Item not found.");
case GeneralExecStatus.failure:
return ProblemBadResponse("Item update failed.");
case GeneralExecStatus.success:
return Ok(_mapper.Map<ItemViewModel>(exec.Result!));
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpPost("items")]
public async Task<ObjectResult> ItemsPost(ItemChangeCategoryModel request, CancellationToken cancellationToken)
{
var exec = await _itemService.UpdateItemsCategoryAsync(
UserId,
request.category.AsGuid(),
request.Items.Select(x => x.AsGuid()).ToList(),
cancellationToken);
switch (exec)
{
case GeneralExecStatus.not_found:
return ProblemNotFoundResponse("Category not found.");
case GeneralExecStatus.failure:
return ProblemBadResponse("Update failed.");
case GeneralExecStatus.success:
return Ok(new {});
default:
throw new NotSupportedException(exec.ToString());
}
}
}
+189
View File
@@ -0,0 +1,189 @@
namespace MyOffice.Web.Controllers;
using Models.Auth;
using Identity.Domain;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Authorization;
using MyOffice.Data.Models.Users;
using Core.Extensions;
using MyOffice.Core.Identity;
using Core;
using Services.Users;
using Services.Users.Domain;
using MyOffice.Data.Models.Currencies;
using MyOffice.Web.Infrastructure.Attributes;
[ApiController]
[Route("api/user")]
[DefaultFromBody]
public class UserController : BaseApiController
{
private readonly UserManager<ApplicationUser<Guid>> _userManager;
private readonly UserService _userService;
private readonly IEnumerable<IExternalProviderValidator> _externalProviderValidators;
public UserController(
UserManager<ApplicationUser<Guid>> userManager,
UserService userService,
IEnumerable<IExternalProviderValidator> externalProviderValidators
)
{
_userManager = userManager;
_userService = userService;
_externalProviderValidators = externalProviderValidators;
}
[AllowAnonymous]
[HttpPost("register")]
public async Task<IActionResult> Register([FromBody] RegisterModel request)
{
if (!ModelState.IsValid)
return BadRequest(new
{
Succeeded = false,
Errors = ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => new { Code = "Validation", Description = e.ErrorMessage })
});
var email = request.UserName.Trim();
var user = new ApplicationUser<Guid>
{
Id = Guid.NewGuid(),
UserName = email,
Email = email,
IsEmailConfirmed = true,
CurrencyId = CurrencyGlobalIdEnum.USD.ToString(),
};
var result = await _userManager.CreateAsync(user, request.Password);
return Ok(new
{
result.Succeeded,
Errors = result.Errors
.Where(x => !x.Code.Equals("DuplicateUserName"))
.Select(x => new { x.Code, x.Description })
});
}
[Authorize]
[HttpGet("profile")]
public async Task<object> ProfileGet()
{
var user = await _userManager.FindByIdAsync(UserId.ToString());
return user.ToModel(await _userService.GetUserExternals(user.Id));
}
[Authorize]
[HttpPost("profile")]
public async Task<object> ProfileUpdate([FromBody] ProfileModel model)
{
var user = await _userManager.FindByIdAsync(UserId.ToString()); ;
user.FirstName = model.FirstName;
user.LastName = model.LastName;
user.FullName = model.FullName.NullIfEmpty() ?? $"{model.FirstName} {model.LastName}";
user.CurrencyId = model.Currency;
await _userManager.UpdateAsync(user);
return user.ToModel(await _userService.GetUserExternals(user.Id));
}
[Authorize]
[HttpPost("attach")]
public async Task<object> AttachProvider([FromBody] AttachModel model)
{
var validator = _externalProviderValidators.FirstOrDefault(x => x.Provider.EqualsIgnoreCase(model.Provider));
if (validator == null) return ProblemBadResponse("Provider not supported");
var result = await validator.ValidateAsync(model.Token);
if (!result.IsSuccessed) return ProblemBadResponse("Token not valid");
var execResult = await _userService.AddUserExternalAsync(UserId, model.Provider, result.ExternalId!, result.Email!);
switch (execResult.Status)
{
case AddUserExternalStatusEnum.success:
return Ok(new
{
success = true
});
case AddUserExternalStatusEnum.externalid_used:
case AddUserExternalStatusEnum.user_not_valid:
return ProblemBadResponse("Provider already connected");
default:
throw new NotSupportedException(execResult.Status.ToString());
}
}
[Authorize]
[HttpPost("deattach")]
public async Task<object> DeattachProvider([FromBody] DeattachModel model)
{
var exec = await _userService.RemoveUserExternal(UserId, model.Provider);
if (exec.Status == GeneralExecStatus.not_found) return ProblemBadResponse("Provider not connected");
return Ok(new
{
success = true
});
}
}
public class DeattachModel
{
[Required] public string Provider { get; set; } = null!;
}
public class AttachModel
{
[Required] public string Provider { get; set; } = null!;
[Required] public string Token { get; set; } = null!;
}
public class ProfileModel
{
public class ProviderModel
{
public string Provider { get; set; } = null!;
public DateTime CreatedOn { get; set; }
}
public string? Email { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? FullName { get; set; }
public bool? IsEmailConfirmed { get; set; }
public string? Currency { get; set; }
public List<ProviderModel>? Providers { get; set; }
}
public static class ProfileModelExtensions
{
public static ProfileModel ToModel(this ApplicationUser<Guid> user, List<UserExternal> userClaims)
{
return new ProfileModel
{
Email = user.Email,
FirstName = user.FirstName,
LastName = user.LastName,
FullName = user.FullName,
IsEmailConfirmed = user.IsEmailConfirmed,
Currency = user.CurrencyId,
Providers = userClaims?.Select(x => new ProfileModel.ProviderModel
{
Provider = x.Provider,
CreatedOn = x.CreatedOn
}).ToList()
};
}
}