Skip to content

feat(product): T3 — CRUD API controller + JWT authentication#64

Open
devin-ai-integration[bot] wants to merge 2 commits into
devin/product-t2-persistencefrom
devin/product-t3-api-auth
Open

feat(product): T3 — CRUD API controller + JWT authentication#64
devin-ai-integration[bot] wants to merge 2 commits into
devin/product-t2-persistencefrom
devin/product-t3-api-auth

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds JWT Bearer authentication and a full CRUD API controller to the Product microservice, building on the T2 persistence layer.

Auth pipeline (Program.cs):

  • Registers JwtBearer auth with TokenValidationParameters (issuer, audience, lifetime, signing key) read from appsettings.json → Jwt section.
  • UseAuthentication() + UseAuthorization() middleware wired before MapControllers().
  • /healthz remains public; all controller endpoints require a valid Bearer token via [Authorize].

ProductController — class-level [Authorize], injecting ProductDbContext:

  • GET /api/product — list all products (includes ProductCategory for ProductCategoryName)
  • GET /api/product/{id} — single product or 404
  • POST /api/product — create product (validates ProductCategoryId), returns 201 + Location
  • PUT /api/product/{id} — full update (validates ProductCategoryId) or 404
  • DELETE /api/product/{id} — remove or 404
  • GET /api/product/categories — list categories
  • POST /api/product/categories — create category, returns 201

Request DTOs: CreateProductRequest, CreateCategoryRequest (positional records).

Validated: dotnet build succeeds, /healthz → 200, /api/product without token → 401, no forbidden references.

Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/bc5a9156cad244339c8ad8d1601e6dc2
Requested by: @mbatchelor81


Open in Devin Review

- Add Microsoft.AspNetCore.Authentication.JwtBearer package
- Configure JWT Bearer auth in Program.cs (issuer, audience, signing key)
- Add Jwt section to appsettings.json
- Implement full CRUD ProductController with [Authorize]:
  GET/POST /api/product, GET/PUT/DELETE /api/product/{id}
  GET/POST /api/product/categories
- /healthz remains public (200), all other endpoints require valid JWT (401)
@mbatchelor81 mbatchelor81 self-assigned this Jun 26, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 2 additional findings in Devin Review.

Open in Devin Review

Comment thread src/Services/Product/Product.API/Controllers/ProductController.cs
Comment on lines +23 to 43
public async Task<IActionResult> GetAll()
{
// TODO: Implement — migrate logic from monolith's ProductController
return Ok(new { service = "Product", status = "scaffold" });
var products = await _db.Products
.Include(p => p.ProductCategory)
.Select(p => new ProductDto
{
Id = p.Id,
Name = p.Name,
Description = p.Description,
Icon = p.Icon,
BuyingPrice = p.BuyingPrice,
SellingPrice = p.SellingPrice,
UnitsInStock = p.UnitsInStock,
IsActive = p.IsActive,
IsDiscontinued = p.IsDiscontinued,
ProductCategoryName = p.ProductCategory.Name
})
.ToListAsync();

return Ok(products);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 GetAll loads entire product table without pagination

The GetAll() endpoint at line 23 fetches all products from the database without any pagination, filtering, or limit. As the product catalog grows, this will cause increasingly large memory allocations and slow response times. Consider adding pagination parameters (skip/take or page-based) for production readiness.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — pagination would be a good follow-up. The current implementation matches the spec for this task (verbatim from the monolith migration). Pagination can be added in a future iteration when the catalog grows.

Add FK validation for ProductCategoryId in the Update method, matching
the Create method's behavior. Returns 400 Bad Request instead of letting
a DbUpdateException bubble up as 500.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

View 2 additional findings in Devin Review.

Open in Devin Review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Product service is the only one with auth; other services remain unprotected

This PR adds [Authorize] and JWT bearer auth to the Product service, but the Identity and Customer services (Identity.API/Program.cs, Customer.API/Program.cs) have no authentication middleware or [Authorize] attributes. If these services are meant to be protected in the same way, they'll need similar changes. This may be intentional (incremental rollout), but worth confirming that unprotected services are acceptable.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional — this PR is scoped to the Product microservice only (T3). Other services will get their own auth in their respective task PRs.

Comment on lines +147 to +154
{
var entity = await _db.Products.FindAsync(id);
if (entity is null) return NotFound();

_db.Products.Remove(entity);
await _db.SaveChangesAsync();
return NoContent();
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Delete endpoint does not handle FK constraint violation for parent products

The Delete action (line 147-154) does not handle the case where the product being deleted has children (via ParentId). The DbContext configures DeleteBehavior.Restrict for the self-referencing parent-child relationship (ProductDbContext.cs:29), so the database will throw a FK constraint violation exception. This would surface as an unhandled 500 error to the caller instead of a meaningful 409 Conflict or 400 Bad Request. Consider catching DbUpdateException and returning an appropriate error response.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid edge case. The DeleteBehavior.Restrict will indeed throw a DbUpdateException if the product has children. This is a reasonable hardening improvement but goes beyond the current task spec — could be addressed in a follow-up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant