initial commit

This commit is contained in:
2026-03-20 23:52:10 +01:00
parent 05bea695bd
commit ce04cd8d77
38 changed files with 3006 additions and 52 deletions

View File

@@ -0,0 +1,88 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
namespace CityInfo.API.Controllers
{
[Route("api/authentication")]
[ApiController]
public class AuthenticationController : ControllerBase
{
private readonly IConfiguration _configuration;
public class AuthenticationRequestBody
{
public string? UserName { get; set; }
public string? Password { get; set; }
}
internal class CityInfoUser
{
public int UserId { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public CityInfoUser(
int userId, string userName, string firstName, string lastName, string city)
{
UserId = userId;
UserName = userName;
FirstName = firstName;
LastName = lastName;
City = city;
}
}
public AuthenticationController(IConfiguration configuration)
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
}
[HttpPost("authenticate")]
public ActionResult<string> Authenticate(AuthenticationRequestBody authenticationRequestBody)
{
var user = ValidateUserCredentials(authenticationRequestBody.UserName, authenticationRequestBody.Password);
if (user == null)
{
return Unauthorized();
}
var securityKey = new SymmetricSecurityKey(Convert.FromBase64String(_configuration["Authentication:SecretForKey"]));
var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claimsForToken = new List<Claim>
{
new Claim("sub", user.UserId.ToString()),
new Claim("given_name", user.FirstName),
new Claim("family_name", user.LastName),
new Claim("city", user.City)
};
var jwtSecurityToken = new JwtSecurityToken(
_configuration["Authentication:Issuer"],
_configuration["Authentication:Audience"],
claimsForToken,
DateTime.UtcNow,
DateTime.UtcNow.AddHours(1),
signingCredentials);
var tokenToReturn = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
return Ok(tokenToReturn);
}
private CityInfoUser ValidateUserCredentials(string? userName, string? password)
{
return new CityInfoUser(
1,
userName ?? "",
"Nathan",
"Pire",
"Charleroi");
}
}
}

View File

@@ -0,0 +1,63 @@
using AutoMapper;
using CityInfo.API.Models;
using CityInfo.API.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using System.Text.Json;
namespace CityInfo.API.Controllers
{
[ApiController]
[Authorize]
[Route("api/cities")]
public class CitiesController: ControllerBase
{
private readonly ICityInfoRepository _cityInfoRepository;
private readonly IMapper _mapper;
const int maximumPageSize = 20;
public CitiesController(
ICityInfoRepository cityInfoRepository,
IMapper mapper)
{
_cityInfoRepository = cityInfoRepository ?? throw new ArgumentNullException(nameof(cityInfoRepository));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
[HttpGet()]
public async Task<ActionResult<IEnumerable<CityWithoutPointsOfInterestDto>>> GetCities(
string? name, string? searchQuery, int pageNumber = 1, int pageSize = 10)
{
if(pageSize > maximumPageSize)
{
pageSize = maximumPageSize;
}
var (cityEntities, paginationMetaData) = await _cityInfoRepository
.GetCitiesAsync(name, searchQuery,pageNumber, pageSize);
Response.Headers.Append("X-Pagination", JsonSerializer.Serialize(paginationMetaData));
return Ok(_mapper.Map<IEnumerable<CityWithoutPointsOfInterestDto>>(cityEntities));
}
[HttpGet("{id}")]
public async Task<IActionResult> GetCity(int id, bool includePointsOfInterest = false)
{
var city = await _cityInfoRepository.GetCityAsync(id, includePointsOfInterest);
if (city == null)
{
return NotFound();
}
if (includePointsOfInterest)
{
return Ok(_mapper.Map<CityDto>(city));
}
return Ok(_mapper.Map<CityWithoutPointsOfInterestDto>(city));
}
}
}

View File

@@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.StaticFiles;
namespace CityInfo.API.Controllers
{
[Route("api/files")]
[Authorize]
[ApiController]
public class FilesController : ControllerBase
{
private readonly FileExtensionContentTypeProvider _fileExtensionContentTypeProvide;
public FilesController(
FileExtensionContentTypeProvider fileExtensionContentTypeProvider)
{
_fileExtensionContentTypeProvide = fileExtensionContentTypeProvider
?? throw new System.ArgumentNullException(
nameof(FileExtensionContentTypeProvider));
}
[HttpGet("{fileId}")]
public ActionResult GetFile(string fileId)
{
var pathToFile = "email_template.pdf";
if (!System.IO.File.Exists(pathToFile))
{
return NotFound();
}
if (!_fileExtensionContentTypeProvide
.TryGetContentType(pathToFile, out var contentType))
{
contentType = "application/octet-stream";
}
var bytes = System.IO.File.ReadAllBytes(pathToFile);
return File(bytes, contentType , Path.GetFileName(pathToFile));
}
[HttpPost]
public async Task<ActionResult> CreateFile(IFormFile file)
{
if(file.Length == 0 || file.Length > 20971520 || file.ContentType != "application/pdf")
{
return BadRequest("No file or an invalid one has been inputted.");
}
var path = Path.Combine(
Directory.GetCurrentDirectory(),
$"uploaded_file_{Guid.NewGuid()}.pdf");
using (var stream = new FileStream(path, FileMode.Create))
{
await file.CopyToAsync(stream);
}
return Ok("Your file has been uploaded successfully.");
}
}
}

View File

@@ -0,0 +1,183 @@
using AutoMapper;
using CityInfo.API.Entities;
using CityInfo.API.Models;
using CityInfo.API.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.AspNetCore.Mvc;
namespace CityInfo.API.Controllers
{
[Route("api/cities/{cityId}/pointsofinterest/", Name = "GetPointsOfInterest")]
[Authorize(Policy = "MustBeFromCharleroi")]
[ApiController]
public class PointsOfInterestController : ControllerBase
{
private readonly ILogger<PointsOfInterestController> _logger;
private readonly IMailService _mailService;
private readonly ICityInfoRepository _cityInfoRepository;
private readonly IMapper _mapper;
public PointsOfInterestController(
ILogger<PointsOfInterestController> logger,
IMailService mailService,
ICityInfoRepository cityInfoRepository,
IMapper mapper)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_mailService = mailService ?? throw new ArgumentNullException(nameof(mailService));
_cityInfoRepository = cityInfoRepository ?? throw new ArgumentNullException(nameof(cityInfoRepository));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
[HttpGet]
public async Task<ActionResult<IEnumerable<PointOfInterestDto>>> GetPointsOfInterest(int cityId)
{
//throw new Exception("pipi");
try
{
//throw new Exception("caca prout");
var cityName = User.Claims.FirstOrDefault(claim => claim.Type == "city")?.Value;
if (!await _cityInfoRepository.CityNameMatchesCityId(cityName, cityId))
{
return Forbid();
}
if(!await _cityInfoRepository.CityExistAsync(cityId))
{
_logger.LogInformation($"City with Id {cityId} wasn't found when accessing points of interest.");
return NotFound();
}
var pointsOfInterest = await _cityInfoRepository.GetPointsOfinterestForCityAsync(cityId);
return Ok(_mapper.Map<IEnumerable<PointOfInterestDto>>(pointsOfInterest));
}
catch (Exception ex)
{
_logger.LogCritical($"Exception while getting points of interest for city with id {cityId}", ex);
return StatusCode(500, "A problem happened while handling your request.");
}
}
[HttpGet("{pointOfInterestId}", Name = "GetPointOfInterest")]
public async Task<ActionResult<PointOfInterestDto>> GetPointOfInterest(int cityId, int pointOfInterestId)
{
if (!await _cityInfoRepository.CityExistAsync(cityId))
{
_logger.LogInformation($"City with Id {cityId} wasn't found when accessing point of interest.");
return NotFound();
}
var pointOfInterest = await _cityInfoRepository.GetPointOfInterestForCityAsync(cityId, pointOfInterestId);
if (pointOfInterest == null)
{
return NotFound();
}
return Ok(_mapper.Map<PointOfInterestDto>(pointOfInterest));
}
[HttpPost(Name = "CreatePointOfInterest")]
public async Task<ActionResult<PointOfInterestDto>> CreatePointOfInterest(
int cityId,
PointOfInterestForCreationDto pointOfInterest)
{
if (!await _cityInfoRepository.CityExistAsync(cityId))
{
return NotFound();
}
var finalPointOfInterest = _mapper.Map<PointOfInterest>(pointOfInterest);
await _cityInfoRepository.CreatePointOfInterestForCityAsync(cityId, finalPointOfInterest);
await _cityInfoRepository.SaveChangesAsync();
var createdPointOfInterestToReturn = _mapper.Map<PointOfInterestDto>(finalPointOfInterest);
return CreatedAtRoute("GetPointOfInterest",
new
{
cityId = cityId,
pointOfInterestId = createdPointOfInterestToReturn.Id
},
createdPointOfInterestToReturn);
}
[HttpPut("{pointOfInterestId}", Name = "UpdatePointOfInterest")]
public async Task<ActionResult> UpdatePointOfInterest(
int cityId,
int pointOfInterestId,
PointOfInterestForUpdateDto pointOfInterest)
{
if (!await _cityInfoRepository.CityExistAsync(cityId))
{
return NotFound();
}
var pointofInterestEntity = await _cityInfoRepository.GetPointOfInterestForCityAsync(cityId, pointOfInterestId);
if (pointofInterestEntity == null)
{
return NotFound();
}
_mapper.Map(pointOfInterest, pointofInterestEntity);
await _cityInfoRepository.SaveChangesAsync();
return NoContent();
}
[HttpPatch("{pointOfInterestId}", Name = "PartiallyUpdatePointOfInterest")]
public async Task<ActionResult> PartiallyUpdatePointOfInterest(
int cityId,
int pointOfInterestId,
JsonPatchDocument<PointOfInterestForUpdateDto> patchDocument)
{
if (!await _cityInfoRepository.CityExistAsync(cityId))
{
return NotFound();
}
var pointofInterestEntity = await _cityInfoRepository.GetPointOfInterestForCityAsync(cityId, pointOfInterestId);
if (pointofInterestEntity == null)
{
return NotFound();
}
var pointOfInterestToPatch = _mapper.Map<PointOfInterestForUpdateDto>(pointofInterestEntity);
patchDocument.ApplyTo(pointOfInterestToPatch, ModelState);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (!TryValidateModel(pointOfInterestToPatch))
{
return BadRequest(ModelState);
}
_mapper.Map(pointOfInterestToPatch, pointofInterestEntity);
await _cityInfoRepository.SaveChangesAsync();
return NoContent();
}
[HttpDelete("{pointOfInterestId}")]
public async Task<ActionResult> DeletePointOfInterest(
int cityId,
int pointOfInterestId)
{
if (!await _cityInfoRepository.CityExistAsync(cityId))
{
return NotFound();
}
var pointofInterestEntity = await _cityInfoRepository.GetPointOfInterestForCityAsync(cityId, pointOfInterestId);
if (pointofInterestEntity == null)
{
return NotFound();
}
_cityInfoRepository.DeletePointOfInterest(pointofInterestEntity);
await _cityInfoRepository.SaveChangesAsync();
_mailService.Send("Point of interest deleted",
$"Point of Interest {pointofInterestEntity.Name} with Id {pointofInterestEntity.Id} was deleted.");
return NoContent();
}
}
}

View File

@@ -1,26 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace CityInfo.API.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries =
[
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}