Skip to content

Commit

Permalink
Add lab 3 src starting point (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
DamianEdwards authored Feb 5, 2024
1 parent 6632da2 commit e2a65d6
Show file tree
Hide file tree
Showing 229 changed files with 6,888 additions and 0 deletions.
214 changes: 214 additions & 0 deletions labs/3-Add-Identity/src/Catalog.API/Apis/CatalogApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.EntityFrameworkCore;
using eShop.Catalog.API;
using eShop.Catalog.API.Model;
using eShop.Catalog.Data;

namespace Microsoft.AspNetCore.Builder;

public static class CatalogApi
{
private static readonly FileExtensionContentTypeProvider _fileContentTypeProvider = new();

public static IEndpointRouteBuilder MapCatalogApi(this IEndpointRouteBuilder app)
{
// Routes for querying catalog items.
app.MapGet("/items", GetAllItems);
app.MapGet("/items/by", GetItemsByIds);
app.MapGet("/items/{id:int}", GetItemById);
app.MapGet("/items/by/{name:minlength(1)}", GetItemsByName);
app.MapGet("/items/{catalogItemId:int}/pic", GetItemPictureById);

// Routes for resolving catalog items by type and brand.
app.MapGet("/items/type/{typeId}/brand/{brandId?}", GetItemsByBrandAndTypeId);
app.MapGet("/items/type/all/brand/{brandId:int?}", GetItemsByBrandId);
app.MapGet("/catalogtypes", async (CatalogDbContext context) => await context.CatalogTypes.OrderBy(x => x.Type).AsNoTracking().ToListAsync());
app.MapGet("/catalogbrands", async (CatalogDbContext context) => await context.CatalogBrands.OrderBy(x => x.Brand).AsNoTracking().ToListAsync());

return app;
}

public static async Task<Results<Ok<PaginatedItems<CatalogItem>>, BadRequest<string>>> GetAllItems(
[AsParameters] PaginationRequest paginationRequest,
[AsParameters] CatalogServices services)
{
var pageSize = paginationRequest.PageSize;
var pageIndex = paginationRequest.PageIndex;

var totalItems = await services.DbContext.CatalogItems
.LongCountAsync();

var itemsOnPage = await services.DbContext.CatalogItems
.OrderBy(c => c.Name)
.Skip(pageSize * pageIndex)
.Take(pageSize)
.AsNoTracking()
.ToListAsync();

ChangeUriPlaceholder(services.Options.Value, itemsOnPage);

return TypedResults.Ok(new PaginatedItems<CatalogItem>(pageIndex, pageSize, totalItems, itemsOnPage));
}

public static async Task<Ok<List<CatalogItem>>> GetItemsByIds(
[AsParameters] CatalogServices services,
int[] ids)
{
var items = await services.DbContext.CatalogItems
.Where(item => ids.Contains(item.Id))
.AsNoTracking()
.ToListAsync();

ChangeUriPlaceholder(services.Options.Value, items);

return TypedResults.Ok(items);
}

public static async Task<Results<Ok<CatalogItem>, NotFound, BadRequest<string>>> GetItemById(
[AsParameters] CatalogServices services,
int id)
{
if (id <= 0)
{
return TypedResults.BadRequest("Id is not valid.");
}

var item = await services.DbContext.CatalogItems
.Include(ci => ci.CatalogBrand)
.AsNoTracking()
.SingleOrDefaultAsync(ci => ci.Id == id);

if (item == null)
{
return TypedResults.NotFound();
}

item.PictureUri = services.Options.Value.GetPictureUrl(item.Id);

return TypedResults.Ok(item);
}

public static async Task<Ok<PaginatedItems<CatalogItem>>> GetItemsByName(
[AsParameters] PaginationRequest paginationRequest,
[AsParameters] CatalogServices services,
string name)
{
var pageSize = paginationRequest.PageSize;
var pageIndex = paginationRequest.PageIndex;

var totalItems = await services.DbContext.CatalogItems
.Where(c => c.Name.StartsWith(name))
.LongCountAsync();

var itemsOnPage = await services.DbContext.CatalogItems
.Where(c => c.Name.StartsWith(name))
.Skip(pageSize * pageIndex)
.Take(pageSize)
.AsNoTracking()
.ToListAsync();

ChangeUriPlaceholder(services.Options.Value, itemsOnPage);

return TypedResults.Ok(new PaginatedItems<CatalogItem>(pageIndex, pageSize, totalItems, itemsOnPage));
}

public static async Task<Results<NotFound, PhysicalFileHttpResult>> GetItemPictureById(CatalogDbContext context, IWebHostEnvironment environment, int catalogItemId)
{
var item = await context.CatalogItems
.AsNoTracking()
.FirstOrDefaultAsync(i => i.Id == catalogItemId);

if (item is null)
{
return TypedResults.NotFound();
}

var path = GetFullPath(environment.ContentRootPath, item.PictureFileName);

var imageFileExtension = Path.GetExtension(item.PictureFileName);
_fileContentTypeProvider.TryGetContentType(imageFileExtension, out var contentType);
var lastModified = File.GetLastWriteTimeUtc(path);

return TypedResults.PhysicalFile(path, contentType, lastModified: lastModified);
}

public static async Task<Results<BadRequest<string>, RedirectToRouteHttpResult, Ok<PaginatedItems<CatalogItem>>>> GetItemsBySemanticRelevance(
[AsParameters] PaginationRequest paginationRequest,
[AsParameters] CatalogServices services,
string text)
{
return await GetItemsByName(paginationRequest, services, text);
}

public static async Task<Ok<PaginatedItems<CatalogItem>>> GetItemsByBrandAndTypeId(
[AsParameters] PaginationRequest paginationRequest,
[AsParameters] CatalogServices services,
int typeId,
int? brandId)
{
var pageSize = paginationRequest.PageSize;
var pageIndex = paginationRequest.PageIndex;

var query = services.DbContext.CatalogItems.AsQueryable();
query = query.Where(c => c.CatalogTypeId == typeId);

if (brandId is not null)
{
query = query.Where(c => c.CatalogBrandId == brandId);
}

var totalItems = await query
.LongCountAsync();

var itemsOnPage = await query
.Skip(pageSize * pageIndex)
.Take(pageSize)
.AsNoTracking()
.ToListAsync();

ChangeUriPlaceholder(services.Options.Value, itemsOnPage);

return TypedResults.Ok(new PaginatedItems<CatalogItem>(pageIndex, pageSize, totalItems, itemsOnPage));
}

public static async Task<Ok<PaginatedItems<CatalogItem>>> GetItemsByBrandId(
[AsParameters] PaginationRequest paginationRequest,
[AsParameters] CatalogServices services,
int? brandId)
{
var pageSize = paginationRequest.PageSize;
var pageIndex = paginationRequest.PageIndex;

var query = (IQueryable<CatalogItem>)services.DbContext.CatalogItems;

if (brandId is not null)
{
query = query.Where(ci => ci.CatalogBrandId == brandId);
}

var totalItems = await query
.LongCountAsync();

var itemsOnPage = await query
.Skip(pageSize * pageIndex)
.Take(pageSize)
.AsNoTracking()
.ToListAsync();

ChangeUriPlaceholder(services.Options.Value, itemsOnPage);

return TypedResults.Ok(new PaginatedItems<CatalogItem>(pageIndex, pageSize, totalItems, itemsOnPage));
}

private static void ChangeUriPlaceholder(CatalogOptions options, List<CatalogItem> items)
{
foreach (var item in items)
{
item.PictureUri = options.GetPictureUrl(item.Id);
}
}

public static string GetFullPath(string contentRootPath, string pictureFileName) =>
Path.Combine(contentRootPath, "Pics", pictureFileName);
}
15 changes: 15 additions & 0 deletions labs/3-Add-Identity/src/Catalog.API/Catalog.API.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>eShop.Catalog.API</RootNamespace>
<UserSecretsId>d1b521ec-3411-4d39-98c6-8509466ed471</UserSecretsId>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Catalog.Data\Catalog.Data.csproj" />
<ProjectReference Include="..\eShop.ServiceDefaults\eShop.ServiceDefaults.csproj" />
</ItemGroup>

</Project>
10 changes: 10 additions & 0 deletions labs/3-Add-Identity/src/Catalog.API/Catalog.API.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@Catalog.API_HostAddress = http://localhost:5222


GET {{Catalog.API_HostAddress}}/api/v1/catalog/items

###

GET {{Catalog.API_HostAddress}}/api/v1/catalog/items/type/1/brand/2

###
18 changes: 18 additions & 0 deletions labs/3-Add-Identity/src/Catalog.API/CatalogOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Globalization;

namespace eShop.Catalog.API;

public class CatalogOptions
{
public string ApiBasePath { get; set; } = "/api/v1/catalog/";

public string? PicBaseAddress { get; set; } // Set by hosting environment

public string PicBasePathFormat { get; set; } = "items/{0}/pic/";

public string GetPictureUrl(int catalogItemId)
{
// PERF: Not ideal
return PicBaseAddress + ApiBasePath + string.Format(CultureInfo.InvariantCulture, PicBasePathFormat, catalogItemId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using eShop.Catalog.API;
using eShop.Catalog.Data;
using Microsoft.Extensions.Options;

namespace Microsoft.Extensions.Hosting;

public static class HostingExtensions
{
public static void AddApplicationServices(this IHostApplicationBuilder builder)
{
builder.AddNpgsqlDbContext<CatalogDbContext>("CatalogDB");

builder.Services.Configure<CatalogOptions>(builder.Configuration.GetSection(nameof(CatalogOptions)));
}

public static TOptions GetOptions<TOptions>(this IHost host)
where TOptions : class, new()
{
return host.Services.GetRequiredService<IOptions<TOptions>>().Value;
}
}
13 changes: 13 additions & 0 deletions labs/3-Add-Identity/src/Catalog.API/Model/CatalogServices.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.Extensions.Options;
using eShop.Catalog.Data;

namespace eShop.Catalog.API.Model;

public readonly struct CatalogServices(CatalogDbContext dbContext, IOptions<CatalogOptions> options, ILogger<CatalogServices> logger)
{
public CatalogDbContext DbContext { get; } = dbContext;

public IOptions<CatalogOptions> Options { get; } = options;

public ILogger<CatalogServices> Logger { get; } = logger;
};
12 changes: 12 additions & 0 deletions labs/3-Add-Identity/src/Catalog.API/Model/PaginatedItems.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace eShop.Catalog.API.Model;

public class PaginatedItems<TEntity>(int pageIndex, int pageSize, long count, IEnumerable<TEntity> data) where TEntity : class
{
public int PageIndex { get; } = pageIndex;

public int PageSize { get; } = pageSize;

public long Count { get; } = count;

public IEnumerable<TEntity> Data { get;} = data;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace eShop.Catalog.API.Model;

public readonly struct PaginationRequest(int pageSize = 0, int pageIndex = 0)
{
public readonly int PageSize { get; } = pageSize;

public readonly int PageIndex { get; } = pageIndex;
}
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/1.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/10.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/100.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/101.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/11.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/12.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/13.webp
Binary file not shown.
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/15.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/16.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/17.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/18.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/19.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/2.webp
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/22.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/23.webp
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/26.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/27.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/28.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/29.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/3.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/30.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/31.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/32.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/33.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/34.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/35.webp
Binary file not shown.
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/37.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/38.webp
Binary file not shown.
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/4.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/40.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/41.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/42.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/43.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/44.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/45.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/46.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/47.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/48.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/49.webp
Binary file not shown.
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/50.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/51.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/52.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/53.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/54.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/55.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/56.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/57.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/58.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/59.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/6.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/60.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/61.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/62.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/63.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/64.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/65.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/66.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/67.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/68.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/69.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/7.webp
Binary file not shown.
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/71.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/72.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/73.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/74.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/75.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/76.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/77.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/78.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/79.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/8.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/80.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/81.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/82.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/83.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/84.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/85.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/86.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/87.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/88.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/89.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/9.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/90.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/91.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/92.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/93.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/94.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/95.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/96.webp
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/97.webp
Binary file not shown.
Binary file not shown.
Binary file added labs/3-Add-Identity/src/Catalog.API/Pics/99.webp
Binary file not shown.
6 changes: 6 additions & 0 deletions labs/3-Add-Identity/src/Catalog.API/Program.Testing.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Require a public Program class to implement the
// fixture for the WebApplicationFactory in the
// integration tests. Using IVT is not sufficient
// in this case, because the accessibility of the
// `Program` type is checked.
public partial class Program { }
21 changes: 21 additions & 0 deletions labs/3-Add-Identity/src/Catalog.API/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using eShop.Catalog.API;

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();
builder.AddDefaultOpenApi();
builder.AddApplicationServices();

builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseDefaultOpenApi();

app.MapDefaultEndpoints();

app.MapGroup(app.GetOptions<CatalogOptions>().ApiBasePath)
.WithTags("Catalog API")
.MapCatalogApi();

app.Run();
12 changes: 12 additions & 0 deletions labs/3-Add-Identity/src/Catalog.API/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"profiles": {
"http": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "http://localhost:5222/",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"ConnectionStrings": {
"CatalogDB": "Host=localhost;Database=CatalogDB;Username=postgres"
},
"CatalogOptions": {

}
}
27 changes: 27 additions & 0 deletions labs/3-Add-Identity/src/Catalog.API/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"OpenApi": {
"Endpoint": {
"Name": "Catalog.API V1"
},
"Document": {
"Description": "The Catalog Microservice HTTP API. This is a Data-Driven/CRUD microservice sample",
"Title": "eShop - Catalog HTTP API",
"Version": "v1"
}
},
"ConnectionStrings": {
"EventBus": "amqp://localhost"
},
"EventBus": {
"SubscriptionClientName": "Catalog"
},
"CatalogOptions": {
"PicBasePathFormat": "items/{0}/pic/"
}
}
Loading

0 comments on commit e2a65d6

Please sign in to comment.