boilercraft 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +171 -0
  3. package/bin/boilercraft.js +110 -0
  4. package/cli/app.js +596 -0
  5. package/cli/ui.js +356 -0
  6. package/docs/STACK_ENGINE.md +93 -0
  7. package/docs/assets/cli-create.svg +52 -0
  8. package/docs/assets/cli-menu.svg +47 -0
  9. package/docs/stacks/dotnet-core.md +69 -0
  10. package/docs/stacks/laravel-vue.md +46 -0
  11. package/docs/stacks/nextjs.md +58 -0
  12. package/docs/stacks/node-express.md +58 -0
  13. package/docs/stacks/raw-php.md +130 -0
  14. package/main.js +82 -0
  15. package/package.json +86 -0
  16. package/public/index.html +721 -0
  17. package/public/logo.png +17 -0
  18. package/public/logo.svg +17 -0
  19. package/server.js +84 -0
  20. package/services/componentGenerator.js +195 -0
  21. package/services/engine/scaffoldEngine.js +517 -0
  22. package/services/engine/toolInstaller.js +232 -0
  23. package/services/engine/versionCatalog.js +115 -0
  24. package/services/generate.js +46 -0
  25. package/services/projectGenerator.js +686 -0
  26. package/services/readmeGenerator.js +221 -0
  27. package/services/themeGenerator.js +150 -0
  28. package/services/versionResolver.js +176 -0
  29. package/stacks/dotnet-core/overlays/api/Controllers/ProductsController.cs +33 -0
  30. package/stacks/dotnet-core/overlays/api/Program.cs +33 -0
  31. package/stacks/dotnet-core/overlays/api/wwwroot/index.html +38 -0
  32. package/stacks/dotnet-core/overlays/api-auth/Controllers/AuthController.cs +87 -0
  33. package/stacks/dotnet-core/overlays/common/Data/ApplicationDbContext.cs +25 -0
  34. package/stacks/dotnet-core/overlays/common/Models/Product.cs +19 -0
  35. package/stacks/dotnet-core/overlays/mvc/Controllers/ProductsController.cs +84 -0
  36. package/stacks/dotnet-core/overlays/mvc/Views/Home/Index.cshtml +23 -0
  37. package/stacks/dotnet-core/overlays/mvc/Views/Products/Create.cshtml +16 -0
  38. package/stacks/dotnet-core/overlays/mvc/Views/Products/Delete.cshtml +16 -0
  39. package/stacks/dotnet-core/overlays/mvc/Views/Products/Edit.cshtml +17 -0
  40. package/stacks/dotnet-core/overlays/mvc/Views/Products/Index.cshtml +46 -0
  41. package/stacks/dotnet-core/overlays/mvc/Views/Products/_Form.cshtml +19 -0
  42. package/stacks/dotnet-core/overlays/mvc-auth/Controllers/AccountController.cs +98 -0
  43. package/stacks/dotnet-core/overlays/mvc-auth/Models/AppUser.cs +48 -0
  44. package/stacks/dotnet-core/overlays/mvc-auth/Views/Account/Login.cshtml +35 -0
  45. package/stacks/dotnet-core/overlays/mvc-auth/Views/Account/Register.cshtml +41 -0
  46. package/stacks/dotnet-core/overlays/mvc-auth/Views/Shared/_AuthNav.cshtml +22 -0
  47. package/stacks/dotnet-core/snippets/packages-auth.xml +4 -0
  48. package/stacks/dotnet-core/snippets/packages-common.xml +5 -0
  49. package/stacks/dotnet-core/snippets/packages-ef.xml +4 -0
  50. package/stacks/dotnet-core/snippets/packages-mssql.xml +4 -0
  51. package/stacks/dotnet-core/snippets/packages-mysql.xml +4 -0
  52. package/stacks/dotnet-core/snippets/packages-swagger.xml +4 -0
  53. package/stacks/dotnet-core/stack.json +426 -0
  54. package/stacks/laravel/overlays/auth/app/Http/Controllers/Api/AuthController.php +67 -0
  55. package/stacks/laravel/overlays/common/app/Http/Controllers/BoilerCraftController.php +14 -0
  56. package/stacks/laravel/overlays/common/resources/views/boilercraft.blade.php +36 -0
  57. package/stacks/laravel/overlays/inertia-common/resources/views/app.blade.php +15 -0
  58. package/stacks/laravel/overlays/inertia-react/resources/js/Pages/Welcome.jsx +18 -0
  59. package/stacks/laravel/overlays/inertia-react/resources/js/app.jsx +13 -0
  60. package/stacks/laravel/overlays/inertia-vue/resources/js/Pages/Welcome.vue +23 -0
  61. package/stacks/laravel/overlays/inertia-vue/resources/js/app.js +13 -0
  62. package/stacks/laravel/snippets/api-routes.php +9 -0
  63. package/stacks/laravel/snippets/web-routes-inertia.php +7 -0
  64. package/stacks/laravel/snippets/web-routes.php +4 -0
  65. package/stacks/laravel/stack.json +477 -0
  66. package/stacks/nextjs/overlays/auth/src/lib/auth.js +36 -0
  67. package/stacks/nextjs/overlays/auth-app/src/app/api/auth/login/route.js +11 -0
  68. package/stacks/nextjs/overlays/auth-app/src/app/api/auth/register/route.js +14 -0
  69. package/stacks/nextjs/overlays/auth-pages/src/pages/api/auth/login.js +14 -0
  70. package/stacks/nextjs/overlays/auth-pages/src/pages/api/auth/register.js +17 -0
  71. package/stacks/nextjs/overlays/common/.env.example +3 -0
  72. package/stacks/nextjs/overlays/common/.env.local +3 -0
  73. package/stacks/nextjs/overlays/db/mongodb/src/lib/db.js +12 -0
  74. package/stacks/nextjs/overlays/db/mssql/src/lib/db.js +17 -0
  75. package/stacks/nextjs/overlays/db/mysql/src/lib/db.js +13 -0
  76. package/stacks/nextjs/overlays/router-app/src/app/api/health/route.js +10 -0
  77. package/stacks/nextjs/overlays/router-app/src/app/layout.js +16 -0
  78. package/stacks/nextjs/overlays/router-app/src/app/page.js +19 -0
  79. package/stacks/nextjs/overlays/router-pages/src/pages/_app.js +7 -0
  80. package/stacks/nextjs/overlays/router-pages/src/pages/_document.js +13 -0
  81. package/stacks/nextjs/overlays/router-pages/src/pages/api/health.js +8 -0
  82. package/stacks/nextjs/overlays/router-pages/src/pages/index.js +27 -0
  83. package/stacks/nextjs/stack.json +257 -0
  84. package/stacks/node-express/overlays/auth/src/controllers/authController.js +53 -0
  85. package/stacks/node-express/overlays/auth/src/middlewares/auth.js +36 -0
  86. package/stacks/node-express/overlays/auth/src/routes/authRoutes.js +16 -0
  87. package/stacks/node-express/overlays/common/.env.example +5 -0
  88. package/stacks/node-express/overlays/common/public/index.html +31 -0
  89. package/stacks/node-express/overlays/common/src/server.js +32 -0
  90. package/stacks/node-express/overlays/db/mongodb/src/db.js +13 -0
  91. package/stacks/node-express/overlays/db/mssql/src/db.js +22 -0
  92. package/stacks/node-express/overlays/db/mysql/src/db.js +22 -0
  93. package/stacks/node-express/stack.json +165 -0
  94. package/templates/dotnet-bootstrap-mssql/Controllers/AuthController.cs +74 -0
  95. package/templates/dotnet-bootstrap-mssql/Controllers/ProductsController.cs +33 -0
  96. package/templates/dotnet-bootstrap-mssql/Data/ApplicationDbContext.cs +24 -0
  97. package/templates/dotnet-bootstrap-mssql/Models/Product.cs +10 -0
  98. package/templates/dotnet-core/Controllers/AuthController.cs +74 -0
  99. package/templates/laravel-full/app/Http/Controllers/AuthController.php +79 -0
  100. package/templates/laravel-full/app/Http/Controllers/HomeController.php +22 -0
  101. package/templates/laravel-full/app/Models/User.php +49 -0
  102. package/templates/laravel-full/artisan +22 -0
  103. package/templates/laravel-full/bootstrap/app.php +21 -0
  104. package/templates/laravel-full/config/database.php +54 -0
  105. package/templates/laravel-full/database/migrations/2024_01_01_000001_create_users_table.php +27 -0
  106. package/templates/laravel-full/public/index.php +17 -0
  107. package/templates/laravel-full/resources/css/app.css +3 -0
  108. package/templates/laravel-full/resources/js/Pages/Home.vue +76 -0
  109. package/templates/laravel-full/resources/js/app.js +21 -0
  110. package/templates/laravel-full/resources/js/bootstrap.js +3 -0
  111. package/templates/laravel-full/resources/views/app.blade.php +15 -0
  112. package/templates/laravel-full/routes/api.php +8 -0
  113. package/templates/laravel-full/routes/console.php +7 -0
  114. package/templates/laravel-full/routes/web.php +24 -0
  115. package/templates/laravel-full/tailwind.config.js +12 -0
  116. package/templates/laravel-full/vite.config.js +20 -0
  117. package/templates/laravel-vue/app/Http/Controllers/Auth/AuthenticatedSessionController.php +43 -0
  118. package/templates/laravel-vue-mysql/app/Http/Controllers/HomeController.php +25 -0
  119. package/templates/laravel-vue-mysql/resources/js/Pages/Home.vue +70 -0
  120. package/templates/nextjs/src/app/api/auth/login/route.js +28 -0
  121. package/templates/nextjs-mongodb/src/app/api/health/route.js +17 -0
  122. package/templates/nextjs-mongodb/src/lib/mongodb.js +18 -0
  123. package/templates/nextjs-mssql/src/app/api/health/route.js +24 -0
  124. package/templates/nextjs-mssql/src/lib/mssql.js +38 -0
  125. package/templates/node-express/src/controllers/authController.js +53 -0
  126. package/templates/node-express/src/middlewares/auth.js +36 -0
  127. package/templates/node-express/src/routes/authRoutes.js +16 -0
  128. package/templates/node-mongodb/src/controllers/userController.js +30 -0
  129. package/templates/node-mongodb/src/middlewares/errorHandler.js +8 -0
  130. package/templates/node-mongodb/src/models/User.js +23 -0
  131. package/templates/node-mongodb/src/routes/api.js +9 -0
  132. package/templates/raw-php-mysql/app/Controllers/AuthController.php +73 -0
  133. package/templates/raw-php-mysql/app/Controllers/HomeController.php +65 -0
  134. package/templates/raw-php-mysql/app/Models/User.php +60 -0
  135. package/templates/raw-php-mysql/app/Views/404.php +15 -0
  136. package/templates/raw-php-mysql/app/Views/auth/login.php +66 -0
  137. package/templates/raw-php-mysql/app/Views/auth/register.php +71 -0
  138. package/templates/raw-php-mysql/app/Views/dashboard.php +50 -0
  139. package/templates/raw-php-mysql/app/Views/home.php +83 -0
  140. package/templates/raw-php-mysql/config/Auth.php +62 -0
  141. package/templates/raw-php-mysql/config/Database.php +100 -0
  142. package/templates/raw-php-mysql/config/Env.php +30 -0
  143. package/templates/raw-php-mysql/config/Middleware.php +53 -0
  144. package/templates/raw-php-mysql/config/QueryBuilder.php +109 -0
  145. package/templates/raw-php-mysql/config/Router.php +33 -0
  146. package/templates/raw-php-mysql/public/index.php +57 -0
@@ -0,0 +1,87 @@
1
+ using System.IdentityModel.Tokens.Jwt;
2
+ using System.Security.Claims;
3
+ using System.Text;
4
+ using Microsoft.AspNetCore.Authorization;
5
+ using Microsoft.AspNetCore.Mvc;
6
+ using Microsoft.IdentityModel.Tokens;
7
+
8
+ namespace {{NAMESPACE}}.Controllers
9
+ {
10
+ [ApiController]
11
+ [Route("api/[controller]")]
12
+ public class AuthController : ControllerBase
13
+ {
14
+ private readonly IConfiguration _config;
15
+
16
+ public AuthController(IConfiguration config)
17
+ {
18
+ _config = config;
19
+ }
20
+
21
+ public class LoginDto
22
+ {
23
+ public string Email { get; set; } = string.Empty;
24
+ public string Password { get; set; } = string.Empty;
25
+ }
26
+
27
+ public class RegisterDto
28
+ {
29
+ public string Name { get; set; } = string.Empty;
30
+ public string Email { get; set; } = string.Empty;
31
+ public string Password { get; set; } = string.Empty;
32
+ }
33
+
34
+ // Demo implementation: accepts any non-empty credentials.
35
+ // Replace with a real user store (e.g. ASP.NET Core Identity + ApplicationDbContext).
36
+ [HttpPost("login")]
37
+ public IActionResult Login([FromBody] LoginDto dto)
38
+ {
39
+ if (string.IsNullOrWhiteSpace(dto.Email) || string.IsNullOrWhiteSpace(dto.Password))
40
+ {
41
+ return Unauthorized(new { success = false, error = "Invalid credentials" });
42
+ }
43
+
44
+ var token = GenerateJwtToken("1", dto.Email, dto.Email, "user");
45
+ return Ok(new { success = true, token });
46
+ }
47
+
48
+ [HttpPost("register")]
49
+ public IActionResult Register([FromBody] RegisterDto dto)
50
+ {
51
+ var token = GenerateJwtToken("1", dto.Name, dto.Email, "user");
52
+ return Ok(new { success = true, token, user = new { dto.Name, dto.Email } });
53
+ }
54
+
55
+ [Authorize]
56
+ [HttpGet("me")]
57
+ public IActionResult Me()
58
+ {
59
+ return Ok(new
60
+ {
61
+ success = true,
62
+ name = User.FindFirstValue(ClaimTypes.Name),
63
+ email = User.FindFirstValue(ClaimTypes.Email),
64
+ role = User.FindFirstValue(ClaimTypes.Role)
65
+ });
66
+ }
67
+
68
+ private string GenerateJwtToken(string id, string name, string email, string role)
69
+ {
70
+ var key = Encoding.UTF8.GetBytes(_config["Jwt:Key"]!);
71
+ var descriptor = new SecurityTokenDescriptor
72
+ {
73
+ Subject = new ClaimsIdentity(new[]
74
+ {
75
+ new Claim(ClaimTypes.NameIdentifier, id),
76
+ new Claim(ClaimTypes.Name, name),
77
+ new Claim(ClaimTypes.Email, email),
78
+ new Claim(ClaimTypes.Role, role)
79
+ }),
80
+ Expires = DateTime.UtcNow.AddDays(7),
81
+ SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
82
+ };
83
+ var handler = new JwtSecurityTokenHandler();
84
+ return handler.WriteToken(handler.CreateToken(descriptor));
85
+ }
86
+ }
87
+ }
@@ -0,0 +1,25 @@
1
+ using Microsoft.EntityFrameworkCore;
2
+ using {{NAMESPACE}}.Models;
3
+
4
+ namespace {{NAMESPACE}}.Data
5
+ {
6
+ public class ApplicationDbContext : DbContext
7
+ {
8
+ public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
9
+ {
10
+ }
11
+
12
+ public DbSet<Product> Products { get; set; }
13
+ {{DBCONTEXT_EXTRA}}
14
+
15
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
16
+ {
17
+ base.OnModelCreating(modelBuilder);
18
+
19
+ modelBuilder.Entity<Product>().Property(p => p.Price).HasPrecision(18, 2);
20
+ modelBuilder.Entity<Product>().HasData(
21
+ new Product { Id = 1, Name = "Starter Kit", Price = 99.99m, Description = "Built with BoilerCraft Studio" }
22
+ );
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,19 @@
1
+ using System.ComponentModel.DataAnnotations;
2
+
3
+ namespace {{NAMESPACE}}.Models
4
+ {
5
+ public class Product
6
+ {
7
+ public int Id { get; set; }
8
+
9
+ [Required, StringLength(120)]
10
+ public string Name { get; set; } = string.Empty;
11
+
12
+ [Range(0, 1_000_000)]
13
+ [DataType(DataType.Currency)]
14
+ public decimal Price { get; set; }
15
+
16
+ [StringLength(1000)]
17
+ public string Description { get; set; } = string.Empty;
18
+ }
19
+ }
@@ -0,0 +1,84 @@
1
+ using Microsoft.AspNetCore.Mvc;
2
+ using Microsoft.EntityFrameworkCore;
3
+ using {{NAMESPACE}}.Data;
4
+ using {{NAMESPACE}}.Models;
5
+
6
+ namespace {{NAMESPACE}}.Controllers
7
+ {
8
+ {{PRODUCTS_AUTHORIZE}}
9
+ public class ProductsController : Controller
10
+ {
11
+ private readonly ApplicationDbContext _context;
12
+
13
+ public ProductsController(ApplicationDbContext context)
14
+ {
15
+ _context = context;
16
+ }
17
+
18
+ // GET: /Products
19
+ public async Task<IActionResult> Index()
20
+ {
21
+ return View(await _context.Products.OrderBy(p => p.Name).ToListAsync());
22
+ }
23
+
24
+ // GET: /Products/Create
25
+ public IActionResult Create()
26
+ {
27
+ return View(new Product());
28
+ }
29
+
30
+ // POST: /Products/Create
31
+ [HttpPost]
32
+ [ValidateAntiForgeryToken]
33
+ public async Task<IActionResult> Create([Bind("Name,Price,Description")] Product product)
34
+ {
35
+ if (!ModelState.IsValid) return View(product);
36
+ _context.Add(product);
37
+ await _context.SaveChangesAsync();
38
+ TempData["Status"] = $"\"{product.Name}\" was created.";
39
+ return RedirectToAction(nameof(Index));
40
+ }
41
+
42
+ // GET: /Products/Edit/5
43
+ public async Task<IActionResult> Edit(int id)
44
+ {
45
+ var product = await _context.Products.FindAsync(id);
46
+ return product == null ? NotFound() : View(product);
47
+ }
48
+
49
+ // POST: /Products/Edit/5
50
+ [HttpPost]
51
+ [ValidateAntiForgeryToken]
52
+ public async Task<IActionResult> Edit(int id, [Bind("Id,Name,Price,Description")] Product product)
53
+ {
54
+ if (id != product.Id) return NotFound();
55
+ if (!ModelState.IsValid) return View(product);
56
+ _context.Update(product);
57
+ await _context.SaveChangesAsync();
58
+ TempData["Status"] = $"\"{product.Name}\" was updated.";
59
+ return RedirectToAction(nameof(Index));
60
+ }
61
+
62
+ // GET: /Products/Delete/5
63
+ public async Task<IActionResult> Delete(int id)
64
+ {
65
+ var product = await _context.Products.FindAsync(id);
66
+ return product == null ? NotFound() : View(product);
67
+ }
68
+
69
+ // POST: /Products/Delete/5
70
+ [HttpPost, ActionName("Delete")]
71
+ [ValidateAntiForgeryToken]
72
+ public async Task<IActionResult> DeleteConfirmed(int id)
73
+ {
74
+ var product = await _context.Products.FindAsync(id);
75
+ if (product != null)
76
+ {
77
+ _context.Products.Remove(product);
78
+ await _context.SaveChangesAsync();
79
+ TempData["Status"] = $"\"{product.Name}\" was deleted.";
80
+ }
81
+ return RedirectToAction(nameof(Index));
82
+ }
83
+ }
84
+ }
@@ -0,0 +1,23 @@
1
+ @{
2
+ ViewData["Title"] = "Home";
3
+ }
4
+
5
+ <div style="max-width: 900px; margin: 0 auto; text-align: center;">
6
+ <h1 class="display-5 fw-bold">{{APP_TITLE}}</h1>
7
+ <p style="color: var(--color-text-muted);">{{APP_DESC}}</p>
8
+
9
+ <div class="my-4">
10
+ <a asp-controller="Products" asp-action="Index" class="btn btn-primary btn-lg">Manage products →</a>
11
+ </div>
12
+
13
+ <div style="text-align: left; padding: 2rem; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-base); margin-top: 2rem;">
14
+ <h3 style="margin-top: 0;">Active Architecture:</h3>
15
+ <ul>
16
+ <li><strong>Framework:</strong> ASP.NET Core MVC (.NET {{VERSION}})</li>
17
+ <li><strong>Database:</strong> {{DATABASE}} (EF Core)</li>
18
+ <li><strong>Theme:</strong> {{THEME}}</li>
19
+ </ul>
20
+ <hr style="border: none; border-top: 1px solid var(--color-border); margin: 1.5rem 0;" />
21
+ {{UI_COMPONENTS_RAZOR}}
22
+ </div>
23
+ </div>
@@ -0,0 +1,16 @@
1
+ @model Product
2
+ @{
3
+ ViewData["Title"] = "New product";
4
+ }
5
+
6
+ <h1 class="h3 mb-4">New product</h1>
7
+
8
+ <form asp-action="Create" method="post" style="max-width: 560px;">
9
+ <partial name="_Form" model="Model" />
10
+ <button type="submit" class="btn btn-primary">Create</button>
11
+ <a asp-action="Index" class="btn btn-link">Cancel</a>
12
+ </form>
13
+
14
+ @section Scripts {
15
+ <partial name="_ValidationScriptsPartial" />
16
+ }
@@ -0,0 +1,16 @@
1
+ @model Product
2
+ @{
3
+ ViewData["Title"] = "Delete product";
4
+ }
5
+
6
+ <h1 class="h3 mb-4">Delete product</h1>
7
+
8
+ <div class="alert alert-warning" style="max-width: 560px;">
9
+ Delete <strong>@Model.Name</strong> (@Model.Price.ToString("0.00"))? This cannot be undone.
10
+ </div>
11
+
12
+ <form asp-action="Delete" method="post">
13
+ <input type="hidden" asp-for="Id" />
14
+ <button type="submit" class="btn btn-danger">Delete</button>
15
+ <a asp-action="Index" class="btn btn-link">Cancel</a>
16
+ </form>
@@ -0,0 +1,17 @@
1
+ @model Product
2
+ @{
3
+ ViewData["Title"] = "Edit product";
4
+ }
5
+
6
+ <h1 class="h3 mb-4">Edit product</h1>
7
+
8
+ <form asp-action="Edit" method="post" style="max-width: 560px;">
9
+ <input type="hidden" asp-for="Id" />
10
+ <partial name="_Form" model="Model" />
11
+ <button type="submit" class="btn btn-primary">Save</button>
12
+ <a asp-action="Index" class="btn btn-link">Cancel</a>
13
+ </form>
14
+
15
+ @section Scripts {
16
+ <partial name="_ValidationScriptsPartial" />
17
+ }
@@ -0,0 +1,46 @@
1
+ @model IEnumerable<Product>
2
+ @{
3
+ ViewData["Title"] = "Products";
4
+ }
5
+
6
+ <div class="d-flex justify-content-between align-items-center mb-4">
7
+ <h1 class="h3 mb-0">Products</h1>
8
+ <a asp-action="Create" class="btn btn-primary">+ New product</a>
9
+ </div>
10
+
11
+ @if (TempData["Status"] is string status)
12
+ {
13
+ <div class="alert alert-success">@status</div>
14
+ }
15
+
16
+ @if (!Model.Any())
17
+ {
18
+ <p class="text-muted">No products yet. <a asp-action="Create">Create the first one</a>.</p>
19
+ }
20
+ else
21
+ {
22
+ <table class="table align-middle">
23
+ <thead>
24
+ <tr>
25
+ <th>@Html.DisplayNameFor(m => m.Name)</th>
26
+ <th class="text-end">@Html.DisplayNameFor(m => m.Price)</th>
27
+ <th>@Html.DisplayNameFor(m => m.Description)</th>
28
+ <th></th>
29
+ </tr>
30
+ </thead>
31
+ <tbody>
32
+ @foreach (var item in Model)
33
+ {
34
+ <tr>
35
+ <td>@item.Name</td>
36
+ <td class="text-end">@item.Price.ToString("0.00")</td>
37
+ <td class="text-muted">@item.Description</td>
38
+ <td class="text-end text-nowrap">
39
+ <a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-sm btn-outline-secondary">Edit</a>
40
+ <a asp-action="Delete" asp-route-id="@item.Id" class="btn btn-sm btn-outline-danger">Delete</a>
41
+ </td>
42
+ </tr>
43
+ }
44
+ </tbody>
45
+ </table>
46
+ }
@@ -0,0 +1,19 @@
1
+ @model Product
2
+
3
+ <div asp-validation-summary="ModelOnly" class="text-danger mb-3"></div>
4
+
5
+ <div class="mb-3">
6
+ <label asp-for="Name" class="form-label"></label>
7
+ <input asp-for="Name" class="form-control" autofocus />
8
+ <span asp-validation-for="Name" class="text-danger"></span>
9
+ </div>
10
+ <div class="mb-3">
11
+ <label asp-for="Price" class="form-label"></label>
12
+ <input asp-for="Price" class="form-control" step="0.01" />
13
+ <span asp-validation-for="Price" class="text-danger"></span>
14
+ </div>
15
+ <div class="mb-3">
16
+ <label asp-for="Description" class="form-label"></label>
17
+ <textarea asp-for="Description" class="form-control" rows="3"></textarea>
18
+ <span asp-validation-for="Description" class="text-danger"></span>
19
+ </div>
@@ -0,0 +1,98 @@
1
+ using System.Security.Claims;
2
+ using Microsoft.AspNetCore.Authentication;
3
+ using Microsoft.AspNetCore.Authentication.Cookies;
4
+ using Microsoft.AspNetCore.Identity;
5
+ using Microsoft.AspNetCore.Mvc;
6
+ using Microsoft.EntityFrameworkCore;
7
+ using {{NAMESPACE}}.Data;
8
+ using {{NAMESPACE}}.Models;
9
+
10
+ namespace {{NAMESPACE}}.Controllers
11
+ {
12
+ // Cookie-based sign-in with passwords hashed by ASP.NET Core's PasswordHasher.
13
+ // For roles, 2FA, lockout etc. you can move to full ASP.NET Core Identity later.
14
+ public class AccountController : Controller
15
+ {
16
+ private readonly ApplicationDbContext _context;
17
+ private readonly PasswordHasher<AppUser> _hasher = new();
18
+
19
+ public AccountController(ApplicationDbContext context)
20
+ {
21
+ _context = context;
22
+ }
23
+
24
+ [HttpGet]
25
+ public IActionResult Login(string? returnUrl = null)
26
+ {
27
+ ViewData["ReturnUrl"] = returnUrl;
28
+ return View(new LoginViewModel());
29
+ }
30
+
31
+ [HttpPost]
32
+ [ValidateAntiForgeryToken]
33
+ public async Task<IActionResult> Login(LoginViewModel model, string? returnUrl = null)
34
+ {
35
+ ViewData["ReturnUrl"] = returnUrl;
36
+ if (!ModelState.IsValid) return View(model);
37
+
38
+ var user = await _context.Users.SingleOrDefaultAsync(u => u.Email == model.Email);
39
+ if (user == null || _hasher.VerifyHashedPassword(user, user.PasswordHash, model.Password) == PasswordVerificationResult.Failed)
40
+ {
41
+ ModelState.AddModelError(string.Empty, "Invalid email or password.");
42
+ return View(model);
43
+ }
44
+
45
+ await SignInAsync(user, model.RememberMe);
46
+ return Url.IsLocalUrl(returnUrl) ? Redirect(returnUrl!) : RedirectToAction("Index", "Home");
47
+ }
48
+
49
+ [HttpGet]
50
+ public IActionResult Register()
51
+ {
52
+ return View(new RegisterViewModel());
53
+ }
54
+
55
+ [HttpPost]
56
+ [ValidateAntiForgeryToken]
57
+ public async Task<IActionResult> Register(RegisterViewModel model)
58
+ {
59
+ if (!ModelState.IsValid) return View(model);
60
+
61
+ if (await _context.Users.AnyAsync(u => u.Email == model.Email))
62
+ {
63
+ ModelState.AddModelError(nameof(model.Email), "An account with this email already exists.");
64
+ return View(model);
65
+ }
66
+
67
+ var user = new AppUser { Name = model.Name, Email = model.Email };
68
+ user.PasswordHash = _hasher.HashPassword(user, model.Password);
69
+ _context.Users.Add(user);
70
+ await _context.SaveChangesAsync();
71
+
72
+ await SignInAsync(user, isPersistent: false);
73
+ return RedirectToAction("Index", "Home");
74
+ }
75
+
76
+ [HttpPost]
77
+ [ValidateAntiForgeryToken]
78
+ public async Task<IActionResult> Logout()
79
+ {
80
+ await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
81
+ return RedirectToAction("Index", "Home");
82
+ }
83
+
84
+ private Task SignInAsync(AppUser user, bool isPersistent)
85
+ {
86
+ var claims = new List<Claim>
87
+ {
88
+ new(ClaimTypes.NameIdentifier, user.Id.ToString()),
89
+ new(ClaimTypes.Name, user.Name),
90
+ new(ClaimTypes.Email, user.Email),
91
+ new(ClaimTypes.Role, user.Role)
92
+ };
93
+ var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme));
94
+ return HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal,
95
+ new AuthenticationProperties { IsPersistent = isPersistent });
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,48 @@
1
+ using System.ComponentModel.DataAnnotations;
2
+
3
+ namespace {{NAMESPACE}}.Models
4
+ {
5
+ public class AppUser
6
+ {
7
+ public int Id { get; set; }
8
+
9
+ [Required, StringLength(100)]
10
+ public string Name { get; set; } = string.Empty;
11
+
12
+ [Required, EmailAddress, StringLength(256)]
13
+ public string Email { get; set; } = string.Empty;
14
+
15
+ [Required]
16
+ public string PasswordHash { get; set; } = string.Empty;
17
+
18
+ [StringLength(20)]
19
+ public string Role { get; set; } = "user";
20
+ }
21
+
22
+ public class LoginViewModel
23
+ {
24
+ [Required, EmailAddress]
25
+ public string Email { get; set; } = string.Empty;
26
+
27
+ [Required, DataType(DataType.Password)]
28
+ public string Password { get; set; } = string.Empty;
29
+
30
+ [Display(Name = "Remember me")]
31
+ public bool RememberMe { get; set; }
32
+ }
33
+
34
+ public class RegisterViewModel
35
+ {
36
+ [Required, StringLength(100)]
37
+ public string Name { get; set; } = string.Empty;
38
+
39
+ [Required, EmailAddress]
40
+ public string Email { get; set; } = string.Empty;
41
+
42
+ [Required, StringLength(100, MinimumLength = 8), DataType(DataType.Password)]
43
+ public string Password { get; set; } = string.Empty;
44
+
45
+ [Required, DataType(DataType.Password), Compare(nameof(Password)), Display(Name = "Confirm password")]
46
+ public string ConfirmPassword { get; set; } = string.Empty;
47
+ }
48
+ }
@@ -0,0 +1,35 @@
1
+ @model LoginViewModel
2
+ @{
3
+ ViewData["Title"] = "Log in";
4
+ }
5
+
6
+ <div style="max-width: 420px; margin: 2rem auto;">
7
+ <h1 class="h3 mb-4">Log in</h1>
8
+
9
+ <form asp-action="Login" asp-route-returnUrl="@ViewData["ReturnUrl"]" method="post">
10
+ <div asp-validation-summary="ModelOnly" class="text-danger mb-3"></div>
11
+
12
+ <div class="mb-3">
13
+ <label asp-for="Email" class="form-label"></label>
14
+ <input asp-for="Email" class="form-control" autocomplete="username" autofocus />
15
+ <span asp-validation-for="Email" class="text-danger"></span>
16
+ </div>
17
+ <div class="mb-3">
18
+ <label asp-for="Password" class="form-label"></label>
19
+ <input asp-for="Password" class="form-control" autocomplete="current-password" />
20
+ <span asp-validation-for="Password" class="text-danger"></span>
21
+ </div>
22
+ <div class="form-check mb-3">
23
+ <input asp-for="RememberMe" class="form-check-input" />
24
+ <label asp-for="RememberMe" class="form-check-label"></label>
25
+ </div>
26
+
27
+ <button type="submit" class="btn btn-primary w-100">Log in</button>
28
+ </form>
29
+
30
+ <p class="mt-3 text-center">No account? <a asp-action="Register">Create one</a></p>
31
+ </div>
32
+
33
+ @section Scripts {
34
+ <partial name="_ValidationScriptsPartial" />
35
+ }
@@ -0,0 +1,41 @@
1
+ @model RegisterViewModel
2
+ @{
3
+ ViewData["Title"] = "Create account";
4
+ }
5
+
6
+ <div style="max-width: 420px; margin: 2rem auto;">
7
+ <h1 class="h3 mb-4">Create account</h1>
8
+
9
+ <form asp-action="Register" method="post">
10
+ <div asp-validation-summary="ModelOnly" class="text-danger mb-3"></div>
11
+
12
+ <div class="mb-3">
13
+ <label asp-for="Name" class="form-label"></label>
14
+ <input asp-for="Name" class="form-control" autocomplete="name" autofocus />
15
+ <span asp-validation-for="Name" class="text-danger"></span>
16
+ </div>
17
+ <div class="mb-3">
18
+ <label asp-for="Email" class="form-label"></label>
19
+ <input asp-for="Email" class="form-control" autocomplete="username" />
20
+ <span asp-validation-for="Email" class="text-danger"></span>
21
+ </div>
22
+ <div class="mb-3">
23
+ <label asp-for="Password" class="form-label"></label>
24
+ <input asp-for="Password" class="form-control" autocomplete="new-password" />
25
+ <span asp-validation-for="Password" class="text-danger"></span>
26
+ </div>
27
+ <div class="mb-3">
28
+ <label asp-for="ConfirmPassword" class="form-label"></label>
29
+ <input asp-for="ConfirmPassword" class="form-control" autocomplete="new-password" />
30
+ <span asp-validation-for="ConfirmPassword" class="text-danger"></span>
31
+ </div>
32
+
33
+ <button type="submit" class="btn btn-primary w-100">Create account</button>
34
+ </form>
35
+
36
+ <p class="mt-3 text-center">Already registered? <a asp-action="Login">Log in</a></p>
37
+ </div>
38
+
39
+ @section Scripts {
40
+ <partial name="_ValidationScriptsPartial" />
41
+ }
@@ -0,0 +1,22 @@
1
+ <ul class="navbar-nav">
2
+ @if (User.Identity?.IsAuthenticated == true)
3
+ {
4
+ <li class="nav-item">
5
+ <span class="nav-link text-dark">Hello, @User.Identity.Name</span>
6
+ </li>
7
+ <li class="nav-item">
8
+ <form asp-controller="Account" asp-action="Logout" method="post" class="d-inline">
9
+ <button type="submit" class="nav-link btn btn-link text-dark">Log out</button>
10
+ </form>
11
+ </li>
12
+ }
13
+ else
14
+ {
15
+ <li class="nav-item">
16
+ <a class="nav-link text-dark" asp-controller="Account" asp-action="Login">Log in</a>
17
+ </li>
18
+ <li class="nav-item">
19
+ <a class="nav-link text-dark" asp-controller="Account" asp-action="Register">Register</a>
20
+ </li>
21
+ }
22
+ </ul>
@@ -0,0 +1,4 @@
1
+ <ItemGroup>
2
+ <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="{{VERSION}}.0.*" />
3
+ </ItemGroup>
4
+
@@ -0,0 +1,5 @@
1
+ <ItemGroup>
2
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="{{VERSION}}.0.*" />
3
+ <PackageReference Include="Swashbuckle.AspNetCore" Version="{{SWASHBUCKLE_VERSION}}" />
4
+ </ItemGroup>
5
+
@@ -0,0 +1,4 @@
1
+ <ItemGroup>
2
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="{{VERSION}}.0.*" />
3
+ </ItemGroup>
4
+
@@ -0,0 +1,4 @@
1
+ <ItemGroup>
2
+ <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="{{VERSION}}.0.*" />
3
+ </ItemGroup>
4
+
@@ -0,0 +1,4 @@
1
+ <ItemGroup>
2
+ <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="{{VERSION}}.0.*" />
3
+ </ItemGroup>
4
+
@@ -0,0 +1,4 @@
1
+ <ItemGroup>
2
+ <PackageReference Include="Swashbuckle.AspNetCore" Version="{{SWASHBUCKLE_VERSION}}" />
3
+ </ItemGroup>
4
+