Skip to content

Commit de6dd0f

Browse files
pnwpedrojrodewig
andauthored
Various fixes to the sample app (#6)
* Various fixes to the sample app * Fix db creation command in README * README: Update filename * PR feedback --------- Co-authored-by: James Rodewig <[email protected]>
1 parent 686ae8a commit de6dd0f

22 files changed

+471
-630
lines changed

Diff for: .gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ project.lock.json
66
nupkg/
77
.env
88

9+
# Fauna
10+
.fauna-project
11+
fauna-dotnet/
12+
913
# Visual Studio Code
1014
.vscode
1115

Diff for: Dockerfile

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
22

33
COPY ./SampleApp.sln /app/SampleApp.sln
4-
COPY ./fauna-dotnet/Fauna/Fauna.csproj /app/fauna-dotnet/Fauna/Fauna.csproj
5-
COPY ./sample-app/dotnet-sample-app.csproj /app/sample-app/dotnet-sample-app.csproj
4+
COPY ./sample-app/DotNetSampleApp.csproj /app/sample-app/DotNetSampleApp.csproj
65

76
WORKDIR /app
87
RUN dotnet restore
@@ -20,4 +19,4 @@ COPY --from=build /app/publish .
2019

2120
EXPOSE 8080
2221

23-
ENTRYPOINT ["dotnet", "dotnet-sample-app.dll"]
22+
ENTRYPOINT ["dotnet", "DotNetSampleApp.dll"]

Diff for: README.md

+16-21
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ docs](https://docs.fauna.com/fauna/current/tools/shell/).
8888
```sh
8989
git clone [email protected]:fauna/dotnet-sample-app.git
9090
cd dotnet-sample-app
91-
git submodule init && git submodule update
9291
```
9392

9493
2. Log in to Fauna using the Fauna CLI:
@@ -114,7 +113,7 @@ docs](https://docs.fauna.com/fauna/current/tools/shell/).
114113
3. Use the Fauna CLI to create the `EcommerceDotnet` database:
115114

116115
```sh
117-
fauna create-database --environment='' EcommerceDotnet
116+
fauna create-database EcommerceDotnet
118117
```
119118

120119
4. Create a
@@ -188,7 +187,8 @@ docs](https://docs.fauna.com/fauna/current/tools/shell/).
188187
The app runs an HTTP API server. From the `sample-app` directory, run:
189188
190189
```sh
191-
dotnet run
190+
export $(grep -v '^#' .env | xargs) && \
191+
FAUNA_SECRET=$FAUNA_SECRET dotnet run
192192
```
193193
194194
Once started, the local server is available at http://localhost:5049.
@@ -274,28 +274,22 @@ Customer documents and related API responses:
274274
275275
Save `schema/collections.fsl`.
276276
277-
3. In `schema/functions.fsl`, add the `totalPurchaseAmt` field to the
278-
`returnCustomer` UDF's projection:
277+
3. In `sample-app/Controllers/QuerySnippets.cs`, add the `totalPurchaseAmt` field to the
278+
`CustomerResponse` method's projection:
279279
280280
```diff
281281
...
282-
function returnCustomer(customer: Customer): Any {
283-
customer {
284-
id,
285-
name,
286-
email,
287-
+ address,
288-
+ totalPurchaseAmt
289-
}
282+
customer {
283+
id,
284+
name,
285+
email,
286+
+ address,
287+
+ totalPurchaseAmt
290288
}
291289
...
292290
```
293291
294-
Save `schema/collections.fsl`.
295-
296-
297-
4. Push the updated `.fsl` files in the `schema` directory to the `EcommerceDotnet`
298-
database to stage the changes:
292+
4. Push the updated schema to the `EcommerceDotnet` database:
299293
300294
```sh
301295
fauna schema push
@@ -316,7 +310,7 @@ Customer documents and related API responses:
316310
fauna schema commit
317311
```
318312
319-
7. In `sample-app/Models/Customers.cs`, add the
313+
7. In `sample-app/Models/Customer.cs`, add the
320314
`totalPurchaseAmt` field to the `Customer` class:
321315
322316
```diff
@@ -344,15 +338,16 @@ Customer documents and related API responses:
344338
}
345339
```
346340
347-
Save `sample-app/Models/Customers.cs`.
341+
Save `sample-app/Models/Customer.cs`.
348342
349343
Customer-related endpoints use this template to project Customer
350344
document fields in responses.
351345
352346
8. Start the app server:
353347
354348
```sh
355-
dotnet run
349+
export $(grep -v '^#' .env | xargs) && \
350+
FAUNA_SECRET=$FAUNA_SECRET dotnet run
356351
```
357352

358353
If using Docker, run:

Diff for: SampleApp.sln

+1-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 17
44
VisualStudioVersion = 17.0.31903.59
55
MinimumVisualStudioVersion = 10.0.40219.1
6-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet-sample-app", "sample-app\dotnet-sample-app.csproj", "{9EB6EE06-4E63-4511-852E-48AA0A9EFB21}"
7-
EndProject
8-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fauna", "fauna-dotnet\Fauna\Fauna.csproj", "{FAAE672B-E9CD-4B7C-98DD-1CE95B1DF0AA}"
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetSampleApp", "sample-app\DotNetSampleApp.csproj", "{9EB6EE06-4E63-4511-852E-48AA0A9EFB21}"
97
EndProject
108
Global
119
GlobalSection(SolutionConfigurationPlatforms) = preSolution

Diff for: fauna-dotnet

Submodule fauna-dotnet deleted from 056009b

Diff for: sample-app/Controllers/Customers.cs

+131-43
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
using dotnet_sample_app.Models;
2-
using dotnet_sample_app.Repositories;
1+
using DotNetSampleApp.Models;
32
using Fauna;
43
using Fauna.Types;
54
using Microsoft.AspNetCore.Mvc;
65

7-
namespace dotnet_sample_app.Controllers;
6+
namespace DotNetSampleApp.Controllers;
87

98
/// <summary>
109
/// Customers controller
@@ -14,21 +13,6 @@ namespace dotnet_sample_app.Controllers;
1413
Route("/[controller]")]
1514
public class Customers(Client client) : ControllerBase
1615
{
17-
private readonly CustomerDb _customerDb = client.DataContext<CustomerDb>();
18-
19-
/// <summary>
20-
/// Creates a new Customer
21-
/// </summary>
22-
/// <returns>Customer Details</returns>
23-
[ProducesResponseType(typeof(Customer), StatusCodes.Status201Created)]
24-
[ProducesResponseType(StatusCodes.Status400BadRequest)]
25-
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
26-
[HttpPost]
27-
public async Task<IActionResult> CreateCustomer(CustomerRequest customer)
28-
{
29-
return StatusCode(StatusCodes.Status201Created, await _customerDb.Create(customer));
30-
}
31-
3216

3317
/// <summary>
3418
/// Get Customer by ID
@@ -41,9 +25,43 @@ public async Task<IActionResult> CreateCustomer(CustomerRequest customer)
4125
[HttpGet("{customerId}")]
4226
public async Task<IActionResult> GetCustomer([FromRoute] string customerId)
4327
{
44-
return Ok(await _customerDb.Get(customerId));
28+
// Get the Customer document by `id`, using the ! operator to assert that the document exists.
29+
// If the document does not exist, Fauna will throw a document_not_found error.
30+
//
31+
// Use projection to only return the fields you need.
32+
var query = Query.FQL($"""
33+
let customer: Any = Customer.byId({customerId})!
34+
{QuerySnippets.CustomerResponse()}
35+
""");
36+
37+
// Connect to fauna using the client. The query method accepts an FQL query
38+
// as a parameter and a generic type parameter representing the return type.
39+
var res = await client.QueryAsync<Customer>(query);
40+
return Ok(res.Data);
4541
}
4642

43+
/// <summary>
44+
/// Creates a new Customer
45+
/// </summary>
46+
/// <returns>Customer Details</returns>
47+
[ProducesResponseType(typeof(Customer), StatusCodes.Status201Created)]
48+
[ProducesResponseType(StatusCodes.Status400BadRequest)]
49+
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
50+
[HttpPost]
51+
public async Task<IActionResult> CreateCustomer(CustomerRequest customer)
52+
{
53+
// Create a new Customer document with the provided fields.
54+
var query = Query.FQL($"""
55+
let customer: Any = Customer.create({customer})
56+
{QuerySnippets.CustomerResponse()}
57+
""");
58+
59+
// Connect to fauna using the client. The query method accepts an FQL query
60+
// as a parameter and a generic type parameter representing the return type.
61+
var res = await client.QueryAsync<Customer>(query);
62+
return StatusCode(StatusCodes.Status201Created, res.Data);
63+
}
64+
4765
/// <summary>
4866
/// Update Customer Details
4967
/// </summary>
@@ -52,26 +70,75 @@ public async Task<IActionResult> GetCustomer([FromRoute] string customerId)
5270
[ProducesResponseType(StatusCodes.Status400BadRequest)]
5371
[ProducesResponseType(StatusCodes.Status404NotFound)]
5472
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
55-
[HttpPatch("{customerId}")]
73+
[HttpPost("{customerId}")]
5674
public async Task<IActionResult> UpdateCustomer(
5775
[FromRoute] string customerId,
5876
CustomerRequest customer)
5977
{
60-
return Ok(await _customerDb.Update(customerId, customer));
78+
// Get the Customer document by `customerId`, using the ! operator to assert that the document exists.
79+
// If the document does not exist, Fauna will throw a document_not_found error.
80+
//
81+
// All unannotated fields and fields annotated with @FaunaField will be serialized, including
82+
// those with `null` values. When an update is made to a field with a null value, that value of
83+
// that field is unset on the document. Partial updates must be made with a dedicated class,
84+
// anonymous class, or Map.
85+
//
86+
// Use projection to only return the fields you need.
87+
var query = Query.FQL($"""
88+
let customer: Any = Customer.byId({customerId})!.update({customer})
89+
{QuerySnippets.CustomerResponse()}
90+
""");
91+
92+
// Connect to fauna using the client. The query method accepts an FQL query
93+
// as a parameter and a generic type parameter representing the return type.
94+
var res = await client.QueryAsync<Customer>(query);
95+
return StatusCode(StatusCodes.Status201Created, res.Data);
6196
}
6297

6398
/// <summary>
6499
/// Return all orders for a specific customer.
65100
/// </summary>
66101
/// <param name="customerId">Customer ID</param>
102+
/// <param name="afterToken">The after token for pagination.</param>
103+
/// <param name="pageSize">A page size. Ignored if after token is provided.</param>
67104
/// <returns>List of Orders.</returns>
68105
[ProducesResponseType(typeof(Page<Order>), StatusCodes.Status200OK)]
69106
[ProducesResponseType(StatusCodes.Status404NotFound)]
70107
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
71108
[HttpGet("{customerId}/orders")]
72-
public async Task<IActionResult> GetOrdersByCustomer([FromRoute] string customerId)
109+
public async Task<IActionResult> GetOrdersByCustomer(
110+
[FromRoute] string customerId,
111+
[FromQuery] string? afterToken,
112+
[FromQuery] int pageSize = 10)
73113
{
74-
return Ok(await _customerDb.GetOrdersByCustomer(customerId));
114+
// The `afterToken` parameter contains a Fauna `after` cursor.
115+
// `after` cursors may contain special characters, such as `.` or `+`).
116+
// Make sure to URL encode the `afterToken` value to preserve these
117+
// characters in URLs.
118+
var query = !string.IsNullOrEmpty(afterToken)
119+
120+
// Paginate with the after token if it's present.
121+
? Query.FQL($"Set.paginate({afterToken})")
122+
123+
// Define an FQL query to retrieve a page of orders for a given customer.
124+
// Get the Customer document by id, using the ! operator to assert that the document exists.
125+
// If the document does not exist, Fauna will throw a document_not_found error. We then
126+
// use the Order.byCustomer index to retrieve all orders for that customer and map over
127+
// the results to return only the fields we care about.
128+
: Query.FQL(@$"""
129+
let customer: Any = Customer.byId({customerId})!
130+
Order.byCustomer(customer).pageSize({pageSize}).map((order) => {{
131+
let order: Any = order
132+
133+
// Return the order.
134+
{QuerySnippets.OrderResponse()}
135+
}})
136+
""");
137+
138+
// Connect to fauna using the client. The query method accepts an FQL query
139+
// as a parameter and a generic type parameter representing the return type.
140+
var result = await client.QueryAsync<Page<Order>>(query);
141+
return Ok(result.Data);
75142
}
76143

77144

@@ -86,7 +153,19 @@ public async Task<IActionResult> GetOrdersByCustomer([FromRoute] string customer
86153
[HttpPost("{customerId}/cart")]
87154
public async Task<IActionResult> CreateCart([FromRoute] string customerId)
88155
{
89-
return Ok(await _customerDb.GetOrCreateCart(customerId));
156+
// Call our getOrCreateCart UDF to get the customer's cart. The function
157+
// definition can be found 'server/schema/functions.fsl'.
158+
var query = Query.FQL($"""
159+
let order: Any = getOrCreateCart({customerId})
160+
161+
// Return the cart.
162+
{QuerySnippets.OrderResponse()}
163+
""");
164+
165+
// Connect to fauna using the client. The query method accepts an FQL query
166+
// as a parameter and a generic type parameter representing the return type.
167+
var res = await client.QueryAsync<Order>(query);
168+
return StatusCode(StatusCodes.Status201Created, res.Data);
90169
}
91170

92171
/// <summary>
@@ -101,7 +180,20 @@ public async Task<IActionResult> CreateCart([FromRoute] string customerId)
101180
[HttpPost("{customerId}/cart/item")]
102181
public async Task<IActionResult> AddItemToCart([FromRoute] string customerId, AddItemToCartRequest item)
103182
{
104-
return Ok(await _customerDb.AddItemToCart(customerId, item));
183+
// Call our createOrUpdateCartItem UDF to add an item to the customer's cart. The function
184+
// definition can be found 'server/schema/functions.fsl'.
185+
var query = Query.FQL($"""
186+
let req = {item}
187+
let order: Any = createOrUpdateCartItem({customerId}, req.productName, req.quantity)
188+
189+
// Return the cart as an OrderResponse object.
190+
{QuerySnippets.OrderResponse()}
191+
""");
192+
193+
// Connect to fauna using the client. The query method accepts an FQL query
194+
// as a parameter and a generic type parameter representing the return type.
195+
var res = await client.QueryAsync<Order>(query);
196+
return StatusCode(StatusCodes.Status201Created, res.Data);
105197
}
106198

107199
/// <summary>
@@ -115,22 +207,18 @@ public async Task<IActionResult> AddItemToCart([FromRoute] string customerId, Ad
115207
[HttpGet("{customerId}/cart")]
116208
public async Task<IActionResult> GetCart([FromRoute] string customerId)
117209
{
118-
return Ok(await _customerDb.GetOrCreateCart(customerId));
119-
}
120-
121-
/// <summary>
122-
/// Delete Customer
123-
/// </summary>
124-
/// <param name="customerId">Customer ID</param>
125-
/// <returns>No Content</returns>
126-
[ProducesResponseType(typeof(Cart), StatusCodes.Status200OK)]
127-
[ProducesResponseType(StatusCodes.Status400BadRequest)]
128-
[ProducesResponseType(StatusCodes.Status404NotFound)]
129-
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
130-
[HttpDelete("{customerId}")]
131-
public async Task<IActionResult> DeleteCustomer([FromRoute] string customerId)
132-
{
133-
await _customerDb.Delete(customerId);
134-
return NoContent();
210+
// Get the customer's cart by id, using the ! operator to assert that the document exists.
211+
// If the document does not exist, Fauna will throw a document_not_found error.
212+
var query = Query.FQL($"""
213+
let order: Any = Customer.byId({customerId})!.cart
214+
215+
// Return the cart as an OrderResponse object.
216+
{QuerySnippets.OrderResponse()}
217+
""");
218+
219+
// Connect to fauna using the client. The query method accepts an FQL query
220+
// as a parameter and a generic type parameter representing the return type.
221+
var res = await client.QueryAsync<Order>(query);
222+
return StatusCode(StatusCodes.Status201Created, res.Data);
135223
}
136-
}
224+
}

0 commit comments

Comments
 (0)