source: KernelRecordsMVC.Web/Controllers/AdminController.cs@ 595d2e5

main
Last change on this file since 595d2e5 was 595d2e5, checked in by mmilevski <markomilevski3@…>, 4 weeks ago

Various UI bugfixes.

  • Property mode set to 100644
File size: 38.8 KB
Line 
1using KernelRecordsMVC.Application.ViewModels;
2using KernelRecordsMVC.Domain.Enums;
3using KernelRecordsMVC.Domain.Models;
4using KernelRecordsMVC.Infrastructure.Data;
5using KernelRecordsMVC.Models;
6using Microsoft.AspNetCore.Mvc;
7using Microsoft.EntityFrameworkCore;
8
9namespace KernelRecordsMVC.Web.Controllers;
10
11public class AdminController : Controller
12{
13 private readonly KernelRecordsContext _context;
14
15 public AdminController(KernelRecordsContext context)
16 {
17 _context = context;
18 }
19
20
21 // =========================================================
22 // ADMIN DASHBOARD
23 // =========================================================
24
25 [HttpGet]
26 public IActionResult Index()
27 {
28 if (!IsAdmin())
29 return Forbid();
30
31
32 ViewBag.ProductCount =
33 _context.Products.Count();
34
35 ViewBag.ReleaseCount =
36 _context.Releases.Count();
37
38 ViewBag.OutOfStockCount =
39 _context.Products.Count(x =>
40 x.Stock <= 0);
41
42 ViewBag.LowStockCount =
43 _context.Products.Count(x =>
44 x.Stock > 0 &&
45 x.Stock <= 5);
46
47
48 return View();
49 }
50
51
52 // =========================================================
53 // PRODUCTS
54 // =========================================================
55
56 [HttpGet]
57 public IActionResult Products()
58 {
59 if (!IsProductManager())
60 return Forbid();
61
62
63 var products = _context.Products
64 .Include(p => p.Release)
65 .OrderBy(p => p.Release.Title)
66 .ThenBy(p => p.Format)
67 .ToList();
68
69
70 return View(products);
71 }
72
73
74 // =========================================================
75 // CREATE PRODUCT - GET
76 // =========================================================
77
78 [HttpGet]
79 public IActionResult CreateProduct()
80 {
81 if (!IsProductManager())
82 return Forbid();
83
84
85 LoadReleases();
86
87
88 return View(
89 new CreateProductViewModel());
90 }
91
92
93 // =========================================================
94 // CREATE PRODUCT - POST
95 // =========================================================
96
97 [HttpPost]
98 [ValidateAntiForgeryToken]
99 public IActionResult CreateProduct(
100 CreateProductViewModel model)
101 {
102 if (!IsProductManager())
103 return Forbid();
104
105
106 if (!ModelState.IsValid)
107 {
108 LoadReleases();
109
110 return View(model);
111 }
112
113
114 var release = _context.Releases
115 .FirstOrDefault(x =>
116 x.ReleaseId == model.ReleaseId);
117
118
119 if (release == null)
120 {
121 ModelState.AddModelError(
122 nameof(model.ReleaseId),
123 "The selected release does not exist.");
124
125
126 LoadReleases();
127
128
129 return View(model);
130 }
131
132
133 var alreadyExists =
134 _context.Products.Any(p =>
135 p.ReleaseId == model.ReleaseId &&
136 p.Format == model.Format);
137
138
139 if (alreadyExists)
140 {
141 ModelState.AddModelError(
142 nameof(model.Format),
143 "This release already has a product in this format.");
144
145
146 LoadReleases();
147
148
149 return View(model);
150 }
151
152
153 using var transaction =
154 _context.Database.BeginTransaction();
155
156
157 try
158 {
159 var product = new Product
160 {
161 ProductId =
162 GetNextProductId(),
163
164 ReleaseId =
165 model.ReleaseId,
166
167 Format =
168 model.Format,
169
170 Price =
171 model.Price,
172
173 ProductDescription =
174 model.ProductDescription,
175
176 Stock =
177 model.Stock
178 };
179
180
181 _context.Products.Add(product);
182
183 _context.SaveChanges();
184
185
186 CreateModification(
187 ModificationType.CREATE,
188 product.ProductId);
189
190
191 transaction.Commit();
192
193
194 TempData["Success"] =
195 $"{release.Title} ({product.Format}) was created successfully.";
196
197
198 return RedirectToAction(
199 nameof(Products));
200 }
201 catch
202 {
203 transaction.Rollback();
204
205 throw;
206 }
207 }
208
209
210 // =========================================================
211 // EDIT PRODUCT - GET
212 // =========================================================
213
214 [HttpGet]
215 public IActionResult EditProduct(long id)
216 {
217 if (!IsProductManager())
218 return Forbid();
219
220
221 var product = _context.Products
222 .Include(p => p.Release)
223 .FirstOrDefault(p =>
224 p.ProductId == id);
225
226
227 if (product == null)
228 return NotFound();
229
230
231 var model =
232 new EditProductViewModel
233 {
234 ProductId =
235 product.ProductId,
236
237 ReleaseId =
238 product.ReleaseId,
239
240 ReleaseTitle =
241 product.Release.Title,
242
243 Format =
244 product.Format,
245
246 Price =
247 product.Price,
248
249 ProductDescription =
250 product.ProductDescription,
251
252 Stock =
253 product.Stock,
254
255 ModificationType =
256 ModificationType.UPDATE
257 };
258
259
260 return View(model);
261 }
262
263
264 // =========================================================
265 // EDIT PRODUCT - POST
266 // =========================================================
267
268 [HttpPost]
269 [ValidateAntiForgeryToken]
270 public IActionResult EditProduct(
271 EditProductViewModel model)
272 {
273 if (!IsProductManager())
274 return Forbid();
275
276
277 var product = _context.Products
278 .Include(p => p.Release)
279 .FirstOrDefault(p =>
280 p.ProductId == model.ProductId);
281
282
283 if (product == null)
284 return NotFound();
285
286
287 // These values come from the database.
288 model.ReleaseId =
289 product.ReleaseId;
290
291 model.ReleaseTitle =
292 product.Release.Title;
293
294 model.Format =
295 product.Format;
296
297
298 // Display-only fields should not block
299 // validation of the submitted form.
300 ModelState.Remove(
301 nameof(model.ReleaseId));
302
303 ModelState.Remove(
304 nameof(model.ReleaseTitle));
305
306 ModelState.Remove(
307 nameof(model.Format));
308
309
310 // =====================================================
311 // DISCOUNT VALIDATION
312 // =====================================================
313
314 if (model.ModificationType ==
315 ModificationType.DISCOUNT)
316 {
317 if (!model.Discount.HasValue)
318 {
319 ModelState.AddModelError(
320 nameof(model.Discount),
321 "Please enter a discount percentage.");
322 }
323 else if (
324 model.Discount.Value <= 0 ||
325 model.Discount.Value >= 100)
326 {
327 ModelState.AddModelError(
328 nameof(model.Discount),
329 "Discount must be greater than 0 and less than 100.");
330 }
331 }
332
333
334 if (!ModelState.IsValid)
335 return View(model);
336
337
338 using var transaction =
339 _context.Database.BeginTransaction();
340
341
342 try
343 {
344 // =================================================
345 // APPLY DISCOUNT
346 // =================================================
347
348 if (model.ModificationType ==
349 ModificationType.DISCOUNT)
350 {
351 var discountPercentage =
352 model.Discount!.Value;
353
354
355 var oldPrice =
356 product.Price;
357
358
359 var discountAmount =
360 oldPrice *
361 (discountPercentage / 100m);
362
363
364 var newPrice =
365 oldPrice - discountAmount;
366
367
368 newPrice =
369 Math.Round(
370 newPrice,
371 2,
372 MidpointRounding.AwayFromZero);
373
374
375 // Product.Price becomes the
376 // actual current sale price.
377 product.Price =
378 newPrice;
379
380
381 _context.Products.Update(
382 product);
383
384 _context.SaveChanges();
385
386
387 CreateModification(
388 ModificationType.DISCOUNT,
389 product.ProductId,
390 discountPercentage);
391
392
393 transaction.Commit();
394
395
396 TempData["Success"] =
397 $"{discountPercentage:0.##}% discount applied. " +
398 $"Price changed from {oldPrice:C} to {newPrice:C}.";
399
400
401 return RedirectToAction(
402 nameof(Products));
403 }
404
405
406 // =================================================
407 // NORMAL UPDATE
408 // =================================================
409
410 product.Price =
411 model.Price;
412
413 product.ProductDescription =
414 model.ProductDescription;
415
416 product.Stock =
417 model.Stock;
418
419
420 _context.Products.Update(
421 product);
422
423 _context.SaveChanges();
424
425
426 CreateModification(
427 ModificationType.UPDATE,
428 product.ProductId);
429
430
431 transaction.Commit();
432
433
434 TempData["Success"] =
435 $"{product.Release.Title} was updated successfully.";
436
437
438 return RedirectToAction(
439 nameof(Products));
440 }
441 catch
442 {
443 transaction.Rollback();
444
445 throw;
446 }
447 }
448
449
450 // =========================================================
451 // CREATE RELEASE - GET
452 // =========================================================
453
454 [HttpGet]
455 public IActionResult CreateRelease()
456 {
457 if (!IsAdmin())
458 return Forbid();
459
460
461 LoadArtists();
462
463
464 var model =
465 new CreateReleaseViewModel
466 {
467 ReleaseDate =
468 DateTime.Today,
469
470 ReleaseType =
471 "ALBUM",
472
473 // Start an album with one empty track.
474 Tracks =
475 new List<CreateTrackViewModel>
476 {
477 new CreateTrackViewModel()
478 }
479 };
480
481
482 return View(model);
483 }
484
485
486 // =========================================================
487 // CREATE RELEASE - POST
488 // =========================================================
489
490 [HttpPost]
491 [ValidateAntiForgeryToken]
492 public IActionResult CreateRelease(
493 CreateReleaseViewModel model)
494 {
495 if (!IsAdmin())
496 return Forbid();
497
498
499 // =========================================================
500 // NORMALIZE RELEASE TYPE
501 // =========================================================
502
503 model.ReleaseType =
504 model.ReleaseType?.Trim().ToUpperInvariant()
505 ?? string.Empty;
506
507
508 if (model.ReleaseType != "ALBUM" &&
509 model.ReleaseType != "SINGLE")
510 {
511 ModelState.AddModelError(
512 nameof(model.ReleaseType),
513 "Please select Album or Single.");
514 }
515
516
517 // =========================================================
518 // MAIN ARTIST VALIDATION
519 // =========================================================
520
521 var mainArtistExists =
522 _context.Artists.Any(a =>
523 a.ArtistId == model.MainArtistId);
524
525
526 if (!mainArtistExists)
527 {
528 ModelState.AddModelError(
529 nameof(model.MainArtistId),
530 "Please select a valid main artist.");
531 }
532
533
534 // =========================================================
535 // FEATURED ARTISTS
536 // =========================================================
537
538 model.FeaturedArtistIds ??=
539 new List<long>();
540
541
542 model.FeaturedArtistIds =
543 model.FeaturedArtistIds
544 .Distinct()
545 .Where(id =>
546 id != model.MainArtistId)
547 .ToList();
548
549
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 // Album track values do not apply to a single.
575 model.Tracks =
576 new List<CreateTrackViewModel>();
577
578
579 var trackModelStateKeys =
580 ModelState.Keys
581 .Where(key =>
582 key.StartsWith(
583 "Tracks[",
584 StringComparison.OrdinalIgnoreCase))
585 .ToList();
586
587
588 foreach (var key in trackModelStateKeys)
589 {
590 ModelState.Remove(key);
591 }
592
593
594 if (string.IsNullOrWhiteSpace(
595 model.SingleDuration))
596 {
597 ModelState.AddModelError(
598 nameof(model.SingleDuration),
599 "Duration is required for a single.");
600 }
601 else if (!System.Text.RegularExpressions.Regex.IsMatch(
602 model.SingleDuration.Trim(),
603 @"^\d{1,2}:[0-5]\d$"))
604 {
605 ModelState.AddModelError(
606 nameof(model.SingleDuration),
607 "Duration must be in MM:SS format, for example 03:42.");
608 }
609 }
610
611
612 // =========================================================
613 // ALBUM VALIDATION
614 // =========================================================
615
616 if (model.ReleaseType == "ALBUM")
617 {
618 // Single duration does not apply to an album.
619 ModelState.Remove(
620 nameof(model.SingleDuration));
621
622 model.SingleDuration =
623 null;
624
625
626 model.Tracks ??=
627 new List<CreateTrackViewModel>();
628
629
630 // The model binder may already have added validation
631 // errors for blank track rows. Remove those and validate
632 // the cleaned list ourselves.
633 var existingTrackKeys =
634 ModelState.Keys
635 .Where(key =>
636 key.StartsWith(
637 "Tracks[",
638 StringComparison.OrdinalIgnoreCase))
639 .ToList();
640
641
642 foreach (var key in existingTrackKeys)
643 {
644 ModelState.Remove(key);
645 }
646
647
648 model.Tracks =
649 model.Tracks
650 .Where(t =>
651 !string.IsNullOrWhiteSpace(
652 t.SongName) ||
653 !string.IsNullOrWhiteSpace(
654 t.SongDuration))
655 .ToList();
656
657
658 if (model.Tracks.Count == 0)
659 {
660 ModelState.AddModelError(
661 nameof(model.Tracks),
662 "An album must contain at least one track.");
663 }
664
665
666 for (var i = 0;
667 i < model.Tracks.Count;
668 i++)
669 {
670 var track =
671 model.Tracks[i];
672
673
674 if (string.IsNullOrWhiteSpace(
675 track.SongName))
676 {
677 ModelState.AddModelError(
678 $"Tracks[{i}].SongName",
679 $"Track {i + 1}: name is required.");
680 }
681
682
683 if (string.IsNullOrWhiteSpace(
684 track.SongDuration))
685 {
686 ModelState.AddModelError(
687 $"Tracks[{i}].SongDuration",
688 $"Track {i + 1}: duration is required.");
689 }
690
691
692 track.ArtistIds ??=
693 new List<long>();
694
695
696 track.ArtistIds =
697 track.ArtistIds
698 .Distinct()
699 .ToList();
700
701
702 if (track.ArtistIds.Count > 0)
703 {
704 var validTrackArtistCount =
705 _context.Artists.Count(a =>
706 track.ArtistIds.Contains(
707 a.ArtistId));
708
709
710 if (validTrackArtistCount !=
711 track.ArtistIds.Count)
712 {
713 ModelState.AddModelError(
714 $"Tracks[{i}].ArtistIds",
715 $"Track {i + 1}: one or more artists are invalid.");
716 }
717 }
718 }
719 }
720
721
722 // =========================================================
723 // VALIDATION FAILED
724 // =========================================================
725
726 if (!ModelState.IsValid)
727 {
728 LoadArtists();
729
730 return View(model);
731 }
732
733
734 // =========================================================
735 // DATABASE TRANSACTION
736 // =========================================================
737
738 using var transaction =
739 _context.Database.BeginTransaction();
740
741
742 try
743 {
744 // =====================================================
745 // RELEASE
746 // =====================================================
747
748 var release =
749 new Release
750 {
751 ReleaseId =
752 GetNextReleaseId(),
753
754 Title =
755 model.Title.Trim(),
756
757 RecordLabel =
758 string.IsNullOrWhiteSpace(
759 model.RecordLabel)
760 ? null
761 : model.RecordLabel.Trim(),
762
763 Genre =
764 model.Genre.Trim(),
765
766 ReleaseDate =
767 model.ReleaseDate,
768
769 CoverPhoto =
770 model.CoverPhoto.Trim()
771 };
772
773
774 _context.Releases.Add(
775 release);
776
777 _context.SaveChanges();
778
779
780 // =====================================================
781 // RELEASE ARTISTS
782 // =====================================================
783
784 _context.ReleaseArtists.Add(
785 new ReleaseArtist
786 {
787 ReleaseId =
788 release.ReleaseId,
789
790 ArtistId =
791 model.MainArtistId,
792
793 ReleaseOrdinal =
794 1,
795
796 Type =
797 ArtistReleaseType.MAIN
798 });
799
800
801 long releaseOrdinal = 2;
802
803
804 foreach (var artistId
805 in model.FeaturedArtistIds)
806 {
807 _context.ReleaseArtists.Add(
808 new ReleaseArtist
809 {
810 ReleaseId =
811 release.ReleaseId,
812
813 ArtistId =
814 artistId,
815
816 ReleaseOrdinal =
817 releaseOrdinal++,
818
819 Type =
820 ArtistReleaseType.FEATURE
821 });
822 }
823
824
825 _context.SaveChanges();
826
827
828 // =====================================================
829 // ALBUM
830 // =====================================================
831
832 if (model.ReleaseType == "ALBUM")
833 {
834 var album =
835 new Album
836 {
837 ReleaseId =
838 release.ReleaseId
839 };
840
841
842 _context.Albums.Add(
843 album);
844
845 _context.SaveChanges();
846
847
848 // Get the next song ID once for this request.
849 // Each track then gets a unique incremented ID.
850 var nextSongId =
851 GetNextSongId();
852
853
854 foreach (var trackModel
855 in model.Tracks)
856 {
857 var song =
858 new Song
859 {
860 SongId =
861 nextSongId++,
862
863 SongName =
864 trackModel.SongName.Trim(),
865
866 SongDuration =
867 trackModel.SongDuration.Trim()
868 };
869
870
871 _context.Songs.Add(
872 song);
873
874 _context.SaveChanges();
875
876
877 _context.AlbumSongs.Add(
878 new AlbumSong
879 {
880 AlbumId =
881 release.ReleaseId,
882
883 SongId =
884 song.SongId
885 });
886
887
888 var trackArtistIds =
889 trackModel.ArtistIds
890 .Distinct()
891 .ToList();
892
893
894 // Default to the main release artist when
895 // no track-specific artists were selected.
896 if (trackArtistIds.Count == 0)
897 {
898 trackArtistIds.Add(
899 model.MainArtistId);
900 }
901
902
903 long songOrdinal = 1;
904
905
906 foreach (var artistId
907 in trackArtistIds)
908 {
909 _context.SongArtists.Add(
910 new SongArtist
911 {
912 SongId =
913 song.SongId,
914
915 ArtistId =
916 artistId,
917
918 SongOrdinal =
919 songOrdinal++
920 });
921 }
922
923
924 _context.SaveChanges();
925 }
926 }
927
928
929 // =====================================================
930 // SINGLE
931 // =====================================================
932
933 else
934 {
935 _context.SingleReleases.Add(
936 new SingleRelease
937 {
938 ReleaseId =
939 release.ReleaseId,
940
941 Duration =
942 model.SingleDuration!.Trim()
943 });
944
945
946 _context.SaveChanges();
947 }
948
949
950 transaction.Commit();
951
952
953 TempData["Success"] =
954 $"{release.Title} was created successfully.";
955
956
957 return RedirectToAction(
958 nameof(Releases));
959 }
960 catch (Exception ex)
961 {
962 transaction.Rollback();
963
964
965 Console.WriteLine(
966 "CREATE RELEASE FAILED:");
967
968 Console.WriteLine(ex);
969
970
971 ModelState.AddModelError(
972 string.Empty,
973 ex.InnerException?.Message
974 ?? ex.Message);
975
976
977 LoadArtists();
978
979
980 return View(model);
981 }
982 }
983
984
985 // =========================================================
986 // AUTHORIZATION HELPERS
987 // =========================================================
988
989 private bool IsAdmin()
990 {
991 return HttpContext.Session
992 .GetString("Role") ==
993 "Admin";
994 }
995
996
997 private bool IsProductManager()
998 {
999 var role =
1000 HttpContext.Session
1001 .GetString("Role");
1002
1003 var adminType =
1004 HttpContext.Session
1005 .GetString("AdminType");
1006
1007
1008 return role == "Admin" &&
1009 (
1010 adminType == "PRODUCT_MANAGER" ||
1011 adminType == "SUPER_ADMIN"
1012 );
1013 }
1014
1015
1016 // =========================================================
1017 // CURRENT ADMIN ID
1018 // =========================================================
1019
1020 private long? GetCurrentUserId()
1021 {
1022 var userId =
1023 HttpContext.Session
1024 .GetInt32("UserId");
1025
1026
1027 if (!userId.HasValue)
1028 return null;
1029
1030
1031 return userId.Value;
1032 }
1033
1034
1035 // =========================================================
1036 // LOAD RELEASES
1037 // =========================================================
1038
1039 private void LoadReleases()
1040 {
1041 ViewBag.Releases =
1042 _context.Releases
1043 .OrderBy(x =>
1044 x.Title)
1045 .ToList();
1046 }
1047
1048
1049 // =========================================================
1050 // LOAD ARTISTS
1051 // =========================================================
1052
1053 private void LoadArtists()
1054 {
1055 ViewBag.Artists =
1056 _context.Artists
1057 .OrderBy(a =>
1058 a.ArtistName)
1059 .ToList();
1060 }
1061
1062
1063 // =========================================================
1064 // NEXT PRODUCT ID
1065 // =========================================================
1066
1067 private long GetNextProductId()
1068 {
1069 var maxId =
1070 _context.Products
1071 .Select(x =>
1072 (long?)x.ProductId)
1073 .Max();
1074
1075
1076 return (maxId ?? 0) + 1;
1077 }
1078
1079
1080 // =========================================================
1081 // NEXT RELEASE ID
1082 // =========================================================
1083
1084 private long GetNextReleaseId()
1085 {
1086 var maxId =
1087 _context.Releases
1088 .Select(x =>
1089 (long?)x.ReleaseId)
1090 .Max();
1091
1092
1093 return (maxId ?? 0) + 1;
1094 }
1095
1096
1097 // =========================================================
1098 // NEXT SONG ID
1099 // =========================================================
1100
1101 private long GetNextSongId()
1102 {
1103 var maxId =
1104 _context.Songs
1105 .Select(x =>
1106 (long?)x.SongId)
1107 .Max();
1108
1109
1110 return (maxId ?? 0) + 1;
1111 }
1112
1113
1114 // =========================================================
1115 // NEXT MODIFICATION ID
1116 // =========================================================
1117
1118 private long GetNextModificationId()
1119 {
1120 var maxId =
1121 _context.Modifications
1122 .Select(x =>
1123 (long?)x.ModificationId)
1124 .Max();
1125
1126
1127 return (maxId ?? 0) + 1;
1128 }
1129
1130
1131 // =========================================================
1132 // CREATE MODIFICATION RECORD
1133 // =========================================================
1134
1135 private void CreateModification(
1136 ModificationType type,
1137 long productId,
1138 decimal? discount = null)
1139 {
1140 var adminId =
1141 GetCurrentUserId();
1142
1143
1144 if (adminId == null)
1145 {
1146 throw new InvalidOperationException(
1147 "The current admin could not be identified.");
1148 }
1149
1150
1151 var modification =
1152 new Modification
1153 {
1154 ModificationId =
1155 GetNextModificationId(),
1156
1157 AdminId =
1158 adminId.Value,
1159
1160 DateModified =
1161 DateTime.Today,
1162
1163 TypeOfModification =
1164 type,
1165
1166 Discount =
1167 discount
1168 };
1169
1170
1171 _context.Modifications.Add(
1172 modification);
1173
1174 _context.SaveChanges();
1175
1176
1177 var modificationProduct =
1178 new ModificationProduct
1179 {
1180 ModificationId =
1181 modification.ModificationId,
1182
1183 ProductId =
1184 productId
1185 };
1186
1187
1188 _context.ModificationProducts.Add(
1189 modificationProduct);
1190
1191 _context.SaveChanges();
1192 }
1193 // =========================================================
1194// ARTISTS
1195// =========================================================
1196
1197[HttpGet]
1198public IActionResult Artists()
1199{
1200 if (!IsAdmin())
1201 return Forbid();
1202
1203 var artists = _context.Artists
1204 .OrderBy(a => a.ArtistName)
1205 .ToList();
1206
1207 return View(artists);
1208}
1209
1210
1211// =========================================================
1212// CREATE ARTIST - GET
1213// =========================================================
1214
1215[HttpGet]
1216public IActionResult CreateArtist()
1217{
1218 if (!IsAdmin())
1219 return Forbid();
1220
1221 return View(new CreateArtistViewModel());
1222}
1223
1224
1225// =========================================================
1226// CREATE ARTIST - POST
1227// =========================================================
1228
1229[HttpPost]
1230[ValidateAntiForgeryToken]
1231public IActionResult CreateArtist(CreateArtistViewModel model)
1232{
1233 if (!IsAdmin())
1234 return Forbid();
1235
1236 if (!ModelState.IsValid)
1237 return View(model);
1238
1239 var duplicateExists =
1240 _context.Artists.Any(a =>
1241 a.ArtistName.ToLower() ==
1242 model.ArtistName.Trim().ToLower());
1243
1244 if (duplicateExists)
1245 {
1246 ModelState.AddModelError(
1247 nameof(model.ArtistName),
1248 "An artist with this name already exists.");
1249
1250 return View(model);
1251 }
1252
1253 var artist = new Artist
1254 {
1255 ArtistId = GetNextArtistId(),
1256
1257 ArtistName =
1258 model.ArtistName.Trim(),
1259
1260 ArtistDescription =
1261 string.IsNullOrWhiteSpace(model.ArtistDescription)
1262 ? null
1263 : model.ArtistDescription.Trim(),
1264
1265 ArtistPhoto =
1266 string.IsNullOrWhiteSpace(model.ArtistPhoto)
1267 ? null
1268 : model.ArtistPhoto.Trim()
1269 };
1270
1271 _context.Artists.Add(artist);
1272 _context.SaveChanges();
1273
1274 TempData["Success"] =
1275 $"{artist.ArtistName} was created successfully.";
1276
1277 return RedirectToAction(nameof(Artists));
1278}
1279
1280
1281// =========================================================
1282// EDIT ARTIST - GET
1283// =========================================================
1284
1285[HttpGet]
1286public IActionResult EditArtist(long id)
1287{
1288 if (!IsAdmin())
1289 return Forbid();
1290
1291 var artist = _context.Artists
1292 .FirstOrDefault(a =>
1293 a.ArtistId == id);
1294
1295 if (artist == null)
1296 return NotFound();
1297
1298 var model = new EditArtistViewModel
1299 {
1300 ArtistId =
1301 artist.ArtistId,
1302
1303 ArtistName =
1304 artist.ArtistName,
1305
1306 ArtistDescription =
1307 artist.ArtistDescription,
1308
1309 ArtistPhoto =
1310 artist.ArtistPhoto
1311 };
1312
1313 return View(model);
1314}
1315
1316
1317// =========================================================
1318// EDIT ARTIST - POST
1319// =========================================================
1320
1321[HttpPost]
1322[ValidateAntiForgeryToken]
1323public IActionResult EditArtist(EditArtistViewModel model)
1324{
1325 if (!IsAdmin())
1326 return Forbid();
1327
1328 var artist = _context.Artists
1329 .FirstOrDefault(a =>
1330 a.ArtistId == model.ArtistId);
1331
1332 if (artist == null)
1333 return NotFound();
1334
1335 if (!ModelState.IsValid)
1336 return View(model);
1337
1338 var duplicateExists =
1339 _context.Artists.Any(a =>
1340 a.ArtistId != model.ArtistId &&
1341 a.ArtistName.ToLower() ==
1342 model.ArtistName.Trim().ToLower());
1343
1344 if (duplicateExists)
1345 {
1346 ModelState.AddModelError(
1347 nameof(model.ArtistName),
1348 "Another artist already uses this name.");
1349
1350 return View(model);
1351 }
1352
1353 artist.ArtistName =
1354 model.ArtistName.Trim();
1355
1356 artist.ArtistDescription =
1357 string.IsNullOrWhiteSpace(model.ArtistDescription)
1358 ? null
1359 : model.ArtistDescription.Trim();
1360
1361 artist.ArtistPhoto =
1362 string.IsNullOrWhiteSpace(model.ArtistPhoto)
1363 ? null
1364 : model.ArtistPhoto.Trim();
1365
1366 _context.Artists.Update(artist);
1367 _context.SaveChanges();
1368
1369 TempData["Success"] =
1370 $"{artist.ArtistName} was updated successfully.";
1371
1372 return RedirectToAction(nameof(Artists));
1373}
1374private long GetNextArtistId()
1375{
1376 var maxId = _context.Artists
1377 .Select(x => (long?)x.ArtistId)
1378 .Max();
1379
1380 return (maxId ?? 0) + 1;
1381}
1382// =========================================================
1383// RELEASES
1384// =========================================================
1385
1386[HttpGet]
1387public IActionResult Releases()
1388{
1389 if (!IsAdmin())
1390 return Forbid();
1391
1392 var releases = _context.Releases
1393 .Include(r => r.ReleaseArtists)
1394 .ThenInclude(ra => ra.Artist)
1395 .OrderBy(r => r.Title)
1396 .ToList();
1397
1398 return View(releases);
1399}
1400
1401
1402// =========================================================
1403// EDIT RELEASE - GET
1404// =========================================================
1405
1406 [HttpGet]
1407 public IActionResult EditRelease(long id)
1408 {
1409 if (!IsAdmin())
1410 return Forbid();
1411
1412
1413 var release = _context.Releases
1414 .Include(r => r.ReleaseArtists)
1415 .ThenInclude(ra => ra.Artist)
1416 .FirstOrDefault(r =>
1417 r.ReleaseId == id);
1418
1419
1420 if (release == null)
1421 return NotFound();
1422
1423
1424 var mainArtist = release.ReleaseArtists
1425 .FirstOrDefault(ra =>
1426 ra.Type == ArtistReleaseType.MAIN);
1427
1428
1429 var featuredArtists = release.ReleaseArtists
1430 .Where(ra =>
1431 ra.Type == ArtistReleaseType.FEATURE)
1432 .OrderBy(ra => ra.ReleaseOrdinal)
1433 .Select(ra => ra.ArtistId)
1434 .ToList();
1435
1436
1437 var model = new EditReleaseViewModel
1438 {
1439 ReleaseId =
1440 release.ReleaseId,
1441
1442 Title =
1443 release.Title,
1444
1445 RecordLabel =
1446 release.RecordLabel,
1447
1448 Genre =
1449 release.Genre,
1450
1451 ReleaseDate =
1452 release.ReleaseDate,
1453
1454 CoverPhoto =
1455 release.CoverPhoto,
1456
1457 MainArtistId =
1458 mainArtist?.ArtistId ?? 0,
1459
1460 FeaturedArtistIds =
1461 featuredArtists
1462 };
1463
1464
1465 LoadArtists();
1466
1467
1468 return View(model);
1469 }
1470
1471
1472// =========================================================
1473// EDIT RELEASE - POST
1474// =========================================================
1475
1476[HttpPost]
1477[ValidateAntiForgeryToken]
1478public IActionResult EditRelease(
1479 EditReleaseViewModel model)
1480{
1481 if (!IsAdmin())
1482 return Forbid();
1483
1484
1485 var release = _context.Releases
1486 .Include(r => r.ReleaseArtists)
1487 .FirstOrDefault(r =>
1488 r.ReleaseId == model.ReleaseId);
1489
1490
1491 if (release == null)
1492 return NotFound();
1493
1494
1495 // ==========================================
1496 // ARTIST VALIDATION
1497 // ==========================================
1498
1499 var mainArtistExists =
1500 _context.Artists.Any(a =>
1501 a.ArtistId == model.MainArtistId);
1502
1503
1504 if (!mainArtistExists)
1505 {
1506 ModelState.AddModelError(
1507 nameof(model.MainArtistId),
1508 "Please select a valid main artist.");
1509 }
1510
1511
1512 model.FeaturedArtistIds ??=
1513 new List<long>();
1514
1515
1516 model.FeaturedArtistIds =
1517 model.FeaturedArtistIds
1518 .Distinct()
1519 .Where(id =>
1520 id != model.MainArtistId)
1521 .ToList();
1522
1523
1524 if (model.FeaturedArtistIds.Count > 0)
1525 {
1526 var validFeaturedCount =
1527 _context.Artists.Count(a =>
1528 model.FeaturedArtistIds
1529 .Contains(a.ArtistId));
1530
1531
1532 if (validFeaturedCount !=
1533 model.FeaturedArtistIds.Count)
1534 {
1535 ModelState.AddModelError(
1536 nameof(model.FeaturedArtistIds),
1537 "One or more featured artists are invalid.");
1538 }
1539 }
1540
1541
1542 if (!ModelState.IsValid)
1543 {
1544 LoadArtists();
1545
1546 return View(model);
1547 }
1548
1549
1550 using var transaction =
1551 _context.Database.BeginTransaction();
1552
1553
1554 try
1555 {
1556 // ==========================================
1557 // UPDATE RELEASE INFORMATION
1558 // ==========================================
1559
1560 release.Title =
1561 model.Title.Trim();
1562
1563 release.RecordLabel =
1564 string.IsNullOrWhiteSpace(
1565 model.RecordLabel)
1566 ? null
1567 : model.RecordLabel.Trim();
1568
1569 release.Genre =
1570 model.Genre.Trim();
1571
1572 release.ReleaseDate =
1573 model.ReleaseDate;
1574
1575 release.CoverPhoto =
1576 model.CoverPhoto.Trim();
1577
1578
1579 _context.Releases.Update(
1580 release);
1581
1582
1583 // ==========================================
1584 // REMOVE OLD RELEASE ARTIST LINKS
1585 // ==========================================
1586
1587 var existingArtistLinks =
1588 release.ReleaseArtists.ToList();
1589
1590
1591 _context.ReleaseArtists.RemoveRange(
1592 existingArtistLinks);
1593
1594
1595 _context.SaveChanges();
1596
1597
1598 // ==========================================
1599 // ADD MAIN ARTIST
1600 // ==========================================
1601
1602 _context.ReleaseArtists.Add(
1603 new ReleaseArtist
1604 {
1605 ReleaseId =
1606 release.ReleaseId,
1607
1608 ArtistId =
1609 model.MainArtistId,
1610
1611 ReleaseOrdinal =
1612 1,
1613
1614 Type =
1615 ArtistReleaseType.MAIN
1616 });
1617
1618
1619 // ==========================================
1620 // ADD FEATURED ARTISTS
1621 // ==========================================
1622
1623 long ordinal = 2;
1624
1625
1626 foreach (var artistId
1627 in model.FeaturedArtistIds)
1628 {
1629 _context.ReleaseArtists.Add(
1630 new ReleaseArtist
1631 {
1632 ReleaseId =
1633 release.ReleaseId,
1634
1635 ArtistId =
1636 artistId,
1637
1638 ReleaseOrdinal =
1639 ordinal++,
1640
1641 Type =
1642 ArtistReleaseType.FEATURE
1643 });
1644 }
1645
1646
1647 _context.SaveChanges();
1648
1649
1650 transaction.Commit();
1651
1652
1653 TempData["Success"] =
1654 $"{release.Title} was updated successfully.";
1655
1656
1657 return RedirectToAction(
1658 nameof(Releases));
1659 }
1660 catch
1661 {
1662 transaction.Rollback();
1663
1664 throw;
1665 }
1666}
1667}
Note: See TracBrowser for help on using the repository browser.