Skip to content
Draft
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
7 changes: 6 additions & 1 deletion TescEvents.sln.DotSettings.user
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<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:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=58ea2333_002Dd423_002D48cb_002D9626_002Dfb87450ca04a/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="Test1" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;TestAncestor&gt;
&lt;TestId&gt;xUnit::31A35706-A546-4343-B1AB-D43522668AF5::net6.0::Tests.UnitTest1&lt;/TestId&gt;
&lt;TestId&gt;xUnit::31A35706-A546-4343-B1AB-D43522668AF5::net6.0::Tests.UnitTests.EventTest&lt;/TestId&gt;
&lt;TestId&gt;xUnit::31A35706-A546-4343-B1AB-D43522668AF5::net6.0::Tests.UnitTests.EventValidatorTest.TestEventBasic&lt;/TestId&gt;
&lt;TestId&gt;xUnit::31A35706-A546-4343-B1AB-D43522668AF5::net6.0::Tests.UnitTests.EventValidatorTest.TestEndIsBeforeOrEqualToStart&lt;/TestId&gt;
&lt;TestId&gt;xUnit::31A35706-A546-4343-B1AB-D43522668AF5::net6.0::Tests.UnitTests.EventValidatorTest.TestEventTitleEmptyOrNull&lt;/TestId&gt;
&lt;TestId&gt;xUnit::31A35706-A546-4343-B1AB-D43522668AF5::net6.0::Tests.UnitTests.EventValidatorTest.TestEventDescriptionEmptyOrNull&lt;/TestId&gt;
&lt;TestId&gt;xUnit::31A35706-A546-4343-B1AB-D43522668AF5::net6.0::Tests.UnitTests.EventValidatorTest&lt;/TestId&gt;
&lt;/TestAncestor&gt;
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary>
85 changes: 85 additions & 0 deletions TescEvents/Controllers/AuthController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System.ComponentModel.DataAnnotations;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using AutoMapper;
using FluentValidation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using TescEvents.DTOs;
using TescEvents.DTOs.Users;
using TescEvents.Models;
using TescEvents.Repositories;
using TescEvents.Utilities;
using static BCrypt.Net.BCrypt;
using ValidationResult = FluentValidation.Results.ValidationResult;

namespace TescEvents.Controllers;

[ApiController]
[Route("/api/[controller]")]
public class AuthController : ControllerBase {
private readonly IStudentRepository studentRepository;
private readonly IValidator<Student> studentValidator;
private readonly IMapper mapper;

public AuthController(IStudentRepository studentRepository, IMapper mapper, IValidator<Student> studentValidator) {
this.studentRepository = studentRepository;
this.mapper = mapper;
this.studentValidator = studentValidator;
}

[AllowAnonymous]
[HttpPost("register", Name = nameof(RegisterUser))]
public async Task<IActionResult> RegisterUser([Required] [FromForm] UserCreateRequestDTO userReq) {
var userEntity = mapper.Map<Student>(userReq);

var validationResult = await studentValidator.ValidateAsync(userEntity);
if (!validationResult.IsValid) return BadRequest(
validationResult.Errors
.Select(error => error.ErrorMessage));

var salt = GenerateSalt();
userEntity.Salt = salt;
userEntity.PasswordHash = HashPassword(userReq.Password, salt);
studentRepository.CreateUser(userEntity);

var userResponse = mapper.Map<UserResponseDTO>(userEntity);
return CreatedAtRoute(new {
action = "GetUser",
controller = "Users",
userResponse.Id
},
userResponse);
}

[AllowAnonymous]
[HttpPost(Name = nameof(AuthenticateUser))]
public async Task<IActionResult> AuthenticateUser([FromForm] string username, [FromForm] string password) {
var user = studentRepository.GetUserByUsername(username);
if (user == null) return NotFound();

if (HashPassword(password, user.Salt) != user.PasswordHash) return Unauthorized();

var issuer = Environment.GetEnvironmentVariable("JWT_ISSUER");
var audience = Environment.GetEnvironmentVariable("JWT_AUDIENCE");
var key = Encoding.ASCII.GetBytes(Environment.GetEnvironmentVariable("JWT_KEY")!);
var tokenDescriptor = new SecurityTokenDescriptor {
Subject = new ClaimsIdentity(new[] {
new Claim(ClaimTypes.Actor, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
}),
Expires = DateTime.UtcNow.AddMinutes(AppSettings.VALID_JWT_LENGTH_DAYS),
Issuer = issuer,
Audience = audience,
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha512Signature),
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
var jwt = tokenHandler.WriteToken(token);
return Ok(jwt);
}
}
74 changes: 69 additions & 5 deletions TescEvents/Controllers/EventsController.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,92 @@
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
using AutoMapper;
using FluentValidation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TescEvents.DTOs;
using TescEvents.DTOs.Events;
using TescEvents.Models;
using TescEvents.Repositories;
using TescEvents.Services;

namespace TescEvents.Controllers;

[ApiController]
[Route("/api/[controller]")]
public class EventsController : ControllerBase {
private readonly IEventRepository eventRepository;
private readonly IEventRegistrationRepository registrationRepository;
private readonly IStudentRepository studentRepository;
private readonly IMapper mapper;
private readonly IValidator<Event> validator;
private readonly IUploadService uploadService;

public EventsController(IEventRepository eventRepository) {
public EventsController(IEventRepository eventRepository,
IEventRegistrationRepository registrationRepository,
IMapper mapper,
IValidator<Event> validator,
IStudentRepository studentRepository,
IUploadService uploadService) {
this.eventRepository = eventRepository;
this.registrationRepository = registrationRepository;
this.mapper = mapper;
this.validator = validator;
this.studentRepository = studentRepository;
this.uploadService = uploadService;
}

[HttpGet(Name = nameof(GetEvents))]
public IEnumerable<Event> GetEvents(string? start = "", string? end = "") {
public IActionResult GetEvents(string? start = "", string? end = "") {
if (!DateTime.TryParse(start, out var startFilter)) {
startFilter = DateTime.UnixEpoch;
startFilter = DateTime.Now;
}

if (!DateTime.TryParse(end, out var endFilter)) {
endFilter = DateTime.Now;
endFilter = DateTime.MaxValue;
}

return eventRepository.FindByCondition(e => e.Start >= startFilter && e.End <= endFilter);
return Ok(eventRepository
.GetAllEventsWithinRange(startFilter.ToUniversalTime(),
endFilter.ToUniversalTime()));
}

[Authorize]
[HttpPost(Name = nameof(CreateEvent))]
public async Task<IActionResult> CreateEvent([Required] [FromForm] EventCreateRequestDTO e) {
var eventEntity = mapper.Map<Event>(e);
var validationResult = await validator.ValidateAsync(eventEntity);

if (!validationResult.IsValid) return BadRequest(
validationResult.Errors.Select(error => error.ErrorMessage));

eventRepository.Create(eventEntity);
// Upload image to AWS
if (e.Thumbnail != null)
uploadService.UploadFileToPath(e.Thumbnail, "");
if (e.Cover != null)
uploadService.UploadFileToPath(e.Cover, "");
eventRepository.Save();

var eventResponse = mapper.Map<EventPublicResponseDTO>(eventEntity);
return CreatedAtRoute(nameof(CreateEvent), new { Id = eventResponse.Id }, eventResponse);
}

[Authorize]
[HttpPost("event/{eventId}/register", Name = nameof(RegisterForEvent))]
public async Task<IActionResult> RegisterForEvent(string eventId) {
var _event = eventRepository.GetEventByUuid(Guid.Parse(eventId));
if (_event == null) return NotFound();

var studentId = HttpContext.User.FindFirstValue(ClaimTypes.Actor);
if (studentId == null) return Unauthorized();
var student = studentRepository.GetUserByUuid(Guid.Parse(studentId));
if (student == null) return Unauthorized();

registrationRepository.RegisterStudentForEvent(student, _event);
// TODO: Send registration email confirmations

return Ok();
}
}
30 changes: 30 additions & 0 deletions TescEvents/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using TescEvents.DTOs.Users;
using TescEvents.Repositories;

namespace TescEvents.Controllers;

[ApiController]
[Route("/api/[controller]")]
public class UsersController : ControllerBase {
private readonly IStudentRepository studentRepository;
private readonly IMapper mapper;

public UsersController(IStudentRepository studentRepository, IMapper mapper) {
this.studentRepository = studentRepository;
this.mapper = mapper;
}

[AllowAnonymous]
[HttpGet(Name = nameof(GetUser))]
[Route("user/{uuid}")]
public async Task<IActionResult> GetUser(string uuid) {
var user = studentRepository.GetUserByUuid(Guid.Parse(uuid));
if (user == null) return NotFound();

var userResponse = mapper.Map<UserResponseDTO>(user);
return Ok(userResponse);
}
}
12 changes: 12 additions & 0 deletions TescEvents/DTOs/Events/EventCreateRequestDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;

namespace TescEvents.DTOs.Events;

public class EventCreateRequestDTO {
public string Title { get; set; }
public string Description { get; set; }
public IFormFile? Thumbnail { get; set; }
public IFormFile? Cover { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
11 changes: 11 additions & 0 deletions TescEvents/DTOs/Events/EventPublicResponseDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace TescEvents.DTOs.Events;

public class EventPublicResponseDTO {
public string Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Thumbnail { get; set; }
public string Cover { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
8 changes: 8 additions & 0 deletions TescEvents/DTOs/Users/UserCreateRequestDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace TescEvents.DTOs.Users;

public class UserCreateRequestDTO {
public string Username { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
9 changes: 9 additions & 0 deletions TescEvents/DTOs/Users/UserResponseDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace TescEvents.DTOs.Users;

public class UserResponseDTO {
public Guid Id { get; set; }
public string Username { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserType { get; set; }
}
10 changes: 7 additions & 3 deletions TescEvents/Entities/RepositoryContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ public class RepositoryContext : DbContext {
public RepositoryContext(DbContextOptions options) : base(options) { }

protected override void OnConfiguring(DbContextOptionsBuilder options) {
options.UseNpgsql(AppSettings.ConnectionString);
if (!options.IsConfigured) {
options.UseNpgsql(AppSettings.ConnectionString);
}
}

public DbSet<Event>? Events { get; set; }

public virtual DbSet<Event>? Events { get; set; }
public virtual DbSet<EventRegistration>? EventRegistrations { get; set; }
public virtual DbSet<Student>? Students { get; set; }
}
64 changes: 0 additions & 64 deletions TescEvents/Migrations/0001-AddEventsTable.Designer.cs

This file was deleted.

37 changes: 0 additions & 37 deletions TescEvents/Migrations/0001-AddEventsTable.cs

This file was deleted.

Loading