Initial commit
This commit is contained in:
37
backend/README.md
Normal file
37
backend/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# YPS Beer (backend)
|
||||
|
||||
A super-simple backend for viewing, searching and favouring Beers from the [PunkAPI](https://punkapi.com/).
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
### Requirements
|
||||
|
||||
To run in development mode, you will need:
|
||||
* [.NET 8](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
|
||||
|
||||
> NOTE: By default, the app will run with an `InMemory` database, meaning all data is lost when shutting down the server. When you restart the server, you will need to re-create your account and all login sessions will be lost.
|
||||
|
||||
|
||||
### Running locally
|
||||
To run in development mode, run:
|
||||
```sh
|
||||
dotnet run
|
||||
```
|
||||
|
||||
### Configuring
|
||||
The app should already be setup to work in development mode with the frontend. For configuration options, use `YPS.Beer/appsettings{.Development}.json`
|
||||
|
||||
## Technology
|
||||
To create this project, the following was used:
|
||||
* .NET 8
|
||||
* Identity Server
|
||||
* Entity Framework
|
||||
* NUnit & NSubstitute
|
||||
|
||||
## To improve:
|
||||
### Testing
|
||||
I would have preferred to get better integration testing in this project instead of just unit tests, given more time.
|
||||
|
||||
### Better Error Handling
|
||||
There are places where the error handling isn't the best, and instead of returning generic responses it should be returning more specific ones.
|
||||
94
backend/YPS.Beer.Tests/Controllers/BeerControllerTests.cs
Normal file
94
backend/YPS.Beer.Tests/Controllers/BeerControllerTests.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using NUnit.Framework;
|
||||
using YPS.Beer.Controllers;
|
||||
using YPS.Beer.Services;
|
||||
|
||||
namespace YPS.Beer.Tests.Controllers;
|
||||
|
||||
public class BeerControllerTests
|
||||
{
|
||||
[Test]
|
||||
public async Task GetBeer_ReturnsNotFound_WhenNoBeerFound()
|
||||
{
|
||||
var mockService = Substitute.For<IPunkService>();
|
||||
mockService.GetBeer(1).ReturnsNull();
|
||||
|
||||
var controller = new BeerController(mockService);
|
||||
|
||||
var result = await controller.GetBeer(1);
|
||||
|
||||
Assert.That(result, Is.TypeOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetBeer_ReturnsBeer_WhenBeerFound()
|
||||
{
|
||||
var mockService = Substitute.For<IPunkService>();
|
||||
mockService.GetBeer(1).Returns(new Models.Beer
|
||||
{
|
||||
Id = 1,
|
||||
Name = "Some beer!",
|
||||
});
|
||||
|
||||
var controller = new BeerController(mockService);
|
||||
|
||||
var result = await controller.GetBeer(1);
|
||||
var resultObject = result as OkObjectResult;
|
||||
var resultValue = resultObject?.Value as Models.Beer;
|
||||
|
||||
Assert.That(resultValue, Is.Not.Null);
|
||||
Assert.That(resultValue.Id, Is.EqualTo(1));
|
||||
Assert.That(resultValue.Name, Is.EqualTo("Some beer!"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SearchBeer_ReturnsEmptyArray_WhenNoBeerFound()
|
||||
{
|
||||
var mockService = Substitute.For<IPunkService>();
|
||||
mockService.FindBeers("asdf").Returns(Array.Empty<Models.Beer>());
|
||||
|
||||
var controller = new BeerController(mockService);
|
||||
|
||||
var result = await controller.SearchBeer("asdf");
|
||||
var resultObject = result as OkObjectResult;
|
||||
var resultValue = resultObject?.Value as Models.Beer[];
|
||||
|
||||
Assert.That(resultValue, Is.Not.Null);
|
||||
Assert.That(resultValue, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SearchBeer_ReturnsBeers_WhenBeersFound()
|
||||
{
|
||||
var mockService = Substitute.For<IPunkService>();
|
||||
mockService.FindBeers("asdf").Returns(new []
|
||||
{
|
||||
new Models.Beer
|
||||
{
|
||||
Id = 1,
|
||||
Name = "First beer!",
|
||||
},
|
||||
new Models.Beer
|
||||
{
|
||||
Id = 14,
|
||||
Name = "Another beer!",
|
||||
},
|
||||
new Models.Beer
|
||||
{
|
||||
Id = 45,
|
||||
Name = "IPA",
|
||||
},
|
||||
});
|
||||
|
||||
var controller = new BeerController(mockService);
|
||||
|
||||
var result = await controller.SearchBeer("asdf");
|
||||
var resultObject = result as OkObjectResult;
|
||||
var resultValue = resultObject?.Value as Models.Beer[];
|
||||
|
||||
Assert.That(resultValue, Is.Not.Null);
|
||||
Assert.That(resultValue, Has.Length.EqualTo(3));
|
||||
}
|
||||
}
|
||||
202
backend/YPS.Beer.Tests/Controllers/FavouritesControllerTests.cs
Normal file
202
backend/YPS.Beer.Tests/Controllers/FavouritesControllerTests.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSubstitute;
|
||||
using NUnit.Framework;
|
||||
using YPS.Beer.Controllers;
|
||||
using YPS.Beer.DTOs.Requests;
|
||||
using YPS.Beer.Models;
|
||||
using YPS.Beer.Services;
|
||||
|
||||
namespace YPS.Beer.Tests.Controllers;
|
||||
|
||||
public class FavouritesControllerTests
|
||||
{
|
||||
[Test]
|
||||
public async Task GetFavourites_ReturnsEmptyArray_WhenUserHasNoFavourites()
|
||||
{
|
||||
var beerService = Substitute.For<IBeerService>();
|
||||
var punkService = Substitute.For<IPunkService>();
|
||||
|
||||
beerService.GetUserById("123456").Returns(new User
|
||||
{
|
||||
Id = "123456",
|
||||
});
|
||||
|
||||
var identity = new GenericIdentity("adf");
|
||||
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "123456"));
|
||||
|
||||
var controller = new FavouritesController(beerService, punkService)
|
||||
{
|
||||
ControllerContext = new ControllerContext
|
||||
{
|
||||
HttpContext = new DefaultHttpContext
|
||||
{
|
||||
User = new ClaimsPrincipal(identity),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var result = await controller.GetFavourites();
|
||||
var resultObject = result as OkObjectResult;
|
||||
var resultValue = resultObject?.Value as IEnumerable<Models.Beer>;
|
||||
|
||||
Assert.That(resultValue, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetFavourites_ReturnsFavouriteBeers_WhenUserHasFavourites()
|
||||
{
|
||||
var beerService = Substitute.For<IBeerService>();
|
||||
var punkService = Substitute.For<IPunkService>();
|
||||
|
||||
beerService.GetUserById("123456").Returns(new User
|
||||
{
|
||||
Id = "123456",
|
||||
Favourites =
|
||||
{
|
||||
new Favourite
|
||||
{
|
||||
Id = 1,
|
||||
BeerId = 34,
|
||||
UserId = "123456",
|
||||
},
|
||||
new Favourite
|
||||
{
|
||||
Id = 2,
|
||||
BeerId = 4,
|
||||
UserId = "123456",
|
||||
},
|
||||
new Favourite
|
||||
{
|
||||
Id = 3,
|
||||
BeerId = 56,
|
||||
UserId = "654321",
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
punkService.GetBeers(Arg.Is<int[]>(ids => ids[0] == 34 && ids[1] == 4)).Returns(new List<Models.Beer>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = 34,
|
||||
Name = "This beer!",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 4,
|
||||
Name = "Another beer!",
|
||||
}
|
||||
});
|
||||
|
||||
var identity = new GenericIdentity("adf");
|
||||
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "123456"));
|
||||
|
||||
var controller = new FavouritesController(beerService, punkService)
|
||||
{
|
||||
ControllerContext = new ControllerContext
|
||||
{
|
||||
HttpContext = new DefaultHttpContext
|
||||
{
|
||||
User = new ClaimsPrincipal(identity),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var result = await controller.GetFavourites();
|
||||
var resultObject = result as OkObjectResult;
|
||||
var resultValue = resultObject?.Value as IEnumerable<Models.Beer>;
|
||||
var resultList = resultValue?.ToArray();
|
||||
|
||||
Assert.That(resultList, Is.Not.Null);
|
||||
Assert.That(resultList, Has.Length.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AddFavourite_ReturnsBadRequest_WhenUserAlreadyHasFavourite()
|
||||
{
|
||||
var beerService = Substitute.For<IBeerService>();
|
||||
var punkService = Substitute.For<IPunkService>();
|
||||
|
||||
beerService.GetUserById("123456").Returns(new User
|
||||
{
|
||||
Id = "123456",
|
||||
Favourites =
|
||||
{
|
||||
new Favourite
|
||||
{
|
||||
Id = 1,
|
||||
BeerId = 34,
|
||||
UserId = "123456",
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
var identity = new GenericIdentity("adf");
|
||||
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "123456"));
|
||||
|
||||
var controller = new FavouritesController(beerService, punkService)
|
||||
{
|
||||
ControllerContext = new ControllerContext
|
||||
{
|
||||
HttpContext = new DefaultHttpContext
|
||||
{
|
||||
User = new ClaimsPrincipal(identity),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var result = await controller.AddFavourite(new AddFavouriteRequest
|
||||
{
|
||||
BeerId = 34,
|
||||
});
|
||||
|
||||
Assert.That(result, Is.TypeOf<BadRequestResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AddFavourite_ReturnsCreated_WhenFavouriteAddedToUser()
|
||||
{
|
||||
var beerService = Substitute.For<IBeerService>();
|
||||
var punkService = Substitute.For<IPunkService>();
|
||||
|
||||
beerService.GetUserById("123456").Returns(new User
|
||||
{
|
||||
Id = "123456",
|
||||
});
|
||||
|
||||
beerService.AddFavouriteToUser("123456", Arg.Is<Favourite>(f => f.BeerId == 34)).Returns(new Favourite
|
||||
{
|
||||
Id = 1,
|
||||
UserId = "123456",
|
||||
BeerId = 34,
|
||||
});
|
||||
|
||||
|
||||
var identity = new GenericIdentity("adf");
|
||||
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "123456"));
|
||||
|
||||
var controller = new FavouritesController(beerService, punkService)
|
||||
{
|
||||
ControllerContext = new ControllerContext
|
||||
{
|
||||
HttpContext = new DefaultHttpContext
|
||||
{
|
||||
User = new ClaimsPrincipal(identity),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var result = await controller.AddFavourite(new AddFavouriteRequest
|
||||
{
|
||||
BeerId = 34,
|
||||
});
|
||||
|
||||
Assert.That(result, Is.TypeOf<CreatedResult>());
|
||||
|
||||
await beerService.Received(1).AddFavouriteToUser("123456", Arg.Is<Favourite>(f => f.BeerId == 34));
|
||||
}
|
||||
}
|
||||
26
backend/YPS.Beer.Tests/YPS.Beer.Tests.csproj
Normal file
26
backend/YPS.Beer.Tests/YPS.Beer.Tests.csproj
Normal file
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1"/>
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"/>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\YPS.Beer\YPS.Beer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
28
backend/YPS.Beer.sln
Normal file
28
backend/YPS.Beer.sln
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YPS.Beer", "YPS.Beer\YPS.Beer.csproj", "{A7EF451D-0BEE-45A1-9B4A-2932CE799B27}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YPS.Beer.Tests", "YPS.Beer.Tests\YPS.Beer.Tests.csproj", "{62B70C52-2767-4A00-8E1F-716A9BC7A490}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A7EF451D-0BEE-45A1-9B4A-2932CE799B27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A7EF451D-0BEE-45A1-9B4A-2932CE799B27}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A7EF451D-0BEE-45A1-9B4A-2932CE799B27}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A7EF451D-0BEE-45A1-9B4A-2932CE799B27}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{62B70C52-2767-4A00-8E1F-716A9BC7A490}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{62B70C52-2767-4A00-8E1F-716A9BC7A490}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{62B70C52-2767-4A00-8E1F-716A9BC7A490}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{62B70C52-2767-4A00-8E1F-716A9BC7A490}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
16
backend/YPS.Beer/Controllers/AuthController.cs
Normal file
16
backend/YPS.Beer/Controllers/AuthController.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace YPS.Beer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
[HttpGet("logout")]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
await HttpContext.SignOutAsync();
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
32
backend/YPS.Beer/Controllers/BeerController.cs
Normal file
32
backend/YPS.Beer/Controllers/BeerController.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using YPS.Beer.Services;
|
||||
|
||||
namespace YPS.Beer.Controllers;
|
||||
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
public class BeerController : ControllerBase
|
||||
{
|
||||
private readonly IPunkService _punkService;
|
||||
|
||||
public BeerController(IPunkService punkService)
|
||||
{
|
||||
_punkService = punkService;
|
||||
}
|
||||
|
||||
[HttpGet("/{id}")]
|
||||
public async Task<IActionResult> GetBeer(int id)
|
||||
{
|
||||
var beer = await _punkService.GetBeer(id);
|
||||
|
||||
return beer is null ? NotFound() : Ok(beer);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> SearchBeer(string search)
|
||||
{
|
||||
return Ok(await _punkService.FindBeers(search));
|
||||
}
|
||||
}
|
||||
61
backend/YPS.Beer/Controllers/FavouritesController.cs
Normal file
61
backend/YPS.Beer/Controllers/FavouritesController.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using YPS.Beer.DTOs.Requests;
|
||||
using YPS.Beer.Models;
|
||||
using YPS.Beer.Services;
|
||||
|
||||
namespace YPS.Beer.Controllers;
|
||||
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
public class FavouritesController : ControllerBase
|
||||
{
|
||||
private readonly IBeerService _beerService;
|
||||
private readonly IPunkService _punkService;
|
||||
|
||||
public FavouritesController(IBeerService beerService, IPunkService punkService)
|
||||
{
|
||||
_beerService = beerService;
|
||||
_punkService = punkService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetFavourites()
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
|
||||
if (userId is null)
|
||||
return NotFound();
|
||||
|
||||
var user = await _beerService.GetUserById(userId);
|
||||
|
||||
if (user is null)
|
||||
return NotFound();
|
||||
|
||||
var favourites = await _punkService.GetBeers(user.Favourites.Select(f => f.BeerId).ToArray());
|
||||
|
||||
return Ok(favourites);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> AddFavourite([FromBody] AddFavouriteRequest request)
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
|
||||
if (userId is null)
|
||||
return NotFound();
|
||||
|
||||
var favourite = await _beerService.AddFavouriteToUser(userId, new Favourite
|
||||
{
|
||||
BeerId = request.BeerId,
|
||||
UserId = userId,
|
||||
});
|
||||
|
||||
if (favourite is null)
|
||||
return BadRequest();
|
||||
|
||||
return Created();
|
||||
}
|
||||
}
|
||||
6
backend/YPS.Beer/DTOs/Requests/AddFavouriteRequest.cs
Normal file
6
backend/YPS.Beer/DTOs/Requests/AddFavouriteRequest.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace YPS.Beer.DTOs.Requests;
|
||||
|
||||
public class AddFavouriteRequest
|
||||
{
|
||||
public int BeerId { get; set; }
|
||||
}
|
||||
22
backend/YPS.Beer/Data/BeerContext.cs
Normal file
22
backend/YPS.Beer/Data/BeerContext.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using YPS.Beer.Models;
|
||||
|
||||
namespace YPS.Beer.Data;
|
||||
|
||||
public class BeerContext : IdentityDbContext<User>
|
||||
{
|
||||
public BeerContext(DbContextOptions<BeerContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
builder.Entity<User>()
|
||||
.HasMany(u => u.Favourites)
|
||||
.WithOne(f => f.User)
|
||||
.HasForeignKey("UserId");
|
||||
|
||||
base.OnModelCreating(builder);
|
||||
}
|
||||
}
|
||||
304
backend/YPS.Beer/Migrations/20231206021610_InitialCreate.Designer.cs
generated
Normal file
304
backend/YPS.Beer/Migrations/20231206021610_InitialCreate.Designer.cs
generated
Normal file
@@ -0,0 +1,304 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using YPS.Beer.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace YPS.Beer.Migrations
|
||||
{
|
||||
[DbContext(typeof(BeerContext))]
|
||||
[Migration("20231206021610_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("YPS.Beer.Models.Favourite", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BeerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Favourite");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("YPS.Beer.Models.User", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("YPS.Beer.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("YPS.Beer.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("YPS.Beer.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("YPS.Beer.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("YPS.Beer.Models.Favourite", b =>
|
||||
{
|
||||
b.HasOne("YPS.Beer.Models.User", "User")
|
||||
.WithMany("Favourites")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("YPS.Beer.Models.User", b =>
|
||||
{
|
||||
b.Navigation("Favourites");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
250
backend/YPS.Beer/Migrations/20231206021610_InitialCreate.cs
Normal file
250
backend/YPS.Beer/Migrations/20231206021610_InitialCreate.cs
Normal file
@@ -0,0 +1,250 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace YPS.Beer.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetRoles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||
NormalizedName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||
ConcurrencyStamp = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUsers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
UserName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||
NormalizedUserName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||
Email = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||
NormalizedEmail = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||
EmailConfirmed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
PasswordHash = table.Column<string>(type: "TEXT", nullable: true),
|
||||
SecurityStamp = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ConcurrencyStamp = table.Column<string>(type: "TEXT", nullable: true),
|
||||
PhoneNumber = table.Column<string>(type: "TEXT", nullable: true),
|
||||
PhoneNumberConfirmed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
TwoFactorEnabled = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
LockoutEnd = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
||||
LockoutEnabled = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
AccessFailedCount = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetRoleClaims",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
RoleId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ClaimType = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ClaimValue = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
|
||||
column: x => x.RoleId,
|
||||
principalTable: "AspNetRoles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUserClaims",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
UserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ClaimType = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ClaimValue = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUserLogins",
|
||||
columns: table => new
|
||||
{
|
||||
LoginProvider = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ProviderKey = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ProviderDisplayName = table.Column<string>(type: "TEXT", nullable: true),
|
||||
UserId = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUserRoles",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
RoleId = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
|
||||
column: x => x.RoleId,
|
||||
principalTable: "AspNetRoles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUserTokens",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
LoginProvider = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Value = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Favourite",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
UserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
BeerId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Favourite", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Favourite_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AspNetRoleClaims_RoleId",
|
||||
table: "AspNetRoleClaims",
|
||||
column: "RoleId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "RoleNameIndex",
|
||||
table: "AspNetRoles",
|
||||
column: "NormalizedName",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AspNetUserClaims_UserId",
|
||||
table: "AspNetUserClaims",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AspNetUserLogins_UserId",
|
||||
table: "AspNetUserLogins",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AspNetUserRoles_RoleId",
|
||||
table: "AspNetUserRoles",
|
||||
column: "RoleId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "EmailIndex",
|
||||
table: "AspNetUsers",
|
||||
column: "NormalizedEmail");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UserNameIndex",
|
||||
table: "AspNetUsers",
|
||||
column: "NormalizedUserName",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Favourite_UserId",
|
||||
table: "Favourite",
|
||||
column: "UserId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetRoleClaims");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUserClaims");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUserLogins");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUserRoles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUserTokens");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Favourite");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetRoles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUsers");
|
||||
}
|
||||
}
|
||||
}
|
||||
301
backend/YPS.Beer/Migrations/BeerContextModelSnapshot.cs
Normal file
301
backend/YPS.Beer/Migrations/BeerContextModelSnapshot.cs
Normal file
@@ -0,0 +1,301 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using YPS.Beer.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace YPS.Beer.Migrations
|
||||
{
|
||||
[DbContext(typeof(BeerContext))]
|
||||
partial class BeerContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("YPS.Beer.Models.Favourite", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BeerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Favourite");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("YPS.Beer.Models.User", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("YPS.Beer.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("YPS.Beer.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("YPS.Beer.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("YPS.Beer.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("YPS.Beer.Models.Favourite", b =>
|
||||
{
|
||||
b.HasOne("YPS.Beer.Models.User", "User")
|
||||
.WithMany("Favourites")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("YPS.Beer.Models.User", b =>
|
||||
{
|
||||
b.Navigation("Favourites");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
22
backend/YPS.Beer/Models/Beer.cs
Normal file
22
backend/YPS.Beer/Models/Beer.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace YPS.Beer.Models;
|
||||
|
||||
public class Beer
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = null!;
|
||||
public string Tagline { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("first_brewed")]
|
||||
public string FirstBrewed { get; set; } = null!;
|
||||
public string Description { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("image_url")]
|
||||
public string ImageUrl { get; set; } = null!;
|
||||
public float Abv { get; set; }
|
||||
public float Iby { get; set; }
|
||||
|
||||
[JsonPropertyName("food_pairing")]
|
||||
public string[] FoodPairing { get; set; } = null!;
|
||||
}
|
||||
9
backend/YPS.Beer/Models/Favourite.cs
Normal file
9
backend/YPS.Beer/Models/Favourite.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace YPS.Beer.Models;
|
||||
|
||||
public class Favourite
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string UserId { get; set; } = null!;
|
||||
public User User { get; set; } = null!;
|
||||
public int BeerId { get; set; }
|
||||
}
|
||||
8
backend/YPS.Beer/Models/User.cs
Normal file
8
backend/YPS.Beer/Models/User.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace YPS.Beer.Models;
|
||||
|
||||
public class User : IdentityUser
|
||||
{
|
||||
public ICollection<Favourite> Favourites { get; } = new List<Favourite>();
|
||||
}
|
||||
44
backend/YPS.Beer/Program.cs
Normal file
44
backend/YPS.Beer/Program.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using YPS.Beer.Data;
|
||||
using YPS.Beer.Models;
|
||||
using YPS.Beer.Services;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddHttpClient<IPunkService, PunkService>();
|
||||
builder.Services.AddScoped<IBeerService, BeerService>();
|
||||
|
||||
builder.Services.AddDbContext<BeerContext>(options => options.UseInMemoryDatabase("yps-beer"));
|
||||
|
||||
builder.Services.AddIdentityCore<User>()
|
||||
.AddEntityFrameworkStores<BeerContext>()
|
||||
.AddApiEndpoints();
|
||||
|
||||
builder.Services.AddAuthentication(IdentityConstants.ApplicationScheme)
|
||||
.AddBearerToken(IdentityConstants.BearerScheme)
|
||||
.AddApplicationCookie();
|
||||
builder.Services.AddAuthorizationBuilder();
|
||||
|
||||
builder.Services.AddCors();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
|
||||
app.UseCors(cors => cors.AllowAnyHeader().AllowAnyMethod().SetIsOriginAllowed(_ => true).AllowCredentials());
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.MapIdentityApi<User>();
|
||||
|
||||
app.Run();
|
||||
41
backend/YPS.Beer/Properties/launchSettings.json
Normal file
41
backend/YPS.Beer/Properties/launchSettings.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:21894",
|
||||
"sslPort": 44357
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5279",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7084;http://localhost:5279",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
backend/YPS.Beer/Services/BeerService.cs
Normal file
37
backend/YPS.Beer/Services/BeerService.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using YPS.Beer.Data;
|
||||
using YPS.Beer.Models;
|
||||
|
||||
namespace YPS.Beer.Services;
|
||||
|
||||
public class BeerService : IBeerService
|
||||
{
|
||||
private readonly BeerContext _context;
|
||||
|
||||
public BeerService(BeerContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<User?> GetUserById(string userId) =>
|
||||
await _context.Users
|
||||
.Include(u => u.Favourites)
|
||||
.SingleOrDefaultAsync(u => u.Id == userId);
|
||||
|
||||
public async Task<Favourite?> AddFavouriteToUser(string userId, Favourite favourite)
|
||||
{
|
||||
var user = await GetUserById(userId);
|
||||
|
||||
if (user is null)
|
||||
return null;
|
||||
|
||||
if (user.Favourites.Any(f => f.BeerId == favourite.BeerId))
|
||||
return null;
|
||||
|
||||
user.Favourites.Add(favourite);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return favourite;
|
||||
}
|
||||
}
|
||||
9
backend/YPS.Beer/Services/IBeerService.cs
Normal file
9
backend/YPS.Beer/Services/IBeerService.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using YPS.Beer.Models;
|
||||
|
||||
namespace YPS.Beer.Services;
|
||||
|
||||
public interface IBeerService
|
||||
{
|
||||
public Task<User?> GetUserById(string id);
|
||||
public Task<Favourite?> AddFavouriteToUser(string userId, Favourite favourite);
|
||||
}
|
||||
8
backend/YPS.Beer/Services/IPunkService.cs
Normal file
8
backend/YPS.Beer/Services/IPunkService.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace YPS.Beer.Services;
|
||||
|
||||
public interface IPunkService
|
||||
{
|
||||
public Task<Models.Beer?> GetBeer(int id);
|
||||
public Task<IEnumerable<Models.Beer>> GetBeers(params int[] id);
|
||||
public Task<IEnumerable<Models.Beer>> FindBeers(string search);
|
||||
}
|
||||
47
backend/YPS.Beer/Services/PunkService.cs
Normal file
47
backend/YPS.Beer/Services/PunkService.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace YPS.Beer.Services;
|
||||
|
||||
public class PunkService : IPunkService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
private const string BaseUrl = "https://api.punkapi.com/v2/";
|
||||
|
||||
public PunkService(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public async Task<Models.Beer?> GetBeer(int id)
|
||||
{
|
||||
var beer = await _httpClient.GetFromJsonAsync<Models.Beer[]>($"{BaseUrl}beers/{id}");
|
||||
|
||||
return beer?.SingleOrDefault();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Models.Beer>> GetBeers(params int[] ids)
|
||||
{
|
||||
var beers = new List<Models.Beer>();
|
||||
|
||||
foreach (var id in ids)
|
||||
{
|
||||
var beer = await GetBeer(id);
|
||||
|
||||
if (beer is not null)
|
||||
beers.Add(beer);
|
||||
}
|
||||
|
||||
return beers;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Models.Beer>> FindBeers(string search)
|
||||
{
|
||||
search = search.Replace(' ', '_');
|
||||
|
||||
var beers = await _httpClient.GetFromJsonAsync<Models.Beer[]>($"{BaseUrl}beers?beer_name={search}");
|
||||
|
||||
if (beers is null)
|
||||
throw new Exception();
|
||||
|
||||
return beers;
|
||||
}
|
||||
}
|
||||
27
backend/YPS.Beer/YPS.Beer.csproj
Normal file
27
backend/YPS.Beer/YPS.Beer.csproj
Normal file
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="YPS.Beer.Tests"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
8
backend/YPS.Beer/appsettings.Development.json
Normal file
8
backend/YPS.Beer/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
backend/YPS.Beer/appsettings.json
Normal file
9
backend/YPS.Beer/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
Reference in New Issue
Block a user