Changeset fa0fbaf for KernelRecordsMVC.Web/Controllers
- Timestamp:
- 09/01/26 06:11:34 (4 weeks ago)
- Branches:
- main
- Children:
- d2eccb3
- Parents:
- 08aefc6
- Location:
- KernelRecordsMVC.Web/Controllers
- Files:
-
- 3 added
- 5 edited
-
AccountController.cs (modified) (10 diffs)
-
AdminController.cs (modified) (10 diffs)
-
HomeController.cs (modified) (1 diff)
-
OrderController.cs (modified) (5 diffs)
-
ReleaseController.cs (modified) (4 diffs)
-
SaleController.cs (added)
-
TopSellersController.cs (added)
-
WishlistController.cs (added)
Legend:
- Unmodified
- Added
- Removed
-
KernelRecordsMVC.Web/Controllers/AccountController.cs
r08aefc6 rfa0fbaf 1 using Microsoft.AspNetCore.Mvc; 2 using KernelRecordsMVC.Data; 1 using System.Security.Claims; 2 using KernelRecordsMVC.Application.ViewModels; 3 using KernelRecordsMVC.Infrastructure.Data; 3 4 using KernelRecordsMVC.Models; 4 using KernelRecordsMVC.Application.ViewModels; 5 using Microsoft.AspNetCore.Http; 6 7 namespace KernelRecordsMVC.Controllers; 5 using Microsoft.AspNetCore.Authentication; 6 using Microsoft.AspNetCore.Mvc; 7 using Microsoft.EntityFrameworkCore; 8 9 namespace KernelRecordsMVC.Web.Controllers; 8 10 9 11 public class AccountController : Controller … … 16 18 } 17 19 18 // ============================== 20 21 // ========================================================= 19 22 // REGISTER - GET 20 // ============================== 23 // ========================================================= 21 24 22 25 [HttpGet] … … 27 30 28 31 29 // ============================== 32 // ========================================================= 30 33 // REGISTER - POST 31 // ============================== 34 // ========================================================= 32 35 33 36 [HttpPost] … … 61 64 Username = model.Username, 62 65 63 // We'll replace this with proper password hashing64 // in the security cleanup section.66 // TODO: 67 // Replace with password hashing later. 65 68 Password = model.Password, 66 69 … … 74 77 _context.SaveChanges(); 75 78 79 76 80 // Every normal registered user is a Consumer. 77 81 var consumer = new Consumer … … 89 93 90 94 91 // ============================== 95 // ========================================================= 92 96 // LOGIN - GET 93 // ============================== 97 // ========================================================= 94 98 95 99 [HttpGet] … … 100 104 101 105 102 // ============================== 106 // ========================================================= 103 107 // LOGIN - POST 104 // ============================== 108 // ========================================================= 105 109 106 110 [HttpPost] 107 111 [ValidateAntiForgeryToken] 108 public IActionResult Login(LoginViewModel model) 112 public async Task<IActionResult> Login( 113 LoginViewModel model) 109 114 { 110 115 if (!ModelState.IsValid) … … 125 130 } 126 131 132 133 // ===================================================== 134 // DETERMINE ACCOUNT TYPE 135 // ===================================================== 136 137 var admin = _context.Admins 138 .FirstOrDefault(x => x.UserId == user.UserId); 139 140 string role; 141 142 if (admin != null) 143 { 144 role = "Admin"; 145 } 146 else 147 { 148 role = "Consumer"; 149 } 150 151 152 // ===================================================== 153 // CREATE CLAIMS 154 // ===================================================== 155 156 var claims = new List<Claim> 157 { 158 new Claim( 159 ClaimTypes.NameIdentifier, 160 user.UserId.ToString()), 161 162 new Claim( 163 ClaimTypes.Name, 164 user.Username), 165 166 new Claim( 167 ClaimTypes.Email, 168 user.Email), 169 170 new Claim( 171 ClaimTypes.Role, 172 role) 173 }; 174 175 176 if (admin != null) 177 { 178 claims.Add( 179 new Claim( 180 "AdminType", 181 admin.Type.ToString())); 182 } 183 184 185 var identity = new ClaimsIdentity( 186 claims, 187 "Cookies"); 188 189 var principal = 190 new ClaimsPrincipal(identity); 191 192 193 // ===================================================== 194 // SIGN IN 195 // ===================================================== 196 197 await HttpContext.SignInAsync( 198 "Cookies", 199 principal); 200 201 202 // ===================================================== 203 // KEEP SESSION FOR EXISTING CODE 204 // ===================================================== 205 127 206 HttpContext.Session.SetInt32( 128 207 "UserId", … … 133 212 user.Username); 134 213 135 // Determine account type from the actual 136 // ADMINS / CONSUMERS tables. 137 var admin = _context.Admins 138 .FirstOrDefault(x => x.UserId == user.UserId); 214 HttpContext.Session.SetString( 215 "Role", 216 role); 139 217 140 218 if (admin != null) 141 219 { 142 HttpContext.Session.SetString(143 "Role",144 "Admin");145 146 220 HttpContext.Session.SetString( 147 221 "AdminType", 148 222 admin.Type.ToString()); 149 223 } 150 else 151 { 152 HttpContext.Session.SetString( 153 "Role", 154 "Consumer"); 155 } 224 156 225 157 226 return RedirectToAction( … … 161 230 162 231 163 // ============================== 232 // ========================================================= 233 // PROFILE - GET 234 // ========================================================= 235 236 [HttpGet] 237 public IActionResult Profile() 238 { 239 // Get logged-in user's ID from authentication claims. 240 var userIdClaim = User.FindFirst( 241 ClaimTypes.NameIdentifier); 242 243 if (userIdClaim == null) 244 return RedirectToAction(nameof(Login)); 245 246 if (!long.TryParse( 247 userIdClaim.Value, 248 out var userId)) 249 { 250 return RedirectToAction(nameof(Login)); 251 } 252 253 254 // Load user + consumer information. 255 var user = _context.Users 256 .Include(x => x.Consumer) 257 .FirstOrDefault(x => 258 x.UserId == userId); 259 260 if (user == null) 261 return NotFound(); 262 263 264 var model = new ProfileViewModel 265 { 266 UserId = user.UserId, 267 268 Username = user.Username, 269 270 Email = user.Email, 271 272 TelephoneNumber = 273 user.TelephoneNumber, 274 275 ShippingAddress = 276 user.ShippingAddress, 277 278 DateCreated = 279 user.DateCreated, 280 281 PointsCollected = 282 user.Consumer?.PointsCollected ?? 0 283 }; 284 285 286 return View(model); 287 } 288 289 290 // ========================================================= 291 // PROFILE - POST 292 // ========================================================= 293 294 [HttpPost] 295 [ValidateAntiForgeryToken] 296 public async Task<IActionResult> Profile( 297 ProfileViewModel model) 298 { 299 // Get the currently authenticated user. 300 var userIdClaim = User.FindFirst( 301 ClaimTypes.NameIdentifier); 302 303 if (userIdClaim == null) 304 return RedirectToAction(nameof(Login)); 305 306 if (!long.TryParse( 307 userIdClaim.Value, 308 out var userId)) 309 { 310 return RedirectToAction(nameof(Login)); 311 } 312 313 314 if (!ModelState.IsValid) 315 return View(model); 316 317 318 var user = _context.Users 319 .FirstOrDefault(x => 320 x.UserId == userId); 321 322 if (user == null) 323 return NotFound(); 324 325 326 // ===================================================== 327 // CHECK EMAIL 328 // ===================================================== 329 330 var emailExists = _context.Users.Any(x => 331 x.Email == model.Email && 332 x.UserId != userId); 333 334 if (emailExists) 335 { 336 ModelState.AddModelError( 337 nameof(model.Email), 338 "This email is already registered."); 339 340 return View(model); 341 } 342 343 344 // ===================================================== 345 // UPDATE USER 346 // ===================================================== 347 348 user.Email = model.Email; 349 350 user.TelephoneNumber = 351 model.TelephoneNumber; 352 353 user.ShippingAddress = 354 model.ShippingAddress; 355 356 357 _context.SaveChanges(); 358 359 360 TempData["Success"] = 361 "Your profile has been updated successfully."; 362 363 364 // ===================================================== 365 // REFRESH EMAIL CLAIM 366 // ===================================================== 367 368 var claims = new List<Claim> 369 { 370 new Claim( 371 ClaimTypes.NameIdentifier, 372 user.UserId.ToString()), 373 374 new Claim( 375 ClaimTypes.Name, 376 user.Username), 377 378 new Claim( 379 ClaimTypes.Email, 380 user.Email), 381 382 new Claim( 383 ClaimTypes.Role, 384 User.IsInRole("Admin") 385 ? "Admin" 386 : "Consumer") 387 }; 388 389 390 var identity = new ClaimsIdentity( 391 claims, 392 "Cookies"); 393 394 var principal = 395 new ClaimsPrincipal(identity); 396 397 398 await HttpContext.SignInAsync( 399 "Cookies", 400 principal); 401 402 403 // Keep session synchronized. 404 HttpContext.Session.SetString( 405 "Username", 406 user.Username); 407 408 409 return RedirectToAction(nameof(Profile)); 410 } 411 412 413 // ========================================================= 164 414 // LOGOUT 165 // ============================== 166 167 public IActionResult Logout() 168 { 415 // ========================================================= 416 417 [HttpPost] 418 [ValidateAntiForgeryToken] 419 public async Task<IActionResult> Logout() 420 { 421 await HttpContext.SignOutAsync("Cookies"); 422 169 423 HttpContext.Session.Clear(); 170 424 -
KernelRecordsMVC.Web/Controllers/AdminController.cs
r08aefc6 rfa0fbaf 1 using Microsoft.AspNetCore.Mvc; 1 using KernelRecordsMVC.Application.ViewModels; 2 using KernelRecordsMVC.Domain.Enums; 3 using KernelRecordsMVC.Infrastructure.Data; 4 using KernelRecordsMVC.Models; 5 using Microsoft.AspNetCore.Mvc; 2 6 using Microsoft.EntityFrameworkCore; 3 using KernelRecordsMVC.Data; 4 using KernelRecordsMVC.Models; 5 using KernelRecordsMVC.Domain.Enums; 6 using KernelRecordsMVC.Application.ViewModels; 7 using Microsoft.AspNetCore.Http; 8 9 namespace KernelRecordsMVC.Controllers; 7 8 namespace KernelRecordsMVC.Web.Controllers; 10 9 11 10 public class AdminController : Controller … … 19 18 20 19 21 // ========================================== 20 // ========================================================= 22 21 // ADMIN DASHBOARD 23 // ========================================== 22 // ========================================================= 24 23 25 24 [HttpGet] … … 29 28 return Forbid(); 30 29 30 31 ViewBag.ProductCount = 32 _context.Products.Count(); 33 34 ViewBag.ReleaseCount = 35 _context.Releases.Count(); 36 37 ViewBag.OutOfStockCount = 38 _context.Products.Count(x => 39 x.Stock <= 0); 40 41 ViewBag.LowStockCount = 42 _context.Products.Count(x => 43 x.Stock > 0 && 44 x.Stock <= 5); 45 46 31 47 return View(); 32 48 } 33 49 34 50 35 // ========================================== 36 // UC008 37 // NEW PRODUCT - GET 38 // ========================================== 51 // ========================================================= 52 // PRODUCTS 53 // ========================================================= 54 55 [HttpGet] 56 public IActionResult Products() 57 { 58 if (!IsProductManager()) 59 return Forbid(); 60 61 62 var products = _context.Products 63 .Include(p => p.Release) 64 .OrderBy(p => p.Release.Title) 65 .ThenBy(p => p.Format) 66 .ToList(); 67 68 69 return View(products); 70 } 71 72 73 // ========================================================= 74 // CREATE PRODUCT - GET 75 // ========================================================= 39 76 40 77 [HttpGet] … … 44 81 return Forbid(); 45 82 46 ViewBag.Releases = _context.Releases 47 .OrderBy(x => x.Title)48 .ToList(); 49 50 return View( new CreateProductViewModel());51 }52 53 54 // ========================================== 55 // UC00856 // NEWPRODUCT - POST57 // ========================================== 83 84 LoadReleases(); 85 86 87 return View( 88 new CreateProductViewModel()); 89 } 90 91 92 // ========================================================= 93 // CREATE PRODUCT - POST 94 // ========================================================= 58 95 59 96 [HttpPost] … … 65 102 return Forbid(); 66 103 104 67 105 if (!ModelState.IsValid) 68 106 { 69 107 LoadReleases(); 108 70 109 return View(model); 71 110 } 72 111 73 112 74 // Make sure the release actually exists.75 113 var release = _context.Releases 76 114 .FirstOrDefault(x => 77 115 x.ReleaseId == model.ReleaseId); 78 116 117 79 118 if (release == null) 80 119 { 81 120 ModelState.AddModelError( 82 "ReleaseId",121 nameof(model.ReleaseId), 83 122 "The selected release does not exist."); 84 123 124 85 125 LoadReleases(); 126 127 86 128 return View(model); 87 129 } 88 130 89 131 90 // Don't allow duplicate format for91 // the same release.92 var alreadyExists = _context.Products.Any(p =>93 p.ReleaseId == model.ReleaseId &&94 p.Format == model.Format); 132 var alreadyExists = 133 _context.Products.Any(p => 134 p.ReleaseId == model.ReleaseId && 135 p.Format == model.Format); 136 95 137 96 138 if (alreadyExists) 97 139 { 98 140 ModelState.AddModelError( 99 "Format",141 nameof(model.Format), 100 142 "This release already has a product in this format."); 101 143 144 102 145 LoadReleases(); 146 147 103 148 return View(model); 104 149 } 105 150 106 151 107 var product = new Product 108 { 109 ProductId = GetNextProductId(), 110 111 ReleaseId = model.ReleaseId, 112 113 Format = model.Format, 114 115 Price = model.Price, 116 117 ProductDescription = 118 model.ProductDescription, 119 120 Stock = model.Stock 121 }; 122 123 124 _context.Products.Add(product); 125 126 _context.SaveChanges(); 127 128 129 // Record the modification. 130 CreateModification( 131 ModificationType.CREATE, 132 product.ProductId); 133 134 135 TempData["Success"] = 136 "Product created successfully."; 137 138 139 return RedirectToAction( 140 nameof(Index)); 141 } 142 143 144 // ========================================== 145 // HELPERS 146 // ========================================== 152 using var transaction = 153 _context.Database.BeginTransaction(); 154 155 156 try 157 { 158 var product = new Product 159 { 160 ProductId = 161 GetNextProductId(), 162 163 ReleaseId = 164 model.ReleaseId, 165 166 Format = 167 model.Format, 168 169 Price = 170 model.Price, 171 172 ProductDescription = 173 model.ProductDescription, 174 175 Stock = 176 model.Stock 177 }; 178 179 180 _context.Products.Add(product); 181 182 _context.SaveChanges(); 183 184 185 CreateModification( 186 ModificationType.CREATE, 187 product.ProductId); 188 189 190 transaction.Commit(); 191 192 193 TempData["Success"] = 194 $"{release.Title} ({product.Format}) was created successfully."; 195 196 197 return RedirectToAction( 198 nameof(Products)); 199 } 200 catch 201 { 202 transaction.Rollback(); 203 204 throw; 205 } 206 } 207 208 209 // ========================================================= 210 // EDIT PRODUCT - GET 211 // ========================================================= 212 213 [HttpGet] 214 public IActionResult EditProduct(long id) 215 { 216 if (!IsProductManager()) 217 return Forbid(); 218 219 220 var product = _context.Products 221 .Include(p => p.Release) 222 .FirstOrDefault(p => 223 p.ProductId == id); 224 225 226 if (product == null) 227 return NotFound(); 228 229 230 var model = 231 new EditProductViewModel 232 { 233 ProductId = 234 product.ProductId, 235 236 ReleaseId = 237 product.ReleaseId, 238 239 ReleaseTitle = 240 product.Release.Title, 241 242 Format = 243 product.Format, 244 245 Price = 246 product.Price, 247 248 ProductDescription = 249 product.ProductDescription, 250 251 Stock = 252 product.Stock, 253 254 ModificationType = 255 ModificationType.UPDATE 256 }; 257 258 259 return View(model); 260 } 261 262 263 // ========================================================= 264 // EDIT PRODUCT - POST 265 // ========================================================= 266 267 [HttpPost] 268 [ValidateAntiForgeryToken] 269 public IActionResult EditProduct( 270 EditProductViewModel model) 271 { 272 if (!IsProductManager()) 273 return Forbid(); 274 275 276 var product = _context.Products 277 .Include(p => p.Release) 278 .FirstOrDefault(p => 279 p.ProductId == model.ProductId); 280 281 282 if (product == null) 283 return NotFound(); 284 285 286 // These values come from the database. 287 model.ReleaseId = 288 product.ReleaseId; 289 290 model.ReleaseTitle = 291 product.Release.Title; 292 293 model.Format = 294 product.Format; 295 296 297 // Display-only fields should not block 298 // validation of the submitted form. 299 ModelState.Remove( 300 nameof(model.ReleaseId)); 301 302 ModelState.Remove( 303 nameof(model.ReleaseTitle)); 304 305 ModelState.Remove( 306 nameof(model.Format)); 307 308 309 // ===================================================== 310 // DISCOUNT VALIDATION 311 // ===================================================== 312 313 if (model.ModificationType == 314 ModificationType.DISCOUNT) 315 { 316 if (!model.Discount.HasValue) 317 { 318 ModelState.AddModelError( 319 nameof(model.Discount), 320 "Please enter a discount percentage."); 321 } 322 else if ( 323 model.Discount.Value <= 0 || 324 model.Discount.Value >= 100) 325 { 326 ModelState.AddModelError( 327 nameof(model.Discount), 328 "Discount must be greater than 0 and less than 100."); 329 } 330 } 331 332 333 if (!ModelState.IsValid) 334 return View(model); 335 336 337 using var transaction = 338 _context.Database.BeginTransaction(); 339 340 341 try 342 { 343 // ================================================= 344 // APPLY DISCOUNT 345 // ================================================= 346 347 if (model.ModificationType == 348 ModificationType.DISCOUNT) 349 { 350 var discountPercentage = 351 model.Discount!.Value; 352 353 354 var oldPrice = 355 product.Price; 356 357 358 var discountAmount = 359 oldPrice * 360 (discountPercentage / 100m); 361 362 363 var newPrice = 364 oldPrice - discountAmount; 365 366 367 newPrice = 368 Math.Round( 369 newPrice, 370 2, 371 MidpointRounding.AwayFromZero); 372 373 374 // Product.Price becomes the 375 // actual current sale price. 376 product.Price = 377 newPrice; 378 379 380 _context.Products.Update( 381 product); 382 383 _context.SaveChanges(); 384 385 386 CreateModification( 387 ModificationType.DISCOUNT, 388 product.ProductId, 389 discountPercentage); 390 391 392 transaction.Commit(); 393 394 395 TempData["Success"] = 396 $"{discountPercentage:0.##}% discount applied. " + 397 $"Price changed from {oldPrice:C} to {newPrice:C}."; 398 399 400 return RedirectToAction( 401 nameof(Products)); 402 } 403 404 405 // ================================================= 406 // NORMAL UPDATE 407 // ================================================= 408 409 product.Price = 410 model.Price; 411 412 product.ProductDescription = 413 model.ProductDescription; 414 415 product.Stock = 416 model.Stock; 417 418 419 _context.Products.Update( 420 product); 421 422 _context.SaveChanges(); 423 424 425 CreateModification( 426 ModificationType.UPDATE, 427 product.ProductId); 428 429 430 transaction.Commit(); 431 432 433 TempData["Success"] = 434 $"{product.Release.Title} was updated successfully."; 435 436 437 return RedirectToAction( 438 nameof(Products)); 439 } 440 catch 441 { 442 transaction.Rollback(); 443 444 throw; 445 } 446 } 447 448 449 // ========================================================= 450 // CREATE RELEASE - GET 451 // ========================================================= 452 453 [HttpGet] 454 public IActionResult CreateRelease() 455 { 456 if (!IsAdmin()) 457 return Forbid(); 458 459 460 LoadArtists(); 461 462 463 var model = 464 new CreateReleaseViewModel 465 { 466 ReleaseDate = 467 DateTime.Today, 468 469 ReleaseType = 470 "ALBUM", 471 472 // Start an album with one empty track. 473 Tracks = 474 new List<CreateTrackViewModel> 475 { 476 new CreateTrackViewModel() 477 } 478 }; 479 480 481 return View(model); 482 } 483 484 485 // ========================================================= 486 // CREATE RELEASE - POST 487 // ========================================================= 488 489 [HttpPost] 490 [ValidateAntiForgeryToken] 491 public IActionResult CreateRelease( 492 CreateReleaseViewModel model) 493 { 494 if (!IsAdmin()) 495 return Forbid(); 496 497 498 // ===================================================== 499 // RELEASE TYPE VALIDATION 500 // ===================================================== 501 502 if (model.ReleaseType != "ALBUM" && 503 model.ReleaseType != "SINGLE") 504 { 505 ModelState.AddModelError( 506 nameof(model.ReleaseType), 507 "Please select Album or Single."); 508 } 509 510 511 // ===================================================== 512 // MAIN ARTIST VALIDATION 513 // ===================================================== 514 515 var mainArtistExists = 516 _context.Artists.Any(a => 517 a.ArtistId == 518 model.MainArtistId); 519 520 521 if (!mainArtistExists) 522 { 523 ModelState.AddModelError( 524 nameof(model.MainArtistId), 525 "Please select a valid main artist."); 526 } 527 528 529 // ===================================================== 530 // FEATURED ARTISTS 531 // ===================================================== 532 533 model.FeaturedArtistIds ??= 534 new List<long>(); 535 536 537 model.FeaturedArtistIds = 538 model.FeaturedArtistIds 539 .Distinct() 540 .ToList(); 541 542 543 // Do not allow the main artist 544 // to also be a featured artist. 545 model.FeaturedArtistIds.Remove( 546 model.MainArtistId); 547 548 549 // Validate that featured artists exist. 550 if (model.FeaturedArtistIds.Count > 0) 551 { 552 var validFeaturedArtistCount = 553 _context.Artists.Count(a => 554 model.FeaturedArtistIds 555 .Contains(a.ArtistId)); 556 557 558 if (validFeaturedArtistCount != 559 model.FeaturedArtistIds.Count) 560 { 561 ModelState.AddModelError( 562 nameof(model.FeaturedArtistIds), 563 "One or more featured artists are invalid."); 564 } 565 } 566 567 568 // ===================================================== 569 // SINGLE VALIDATION 570 // ===================================================== 571 572 if (model.ReleaseType == "SINGLE") 573 { 574 if (string.IsNullOrWhiteSpace( 575 model.SingleDuration)) 576 { 577 ModelState.AddModelError( 578 nameof(model.SingleDuration), 579 "Duration is required for a single."); 580 } 581 } 582 583 584 // ===================================================== 585 // ALBUM VALIDATION 586 // ===================================================== 587 588 if (model.ReleaseType == "ALBUM") 589 { 590 model.Tracks ??= 591 new List<CreateTrackViewModel>(); 592 593 594 // Remove completely empty rows. 595 model.Tracks = 596 model.Tracks 597 .Where(t => 598 !string.IsNullOrWhiteSpace( 599 t.SongName) || 600 !string.IsNullOrWhiteSpace( 601 t.SongDuration)) 602 .ToList(); 603 604 605 if (model.Tracks.Count == 0) 606 { 607 ModelState.AddModelError( 608 nameof(model.Tracks), 609 "An album must contain at least one track."); 610 } 611 612 613 for (var i = 0; 614 i < model.Tracks.Count; 615 i++) 616 { 617 var track = 618 model.Tracks[i]; 619 620 621 if (string.IsNullOrWhiteSpace( 622 track.SongName)) 623 { 624 ModelState.AddModelError( 625 $"Tracks[{i}].SongName", 626 "Track name is required."); 627 } 628 629 630 if (string.IsNullOrWhiteSpace( 631 track.SongDuration)) 632 { 633 ModelState.AddModelError( 634 $"Tracks[{i}].SongDuration", 635 "Track duration is required."); 636 } 637 638 639 track.ArtistIds ??= 640 new List<long>(); 641 642 643 track.ArtistIds = 644 track.ArtistIds 645 .Distinct() 646 .ToList(); 647 648 649 // Validate selected track artists. 650 if (track.ArtistIds.Count > 0) 651 { 652 var validTrackArtistCount = 653 _context.Artists.Count(a => 654 track.ArtistIds.Contains( 655 a.ArtistId)); 656 657 658 if (validTrackArtistCount != 659 track.ArtistIds.Count) 660 { 661 ModelState.AddModelError( 662 $"Tracks[{i}].ArtistIds", 663 "One or more selected track artists are invalid."); 664 } 665 } 666 } 667 } 668 669 670 if (!ModelState.IsValid) 671 { 672 LoadArtists(); 673 674 return View(model); 675 } 676 677 678 using var transaction = 679 _context.Database.BeginTransaction(); 680 681 682 try 683 { 684 // ================================================= 685 // CREATE RELEASE 686 // ================================================= 687 688 var release = 689 new Release 690 { 691 ReleaseId = 692 GetNextReleaseId(), 693 694 Title = 695 model.Title.Trim(), 696 697 RecordLabel = 698 string.IsNullOrWhiteSpace( 699 model.RecordLabel) 700 ? null 701 : model.RecordLabel.Trim(), 702 703 Genre = 704 model.Genre.Trim(), 705 706 ReleaseDate = 707 model.ReleaseDate, 708 709 CoverPhoto = 710 model.CoverPhoto.Trim() 711 }; 712 713 714 _context.Releases.Add( 715 release); 716 717 _context.SaveChanges(); 718 719 720 // ================================================= 721 // MAIN RELEASE ARTIST 722 // ================================================= 723 724 var mainReleaseArtist = 725 new ReleaseArtist 726 { 727 ReleaseId = 728 release.ReleaseId, 729 730 ArtistId = 731 model.MainArtistId, 732 733 ReleaseOrdinal = 1, 734 735 Type = 736 ArtistReleaseType.MAIN 737 }; 738 739 740 _context.ReleaseArtists.Add( 741 mainReleaseArtist); 742 743 744 // ================================================= 745 // FEATURED RELEASE ARTISTS 746 // ================================================= 747 748 long releaseOrdinal = 2; 749 750 751 foreach (var artistId 752 in model.FeaturedArtistIds) 753 { 754 var featuredArtist = 755 new ReleaseArtist 756 { 757 ReleaseId = 758 release.ReleaseId, 759 760 ArtistId = 761 artistId, 762 763 ReleaseOrdinal = 764 releaseOrdinal++, 765 766 Type = 767 ArtistReleaseType.FEATURE 768 }; 769 770 771 _context.ReleaseArtists.Add( 772 featuredArtist); 773 } 774 775 776 _context.SaveChanges(); 777 778 779 // ================================================= 780 // ALBUM 781 // ================================================= 782 783 if (model.ReleaseType == "ALBUM") 784 { 785 var album = 786 new Album 787 { 788 ReleaseId = 789 release.ReleaseId 790 }; 791 792 793 _context.Albums.Add( 794 album); 795 796 _context.SaveChanges(); 797 798 799 // ============================================= 800 // TRACKS 801 // ============================================= 802 803 foreach (var trackModel 804 in model.Tracks) 805 { 806 var song = 807 new Song 808 { 809 SongId = 810 GetNextSongId(), 811 812 SongName = 813 trackModel 814 .SongName 815 .Trim(), 816 817 SongDuration = 818 trackModel 819 .SongDuration 820 .Trim() 821 }; 822 823 824 _context.Songs.Add( 825 song); 826 827 _context.SaveChanges(); 828 829 830 // ========================================= 831 // LINK SONG TO ALBUM 832 // ========================================= 833 834 var albumSong = 835 new AlbumSong 836 { 837 AlbumId = 838 release.ReleaseId, 839 840 SongId = 841 song.SongId 842 }; 843 844 845 _context.AlbumSongs.Add( 846 albumSong); 847 848 849 // ========================================= 850 // SONG ARTISTS 851 // ========================================= 852 853 var trackArtistIds = 854 trackModel.ArtistIds 855 .Distinct() 856 .ToList(); 857 858 859 // If no track artists were selected, 860 // automatically use the release's 861 // main artist. 862 if (trackArtistIds.Count == 0) 863 { 864 trackArtistIds.Add( 865 model.MainArtistId); 866 } 867 868 869 long songOrdinal = 1; 870 871 872 foreach (var artistId 873 in trackArtistIds) 874 { 875 var songArtist = 876 new SongArtist 877 { 878 SongId = 879 song.SongId, 880 881 ArtistId = 882 artistId, 883 884 SongOrdinal = 885 songOrdinal++ 886 }; 887 888 889 _context.SongArtists.Add( 890 songArtist); 891 } 892 893 894 _context.SaveChanges(); 895 } 896 } 897 898 899 // ================================================= 900 // SINGLE 901 // ================================================= 902 903 else 904 { 905 var single = 906 new SingleRelease 907 { 908 ReleaseId = 909 release.ReleaseId, 910 911 Duration = 912 model.SingleDuration! 913 .Trim() 914 }; 915 916 917 _context.SingleReleases.Add( 918 single); 919 920 _context.SaveChanges(); 921 } 922 923 924 transaction.Commit(); 925 926 927 TempData["Success"] = 928 $"{release.Title} was created successfully."; 929 930 931 return RedirectToAction( 932 nameof(Index)); 933 } 934 catch 935 { 936 transaction.Rollback(); 937 938 throw; 939 } 940 } 941 942 943 // ========================================================= 944 // AUTHORIZATION HELPERS 945 // ========================================================= 147 946 148 947 private bool IsAdmin() 149 948 { 150 949 return HttpContext.Session 151 .GetString("Role") == "Admin"; 950 .GetString("Role") == 951 "Admin"; 152 952 } 153 953 … … 155 955 private bool IsProductManager() 156 956 { 157 var role = HttpContext.Session 158 .GetString("Role"); 159 160 var adminType = HttpContext.Session 161 .GetString("AdminType"); 957 var role = 958 HttpContext.Session 959 .GetString("Role"); 960 961 var adminType = 962 HttpContext.Session 963 .GetString("AdminType"); 964 162 965 163 966 return role == "Admin" && 164 (adminType == "PRODUCT_MANAGER" || 165 adminType == "SUPER_ADMIN"); 166 } 167 967 ( 968 adminType == "PRODUCT_MANAGER" || 969 adminType == "SUPER_ADMIN" 970 ); 971 } 972 973 974 // ========================================================= 975 // CURRENT ADMIN ID 976 // ========================================================= 168 977 169 978 private long? GetCurrentUserId() 170 979 { 171 var value = HttpContext.Session 172 .GetString("UserId"); 173 174 if (long.TryParse(value, out var userId)) 175 return userId; 176 177 return null; 178 } 179 980 var userId = 981 HttpContext.Session 982 .GetInt32("UserId"); 983 984 985 if (!userId.HasValue) 986 return null; 987 988 989 return userId.Value; 990 } 991 992 993 // ========================================================= 994 // LOAD RELEASES 995 // ========================================================= 996 997 private void LoadReleases() 998 { 999 ViewBag.Releases = 1000 _context.Releases 1001 .OrderBy(x => 1002 x.Title) 1003 .ToList(); 1004 } 1005 1006 1007 // ========================================================= 1008 // LOAD ARTISTS 1009 // ========================================================= 1010 1011 private void LoadArtists() 1012 { 1013 ViewBag.Artists = 1014 _context.Artists 1015 .OrderBy(a => 1016 a.ArtistName) 1017 .ToList(); 1018 } 1019 1020 1021 // ========================================================= 1022 // NEXT PRODUCT ID 1023 // ========================================================= 180 1024 181 1025 private long GetNextProductId() 182 1026 { 183 var maxId = _context.Products 184 .Select(x => (long?)x.ProductId) 185 .Max(); 1027 var maxId = 1028 _context.Products 1029 .Select(x => 1030 (long?)x.ProductId) 1031 .Max(); 1032 186 1033 187 1034 return (maxId ?? 0) + 1; … … 189 1036 190 1037 191 private void LoadReleases() 192 { 193 ViewBag.Releases = _context.Releases 194 .OrderBy(x => x.Title) 195 .ToList(); 196 } 197 1038 // ========================================================= 1039 // NEXT RELEASE ID 1040 // ========================================================= 1041 1042 private long GetNextReleaseId() 1043 { 1044 var maxId = 1045 _context.Releases 1046 .Select(x => 1047 (long?)x.ReleaseId) 1048 .Max(); 1049 1050 1051 return (maxId ?? 0) + 1; 1052 } 1053 1054 1055 // ========================================================= 1056 // NEXT SONG ID 1057 // ========================================================= 1058 1059 private long GetNextSongId() 1060 { 1061 var maxId = 1062 _context.Songs 1063 .Select(x => 1064 (long?)x.SongId) 1065 .Max(); 1066 1067 1068 return (maxId ?? 0) + 1; 1069 } 1070 1071 1072 // ========================================================= 1073 // NEXT MODIFICATION ID 1074 // ========================================================= 1075 1076 private long GetNextModificationId() 1077 { 1078 var maxId = 1079 _context.Modifications 1080 .Select(x => 1081 (long?)x.ModificationId) 1082 .Max(); 1083 1084 1085 return (maxId ?? 0) + 1; 1086 } 1087 1088 1089 // ========================================================= 1090 // CREATE MODIFICATION RECORD 1091 // ========================================================= 198 1092 199 1093 private void CreateModification( … … 202 1096 decimal? discount = null) 203 1097 { 204 var adminId = GetCurrentUserId(); 1098 var adminId = 1099 GetCurrentUserId(); 1100 205 1101 206 1102 if (adminId == null) 207 return; 208 209 210 var modification = new Modification 211 { 212 ModificationId = 213 GetNextModificationId(), 214 215 AdminId = adminId.Value, 216 217 DateModified = DateTime.Today, 218 219 TypeOfModification = type, 220 221 Discount = discount 222 }; 223 224 225 _context.Modifications.Add(modification); 1103 { 1104 throw new InvalidOperationException( 1105 "The current admin could not be identified."); 1106 } 1107 1108 1109 var modification = 1110 new Modification 1111 { 1112 ModificationId = 1113 GetNextModificationId(), 1114 1115 AdminId = 1116 adminId.Value, 1117 1118 DateModified = 1119 DateTime.Today, 1120 1121 TypeOfModification = 1122 type, 1123 1124 Discount = 1125 discount 1126 }; 1127 1128 1129 _context.Modifications.Add( 1130 modification); 226 1131 227 1132 _context.SaveChanges(); … … 234 1139 modification.ModificationId, 235 1140 236 ProductId = productId 1141 ProductId = 1142 productId 237 1143 }; 238 1144 … … 243 1149 _context.SaveChanges(); 244 1150 } 245 246 247 private long GetNextModificationId()248 {249 var maxId = _context.Modifications250 .Select(x => (long?)x.ModificationId)251 .Max();252 253 return (maxId ?? 0) + 1;254 }255 // ==========================================256 // UC009257 // MODIFY PRODUCT - GET258 // ==========================================259 260 [HttpGet]261 public IActionResult EditProduct(long id)262 {263 if (!IsProductManager())264 return Forbid();265 266 var product = _context.Products267 .Include(p => p.Release)268 .FirstOrDefault(p => p.ProductId == id);269 270 if (product == null)271 return NotFound();272 273 var model = new EditProductViewModel274 {275 ProductId = product.ProductId,276 ReleaseId = product.ReleaseId,277 ReleaseTitle = product.Release.Title,278 Format = product.Format,279 Price = product.Price,280 ProductDescription = product.ProductDescription,281 Stock = product.Stock282 };283 284 return View(model);285 }286 // ==========================================287 // UC009288 // MODIFY PRODUCT - POST289 // ==========================================290 291 [HttpPost]292 [ValidateAntiForgeryToken]293 public IActionResult EditProduct(294 EditProductViewModel model)295 {296 if (!IsProductManager())297 return Forbid();298 299 var product = _context.Products300 .Include(p => p.Release)301 .FirstOrDefault(p =>302 p.ProductId == model.ProductId);303 304 if (product == null)305 return NotFound();306 307 if (!ModelState.IsValid)308 {309 model.ReleaseTitle = product.Release.Title;310 model.ReleaseId = product.ReleaseId;311 model.Format = product.Format;312 313 return View(model);314 }315 316 317 // ==========================================318 // DISCOUNT319 // ==========================================320 321 if (model.ModificationType ==322 ModificationType.DISCOUNT)323 {324 if (!model.Discount.HasValue)325 {326 ModelState.AddModelError(327 "Discount",328 "Please enter a discount percentage.");329 330 model.ReleaseTitle = product.Release.Title;331 model.ReleaseId = product.ReleaseId;332 model.Format = product.Format;333 334 return View(model);335 }336 337 product.Price =338 product.Price *339 (1 - model.Discount.Value / 100m);340 }341 else342 {343 // ==========================================344 // NORMAL UPDATE345 // ==========================================346 347 product.Price = model.Price;348 349 product.ProductDescription =350 model.ProductDescription;351 352 product.Stock = model.Stock;353 }354 355 356 _context.SaveChanges();357 358 359 // ==========================================360 // RECORD MODIFICATION361 // ==========================================362 363 CreateModification(364 model.ModificationType,365 product.ProductId,366 model.Discount);367 368 369 TempData["Success"] =370 "Product modified successfully.";371 372 return RedirectToAction(nameof(Index));373 1151 } 374 [HttpGet]375 public IActionResult Products()376 {377 if (!IsProductManager())378 return Forbid();379 380 var products = _context.Products381 .Include(p => p.Release)382 .OrderBy(p => p.Release.Title)383 .ThenBy(p => p.Format)384 .ToList();385 386 return View(products);387 }388 } -
KernelRecordsMVC.Web/Controllers/HomeController.cs
r08aefc6 rfa0fbaf 1 1 using System.Diagnostics; 2 using KernelRecordsMVC.Models; 2 3 using Microsoft.AspNetCore.Mvc; 3 using KernelRecordsMVC.Models;4 using Microsoft.Extensions.Logging;5 4 6 namespace KernelRecordsMVC. Controllers;5 namespace KernelRecordsMVC.Web.Controllers; 7 6 8 7 public class HomeController : Controller -
KernelRecordsMVC.Web/Controllers/OrderController.cs
r08aefc6 rfa0fbaf 1 using Microsoft.AspNetCore.Http;2 3 namespace KernelRecordsMVC.Controllers;4 1 using KernelRecordsMVC.Application.ViewModels; 2 using KernelRecordsMVC.Domain.Enums; 3 using KernelRecordsMVC.Infrastructure.Data; 4 using KernelRecordsMVC.Models; 5 5 using Microsoft.AspNetCore.Mvc; 6 6 using Microsoft.EntityFrameworkCore; 7 using KernelRecordsMVC.Data; 8 using KernelRecordsMVC.Models; 9 using KernelRecordsMVC.Domain.Enums; 10 using KernelRecordsMVC.Application.ViewModels; 7 8 namespace KernelRecordsMVC.Web.Controllers; 11 9 12 10 public class OrderController : Controller … … 67 65 order = new Order 68 66 { 69 OrderId = GetNextOrderId(),70 67 UserId = userId.Value, 71 72 // The actual payment method will be73 // selected during checkout.74 68 PaymentMethod = PaymentMethodType.CARD, 75 76 69 PurchaseDate = DateTime.Today, 77 78 70 PointsEarned = 0, 79 80 71 PointsUsed = null, 81 82 72 Status = OrderStatusType.PENDING 83 73 }; … … 113 103 else 114 104 { 105 var discount = _context.ModificationProducts 106 .Include(mp => mp.Modification) 107 .Where(mp => 108 mp.ProductId == product.ProductId && 109 mp.Modification.TypeOfModification == 110 ModificationType.DISCOUNT && 111 mp.Modification.Discount.HasValue && 112 mp.Modification.Discount.Value > 0) 113 .Select(mp => mp.Modification) 114 .OrderByDescending(m => m.DateModified) 115 .FirstOrDefault(); 116 117 var priceAtPurchase = product.Price; 118 119 if (discount != null) 120 { 121 priceAtPurchase = 122 product.Price - 123 (product.Price * discount.Discount!.Value / 100m); 124 125 priceAtPurchase = Math.Round( 126 priceAtPurchase, 127 2); 128 } 129 115 130 var orderProduct = new OrderProduct 116 131 { … … 186 201 187 202 Stock = 188 op.Product.Stock 203 op.Product.Stock, 204 205 CoverPhoto = op.Product.Release.CoverPhoto 189 206 }) 190 207 .ToList(); … … 294 311 private long? GetUserId() 295 312 { 296 var value = 297 HttpContext.Session.GetString("UserId"); 298 299 if (long.TryParse(value, out var userId)) 300 return userId; 313 var userId = HttpContext.Session.GetInt32("UserId"); 314 315 if (userId.HasValue) 316 return userId.Value; 301 317 302 318 return null; 303 319 } 304 305 306 // ==========================================307 // ORDER ID308 // ==========================================309 310 private long GetNextOrderId()311 {312 var maxId = _context.Orders313 .Select(x => (long?)x.OrderId)314 .Max();315 316 return (maxId ?? 0) + 1;317 }318 320 } -
KernelRecordsMVC.Web/Controllers/ReleaseController.cs
r08aefc6 rfa0fbaf 1 using Microsoft.AspNetCore.Mvc; 1 using KernelRecordsMVC.Domain.Enums; 2 using KernelRecordsMVC.Infrastructure.Data; 3 using Microsoft.AspNetCore.Mvc; 2 4 using Microsoft.EntityFrameworkCore; 3 using KernelRecordsMVC.Data;4 5 5 namespace KernelRecordsMVC. Controllers;6 namespace KernelRecordsMVC.Web.Controllers; 6 7 7 8 public class ReleaseController : Controller … … 14 15 } 15 16 16 // UC003 - Browse Releases 17 public IActionResult Index(string? search, string? genre) 17 // ========================================================= 18 // BROWSE RELEASES 19 // ========================================================= 20 21 public IActionResult Index( 22 string? search, 23 string? genre, 24 string? format, 25 string? type, 26 string? sort) 18 27 { 19 28 var query = _context.Releases 20 29 .Include(r => r.Products) 21 30 .Include(r => r.ReleaseArtists) 22 .ThenInclude(ra => ra.Artist) 31 .ThenInclude(ra => ra.Artist) 32 .Include(r => r.Album) 33 .Include(r => r.SingleRelease) 23 34 .AsQueryable(); 24 35 36 // SEARCH 25 37 if (!string.IsNullOrWhiteSpace(search)) 26 38 { 39 search = search.Trim(); 40 27 41 query = query.Where(r => 28 r.Title.ToLower().Contains(search.ToLower())); 42 r.Title.ToLower().Contains(search.ToLower()) || 43 r.Genre.ToLower().Contains(search.ToLower()) || 44 r.ReleaseArtists.Any(ra => 45 ra.Artist.ArtistName.ToLower() 46 .Contains(search.ToLower()))); 29 47 } 30 48 49 // GENRE 31 50 if (!string.IsNullOrWhiteSpace(genre)) 32 51 { … … 35 54 } 36 55 37 var releases = query 38 .OrderBy(r => r.Title) 39 .ToList(); 56 // FORMAT 57 if (!string.IsNullOrWhiteSpace(format)) 58 { 59 query = query.Where(r => 60 r.Products.Any(p => 61 p.Format.ToString() == format)); 62 } 63 64 // TYPE 65 if (!string.IsNullOrWhiteSpace(type)) 66 { 67 if (type.Equals("Album", StringComparison.OrdinalIgnoreCase)) 68 { 69 query = query.Where(r => r.Album != null); 70 } 71 else if (type.Equals("Single", StringComparison.OrdinalIgnoreCase)) 72 { 73 query = query.Where(r => r.SingleRelease != null); 74 } 75 } 76 77 // SORT 78 query = sort switch 79 { 80 "titleDesc" => 81 query.OrderByDescending(r => r.Title), 82 83 "newest" => 84 query.OrderByDescending(r => r.ReleaseDate), 85 86 "oldest" => 87 query.OrderBy(r => r.ReleaseDate), 88 89 "format" => 90 query.OrderBy(r => 91 r.Products 92 .Select(p => p.Format.ToString()) 93 .FirstOrDefault()), 94 95 "priceAsc" => 96 query.OrderBy(r => 97 r.Products 98 .Select(p => (decimal?)p.Price) 99 .Min()), 100 101 "priceDesc" => 102 query.OrderByDescending(r => 103 r.Products 104 .Select(p => (decimal?)p.Price) 105 .Max()), 106 107 _ => 108 query.OrderBy(r => r.Title) 109 }; 110 111 var releases = query.ToList(); 40 112 41 113 ViewBag.Search = search; 42 114 ViewBag.Genre = genre; 115 ViewBag.Format = format; 116 ViewBag.Type = type; 117 ViewBag.Sort = sort; 43 118 44 119 ViewBag.Genres = _context.Releases … … 48 123 .ToList(); 49 124 125 ViewBag.Formats = Enum 126 .GetValues<ProductFormat>() 127 .Select(x => x.ToString()) 128 .ToList(); 129 50 130 return View(releases); 51 131 } 52 132 53 133 54 // Release details 134 // ========================================================= 135 // DETAILS 136 // ========================================================= 137 55 138 public IActionResult Details(long id) 56 139 { 57 140 var release = _context.Releases 58 141 .Include(r => r.Products) 142 59 143 .Include(r => r.ReleaseArtists) 60 .ThenInclude(ra => ra.Artist) 144 .ThenInclude(ra => ra.Artist) 145 61 146 .Include(r => r.Album) 62 .ThenInclude(a => a!.AlbumSongs) 63 .ThenInclude(x => x.Song) 147 .ThenInclude(a => a!.AlbumSongs) 148 .ThenInclude(x => x.Song) 149 .ThenInclude(s => s.SongArtists) 150 .ThenInclude(sa => sa.Artist) 151 64 152 .Include(r => r.SingleRelease) 65 .ThenInclude(s => s!.SingleFeatures) 66 .ThenInclude(x => x.Song) 153 67 154 .FirstOrDefault(r => r.ReleaseId == id); 68 155
Note:
See TracChangeset
for help on using the changeset viewer.
