1
1
using System . ComponentModel ;
2
+ using System . ComponentModel . DataAnnotations ;
2
3
using Microsoft . AspNetCore . Http . HttpResults ;
3
4
using Microsoft . AspNetCore . Mvc ;
4
5
using Microsoft . AspNetCore . Mvc . Infrastructure ;
@@ -8,16 +9,25 @@ namespace eShop.Catalog.API;
8
9
9
10
public static class CatalogApi
10
11
{
11
- public static IEndpointRouteBuilder MapCatalogApiV1 ( this IEndpointRouteBuilder app )
12
+ public static IEndpointRouteBuilder MapCatalogApi ( this IEndpointRouteBuilder app )
12
13
{
13
- var api = app . MapGroup ( "api/catalog" ) . HasApiVersion ( 1.0 ) ;
14
+ // RouteGroupBuilder for catalog endpoints
15
+ var vApi = app . NewVersionedApi ( "Catalog" ) ;
16
+ var api = vApi . MapGroup ( "api/catalog" ) . HasApiVersion ( 1 , 0 ) . HasApiVersion ( 2 , 0 ) ;
17
+ var v1 = vApi . MapGroup ( "api/catalog" ) . HasApiVersion ( 1 , 0 ) ;
18
+ var v2 = vApi . MapGroup ( "api/catalog" ) . HasApiVersion ( 2 , 0 ) ;
14
19
15
20
// Routes for querying catalog items.
16
- api . MapGet ( "/items" , GetAllItems )
21
+ v1 . MapGet ( "/items" , GetAllItemsV1 )
17
22
. WithName ( "ListItems" )
18
23
. WithSummary ( "List catalog items" )
19
24
. WithDescription ( "Get a paginated list of items in the catalog." )
20
25
. WithTags ( "Items" ) ;
26
+ v2 . MapGet ( "/items" , GetAllItems )
27
+ . WithName ( "ListItems-V2" )
28
+ . WithSummary ( "List catalog items" )
29
+ . WithDescription ( "Get a paginated list of items in the catalog." )
30
+ . WithTags ( "Items" ) ;
21
31
api . MapGet ( "/items/by" , GetItemsByIds )
22
32
. WithName ( "BatchGetItems" )
23
33
. WithSummary ( "Batch get catalog items" )
@@ -28,7 +38,7 @@ public static IEndpointRouteBuilder MapCatalogApiV1(this IEndpointRouteBuilder a
28
38
. WithSummary ( "Get catalog item" )
29
39
. WithDescription ( "Get an item from the catalog" )
30
40
. WithTags ( "Items" ) ;
31
- api . MapGet ( "/items/by/{name:minlength(1)}" , GetItemsByName )
41
+ v1 . MapGet ( "/items/by/{name:minlength(1)}" , GetItemsByName )
32
42
. WithName ( "GetItemsByName" )
33
43
. WithSummary ( "Get catalog items by name" )
34
44
. WithDescription ( "Get a paginated list of catalog items with the specified name." )
@@ -40,19 +50,26 @@ public static IEndpointRouteBuilder MapCatalogApiV1(this IEndpointRouteBuilder a
40
50
. WithTags ( "Items" ) ;
41
51
42
52
// Routes for resolving catalog items using AI.
43
- api . MapGet ( "/items/withsemanticrelevance/{text:minlength(1)}" , GetItemsBySemanticRelevance )
53
+ v1 . MapGet ( "/items/withsemanticrelevance/{text:minlength(1)}" , GetItemsBySemanticRelevanceV1 )
44
54
. WithName ( "GetRelevantItems" )
45
55
. WithSummary ( "Search catalog for relevant items" )
46
56
. WithDescription ( "Search the catalog for items related to the specified text" )
47
57
. WithTags ( "Search" ) ;
48
58
59
+ // Routes for resolving catalog items using AI.
60
+ v2 . MapGet ( "/items/withsemanticrelevance" , GetItemsBySemanticRelevance )
61
+ . WithName ( "GetRelevantItems-V2" )
62
+ . WithSummary ( "Search catalog for relevant items" )
63
+ . WithDescription ( "Search the catalog for items related to the specified text" )
64
+ . WithTags ( "Search" ) ;
65
+
49
66
// Routes for resolving catalog items by type and brand.
50
- api . MapGet ( "/items/type/{typeId}/brand/{brandId?}" , GetItemsByBrandAndTypeId )
67
+ v1 . MapGet ( "/items/type/{typeId}/brand/{brandId?}" , GetItemsByBrandAndTypeId )
51
68
. WithName ( "GetItemsByTypeAndBrand" )
52
69
. WithSummary ( "Get catalog items by type and brand" )
53
70
. WithDescription ( "Get catalog items of the specified type and brand" )
54
71
. WithTags ( "Types" ) ;
55
- api . MapGet ( "/items/type/all/brand/{brandId:int?}" , GetItemsByBrandId )
72
+ v1 . MapGet ( "/items/type/all/brand/{brandId:int?}" , GetItemsByBrandId )
56
73
. WithName ( "GetItemsByBrand" )
57
74
. WithSummary ( "List catalog items by brand" )
58
75
. WithDescription ( "Get a list of catalog items for the specified brand" )
@@ -73,11 +90,16 @@ public static IEndpointRouteBuilder MapCatalogApiV1(this IEndpointRouteBuilder a
73
90
. WithTags ( "Brands" ) ;
74
91
75
92
// Routes for modifying catalog items.
76
- api . MapPut ( "/items" , UpdateItem )
93
+ v1 . MapPut ( "/items" , UpdateItemV1 )
77
94
. WithName ( "UpdateItem" )
78
95
. WithSummary ( "Create or replace a catalog item" )
79
96
. WithDescription ( "Create or replace a catalog item" )
80
97
. WithTags ( "Items" ) ;
98
+ v2 . MapPut ( "/items/{id:int}" , UpdateItem )
99
+ . WithName ( "UpdateItem-V2" )
100
+ . WithSummary ( "Create or replace a catalog item" )
101
+ . WithDescription ( "Create or replace a catalog item" )
102
+ . WithTags ( "Items" ) ;
81
103
api . MapPost ( "/items" , CreateItem )
82
104
. WithName ( "CreateItem" )
83
105
. WithSummary ( "Create a catalog item" )
@@ -91,17 +113,43 @@ public static IEndpointRouteBuilder MapCatalogApiV1(this IEndpointRouteBuilder a
91
113
}
92
114
93
115
[ ProducesResponseType < ProblemDetails > ( StatusCodes . Status400BadRequest , "application/problem+json" ) ]
94
- public static async Task < Ok < PaginatedItems < CatalogItem > > > GetAllItems (
116
+ public static async Task < Ok < PaginatedItems < CatalogItem > > > GetAllItemsV1 (
95
117
[ AsParameters ] PaginationRequest paginationRequest ,
96
118
[ AsParameters ] CatalogServices services )
119
+ {
120
+ return await GetAllItems ( paginationRequest , services , null , null , null ) ;
121
+ }
122
+
123
+ [ ProducesResponseType < ProblemDetails > ( StatusCodes . Status400BadRequest , "application/problem+json" ) ]
124
+ public static async Task < Ok < PaginatedItems < CatalogItem > > > GetAllItems (
125
+ [ AsParameters ] PaginationRequest paginationRequest ,
126
+ [ AsParameters ] CatalogServices services ,
127
+ [ Description ( "The name of the item to return" ) ] string name ,
128
+ [ Description ( "The type of items to return" ) ] int ? type ,
129
+ [ Description ( "The brand of items to return" ) ] int ? brand )
97
130
{
98
131
var pageSize = paginationRequest . PageSize ;
99
132
var pageIndex = paginationRequest . PageIndex ;
100
133
101
- var totalItems = await services . Context . CatalogItems
134
+ var root = ( IQueryable < CatalogItem > ) services . Context . CatalogItems ;
135
+
136
+ if ( name is not null )
137
+ {
138
+ root = root . Where ( c => c . Name . StartsWith ( name ) ) ;
139
+ }
140
+ if ( type is not null )
141
+ {
142
+ root = root . Where ( c => c . CatalogTypeId == type ) ;
143
+ }
144
+ if ( brand is not null )
145
+ {
146
+ root = root . Where ( c => c . CatalogBrandId == brand ) ;
147
+ }
148
+
149
+ var totalItems = await root
102
150
. LongCountAsync ( ) ;
103
151
104
- var itemsOnPage = await services . Context . CatalogItems
152
+ var itemsOnPage = await root
105
153
. OrderBy ( c => c . Name )
106
154
. Skip ( pageSize * pageIndex )
107
155
. Take ( pageSize )
@@ -148,20 +196,7 @@ public static async Task<Ok<PaginatedItems<CatalogItem>>> GetItemsByName(
148
196
[ AsParameters ] CatalogServices services ,
149
197
[ Description ( "The name of the item to return" ) ] string name )
150
198
{
151
- var pageSize = paginationRequest . PageSize ;
152
- var pageIndex = paginationRequest . PageIndex ;
153
-
154
- var totalItems = await services . Context . CatalogItems
155
- . Where ( c => c . Name . StartsWith ( name ) )
156
- . LongCountAsync ( ) ;
157
-
158
- var itemsOnPage = await services . Context . CatalogItems
159
- . Where ( c => c . Name . StartsWith ( name ) )
160
- . Skip ( pageSize * pageIndex )
161
- . Take ( pageSize )
162
- . ToListAsync ( ) ;
163
-
164
- return TypedResults . Ok ( new PaginatedItems < CatalogItem > ( pageIndex , pageSize , totalItems , itemsOnPage ) ) ;
199
+ return await GetAllItems ( paginationRequest , services , name , null , null ) ;
165
200
}
166
201
167
202
[ ProducesResponseType < byte [ ] > ( StatusCodes . Status200OK , "application/octet-stream" ,
@@ -189,10 +224,20 @@ public static async Task<Results<PhysicalFileHttpResult,NotFound>> GetItemPictur
189
224
}
190
225
191
226
[ ProducesResponseType < ProblemDetails > ( StatusCodes . Status400BadRequest , "application/problem+json" ) ]
192
- public static async Task < Results < Ok < PaginatedItems < CatalogItem > > , RedirectToRouteHttpResult > > GetItemsBySemanticRelevance (
227
+ public static async Task < Results < Ok < PaginatedItems < CatalogItem > > , RedirectToRouteHttpResult > > GetItemsBySemanticRelevanceV1 (
193
228
[ AsParameters ] PaginationRequest paginationRequest ,
194
229
[ AsParameters ] CatalogServices services ,
195
230
[ Description ( "The text string to use when search for related items in the catalog" ) ] string text )
231
+
232
+ {
233
+ return await GetItemsBySemanticRelevance ( paginationRequest , services , text ) ;
234
+ }
235
+
236
+ [ ProducesResponseType < ProblemDetails > ( StatusCodes . Status400BadRequest , "application/problem+json" ) ]
237
+ public static async Task < Results < Ok < PaginatedItems < CatalogItem > > , RedirectToRouteHttpResult > > GetItemsBySemanticRelevance (
238
+ [ AsParameters ] PaginationRequest paginationRequest ,
239
+ [ AsParameters ] CatalogServices services ,
240
+ [ Description ( "The text string to use when search for related items in the catalog" ) , Required , MinLength ( 1 ) ] string text )
196
241
{
197
242
var pageSize = paginationRequest . PageSize ;
198
243
var pageIndex = paginationRequest . PageIndex ;
@@ -243,25 +288,7 @@ public static async Task<Ok<PaginatedItems<CatalogItem>>> GetItemsByBrandAndType
243
288
[ Description ( "The type of items to return" ) ] int typeId ,
244
289
[ Description ( "The brand of items to return" ) ] int ? brandId )
245
290
{
246
- var pageSize = paginationRequest . PageSize ;
247
- var pageIndex = paginationRequest . PageIndex ;
248
-
249
- var root = ( IQueryable < CatalogItem > ) services . Context . CatalogItems ;
250
- root = root . Where ( c => c . CatalogTypeId == typeId ) ;
251
- if ( brandId is not null )
252
- {
253
- root = root . Where ( c => c . CatalogBrandId == brandId ) ;
254
- }
255
-
256
- var totalItems = await root
257
- . LongCountAsync ( ) ;
258
-
259
- var itemsOnPage = await root
260
- . Skip ( pageSize * pageIndex )
261
- . Take ( pageSize )
262
- . ToListAsync ( ) ;
263
-
264
- return TypedResults . Ok ( new PaginatedItems < CatalogItem > ( pageIndex , pageSize , totalItems , itemsOnPage ) ) ;
291
+ return await GetAllItems ( paginationRequest , services , null , typeId , brandId ) ;
265
292
}
266
293
267
294
[ ProducesResponseType < ProblemDetails > ( StatusCodes . Status400BadRequest , "application/problem+json" ) ]
@@ -270,38 +297,35 @@ public static async Task<Ok<PaginatedItems<CatalogItem>>> GetItemsByBrandId(
270
297
[ AsParameters ] CatalogServices services ,
271
298
[ Description ( "The brand of items to return" ) ] int ? brandId )
272
299
{
273
- var pageSize = paginationRequest . PageSize ;
274
- var pageIndex = paginationRequest . PageIndex ;
275
-
276
- var root = ( IQueryable < CatalogItem > ) services . Context . CatalogItems ;
300
+ return await GetAllItems ( paginationRequest , services , null , null , brandId ) ;
301
+ }
277
302
278
- if ( brandId is not null )
303
+ public static async Task < Results < Created , BadRequest < ProblemDetails > , NotFound < ProblemDetails > > > UpdateItemV1 (
304
+ HttpContext httpContext ,
305
+ [ AsParameters ] CatalogServices services ,
306
+ CatalogItem productToUpdate )
307
+ {
308
+ if ( productToUpdate ? . Id == null )
279
309
{
280
- root = root . Where ( ci => ci . CatalogBrandId == brandId ) ;
310
+ return TypedResults . BadRequest < ProblemDetails > ( new ( ) {
311
+ Detail = "Item id must be provided in the request body."
312
+ } ) ;
281
313
}
282
-
283
- var totalItems = await root
284
- . LongCountAsync ( ) ;
285
-
286
- var itemsOnPage = await root
287
- . Skip ( pageSize * pageIndex )
288
- . Take ( pageSize )
289
- . ToListAsync ( ) ;
290
-
291
- return TypedResults . Ok ( new PaginatedItems < CatalogItem > ( pageIndex , pageSize , totalItems , itemsOnPage ) ) ;
314
+ return await UpdateItem ( httpContext , productToUpdate . Id , services , productToUpdate ) ;
292
315
}
293
316
294
- public static async Task < Results < Created , NotFound < ProblemDetails > > > UpdateItem (
317
+ public static async Task < Results < Created , BadRequest < ProblemDetails > , NotFound < ProblemDetails > > > UpdateItem (
295
318
HttpContext httpContext ,
319
+ [ Description ( "The id of the catalog item to delete" ) ] int id ,
296
320
[ AsParameters ] CatalogServices services ,
297
321
CatalogItem productToUpdate )
298
322
{
299
- var catalogItem = await services . Context . CatalogItems . SingleOrDefaultAsync ( i => i . Id == productToUpdate . Id ) ;
323
+ var catalogItem = await services . Context . CatalogItems . SingleOrDefaultAsync ( i => i . Id == id ) ;
300
324
301
325
if ( catalogItem == null )
302
326
{
303
327
return TypedResults . NotFound < ProblemDetails > ( new ( ) {
304
- Detail = $ "Item with id { productToUpdate . Id } not found."
328
+ Detail = $ "Item with id { id } not found."
305
329
} ) ;
306
330
}
307
331
@@ -328,7 +352,7 @@ public static async Task<Results<Created, NotFound<ProblemDetails>>> UpdateItem(
328
352
{
329
353
await services . Context . SaveChangesAsync ( ) ;
330
354
}
331
- return TypedResults . Created ( $ "/api/catalog/items/{ productToUpdate . Id } ") ;
355
+ return TypedResults . Created ( $ "/api/catalog/items/{ id } ") ;
332
356
}
333
357
334
358
[ ProducesResponseType < ProblemDetails > ( StatusCodes . Status400BadRequest , "application/problem+json" ) ]
0 commit comments