-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6632da2
commit e2a65d6
Showing
229 changed files
with
6,888 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
### |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
labs/3-Add-Identity/src/Catalog.API/Extensions/HostingExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
13
labs/3-Add-Identity/src/Catalog.API/Model/CatalogServices.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
12
labs/3-Add-Identity/src/Catalog.API/Model/PaginatedItems.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
8 changes: 8 additions & 0 deletions
8
labs/3-Add-Identity/src/Catalog.API/Model/PaginationRequest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
12
labs/3-Add-Identity/src/Catalog.API/Properties/launchSettings.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
labs/3-Add-Identity/src/Catalog.API/appsettings.Development.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"ConnectionStrings": { | ||
"CatalogDB": "Host=localhost;Database=CatalogDB;Username=postgres" | ||
}, | ||
"CatalogOptions": { | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/" | ||
} | ||
} |
Oops, something went wrong.