diff --git a/src/Backend/Services/Polls/Polls.API/Authorization/Auth0Settings.cs b/src/Backend/Services/Polls/Polls.API/Authorization/Auth0Settings.cs new file mode 100644 index 0000000..dc7ae57 --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Authorization/Auth0Settings.cs @@ -0,0 +1,9 @@ +namespace Polls.API.Authorization; + +public class Auth0Settings +{ + public required string Domain { get; set; } + public required string Authority { get; set; } + public required string Audience { get; set; } + public required string ClientId { get; set; } +} diff --git a/src/Backend/Services/Polls/Polls.API/Authorization/ConfigureJwtBearerOptions.cs b/src/Backend/Services/Polls/Polls.API/Authorization/ConfigureJwtBearerOptions.cs new file mode 100644 index 0000000..be9b7ff --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Authorization/ConfigureJwtBearerOptions.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Options; + +namespace Polls.API.Authorization; + +public class ConfigureJwtBearerOptions( + IOptions auth0Settings) : IConfigureOptions +{ + private readonly Auth0Settings _auth0Settings = auth0Settings.Value; + + public void Configure(JwtBearerOptions options) + { + options.Authority = _auth0Settings.Authority; + options.Audience = _auth0Settings.Audience; + } +} diff --git a/src/Backend/Services/Polls/Polls.API/Authorization/PermissionPolicyProvider.cs b/src/Backend/Services/Polls/Polls.API/Authorization/PermissionPolicyProvider.cs new file mode 100644 index 0000000..fee5a2e --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Authorization/PermissionPolicyProvider.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Authorization; +using Polls.Domain.Authorization; + +namespace Polls.API.Authorization; + +public class PermissionPolicyProvider : IAuthorizationPolicyProvider +{ + public Task GetPolicyAsync(string policyName) + { + var policy = new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .RequireClaim(Permissions.ClaimType, policyName) + .Build(); + + return Task.FromResult(policy); + } + + public Task GetDefaultPolicyAsync() + { + return Task.FromResult( + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .Build()); + } + + public Task GetFallbackPolicyAsync() + { + return Task.FromResult(null); + } +} diff --git a/src/Backend/Services/Polls/Polls.API/Common/Extensions/ClaimsPrincipalExtensions.cs b/src/Backend/Services/Polls/Polls.API/Common/Extensions/ClaimsPrincipalExtensions.cs new file mode 100644 index 0000000..32b1a0c --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Common/Extensions/ClaimsPrincipalExtensions.cs @@ -0,0 +1,24 @@ +using System.Security.Claims; + +namespace Polls.API.Common.Extensions; + +public static class ClaimsPrincipalExtensions +{ + private const string CityIdClaim = "https://citypulse.com/city_id"; + + public static Guid GetUserId(this ClaimsPrincipal user) + { + var claim = user.FindFirstValue(ClaimTypes.NameIdentifier); + return Guid.TryParse(claim, out var id) + ? id + : Guid.Empty; + } + + public static Guid GetCityId(this ClaimsPrincipal user) + { + var claim = user.FindFirstValue(CityIdClaim); + return Guid.TryParse(claim, out var id) + ? id + : Guid.Empty; + } +} diff --git a/src/Backend/Services/Polls/Polls.API/Common/Filters/ResultFilter.cs b/src/Backend/Services/Polls/Polls.API/Common/Filters/ResultFilter.cs new file mode 100644 index 0000000..823d4e2 --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Common/Filters/ResultFilter.cs @@ -0,0 +1,49 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Polls.Domain.Common; +using Polls.Domain.Common.Enums; + +namespace Polls.API.Common.Filters; + +public class ResultFilter : IActionFilter +{ + private const string ErrorsExtensionKey = "errors"; + + public void OnActionExecuting(ActionExecutingContext context) { } + + public void OnActionExecuted(ActionExecutedContext context) + { + if (context.Result is not ObjectResult { Value: Result { IsSuccess: false } result }) + return; + + var statusCode = GetStatusCode(result.Error.Type); + + var problemDetails = new ProblemDetails + { + Status = statusCode, + Title = result.Error.Type.ToString(), + Detail = result.Error.Description, + Instance = context.HttpContext.Request.Path + }; + + if (result.Errors.Count > 1) + problemDetails.Extensions[ErrorsExtensionKey] = result.Errors + .Select(e => e.Description) + .ToArray(); + + context.Result = new ObjectResult(problemDetails) + { + StatusCode = statusCode + }; + } + + private static int GetStatusCode(ErrorType type) => type switch + { + ErrorType.NotFound => StatusCodes.Status404NotFound, + ErrorType.Conflict => StatusCodes.Status409Conflict, + ErrorType.Forbidden => StatusCodes.Status403Forbidden, + ErrorType.Validation => StatusCodes.Status400BadRequest, + + _=> StatusCodes.Status400BadRequest + }; +} diff --git a/src/Backend/Services/Polls/Polls.API/Common/Middleware/ExceptionHandlerMiddleware.cs b/src/Backend/Services/Polls/Polls.API/Common/Middleware/ExceptionHandlerMiddleware.cs new file mode 100644 index 0000000..009fb56 --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Common/Middleware/ExceptionHandlerMiddleware.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Polls.API.Common.Middleware; + +public class ExceptionHandlerMiddleware( + RequestDelegate next, + ILogger logger) +{ + private const string ContentType = "application/json"; + private const string InternalServerErrorTitle = "Internal Server Error"; + private const string InternalServerErrorDetail = "An unexpected error occurred."; + + public async Task InvokeAsync(HttpContext context) + { + try + { + await next(context); + } + catch (Exception ex) + { + logger.LogError( + ex, + "Unhandled exception at {Method} {Path}", + context.Request.Method, + context.Request.Path); + + await HandleExceptionAsync(context); + } + } + + private static async Task HandleExceptionAsync(HttpContext context) + { + context.Response.StatusCode = StatusCodes.Status500InternalServerError; + context.Response.ContentType = ContentType; + + var problemDetails = new ProblemDetails + { + Status = StatusCodes.Status500InternalServerError, + Title = InternalServerErrorTitle, + Detail = InternalServerErrorDetail, + Instance = context.Request.Path + }; + + await context.Response.WriteAsJsonAsync(problemDetails); + } +} diff --git a/src/Backend/Services/Polls/Polls.API/Controllers/Admin/AdminCitiesController.cs b/src/Backend/Services/Polls/Polls.API/Controllers/Admin/AdminCitiesController.cs new file mode 100644 index 0000000..08f4a83 --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Controllers/Admin/AdminCitiesController.cs @@ -0,0 +1,118 @@ +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Polls.API.Requests.Cities; +using Polls.Application.Cities.Commands.ChangeStatus; +using Polls.Application.Cities.Commands.CreateCity; +using Polls.Application.Cities.Commands.DeleteCity; +using Polls.Application.Cities.Commands.UpdateCity; +using Polls.Application.Cities.DTOs; +using Polls.Application.Cities.Queries.GetCities; +using Polls.Application.Cities.Queries.GetCityById; +using Polls.Application.Cities.Queries.GetCityWithPolls; +using Polls.Application.Common.Models; +using Polls.Domain.Authorization; +using Polls.Domain.Common; + +namespace Polls.API.Controllers.Admin; + +[ApiController] +[Route("api/v1/admin/cities")] +[Authorize] +public class AdminCitiesController(ISender sender) : ControllerBase +{ + [HttpGet] + [Authorize(Policy = Permissions.Cities.ReadAny)] + public async Task>> GetCities( + [FromQuery] CityFilter filter, + CancellationToken cancellationToken) + { + var query = new GetCitiesQuery( + Filter: filter, + IncludeOnlyActive: false); + + return await sender.Send(query, cancellationToken); + } + + [HttpGet("{id:guid}")] + [Authorize(Policy = Permissions.Cities.ReadAny)] + public async Task> GetCityById( + Guid id, + CancellationToken cancellationToken) + { + var query = new GetCityByIdQuery( + Id: id, + IncludeOnlyActive: false); + + return await sender.Send(query, cancellationToken); + } + + [HttpGet("{id:guid}/polls")] + [Authorize(Policy = Permissions.Cities.ReadAny)] + public async Task> GetCityWithPolls( + Guid id, + CancellationToken cancellationToken) + { + var query = new GetCityWithPollsQuery( + Id: id, + IncludeOnlyActive: false); + + return await sender.Send(query, cancellationToken); + } + + [HttpPost] + [Authorize(Policy = Permissions.Cities.CreateAny)] + public async Task> CreateCity( + CreateCityRequest request, + CancellationToken cancellationToken) + { + var command = new CreateCityCommand( + Title: request.Title, + Coordinates: request.Coordinates, + Description: request.Description); + + return await sender.Send(command, cancellationToken); + } + + [HttpPut("{id:guid}")] + [Authorize(Policy = Permissions.Cities.UpdateAny)] + public async Task> UpdateCity( + Guid id, + UpdateCityRequest request, + CancellationToken cancellationToken) + { + var command = new UpdateCityCommand( + Id: id, + Title: request.Title, + Coordinates: request.Coordinates, + Description: request.Description); + + return await sender.Send(command, cancellationToken); + } + + [HttpDelete("{id:guid}")] + [Authorize(Policy = Permissions.Cities.DeleteAny)] + public async Task> DeleteCity( + Guid id, + CancellationToken cancellationToken) + { + var command = new DeleteCityCommand( + Id: id); + + return await sender.Send(command, cancellationToken); + } + + [HttpPatch("{id:guid}/status")] + [Authorize(Policy = Permissions.Cities.ChangeStatusAny)] + public async Task> ChangeStatus( + Guid id, + ChangeCityStatusRequest request, + CancellationToken cancellationToken) + { + var command = new ChangeCityStatusCommand( + Id: id, + NewStatus: request.NewStatus); + + return await sender.Send(command, cancellationToken); + } +} diff --git a/src/Backend/Services/Polls/Polls.API/Controllers/Admin/AdminIdeasController.cs b/src/Backend/Services/Polls/Polls.API/Controllers/Admin/AdminIdeasController.cs new file mode 100644 index 0000000..d56bae6 --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Controllers/Admin/AdminIdeasController.cs @@ -0,0 +1,123 @@ +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Polls.API.Common.Extensions; +using Polls.API.Requests.Ideas; +using Polls.Application.Common.Models; +using Polls.Application.Ideas.Commands.ChangeStatus; +using Polls.Application.Ideas.Commands.CreateIdea; +using Polls.Application.Ideas.Commands.DeleteIdea; +using Polls.Application.Ideas.Commands.UpdateIdea; +using Polls.Application.Ideas.DTOs; +using Polls.Application.Ideas.Queries.GetIdeaById; +using Polls.Application.Ideas.Queries.GetIdeas; +using Polls.Application.Ideas.Queries.GetIdeaWithPoll; +using Polls.Domain.Authorization; +using Polls.Domain.Common; + +namespace Polls.API.Controllers.Admin; + +[ApiController] +[Route("api/v1/admin/ideas")] +[Authorize] +public class AdminIdeasController(ISender sender) : ControllerBase +{ + [HttpGet] + [Authorize(Policy = Permissions.Ideas.ReadAny)] + public async Task>> GetIdeas( + [FromQuery] IdeaFilter filter, + CancellationToken cancellationToken) + { + var query = new GetIdeasQuery( + Filter: filter, + IncludeOnlyActive: false); + + return await sender.Send(query, cancellationToken); + } + + [HttpGet("{id:guid}")] + [Authorize(Policy = Permissions.Ideas.ReadAny)] + public async Task> GetIdeaById( + Guid id, + CancellationToken cancellationToken) + { + var query = new GetIdeaByIdQuery( + Id: id, + IncludeOnlyActive: false); + + return await sender.Send(query, cancellationToken); + } + + [HttpGet("{id:guid}/poll")] + [Authorize(Policy = Permissions.Ideas.ReadAny)] + public async Task> GetIdeaWithPoll( + Guid id, + CancellationToken cancellationToken) + { + var query = new GetIdeaWithPollQuery( + Id: id, + IncludeOnlyActive: false); + + return await sender.Send(query, cancellationToken); + } + + [HttpPost("{pollId:guid}")] + [Authorize(Policy = Permissions.Ideas.CreateAny)] + public async Task> CreateIdea( + Guid pollId, + CreateIdeaRequest request, + CancellationToken cancellationToken) + { + var command = new CreateIdeaCommand( + UserId: User.GetUserId(), + PollId: pollId, + Title: request.Title, + Description: request.Description, + BypassRestrictions: true); + + return await sender.Send(command, cancellationToken); + } + + [HttpPut("{id:guid}")] + [Authorize(Policy = Permissions.Ideas.UpdateAny)] + public async Task> UpdateIdea( + Guid id, + UpdateIdeaRequest request, + CancellationToken cancellationToken) + { + var command = new UpdateIdeaCommand( + Id: id, + Title: request.Title, + Description: request.Description, + BypassRestrictions: true); + + return await sender.Send(command, cancellationToken); + } + + [HttpDelete("{id:guid}")] + [Authorize(Policy = Permissions.Ideas.DeleteAny)] + public async Task> DeleteIdea( + Guid id, + CancellationToken cancellationToken) + { + var command = new DeleteIdeaCommand( + Id: id, + BypassRestrictions: true); + + return await sender.Send(command, cancellationToken); + } + + [HttpPatch("{id:guid}/status")] + [Authorize(Policy = Permissions.Ideas.ChangeStatusAny)] + public async Task> ChangeStatus( + Guid id, + ChangeIdeaStatusRequest request, + CancellationToken cancellationToken) + { + var command = new ChangeIdeaStatusCommand( + Id: id, + NewStatus: request.NewStatus); + + return await sender.Send(command, cancellationToken); + } +} diff --git a/src/Backend/Services/Polls/Polls.API/Controllers/Admin/AdminPollsController.cs b/src/Backend/Services/Polls/Polls.API/Controllers/Admin/AdminPollsController.cs new file mode 100644 index 0000000..a65a89b --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Controllers/Admin/AdminPollsController.cs @@ -0,0 +1,126 @@ +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Polls.API.Requests.Polls; +using Polls.Application.Common.Models; +using Polls.Application.Polls.Commands.ChangeStatus; +using Polls.Application.Polls.Commands.CreatePoll; +using Polls.Application.Polls.Commands.DeletePoll; +using Polls.Application.Polls.Commands.UpdatePoll; +using Polls.Application.Polls.DTOs; +using Polls.Application.Polls.Queries.GetPollById; +using Polls.Application.Polls.Queries.GetPolls; +using Polls.Application.Polls.Queries.GetPollWithIdeas; +using Polls.Domain.Authorization; +using Polls.Domain.Common; + +namespace Polls.API.Controllers.Admin; + +[ApiController] +[Route("api/v1/admin/polls")] +[Authorize] +public class AdminPollsController(ISender sender) : ControllerBase +{ + [HttpGet] + [Authorize(Policy = Permissions.Polls.ReadAny)] + public async Task>> GetPolls( + [FromQuery] PollFilter filter, + CancellationToken cancellationToken) + { + var query = new GetPollsQuery( + Filter: filter, + IncludeOnlyActive: false); + + return await sender.Send(query, cancellationToken); + } + + [HttpGet("{id:guid}")] + [Authorize(Policy = Permissions.Polls.ReadAny)] + public async Task> GetPollById( + Guid id, + CancellationToken cancellationToken) + { + var query = new GetPollByIdQuery( + Id: id, + IncludeOnlyActive: false); + + return await sender.Send(query, cancellationToken); + } + + [HttpGet("{id:guid}/ideas")] + [Authorize(Policy = Permissions.Polls.ReadAny)] + public async Task> GetPollWithIdeas( + Guid id, + CancellationToken cancellationToken) + { + var query = new GetPollWithIdeasQuery( + Id: id, + IncludeOnlyActive: false); + + return await sender.Send(query, cancellationToken); + } + + [HttpPost("cities/{cityId:guid}")] + [Authorize(Policy = Permissions.Polls.CreateAny)] + public async Task> CreatePoll( + Guid cityId, + CreatePollRequest request, + CancellationToken cancellationToken) + { + var command = new CreatePollCommand( + CityId: cityId, + Title: request.Title, + Description: request.Description, + Type: request.Type, + EndsAt: request.EndsAt, + BudgetAmount: request.BudgetAmount, + BypassRestrictions: true); + + return await sender.Send(command, cancellationToken); + } + + [HttpPut("{id:guid}")] + [Authorize(Policy = Permissions.Polls.UpdateAny)] + public async Task> UpdatePoll( + Guid id, + UpdatePollRequest request, + CancellationToken cancellationToken) + { + var command = new UpdatePollCommand( + Id: id, + Title: request.Title, + Description: request.Description, + EndsAt: request.EndsAt, + BudgetAmount: request.BudgetAmount, + BypassRestrictions: true); + + return await sender.Send(command, cancellationToken); + } + + [HttpDelete("{id:guid}")] + [Authorize(Policy = Permissions.Polls.DeleteAny)] + public async Task> DeletePoll( + Guid id, + CancellationToken cancellationToken) + { + var command = new DeletePollCommand( + Id: id, + BypassRestrictions: true); + + return await sender.Send(command, cancellationToken); + } + + [HttpPatch("{id:guid}/status")] + [Authorize(Policy = Permissions.Polls.ChangeStatusAny)] + public async Task> ChangeStatus( + Guid id, + ChangePollStatusRequest request, + CancellationToken cancellationToken) + { + var command = new ChangePollStatusCommand( + Id: id, + NewStatus: request.NewStatus); + + return await sender.Send(command, cancellationToken); + } +} diff --git a/src/Backend/Services/Polls/Polls.API/Polls.API.csproj b/src/Backend/Services/Polls/Polls.API/Polls.API.csproj index e40d9b5..7c39c80 100644 --- a/src/Backend/Services/Polls/Polls.API/Polls.API.csproj +++ b/src/Backend/Services/Polls/Polls.API/Polls.API.csproj @@ -7,8 +7,13 @@ + + + + + diff --git a/src/Backend/Services/Polls/Polls.API/Requests/Cities/ChangeCityStatusRequest.cs b/src/Backend/Services/Polls/Polls.API/Requests/Cities/ChangeCityStatusRequest.cs new file mode 100644 index 0000000..1d96e47 --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Requests/Cities/ChangeCityStatusRequest.cs @@ -0,0 +1,8 @@ +using Polls.Domain.Cities.Enums; + +namespace Polls.API.Requests.Cities; + +public record ChangeCityStatusRequest +{ + public required CityStatus NewStatus { get; init; } +} diff --git a/src/Backend/Services/Polls/Polls.API/Requests/Cities/CreateCityRequest.cs b/src/Backend/Services/Polls/Polls.API/Requests/Cities/CreateCityRequest.cs new file mode 100644 index 0000000..417b466 --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Requests/Cities/CreateCityRequest.cs @@ -0,0 +1,10 @@ +using Polls.Application.Cities.DTOs; + +namespace Polls.API.Requests.Cities; + +public record CreateCityRequest +{ + public required string Title { get; init; } + public required CoordinatesDto Coordinates { get; init; } + public string? Description { get; init; } +} diff --git a/src/Backend/Services/Polls/Polls.API/Requests/Cities/UpdateCityRequest.cs b/src/Backend/Services/Polls/Polls.API/Requests/Cities/UpdateCityRequest.cs new file mode 100644 index 0000000..44b5e00 --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Requests/Cities/UpdateCityRequest.cs @@ -0,0 +1,10 @@ +using Polls.Application.Cities.DTOs; + +namespace Polls.API.Requests.Cities; + +public record UpdateCityRequest +{ + public required string Title { get; init; } + public required CoordinatesDto Coordinates { get; init; } + public string? Description { get; init; } +} diff --git a/src/Backend/Services/Polls/Polls.API/Requests/Ideas/ChangeIdeaStatusRequest.cs b/src/Backend/Services/Polls/Polls.API/Requests/Ideas/ChangeIdeaStatusRequest.cs new file mode 100644 index 0000000..8c66a77 --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Requests/Ideas/ChangeIdeaStatusRequest.cs @@ -0,0 +1,8 @@ +using Polls.Domain.Ideas.Enums; + +namespace Polls.API.Requests.Ideas; + +public record ChangeIdeaStatusRequest +{ + public required IdeaStatus NewStatus { get; init; } +} diff --git a/src/Backend/Services/Polls/Polls.API/Requests/Ideas/CreateIdeaRequest.cs b/src/Backend/Services/Polls/Polls.API/Requests/Ideas/CreateIdeaRequest.cs new file mode 100644 index 0000000..2f6dc5c --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Requests/Ideas/CreateIdeaRequest.cs @@ -0,0 +1,7 @@ +namespace Polls.API.Requests.Ideas; + +public record CreateIdeaRequest +{ + public required string Title { get; init; } + public string? Description { get; init; } +} diff --git a/src/Backend/Services/Polls/Polls.API/Requests/Ideas/UpdateIdeaRequest.cs b/src/Backend/Services/Polls/Polls.API/Requests/Ideas/UpdateIdeaRequest.cs new file mode 100644 index 0000000..787d570 --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Requests/Ideas/UpdateIdeaRequest.cs @@ -0,0 +1,7 @@ +namespace Polls.API.Requests.Ideas; + +public record UpdateIdeaRequest +{ + public required string Title { get; init; } + public string? Description { get; init; } +} diff --git a/src/Backend/Services/Polls/Polls.API/Requests/Polls/ChangePollStatusRequest.cs b/src/Backend/Services/Polls/Polls.API/Requests/Polls/ChangePollStatusRequest.cs new file mode 100644 index 0000000..4bdf046 --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Requests/Polls/ChangePollStatusRequest.cs @@ -0,0 +1,8 @@ +using Polls.Domain.Polls.Enums; + +namespace Polls.API.Requests.Polls; + +public record ChangePollStatusRequest +{ + public required PollStatus NewStatus { get; init; } +} diff --git a/src/Backend/Services/Polls/Polls.API/Requests/Polls/CreatePollRequest.cs b/src/Backend/Services/Polls/Polls.API/Requests/Polls/CreatePollRequest.cs new file mode 100644 index 0000000..ae3243c --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Requests/Polls/CreatePollRequest.cs @@ -0,0 +1,12 @@ +using Polls.Domain.Polls.Enums; + +namespace Polls.API.Requests.Polls; + +public record CreatePollRequest +{ + public required string Title { get; init; } + public string? Description { get; init; } + public required PollType Type { get; init; } + public required DateTimeOffset EndsAt { get; init; } + public required decimal BudgetAmount { get; init; } +} diff --git a/src/Backend/Services/Polls/Polls.API/Requests/Polls/UpdatePollRequest.cs b/src/Backend/Services/Polls/Polls.API/Requests/Polls/UpdatePollRequest.cs new file mode 100644 index 0000000..f66a6ee --- /dev/null +++ b/src/Backend/Services/Polls/Polls.API/Requests/Polls/UpdatePollRequest.cs @@ -0,0 +1,9 @@ +namespace Polls.API.Requests.Polls; + +public record UpdatePollRequest +{ + public required string Title { get; init; } + public string? Description { get; init; } + public required DateTimeOffset EndsAt { get; init; } + public required decimal BudgetAmount { get; init; } +} diff --git a/src/Backend/Services/Polls/Polls.Domain/Authorization/Permissions.cs b/src/Backend/Services/Polls/Polls.Domain/Authorization/Permissions.cs new file mode 100644 index 0000000..6e96bd2 --- /dev/null +++ b/src/Backend/Services/Polls/Polls.Domain/Authorization/Permissions.cs @@ -0,0 +1,49 @@ +namespace Polls.Domain.Authorization; + +public static class Permissions +{ + public const string ClaimType = "permissions"; + + public static class Cities + { + public const string ReadAny = "cities.read.any"; + public const string ReadActive = "cities.read.active"; + public const string CreateAny = "cities.create.any"; + public const string UpdateAny = "cities.update.any"; + public const string UpdateOwn = "cities.update.own"; + public const string DeleteAny = "cities.delete.any"; + public const string ChangeStatusAny = "cities.changestatus.any"; + } + + public static class Polls + { + public const string ReadAny = "polls.read.any"; + public const string ReadActive = "polls.read.active"; + public const string CreateCity = "polls.create.city"; + public const string CreateAny = "polls.create.any"; + public const string UpdateCity = "polls.update.city"; + public const string UpdateAny = "polls.update.any"; + public const string DeleteAny = "polls.delete.any"; + public const string DeleteCity = "polls.delete.city"; + public const string VoteCity = "polls.vote.city"; + public const string VoteAny = "polls.vote.any"; + public const string ChangeStatusAny = "polls.changestatus.any"; + } + + public static class Ideas + { + public const string ReadAny = "ideas.read.any"; + public const string ReadActive = "ideas.read.active"; + public const string CreateAny = "ideas.create.any"; + public const string CreateCity = "ideas.create.city"; + public const string CreateUserVoting = "ideas.create.user-voting"; + public const string CreateManagerVoting = "ideas.create.manager-voting"; + public const string UpdateAny = "ideas.update.any"; + public const string UpdateCity = "ideas.update.city"; + public const string UpdateOwn = "ideas.update.own"; + public const string DeleteAny = "ideas.delete.any"; + public const string DeleteCity = "ideas.delete.city"; + public const string DeleteOwn = "ideas.delete.own"; + public const string ChangeStatusAny = "ideas.changestatus.any"; + } +}