initial commit
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
@@ -6,9 +6,33 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<Optimize>False</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.4" />
|
||||
<PackageReference Include="AutoMapper" Version="16.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||
<PackageReference Include="serilog.sinks.console" Version="6.1.1" />
|
||||
<PackageReference Include="serilog.sinks.file" Version="7.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.5" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.16.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="email_template.pdf">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
@CityInfo.API_HostAddress = http://localhost:5244
|
||||
|
||||
GET {{CityInfo.API_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
BIN
CityInfo.API/CityInfo.db
Normal file
BIN
CityInfo.API/CityInfo.db
Normal file
Binary file not shown.
88
CityInfo.API/Controllers/AuthenticationController.cs
Normal file
88
CityInfo.API/Controllers/AuthenticationController.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
63
CityInfo.API/Controllers/CitiesController.cs
Normal file
63
CityInfo.API/Controllers/CitiesController.cs
Normal 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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
54
CityInfo.API/Controllers/FilesController.cs
Normal file
54
CityInfo.API/Controllers/FilesController.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
183
CityInfo.API/Controllers/PointsOfInterestController.cs
Normal file
183
CityInfo.API/Controllers/PointsOfInterestController.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
82
CityInfo.API/DbContexts/CityInfoContext.cs
Normal file
82
CityInfo.API/DbContexts/CityInfoContext.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using CityInfo.API.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CityInfo.API.DbContexts
|
||||
{
|
||||
public class CityInfoContext: DbContext
|
||||
{
|
||||
public DbSet<City> Cities { get; set; }
|
||||
public DbSet<PointOfInterest> PointsOfInterest { get; set; }
|
||||
|
||||
//protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
//{
|
||||
// optionsBuilder.UseSqlite("connectionstring");
|
||||
// base.OnConfiguring(optionsBuilder);
|
||||
//}
|
||||
public CityInfoContext(DbContextOptions<CityInfoContext> options): base(options)
|
||||
{}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<City>()
|
||||
.HasData(
|
||||
new City("New York City")
|
||||
{
|
||||
Id = 1,
|
||||
Description = "The one with that big park."
|
||||
},
|
||||
new City("Antwerp")
|
||||
{
|
||||
Id = 2,
|
||||
Description = "The one with the cathedral that was never really finished."
|
||||
},
|
||||
new City("Paris")
|
||||
{
|
||||
Id = 3,
|
||||
Description = "The one with that big tower."
|
||||
});
|
||||
|
||||
modelBuilder.Entity<PointOfInterest>()
|
||||
.HasData(
|
||||
new PointOfInterest("Central Park")
|
||||
{
|
||||
Id = 1,
|
||||
CityId = 1,
|
||||
Description = "The most visited urban park in the United States."
|
||||
|
||||
},
|
||||
new PointOfInterest("Empire State Building")
|
||||
{
|
||||
Id = 2,
|
||||
CityId = 1,
|
||||
Description = "A 102-story skyscraper located in Midtown Manhattan."
|
||||
},
|
||||
new PointOfInterest("Cathedral")
|
||||
{
|
||||
Id = 3,
|
||||
CityId = 2,
|
||||
Description = "A Gothic style cathedral, conceived by architects Jan and Pieter Appelmans."
|
||||
},
|
||||
new PointOfInterest("Antwerp Central Station")
|
||||
{
|
||||
Id = 4,
|
||||
CityId = 2,
|
||||
Description = "The the finest example of railway architecture in Belgium."
|
||||
},
|
||||
new PointOfInterest("Eiffel Tower")
|
||||
{
|
||||
Id = 5,
|
||||
CityId = 3,
|
||||
Description = "A wrought iron lattice tower on the Champ de Mars, named after engineer Gustave Eiffel."
|
||||
},
|
||||
new PointOfInterest("The Louvre")
|
||||
{
|
||||
Id = 6,
|
||||
CityId = 3,
|
||||
Description = "The world's largest museum."
|
||||
}
|
||||
);
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
CityInfo.API/Entities/City.cs
Normal file
24
CityInfo.API/Entities/City.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace CityInfo.API.Entities
|
||||
{
|
||||
public class City
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public int Id { get; set; }
|
||||
[Required]
|
||||
[MaxLength(50)]
|
||||
public string Name { get; set; }
|
||||
[MaxLength(200)]
|
||||
public string? Description { get; set; }
|
||||
public ICollection<PointOfInterest> PointsOfInterest { get; set; } = new List<PointOfInterest>();
|
||||
|
||||
public City(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
25
CityInfo.API/Entities/PointOfInterest.cs
Normal file
25
CityInfo.API/Entities/PointOfInterest.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace CityInfo.API.Entities
|
||||
{
|
||||
public class PointOfInterest
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public int Id { get; set; }
|
||||
[Required]
|
||||
[MaxLength(50)]
|
||||
public string Name { get; set; }
|
||||
[MaxLength(200)]
|
||||
public string? Description { get; set; }
|
||||
[ForeignKey("CityId")]
|
||||
public City? City { get; set; }
|
||||
public int CityId { get; set; }
|
||||
public PointOfInterest(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
81
CityInfo.API/Migrations/20260310223100_CityInfoDBInitialMigration.Designer.cs
generated
Normal file
81
CityInfo.API/Migrations/20260310223100_CityInfoDBInitialMigration.Designer.cs
generated
Normal file
@@ -0,0 +1,81 @@
|
||||
// <auto-generated />
|
||||
using CityInfo.API.DbContexts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CityInfo.API.Migrations
|
||||
{
|
||||
[DbContext(typeof(CityInfoContext))]
|
||||
[Migration("20260310223100_CityInfoDBInitialMigration")]
|
||||
partial class CityInfoDBInitialMigration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.4");
|
||||
|
||||
modelBuilder.Entity("CityInfo.API.Entities.City", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Cities");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CityInfo.API.Entities.PointOfInterest", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("CityId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CityId");
|
||||
|
||||
b.ToTable("PointsOfInterest");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CityInfo.API.Entities.PointOfInterest", b =>
|
||||
{
|
||||
b.HasOne("CityInfo.API.Entities.City", "City")
|
||||
.WithMany("PointsOfInterest")
|
||||
.HasForeignKey("CityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("City");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CityInfo.API.Entities.City", b =>
|
||||
{
|
||||
b.Navigation("PointsOfInterest");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CityInfo.API.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class CityInfoDBInitialMigration : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Cities",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Name = table.Column<string>(type: "TEXT", maxLength: 50, nullable: false),
|
||||
Description = table.Column<string>(type: "TEXT", maxLength: 200, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Cities", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PointsOfInterest",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Name = table.Column<string>(type: "TEXT", maxLength: 50, nullable: false),
|
||||
CityId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PointsOfInterest", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PointsOfInterest_Cities_CityId",
|
||||
column: x => x.CityId,
|
||||
principalTable: "Cities",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PointsOfInterest_CityId",
|
||||
table: "PointsOfInterest",
|
||||
column: "CityId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "PointsOfInterest");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Cities");
|
||||
}
|
||||
}
|
||||
}
|
||||
85
CityInfo.API/Migrations/20260310223915_CityInfoDBAddPOIDescription.Designer.cs
generated
Normal file
85
CityInfo.API/Migrations/20260310223915_CityInfoDBAddPOIDescription.Designer.cs
generated
Normal file
@@ -0,0 +1,85 @@
|
||||
// <auto-generated />
|
||||
using CityInfo.API.DbContexts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CityInfo.API.Migrations
|
||||
{
|
||||
[DbContext(typeof(CityInfoContext))]
|
||||
[Migration("20260310223915_CityInfoDBAddPOIDescription")]
|
||||
partial class CityInfoDBAddPOIDescription
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.4");
|
||||
|
||||
modelBuilder.Entity("CityInfo.API.Entities.City", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Cities");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CityInfo.API.Entities.PointOfInterest", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("CityId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CityId");
|
||||
|
||||
b.ToTable("PointsOfInterest");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CityInfo.API.Entities.PointOfInterest", b =>
|
||||
{
|
||||
b.HasOne("CityInfo.API.Entities.City", "City")
|
||||
.WithMany("PointsOfInterest")
|
||||
.HasForeignKey("CityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("City");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CityInfo.API.Entities.City", b =>
|
||||
{
|
||||
b.Navigation("PointsOfInterest");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CityInfo.API.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class CityInfoDBAddPOIDescription : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Description",
|
||||
table: "PointsOfInterest",
|
||||
type: "TEXT",
|
||||
maxLength: 200,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Description",
|
||||
table: "PointsOfInterest");
|
||||
}
|
||||
}
|
||||
}
|
||||
149
CityInfo.API/Migrations/20260310224446_CityInfoInitialDataSeed.Designer.cs
generated
Normal file
149
CityInfo.API/Migrations/20260310224446_CityInfoInitialDataSeed.Designer.cs
generated
Normal file
@@ -0,0 +1,149 @@
|
||||
// <auto-generated />
|
||||
using CityInfo.API.DbContexts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CityInfo.API.Migrations
|
||||
{
|
||||
[DbContext(typeof(CityInfoContext))]
|
||||
[Migration("20260310224446_CityInfoInitialDataSeed")]
|
||||
partial class CityInfoInitialDataSeed
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.4");
|
||||
|
||||
modelBuilder.Entity("CityInfo.API.Entities.City", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Cities");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
Description = "The one with that big park.",
|
||||
Name = "New York City"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 2,
|
||||
Description = "The one with the cathedral that was never really finished.",
|
||||
Name = "Antwerp"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 3,
|
||||
Description = "The one with that big tower.",
|
||||
Name = "Paris"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CityInfo.API.Entities.PointOfInterest", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("CityId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CityId");
|
||||
|
||||
b.ToTable("PointsOfInterest");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
CityId = 1,
|
||||
Description = "The most visited urban park in the United States.",
|
||||
Name = "Central Park"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 2,
|
||||
CityId = 1,
|
||||
Description = "A 102-story skyscraper located in Midtown Manhattan.",
|
||||
Name = "Empire State Building"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 3,
|
||||
CityId = 2,
|
||||
Description = "A Gothic style cathedral, conceived by architects Jan and Pieter Appelmans.",
|
||||
Name = "Cathedral"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 4,
|
||||
CityId = 2,
|
||||
Description = "The the finest example of railway architecture in Belgium.",
|
||||
Name = "Antwerp Central Station"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 5,
|
||||
CityId = 3,
|
||||
Description = "A wrought iron lattice tower on the Champ de Mars, named after engineer Gustave Eiffel.",
|
||||
Name = "Eiffel Tower"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 6,
|
||||
CityId = 3,
|
||||
Description = "The world's largest museum.",
|
||||
Name = "The Louvre"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CityInfo.API.Entities.PointOfInterest", b =>
|
||||
{
|
||||
b.HasOne("CityInfo.API.Entities.City", "City")
|
||||
.WithMany("PointsOfInterest")
|
||||
.HasForeignKey("CityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("City");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CityInfo.API.Entities.City", b =>
|
||||
{
|
||||
b.Navigation("PointsOfInterest");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||
|
||||
namespace CityInfo.API.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class CityInfoInitialDataSeed : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.InsertData(
|
||||
table: "Cities",
|
||||
columns: new[] { "Id", "Description", "Name" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ 1, "The one with that big park.", "New York City" },
|
||||
{ 2, "The one with the cathedral that was never really finished.", "Antwerp" },
|
||||
{ 3, "The one with that big tower.", "Paris" }
|
||||
});
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "PointsOfInterest",
|
||||
columns: new[] { "Id", "CityId", "Description", "Name" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ 1, 1, "The most visited urban park in the United States.", "Central Park" },
|
||||
{ 2, 1, "A 102-story skyscraper located in Midtown Manhattan.", "Empire State Building" },
|
||||
{ 3, 2, "A Gothic style cathedral, conceived by architects Jan and Pieter Appelmans.", "Cathedral" },
|
||||
{ 4, 2, "The the finest example of railway architecture in Belgium.", "Antwerp Central Station" },
|
||||
{ 5, 3, "A wrought iron lattice tower on the Champ de Mars, named after engineer Gustave Eiffel.", "Eiffel Tower" },
|
||||
{ 6, 3, "The world's largest museum.", "The Louvre" }
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "PointsOfInterest",
|
||||
keyColumn: "Id",
|
||||
keyValue: 1);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "PointsOfInterest",
|
||||
keyColumn: "Id",
|
||||
keyValue: 2);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "PointsOfInterest",
|
||||
keyColumn: "Id",
|
||||
keyValue: 3);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "PointsOfInterest",
|
||||
keyColumn: "Id",
|
||||
keyValue: 4);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "PointsOfInterest",
|
||||
keyColumn: "Id",
|
||||
keyValue: 5);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "PointsOfInterest",
|
||||
keyColumn: "Id",
|
||||
keyValue: 6);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Cities",
|
||||
keyColumn: "Id",
|
||||
keyValue: 1);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Cities",
|
||||
keyColumn: "Id",
|
||||
keyValue: 2);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Cities",
|
||||
keyColumn: "Id",
|
||||
keyValue: 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
146
CityInfo.API/Migrations/CityInfoContextModelSnapshot.cs
Normal file
146
CityInfo.API/Migrations/CityInfoContextModelSnapshot.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
// <auto-generated />
|
||||
using CityInfo.API.DbContexts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CityInfo.API.Migrations
|
||||
{
|
||||
[DbContext(typeof(CityInfoContext))]
|
||||
partial class CityInfoContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.4");
|
||||
|
||||
modelBuilder.Entity("CityInfo.API.Entities.City", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Cities");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
Description = "The one with that big park.",
|
||||
Name = "New York City"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 2,
|
||||
Description = "The one with the cathedral that was never really finished.",
|
||||
Name = "Antwerp"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 3,
|
||||
Description = "The one with that big tower.",
|
||||
Name = "Paris"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CityInfo.API.Entities.PointOfInterest", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("CityId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CityId");
|
||||
|
||||
b.ToTable("PointsOfInterest");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
CityId = 1,
|
||||
Description = "The most visited urban park in the United States.",
|
||||
Name = "Central Park"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 2,
|
||||
CityId = 1,
|
||||
Description = "A 102-story skyscraper located in Midtown Manhattan.",
|
||||
Name = "Empire State Building"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 3,
|
||||
CityId = 2,
|
||||
Description = "A Gothic style cathedral, conceived by architects Jan and Pieter Appelmans.",
|
||||
Name = "Cathedral"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 4,
|
||||
CityId = 2,
|
||||
Description = "The the finest example of railway architecture in Belgium.",
|
||||
Name = "Antwerp Central Station"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 5,
|
||||
CityId = 3,
|
||||
Description = "A wrought iron lattice tower on the Champ de Mars, named after engineer Gustave Eiffel.",
|
||||
Name = "Eiffel Tower"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 6,
|
||||
CityId = 3,
|
||||
Description = "The world's largest museum.",
|
||||
Name = "The Louvre"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CityInfo.API.Entities.PointOfInterest", b =>
|
||||
{
|
||||
b.HasOne("CityInfo.API.Entities.City", "City")
|
||||
.WithMany("PointsOfInterest")
|
||||
.HasForeignKey("CityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("City");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CityInfo.API.Entities.City", b =>
|
||||
{
|
||||
b.Navigation("PointsOfInterest");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
77
CityInfo.API/Models/CityDataStore.cs
Normal file
77
CityInfo.API/Models/CityDataStore.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
namespace CityInfo.API.Models
|
||||
{
|
||||
public class CityDataStore
|
||||
{
|
||||
public List<CityDto> Cities { get; set; }
|
||||
|
||||
public CityDataStore()
|
||||
{
|
||||
Cities = new List<CityDto>()
|
||||
{
|
||||
new CityDto()
|
||||
{
|
||||
Id = 1,
|
||||
Name = "New York City",
|
||||
Description = "The one with that big park.",
|
||||
PointsOfInterest = new List<PointOfInterestDto>()
|
||||
{
|
||||
new PointOfInterestDto()
|
||||
{
|
||||
Id = 1,
|
||||
Name = "Central Park",
|
||||
Description = "The most visited urban park in the United States."
|
||||
},
|
||||
new PointOfInterestDto()
|
||||
{
|
||||
Id = 2,
|
||||
Name = "Empire State Building",
|
||||
Description = "A 102 Story skyscrapper located in Midtown Manathan."
|
||||
}
|
||||
}
|
||||
},
|
||||
new CityDto()
|
||||
{
|
||||
Id = 2,
|
||||
Name = "Antwerp",
|
||||
Description = "The one with the cathedral that was never really finished.",
|
||||
PointsOfInterest = new List<PointOfInterestDto>()
|
||||
{
|
||||
new PointOfInterestDto()
|
||||
{
|
||||
Id = 3,
|
||||
Name = "Antwerp Cathedral",
|
||||
Description = "a beautiful Cathedral"
|
||||
},
|
||||
new PointOfInterestDto()
|
||||
{
|
||||
Id = 4,
|
||||
Name = "Antwerp central station",
|
||||
Description = "A train station"
|
||||
}
|
||||
}
|
||||
},
|
||||
new CityDto()
|
||||
{
|
||||
Id = 3,
|
||||
Name = "Paris",
|
||||
Description = "The one with that big tower.",
|
||||
PointsOfInterest = new List<PointOfInterestDto>()
|
||||
{
|
||||
new PointOfInterestDto()
|
||||
{
|
||||
Id = 5,
|
||||
Name = "Eiffel Tower",
|
||||
Description = "A big tower."
|
||||
},
|
||||
new PointOfInterestDto()
|
||||
{
|
||||
Id = 6,
|
||||
Name = "The Louvre",
|
||||
Description = "A Museum."
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
16
CityInfo.API/Models/CityDto.cs
Normal file
16
CityInfo.API/Models/CityDto.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace CityInfo.API.Models
|
||||
{
|
||||
public class CityDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
public int NumberOfPointsOfInterest {
|
||||
get
|
||||
{
|
||||
return PointsOfInterest.Count;
|
||||
}
|
||||
}
|
||||
public ICollection<PointOfInterestDto> PointsOfInterest { get; set; } = new List<PointOfInterestDto>();
|
||||
}
|
||||
}
|
||||
9
CityInfo.API/Models/CityWithoutPointsOfInterestDto.cs
Normal file
9
CityInfo.API/Models/CityWithoutPointsOfInterestDto.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace CityInfo.API.Models
|
||||
{
|
||||
public class CityWithoutPointsOfInterestDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
}
|
||||
9
CityInfo.API/Models/PointOfInterestDto.cs
Normal file
9
CityInfo.API/Models/PointOfInterestDto.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace CityInfo.API.Models
|
||||
{
|
||||
public class PointOfInterestDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
}
|
||||
14
CityInfo.API/Models/PointOfInterestForCreationDto.cs
Normal file
14
CityInfo.API/Models/PointOfInterestForCreationDto.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace CityInfo.API.Models
|
||||
{
|
||||
public class PointOfInterestForCreationDto
|
||||
{
|
||||
[Required(ErrorMessage = "You should provide a name value.")]
|
||||
[MaxLength(50)]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(200)]
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
}
|
||||
14
CityInfo.API/Models/PointOfInterestForUpdateDto.cs
Normal file
14
CityInfo.API/Models/PointOfInterestForUpdateDto.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace CityInfo.API.Models
|
||||
{
|
||||
public class PointOfInterestForUpdateDto
|
||||
{
|
||||
[Required(ErrorMessage = "You should provide a name value.")]
|
||||
[MaxLength(50)]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(200)]
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
}
|
||||
15
CityInfo.API/Profiles/CityProfile.cs
Normal file
15
CityInfo.API/Profiles/CityProfile.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using AutoMapper;
|
||||
using CityInfo.API.Entities;
|
||||
using CityInfo.API.Models;
|
||||
|
||||
namespace CityInfo.API.Profiles
|
||||
{
|
||||
public class CityProfile: Profile
|
||||
{
|
||||
public CityProfile()
|
||||
{
|
||||
CreateMap<City, CityWithoutPointsOfInterestDto>();
|
||||
CreateMap<City, CityDto>();
|
||||
}
|
||||
}
|
||||
}
|
||||
17
CityInfo.API/Profiles/PointOfInterestProfile.cs
Normal file
17
CityInfo.API/Profiles/PointOfInterestProfile.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using AutoMapper;
|
||||
using CityInfo.API.Entities;
|
||||
using CityInfo.API.Models;
|
||||
|
||||
namespace CityInfo.API.Profiles
|
||||
{
|
||||
public class PointOfInterestProfile: Profile
|
||||
{
|
||||
public PointOfInterestProfile()
|
||||
{
|
||||
CreateMap<PointOfInterest, PointOfInterestDto>();
|
||||
CreateMap<PointOfInterestForCreationDto, PointOfInterest>();
|
||||
CreateMap<PointOfInterestForUpdateDto, PointOfInterest>();
|
||||
CreateMap<PointOfInterest, PointOfInterestForUpdateDto>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,87 @@
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
using CityInfo.API.DbContexts;
|
||||
using CityInfo.API.Models;
|
||||
using CityInfo.API.Services;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Serilog;
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo.Console()
|
||||
.WriteTo.File("logs/cityinfo.txt", rollingInterval: RollingInterval.Day)
|
||||
.CreateLogger();
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Host.UseSerilog();
|
||||
//builder.logging.clearproviders();
|
||||
//builder.logging.addconsole();
|
||||
// Add services to the container.
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddControllers((options) =>
|
||||
{
|
||||
options.ReturnHttpNotAcceptable = true;
|
||||
})
|
||||
.AddNewtonsoftJson()
|
||||
.AddXmlDataContractSerializerFormatters();
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
builder.Services.AddOpenApi();
|
||||
builder.Services.AddSingleton<FileExtensionContentTypeProvider>();
|
||||
builder.Services.AddSingleton<CityDataStore>();
|
||||
builder.Services.AddDbContext<CityInfoContext>(
|
||||
(dbContextOptins) => dbContextOptins.UseSqlite(
|
||||
builder.Configuration["ConnectionStrings:CityInfoDBConnectionString"]
|
||||
)
|
||||
);
|
||||
builder.Services.AddScoped<ICityInfoRepository, CityInfoRepository>();
|
||||
|
||||
builder.Services.AddProblemDetails();
|
||||
//builder.Services.AddProblemDetails((options) =>
|
||||
// {
|
||||
// options.CustomizeProblemDetails = ctx =>
|
||||
// {
|
||||
// ctx.ProblemDetails.Extensions.Add("additionalInfo", "Addional info example");
|
||||
// ctx.ProblemDetails.Extensions.Add("server", Environment.MachineName);
|
||||
// };
|
||||
// });
|
||||
|
||||
#if DEBUG
|
||||
builder.Services.AddTransient<IMailService, LocalMailService>();
|
||||
#else
|
||||
builder.Services.AddTransient<IMailService, CloudMailService>();
|
||||
#endif
|
||||
|
||||
builder.Services.AddAutoMapper(cfg => { }, AppDomain.CurrentDomain.GetAssemblies());
|
||||
builder.Services.AddAuthentication("Bearer")
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.TokenValidationParameters = new()
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidIssuer = builder.Configuration["Authentication:Issuer"],
|
||||
ValidAudience = builder.Configuration["Authentication:Audience"],
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(builder.Configuration["Authentication:SecretForKey"]))
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
builder.Services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("MustBeFromCharleroi", policy =>
|
||||
{
|
||||
policy.RequireAuthenticatedUser();
|
||||
policy.RequireClaim("city", "Charleroi");
|
||||
});
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler();
|
||||
}
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
@@ -21,8 +95,15 @@ if (app.Environment.IsDevelopment())
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
|
||||
app.Run();
|
||||
|
||||
101
CityInfo.API/Services/CityInfoRepository.cs
Normal file
101
CityInfo.API/Services/CityInfoRepository.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using CityInfo.API.DbContexts;
|
||||
using CityInfo.API.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CityInfo.API.Services
|
||||
{
|
||||
public class CityInfoRepository : ICityInfoRepository
|
||||
{
|
||||
private CityInfoContext _context;
|
||||
public CityInfoRepository(CityInfoContext context)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(CityInfoContext));
|
||||
}
|
||||
public async Task<IEnumerable<City>> GetCitiesAsync()
|
||||
{
|
||||
return await _context.Cities.OrderBy((city) => city.Name).ToListAsync();
|
||||
}
|
||||
public async Task<(IEnumerable<City>, PaginationMetadata)> GetCitiesAsync(string? name, string? searchQuery, int pageNumber, int pageSize)
|
||||
{
|
||||
var collection = _context.Cities as IQueryable<City>;
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
name = name.Trim();
|
||||
collection = collection.Where((city) => city.Name == name);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(searchQuery))
|
||||
{
|
||||
searchQuery = searchQuery.Trim();
|
||||
collection = collection.Where(
|
||||
(city) => (
|
||||
city.Name.Contains(searchQuery) |
|
||||
(city.Description != null && city.Description.Contains(searchQuery))
|
||||
)
|
||||
);
|
||||
}
|
||||
var totalItemCount = await collection.CountAsync();
|
||||
var paginationMetaData = new PaginationMetadata(totalItemCount, pageSize, pageNumber);
|
||||
var collectionToReturn = await collection
|
||||
.OrderBy((city) => city.Name)
|
||||
.Skip(pageSize * (pageNumber - 1))
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return (collectionToReturn, paginationMetaData);
|
||||
}
|
||||
|
||||
public async Task<City?> GetCityAsync(int cityId, bool includePointsOfinterest)
|
||||
{
|
||||
if (includePointsOfinterest)
|
||||
{
|
||||
return await _context.Cities
|
||||
.Include((city) => city.PointsOfInterest)
|
||||
.Where((city) => city.Id == cityId)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
return await _context.Cities
|
||||
.Where((city) => city.Id == cityId)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> CityExistAsync(int cityId)
|
||||
{
|
||||
return await _context.Cities.AnyAsync((city) => city.Id == cityId);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<PointOfInterest>> GetPointsOfinterestForCityAsync(int cityId)
|
||||
{
|
||||
return await _context.PointsOfInterest.Where((poi) => poi.CityId == cityId).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<PointOfInterest?> GetPointOfInterestForCityAsync(int cityId, int pointOfInterestId)
|
||||
{
|
||||
return await _context.PointsOfInterest.Where((poi) => poi.CityId == cityId && poi.Id == pointOfInterestId).FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task CreatePointOfInterestForCityAsync(int cityId, PointOfInterest pointOfInterest)
|
||||
{
|
||||
var city = await GetCityAsync(cityId, includePointsOfinterest: false);
|
||||
if (city != null)
|
||||
{
|
||||
city.PointsOfInterest.Add(pointOfInterest);
|
||||
}
|
||||
}
|
||||
public async Task<bool> SaveChangesAsync()
|
||||
{
|
||||
return (await _context.SaveChangesAsync() >= 0);
|
||||
}
|
||||
public void DeletePointOfInterest(PointOfInterest pointOfInterest)
|
||||
{
|
||||
_context.PointsOfInterest.Remove(pointOfInterest);
|
||||
}
|
||||
|
||||
public async Task<bool> CityNameMatchesCityId(string? cityName, int cityId)
|
||||
{
|
||||
return await _context.Cities.AnyAsync((city) => city.Name == cityName && city.Id == cityId);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
20
CityInfo.API/Services/CloudMailService.cs
Normal file
20
CityInfo.API/Services/CloudMailService.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace CityInfo.API.Services
|
||||
{
|
||||
public class CloudMailService : IMailService
|
||||
{
|
||||
private string _mailTo = string.Empty;
|
||||
private string _mailFrom = string.Empty;
|
||||
public CloudMailService(
|
||||
IConfiguration configuration)
|
||||
{
|
||||
_mailTo = configuration["mailsettings:mailToAddress"];
|
||||
_mailFrom = configuration["mailsettings:mailFromAddress"];
|
||||
}
|
||||
public void Send(string subject, string message)
|
||||
{
|
||||
Console.WriteLine($"Mail from {_mailFrom} to {_mailTo} with {nameof(CloudMailService)}");
|
||||
Console.WriteLine($"Subject: {subject}");
|
||||
Console.WriteLine($"Message: {message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
18
CityInfo.API/Services/ICityInfoRepository.cs
Normal file
18
CityInfo.API/Services/ICityInfoRepository.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using CityInfo.API.Entities;
|
||||
|
||||
namespace CityInfo.API.Services
|
||||
{
|
||||
public interface ICityInfoRepository
|
||||
{
|
||||
Task<IEnumerable<City>> GetCitiesAsync();
|
||||
Task<(IEnumerable<City>, PaginationMetadata)> GetCitiesAsync(string? name, string? searchQuery, int pageNumber, int pageSize);
|
||||
Task<City?> GetCityAsync(int cityId, bool includePointsOfinterest);
|
||||
Task<bool> CityExistAsync(int cityId);
|
||||
Task<IEnumerable<PointOfInterest>> GetPointsOfinterestForCityAsync(int cityId);
|
||||
Task<PointOfInterest?> GetPointOfInterestForCityAsync(int cityId, int pointOfInterestId);
|
||||
Task CreatePointOfInterestForCityAsync(int cityId, PointOfInterest pointOfInterest);
|
||||
void DeletePointOfInterest(PointOfInterest pointOfInterest);
|
||||
Task<bool> CityNameMatchesCityId(string? cityName, int cityId);
|
||||
Task<bool> SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
7
CityInfo.API/Services/IMailService.cs
Normal file
7
CityInfo.API/Services/IMailService.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace CityInfo.API.Services
|
||||
{
|
||||
public interface IMailService
|
||||
{
|
||||
public void Send(string subject, string message);
|
||||
}
|
||||
}
|
||||
21
CityInfo.API/Services/LocalMailService.cs
Normal file
21
CityInfo.API/Services/LocalMailService.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace CityInfo.API.Services
|
||||
{
|
||||
public class LocalMailService:IMailService
|
||||
{
|
||||
private string _mailTo = string.Empty;
|
||||
private string _mailFrom = string.Empty;
|
||||
public LocalMailService(
|
||||
IConfiguration configuration)
|
||||
{
|
||||
_mailTo = configuration["mailsettings:mailToAddress"];
|
||||
_mailFrom = configuration["mailsettings:mailFromAddress"];
|
||||
}
|
||||
|
||||
public void Send(string subject, string message)
|
||||
{
|
||||
Console.WriteLine($"Mail from {_mailFrom} to {_mailTo} with {nameof(LocalMailService)}");
|
||||
Console.WriteLine($"Subject: {subject}");
|
||||
Console.WriteLine($"Message: {message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
18
CityInfo.API/Services/PaginationMetadata.cs
Normal file
18
CityInfo.API/Services/PaginationMetadata.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace CityInfo.API.Services
|
||||
{
|
||||
public class PaginationMetadata
|
||||
{
|
||||
public int TotalItemCount { get; set; }
|
||||
public int TotalPageCount { get; set; }
|
||||
public int PageSize { get; set; }
|
||||
public int CurrentPage { get; set; }
|
||||
|
||||
public PaginationMetadata(int totalItemCount, int pageSize, int currentPage)
|
||||
{
|
||||
TotalItemCount = totalItemCount;
|
||||
PageSize = pageSize;
|
||||
CurrentPage = currentPage;
|
||||
TotalPageCount = (int)Math.Ceiling(totalItemCount / (double)pageSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace CityInfo.API
|
||||
{
|
||||
public class WeatherForecast
|
||||
{
|
||||
public DateOnly Date { get; set; }
|
||||
|
||||
public int TemperatureC { get; set; }
|
||||
|
||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||
|
||||
public string? Summary { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,17 @@
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"CityInfo.API.Controllers": "Information",
|
||||
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"CityInfoDBConnectionString": "Data Source=CityInfo.db"
|
||||
},
|
||||
"Authentication": {
|
||||
"SecretForKey": "RgDldLrK+p+T0JisAKdD7THnT/npmWY14vV3UUiRSVE=",
|
||||
"Issuer": "https://localhost:7289",
|
||||
"Audience": "cityinfoapi"
|
||||
}
|
||||
}
|
||||
|
||||
5
CityInfo.API/appsettings.Production.json
Normal file
5
CityInfo.API/appsettings.Production.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"mailSettings": {
|
||||
"mailToAddress": "admin@mycompany.com",
|
||||
}
|
||||
}
|
||||
@@ -5,5 +5,9 @@
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"mailSettings": {
|
||||
"mailToAddress": "developers@mycompany.com",
|
||||
"mailFromAddress": "noreply@mycompany.com"
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
|
||||
1359
CityInfo.API/email_template.pdf
Normal file
1359
CityInfo.API/email_template.pdf
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user