Skip to content
Merged
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
13 changes: 13 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"csharpier": {
"version": "0.30.6",
"commands": [
"dotnet-csharpier"
],
"rollForward": false
}
}
}
8 changes: 5 additions & 3 deletions core/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ ASPNETCORE_HTTP_PORT=8080
ASPNETCORE_ENVIRONMENT=Development

CONNECTION_STRING=User ID=postgres;Password=test123;Host=host.docker.internal;Port=5432;Database=ps;
SUPABASE_PROJECT_ID=
SUPABASE_SECRET_KEY=
SUPABASE_ANON_KEY=

EMAIL_SERVER=smtp.gmail.com
EMAIL_PORT=587
Expand All @@ -21,6 +18,11 @@ REDIS_CONNECTION_STRING=host.docker.internal:6379,password=eYVX7EwVmmxKPCDmwMtyK
CONTAINER_DIR=/app/volume
CDN_URL=http://localhost:6464

OPENID_ISSUER=https://example.com
OPENID_BASE_URL=[Insert your URL here]
OPENID_CLIENT_ID=[Insert your client id here]
OPENID_CLIENT_SECRET=[Insert your client secret here]

FRONTEND_BASE_URL=http://localhost:3000

RATE_LIMIT_TIME_WINDOW_SECONDS=10
Expand Down
6 changes: 3 additions & 3 deletions core/Controllers/DraftEventsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ namespace api.core.Controllers;
[ApiController]
[Authorize(Policy = AuthPolicies.OrganizerIsActive)]
[Route("api/organizer-drafts")] // TODO : Change route to /api/me/drafts
public class DraftEventsController(ILogger<DraftEventsController> logger, IDraftEventService draftService) : ControllerBase
public class DraftEventsController(ILogger<DraftEventsController> logger, IDraftEventService draftService, IJwtUtils jwtUtils) : ControllerBase
{
/// <summary>
/// Add a draft event to the database. This event will be saved as a draft and will not be visible
Expand All @@ -41,7 +41,7 @@ public IActionResult AddDraft([FromForm] DraftEventRequestDTO draftEvent)
{
logger.LogInformation($"Adding new draft");

var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
var evnt = draftService.AddDraftEvent(userId, draftEvent);

return new OkObjectResult(
Expand All @@ -61,7 +61,7 @@ public IActionResult AddDraft([FromForm] DraftEventRequestDTO draftEvent)
[HttpPatch("{id}")] // TODO: Change this to a HttpPut instead
public IActionResult UpdateDraft(Guid id, [FromForm] DraftEventRequestDTO draftEvent)
{
var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
return draftService.UpdateDraftEvent(userId, id, draftEvent) ? Ok() : BadRequest();
}
}
2 changes: 1 addition & 1 deletion core/Controllers/EventsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public class EventsController(
public ActionResult<IEnumerable<EventResponseDTO>> GetEvents(
[FromQuery] DateTime? startDate,
[FromQuery] DateTime? endDate,
[FromQuery] Guid? organizerId,
[FromQuery] string? organizerId,
[FromQuery] string? title,
[FromQuery] IEnumerable<Guid>? activityAreas,
[FromQuery] IEnumerable<Guid>? tags,
Expand Down
11 changes: 6 additions & 5 deletions core/Controllers/MeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ namespace api.core.controllers;
/// your JWT token.
/// </summary>
/// <param name="userService">The User Service will allows managing your user's data</param>
/// <param name="jwtUtils">The JWT Utils allow to retrieve the ID from the user</param>
[Authorize]
[ApiController]
[Route("api/me")]
public class MeController(IUserService userService) : ControllerBase
public class MeController(IUserService userService, IJwtUtils jwtUtils) : ControllerBase
{
/// <summary>
/// Get the user connected to the API using the JWT token
Expand All @@ -30,8 +31,8 @@ public class MeController(IUserService userService) : ControllerBase
[HttpGet]
public IActionResult GetUser()
{
var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
var organizer = userService.GetUser(userId);
//var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers.Authorization!);
var organizer = userService.GetUser(Request.Headers.Authorization!);

return new OkObjectResult(
new Response<UserResponseDTO>
Expand All @@ -48,7 +49,7 @@ public IActionResult GetUser()
[HttpPatch]
public IActionResult UpdateUser([FromBody] UserUpdateDTO user)
{
var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
return userService.UpdateUser(userId, user) ? Ok() : BadRequest();
}

Expand All @@ -60,7 +61,7 @@ public IActionResult UpdateUser([FromBody] UserUpdateDTO user)
[HttpPatch("avatar")]
public IActionResult UpdateUserAvatar([FromForm] UserAvatarUpdateDTO avatarReq)
{
var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
var url = userService.UpdateUserAvatar(userId, avatarReq.avatarFile);

return new OkObjectResult(
Expand Down
5 changes: 3 additions & 2 deletions core/Controllers/ModeratorEventsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ namespace api.core.Controllers;
public class ModeratorEventsController(
ILogger<ModeratorEventsController> logger,
IEventService eventService,
IUserService userService) : ControllerBase
IUserService userService,
IJwtUtils jwtUtils) : ControllerBase
{
/// <summary>
/// Update the state of an event. This is used for a moderator that needs to
Expand All @@ -41,7 +42,7 @@ public class ModeratorEventsController(
[HttpPatch("{id}/state")]
public IActionResult UpdateEventState(Guid id, [FromQuery] State newState, [FromQuery] string? reason)
{
var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
return eventService.UpdateEventState(userId, id, newState, reason) ? Ok() : BadRequest();
}

Expand Down
10 changes: 5 additions & 5 deletions core/Controllers/OrganizerEventsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace api.core.Controllers;
[ApiController]
[Authorize(Policy = AuthPolicies.OrganizerIsActive)]
[Route("api/organizer-events")]
public class OrganizerEventsController(ILogger<OrganizerEventsController> logger, IEventService eventService) : ControllerBase
public class OrganizerEventsController(ILogger<OrganizerEventsController> logger, IEventService eventService, IJwtUtils jwtUtils) : ControllerBase
{
/// <summary>
/// Fetch events for the currently connected organizer
Expand All @@ -46,7 +46,7 @@ public IActionResult MyEvents(
[FromQuery] State state = State.All
)
{
var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);

logger.LogInformation("Getting events");
var validFilter = new PaginationRequest(pagination.PageNumber, pagination.PageSize);
Expand All @@ -72,7 +72,7 @@ public IActionResult AddEvent([FromForm] EventCreationRequestDTO dto)
{
logger.LogInformation($"Adding new event");

var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
var evnt = eventService.AddEvent(userId, dto);

return new OkObjectResult(
Expand All @@ -85,15 +85,15 @@ public IActionResult AddEvent([FromForm] EventCreationRequestDTO dto)
[HttpDelete("{id}")]
public IActionResult DeleteEvent(Guid id)
{
var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
var isDeleted = eventService.DeleteEvent(userId, id);
return isDeleted ? Ok() : BadRequest();
}

[HttpPatch("{id}")]
public IActionResult UpdateEvent(Guid id, [FromForm] EventUpdateRequestDTO dto)
{
var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!);
return eventService.UpdateEvent(userId, id, dto) ? Ok() : BadRequest();
}
}
61 changes: 30 additions & 31 deletions core/Controllers/OrganizersController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using api.core.data.entities;
using api.core.Data;
using api.core.Data.Entities;
using api.core.Data.Enums;
using api.core.Data.Exceptions;
using api.core.Data.requests;
Expand All @@ -26,15 +27,13 @@ namespace api.core.controllers;
/// for this to work.
/// </summary>
/// <param name="userService">Used to fetch and manage the organizers</param>
/// <param name="authService">Used to create a new user in the Supabase database</param>
/// <param name="emailService">Used to send an email to the newly created organizer</param>
/// <param name="configuration">Used to fetch the FRONTEND_BASE_URL from the environments variables</param>
[Authorize(Policy = AuthPolicies.IsModerator)]
[ApiController]
[Route("api/organizers")]
public class ModeratorUserController(
IUserService userService,
IAuthService authService,
IEmailService emailService,
IConfiguration configuration) : ControllerBase
{
Expand All @@ -49,15 +48,15 @@ public class ModeratorUserController(
/// <exception cref="NotFoundException{Organizer}"></exception>
[AllowAnonymous]
[HttpGet("{organizerId}")]
public IActionResult GetOrganizer(Guid organizerId)
public IActionResult GetOrganizer(string organizerId)
{
var user = userService.GetUser(organizerId);
return user.Type == "Organizer" ?
Ok(new Response<UserResponseDTO>
{
Data = user
})
: throw new NotFoundException<Organizer>();
: throw new NotFoundException<User>();
}

/// <summary>
Expand All @@ -68,32 +67,32 @@ public IActionResult GetOrganizer(Guid organizerId)
/// <param name="organizer"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
[HttpPost]
public async Task<IActionResult> CreateOrganizer([FromBody] UserCreateDTO organizer)
{
var strongPassword = GenerateRandomPassword(12);
var supabaseUser = authService.SignUp(organizer.Email, strongPassword);
_ = Guid.TryParse(supabaseUser, out Guid userId);
var created = userService.AddOrganizer(userId, organizer);
var frontBaseUrl = configuration.GetValue<string>("FRONTEND_BASE_URL") ?? throw new ArgumentNullException("FRONTEND_BASE_URL is not set");
await emailService.SendEmailAsync(
organizer.Email,
"Votre compte Hello!",
new UserCreationModel
{
Title = "Création de votre compte Hello!",
Salutation = $"Bonjour {organizer.Organization},",
AccountCreatedText = "Votre compte Hello a été créé!",
TemporaryPasswordHeader = "Votre mot de passe temporaire est: ",
TemporaryPassword = strongPassword,
LoginButtonText = "Se connecter",
ButtonLink = new Uri($"{frontBaseUrl}/fr/login")
},
emails.EmailsUtils.UserCreationTemplate
);

return Ok(new Response<UserResponseDTO> { Data = created });
}
//[HttpPost]
//public async Task<IActionResult> CreateOrganizer([FromBody] UserCreateDTO organizer)
//{
// var strongPassword = GenerateRandomPassword(12);
// var supabaseUser = authService.SignUp(organizer.Email, strongPassword);
// _ = Guid.TryParse(supabaseUser, out Guid userId);
// var created = userService.AddOrganizer(userId, organizer);
// var frontBaseUrl = configuration.GetValue<string>("FRONTEND_BASE_URL") ?? throw new ArgumentNullException("FRONTEND_BASE_URL is not set");
// await emailService.SendEmailAsync(
// organizer.Email,
// "Votre compte Hello!",
// new UserCreationModel
// {
// Title = "Création de votre compte Hello!",
// Salutation = $"Bonjour {organizer.Organization},",
// AccountCreatedText = "Votre compte Hello a été créé!",
// TemporaryPasswordHeader = "Votre mot de passe temporaire est: ",
// TemporaryPassword = strongPassword,
// LoginButtonText = "Se connecter",
// ButtonLink = new Uri($"{frontBaseUrl}/fr/login")
// },
// emails.EmailsUtils.UserCreationTemplate
// );

// return Ok(new Response<UserResponseDTO> { Data = created });
//}

/// <summary>
/// Get all users with pagination and search term
Expand Down Expand Up @@ -131,7 +130,7 @@ public IActionResult GetUsers(string? search, OrganizerAccountActiveFilter filte
/// <param name="reason">pass a reason for the toggle active change, will be send by email</param>
/// <returns></returns>
[HttpPatch("{organizerId}/toggle")]
public async Task<IActionResult> ToggleOrganizer(Guid organizerId, [FromQuery] string? reason)
public async Task<IActionResult> ToggleOrganizer(string organizerId, [FromQuery] string? reason)
{
var success = userService.ToggleUserActiveState(organizerId);
var organizer = userService.GetUser(organizerId);
Expand Down
76 changes: 68 additions & 8 deletions core/Controllers/TestController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using api.core.Data.Requests;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

using Microsoft.AspNetCore.Mvc;

Expand All @@ -11,17 +14,74 @@
/// <param name="configuration">Used to fetch the SUPABASE_PROJECT_ID and SUPABASE_ANON_KEY from the environment variables</param>
[ApiController]
[Route("api/test")]
public class TestController(IConfiguration configuration) : ControllerBase

Check warning on line 17 in core/Controllers/TestController.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'configuration' is unread.
{

[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginRequestDTO req, CancellationToken ct)
[HttpGet]
public IActionResult Login()
{
var projectId = configuration.GetValue<string>("SUPABASE_PROJECT_ID");
var anonKey = configuration.GetValue<string>("SUPABASE_ANON_KEY");
var redirectionURL = Environment.GetEnvironmentVariable("OPENID_BASE_URL") + "authorize/?";
Dictionary<string, string> queryParameters = new()
{
["client_id"] = Environment.GetEnvironmentVariable("OPENID_CLIENT_ID"),

Check warning on line 26 in core/Controllers/TestController.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference assignment.
["response_type"] = "code",
["redirect_uri"] = "https://localhost:8081/callback/code",
["scope"] = "email,profile,openid",
["state"] = "1234"
};

return Redirect(redirectionURL + string.Join('&', queryParameters.Select(qp => qp.Key + '=' + qp.Value)));
}

[HttpGet]
[Route("/callback/code")]
public async Task<IActionResult> Reception([FromQuery] string code, [FromQuery] string? state)
{
using HttpClient client = new();
string claimUrl = Environment.GetEnvironmentVariable("OPENID_BASE_URL") + "token/";

Dictionary<string, string> body = new()
{
["grant_type"] = "authorization_code",
["redirect_uri"] = Request.Scheme + "://" + Request.Host.Value,
["code"] = code
};

string clientId = Environment.GetEnvironmentVariable("OPENID_CLIENT_ID");

Check warning on line 50 in core/Controllers/TestController.cs

View workflow job for this annotation

GitHub Actions / build

Converting null literal or possible null value to non-nullable type.
string clientSecret = Environment.GetEnvironmentVariable("OPENID_CLIENT_SECRET");

Check warning on line 51 in core/Controllers/TestController.cs

View workflow job for this annotation

GitHub Actions / build

Converting null literal or possible null value to non-nullable type.

using HttpRequestMessage request = new(HttpMethod.Post, claimUrl);

request.Content = new FormUrlEncodedContent(body);
request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}")));

HttpResponseMessage response = await client.SendAsync(request);

var client = new Supabase.Client($"https://{projectId}.supabase.co", anonKey);
var response = await client.Auth.SignInWithPassword(req.Email, req.Password);
return Ok(response);
if (! response.IsSuccessStatusCode)
{
return BadRequest();
}

string contenu = await response.Content.ReadAsStringAsync();

JsonSerializerOptions settings = new()
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};

TokenResponse token = JsonSerializer.Deserialize<TokenResponse>(contenu, settings)!;

return Ok(token);
}
}

[JsonSerializable(typeof(TokenResponse))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower)]
public record class TokenResponse
{
public string AccessToken { get; set; }

Check warning on line 82 in core/Controllers/TestController.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'AccessToken' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string TokenType { get; set; }

Check warning on line 83 in core/Controllers/TestController.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'TokenType' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string Scope { get; set; }

Check warning on line 84 in core/Controllers/TestController.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Scope' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string IdToken { get; set; }

Check warning on line 85 in core/Controllers/TestController.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'IdToken' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public int ExpiresIn { get; set; }
}
14 changes: 10 additions & 4 deletions core/Data/Entities/ActivityArea.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ public partial class ActivityArea : BaseEntity

public string NameEn { get; set; } = null!;

[InverseProperty("ActivityArea")]
public virtual ICollection<Organizer> Organizers { get; set; } = new List<Organizer>();
/// <summary>
/// Groups all Users, no matter their role
/// </summary>
[InverseProperty(nameof(User.ActivityArea))]
public virtual ICollection<User> Users { get; set; } = new List<User>();

[InverseProperty("ActivityArea")]
public virtual ICollection<Moderator> Moderators { get; set; } = new List<Moderator>();
[NotMapped]
public ICollection<User> Organizers => Users.Where(u => u.Role.HasFlag(UserRole.Organizer)).ToList();

[NotMapped]
public ICollection<User> Moderators => Users.Where(u => u.Role.HasFlag(UserRole.Moderator)).ToList();
}
Loading
Loading