Ignore:
Timestamp:
09/01/26 06:11:34 (4 weeks ago)
Author:
mmilevski <markomilevski3@…>
Branches:
main
Children:
d2eccb3
Parents:
08aefc6
Message:

Added major improvements.

Location:
KernelRecordsMVC.Web/Controllers
Files:
3 added
5 edited

Legend:

Unmodified
Added
Removed
  • KernelRecordsMVC.Web/Controllers/AccountController.cs

    r08aefc6 rfa0fbaf  
    1 using Microsoft.AspNetCore.Mvc;
    2 using KernelRecordsMVC.Data;
     1using System.Security.Claims;
     2using KernelRecordsMVC.Application.ViewModels;
     3using KernelRecordsMVC.Infrastructure.Data;
    34using KernelRecordsMVC.Models;
    4 using KernelRecordsMVC.Application.ViewModels;
    5 using Microsoft.AspNetCore.Http;
    6 
    7 namespace KernelRecordsMVC.Controllers;
     5using Microsoft.AspNetCore.Authentication;
     6using Microsoft.AspNetCore.Mvc;
     7using Microsoft.EntityFrameworkCore;
     8
     9namespace KernelRecordsMVC.Web.Controllers;
    810
    911public class AccountController : Controller
    … …  
    1618    }
    1719
    18     // ==============================
     20
     21    // =========================================================
    1922    // REGISTER - GET
    20     // ==============================
     23    // =========================================================
    2124
    2225    [HttpGet]
    … …  
    2730
    2831
    29     // ==============================
     32    // =========================================================
    3033    // REGISTER - POST
    31     // ==============================
     34    // =========================================================
    3235
    3336    [HttpPost]
    … …  
    6164            Username = model.Username,
    6265
    63             // We'll replace this with proper password hashing
    64             // in the security cleanup section.
     66            // TODO:
     67            // Replace with password hashing later.
    6568            Password = model.Password,
    6669
    … …  
    7477        _context.SaveChanges();
    7578
     79
    7680        // Every normal registered user is a Consumer.
    7781        var consumer = new Consumer
    … …  
    8993
    9094
    91     // ==============================
     95    // =========================================================
    9296    // LOGIN - GET
    93     // ==============================
     97    // =========================================================
    9498
    9599    [HttpGet]
    … …  
    100104
    101105
    102     // ==============================
     106    // =========================================================
    103107    // LOGIN - POST
    104     // ==============================
     108    // =========================================================
    105109
    106110    [HttpPost]
    107111    [ValidateAntiForgeryToken]
    108     public IActionResult Login(LoginViewModel model)
     112    public async Task<IActionResult> Login(
     113        LoginViewModel model)
    109114    {
    110115        if (!ModelState.IsValid)
    … …  
    125130        }
    126131
     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
    127206        HttpContext.Session.SetInt32(
    128207            "UserId",
    … …  
    133212            user.Username);
    134213
    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);
    139217
    140218        if (admin != null)
    141219        {
    142             HttpContext.Session.SetString(
    143                 "Role",
    144                 "Admin");
    145 
    146220            HttpContext.Session.SetString(
    147221                "AdminType",
    148222                admin.Type.ToString());
    149223        }
    150         else
    151         {
    152             HttpContext.Session.SetString(
    153                 "Role",
    154                 "Consumer");
    155         }
     224
    156225
    157226        return RedirectToAction(
    … …  
    161230
    162231
    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    // =========================================================
    164414    // 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
    169423        HttpContext.Session.Clear();
    170424
  • KernelRecordsMVC.Web/Controllers/AdminController.cs

    r08aefc6 rfa0fbaf  
    1 using Microsoft.AspNetCore.Mvc;
     1using KernelRecordsMVC.Application.ViewModels;
     2using KernelRecordsMVC.Domain.Enums;
     3using KernelRecordsMVC.Infrastructure.Data;
     4using KernelRecordsMVC.Models;
     5using Microsoft.AspNetCore.Mvc;
    26using 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
     8namespace KernelRecordsMVC.Web.Controllers;
    109
    1110public class AdminController : Controller
    … …  
    1918
    2019
    21     // ==========================================
     20    // =========================================================
    2221    // ADMIN DASHBOARD
    23     // ==========================================
     22    // =========================================================
    2423
    2524    [HttpGet]
    … …  
    2928            return Forbid();
    3029
     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
    3147        return View();
    3248    }
    3349
    3450
    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    // =========================================================
    3976
    4077    [HttpGet]
    … …  
    4481            return Forbid();
    4582
    46         ViewBag.Releases = _context.Releases
    47             .OrderBy(x => x.Title)
    48             .ToList();
    49 
    50         return View(new CreateProductViewModel());
    51     }
    52 
    53 
    54     // ==========================================
    55     // UC008
    56     // NEW PRODUCT - POST
    57     // ==========================================
     83
     84        LoadReleases();
     85
     86
     87        return View(
     88            new CreateProductViewModel());
     89    }
     90
     91
     92    // =========================================================
     93    // CREATE PRODUCT - POST
     94    // =========================================================
    5895
    5996    [HttpPost]
    … …  
    65102            return Forbid();
    66103
     104
    67105        if (!ModelState.IsValid)
    68106        {
    69107            LoadReleases();
     108
    70109            return View(model);
    71110        }
    72111
    73112
    74         // Make sure the release actually exists.
    75113        var release = _context.Releases
    76114            .FirstOrDefault(x =>
    77115                x.ReleaseId == model.ReleaseId);
    78116
     117
    79118        if (release == null)
    80119        {
    81120            ModelState.AddModelError(
    82                 "ReleaseId",
     121                nameof(model.ReleaseId),
    83122                "The selected release does not exist.");
    84123
     124
    85125            LoadReleases();
     126
     127
    86128            return View(model);
    87129        }
    88130
    89131
    90         // Don't allow duplicate format for
    91         // 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
    95137
    96138        if (alreadyExists)
    97139        {
    98140            ModelState.AddModelError(
    99                 "Format",
     141                nameof(model.Format),
    100142                "This release already has a product in this format.");
    101143
     144
    102145            LoadReleases();
     146
     147
    103148            return View(model);
    104149        }
    105150
    106151
    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    // =========================================================
    147946
    148947    private bool IsAdmin()
    149948    {
    150949        return HttpContext.Session
    151             .GetString("Role") == "Admin";
     950            .GetString("Role") ==
     951            "Admin";
    152952    }
    153953
    … …  
    155955    private bool IsProductManager()
    156956    {
    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
    162965
    163966        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    // =========================================================
    168977
    169978    private long? GetCurrentUserId()
    170979    {
    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    // =========================================================
    1801024
    1811025    private long GetNextProductId()
    1821026    {
    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
    1861033
    1871034        return (maxId ?? 0) + 1;
    … …  
    1891036
    1901037
    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    // =========================================================
    1981092
    1991093    private void CreateModification(
    … …  
    2021096        decimal? discount = null)
    2031097    {
    204         var adminId = GetCurrentUserId();
     1098        var adminId =
     1099            GetCurrentUserId();
     1100
    2051101
    2061102        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);
    2261131
    2271132        _context.SaveChanges();
    … …  
    2341139                    modification.ModificationId,
    2351140
    236                 ProductId = productId
     1141                ProductId =
     1142                    productId
    2371143            };
    2381144
    … …  
    2431149        _context.SaveChanges();
    2441150    }
    245 
    246 
    247     private long GetNextModificationId()
    248     {
    249         var maxId = _context.Modifications
    250             .Select(x => (long?)x.ModificationId)
    251             .Max();
    252 
    253         return (maxId ?? 0) + 1;
    254     }
    255     // ==========================================
    256 // UC009
    257 // MODIFY PRODUCT - GET
    258 // ==========================================
    259 
    260     [HttpGet]
    261     public IActionResult EditProduct(long id)
    262     {
    263         if (!IsProductManager())
    264             return Forbid();
    265 
    266         var product = _context.Products
    267             .Include(p => p.Release)
    268             .FirstOrDefault(p => p.ProductId == id);
    269 
    270         if (product == null)
    271             return NotFound();
    272 
    273         var model = new EditProductViewModel
    274         {
    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.Stock
    282         };
    283 
    284         return View(model);
    285     }
    286     // ==========================================
    287 // UC009
    288 // MODIFY PRODUCT - POST
    289 // ==========================================
    290 
    291 [HttpPost]
    292 [ValidateAntiForgeryToken]
    293 public IActionResult EditProduct(
    294     EditProductViewModel model)
    295 {
    296     if (!IsProductManager())
    297         return Forbid();
    298 
    299     var product = _context.Products
    300         .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     // DISCOUNT
    319     // ==========================================
    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     else
    342     {
    343         // ==========================================
    344         // NORMAL UPDATE
    345         // ==========================================
    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 MODIFICATION
    361     // ==========================================
    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));
    3731151}
    374 [HttpGet]
    375 public IActionResult Products()
    376 {
    377     if (!IsProductManager())
    378         return Forbid();
    379 
    380     var products = _context.Products
    381         .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  
    11using System.Diagnostics;
     2using KernelRecordsMVC.Models;
    23using Microsoft.AspNetCore.Mvc;
    3 using KernelRecordsMVC.Models;
    4 using Microsoft.Extensions.Logging;
    54
    6 namespace KernelRecordsMVC.Controllers;
     5namespace KernelRecordsMVC.Web.Controllers;
    76
    87public class HomeController : Controller
  • KernelRecordsMVC.Web/Controllers/OrderController.cs

    r08aefc6 rfa0fbaf  
    1 using Microsoft.AspNetCore.Http;
    2 
    3 namespace KernelRecordsMVC.Controllers;
    4 
     1using KernelRecordsMVC.Application.ViewModels;
     2using KernelRecordsMVC.Domain.Enums;
     3using KernelRecordsMVC.Infrastructure.Data;
     4using KernelRecordsMVC.Models;
    55using Microsoft.AspNetCore.Mvc;
    66using Microsoft.EntityFrameworkCore;
    7 using KernelRecordsMVC.Data;
    8 using KernelRecordsMVC.Models;
    9 using KernelRecordsMVC.Domain.Enums;
    10 using KernelRecordsMVC.Application.ViewModels;
     7
     8namespace KernelRecordsMVC.Web.Controllers;
    119
    1210public class OrderController : Controller
    … …  
    6765            order = new Order
    6866            {
    69                 OrderId = GetNextOrderId(),
    7067                UserId = userId.Value,
    71 
    72                 // The actual payment method will be
    73                 // selected during checkout.
    7468                PaymentMethod = PaymentMethodType.CARD,
    75 
    7669                PurchaseDate = DateTime.Today,
    77 
    7870                PointsEarned = 0,
    79 
    8071                PointsUsed = null,
    81 
    8272                Status = OrderStatusType.PENDING
    8373            };
    … …  
    113103        else
    114104        {
     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
    115130            var orderProduct = new OrderProduct
    116131            {
    … …  
    186201
    187202                    Stock =
    188                         op.Product.Stock
     203                        op.Product.Stock,
     204                   
     205                    CoverPhoto = op.Product.Release.CoverPhoto
    189206                })
    190207                .ToList();
    … …  
    294311    private long? GetUserId()
    295312    {
    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;
    301317
    302318        return null;
    303319    }
    304 
    305 
    306     // ==========================================
    307     // ORDER ID
    308     // ==========================================
    309 
    310     private long GetNextOrderId()
    311     {
    312         var maxId = _context.Orders
    313             .Select(x => (long?)x.OrderId)
    314             .Max();
    315 
    316         return (maxId ?? 0) + 1;
    317     }
    318320}
  • KernelRecordsMVC.Web/Controllers/ReleaseController.cs

    r08aefc6 rfa0fbaf  
    1 using Microsoft.AspNetCore.Mvc;
     1using KernelRecordsMVC.Domain.Enums;
     2using KernelRecordsMVC.Infrastructure.Data;
     3using Microsoft.AspNetCore.Mvc;
    24using Microsoft.EntityFrameworkCore;
    3 using KernelRecordsMVC.Data;
    45
    5 namespace KernelRecordsMVC.Controllers;
     6namespace KernelRecordsMVC.Web.Controllers;
    67
    78public class ReleaseController : Controller
    … …  
    1415    }
    1516
    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)
    1827    {
    1928        var query = _context.Releases
    2029            .Include(r => r.Products)
    2130            .Include(r => r.ReleaseArtists)
    22             .ThenInclude(ra => ra.Artist)
     31                .ThenInclude(ra => ra.Artist)
     32            .Include(r => r.Album)
     33            .Include(r => r.SingleRelease)
    2334            .AsQueryable();
    2435
     36        // SEARCH
    2537        if (!string.IsNullOrWhiteSpace(search))
    2638        {
     39            search = search.Trim();
     40
    2741            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())));
    2947        }
    3048
     49        // GENRE
    3150        if (!string.IsNullOrWhiteSpace(genre))
    3251        {
    … …  
    3554        }
    3655
    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();
    40112
    41113        ViewBag.Search = search;
    42114        ViewBag.Genre = genre;
     115        ViewBag.Format = format;
     116        ViewBag.Type = type;
     117        ViewBag.Sort = sort;
    43118
    44119        ViewBag.Genres = _context.Releases
    … …  
    48123            .ToList();
    49124
     125        ViewBag.Formats = Enum
     126            .GetValues<ProductFormat>()
     127            .Select(x => x.ToString())
     128            .ToList();
     129
    50130        return View(releases);
    51131    }
    52132
    53133
    54     // Release details
     134    // =========================================================
     135    // DETAILS
     136    // =========================================================
     137
    55138    public IActionResult Details(long id)
    56139    {
    57140        var release = _context.Releases
    58141            .Include(r => r.Products)
     142
    59143            .Include(r => r.ReleaseArtists)
    60             .ThenInclude(ra => ra.Artist)
     144                .ThenInclude(ra => ra.Artist)
     145
    61146            .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
    64152            .Include(r => r.SingleRelease)
    65             .ThenInclude(s => s!.SingleFeatures)
    66             .ThenInclude(x => x.Song)
     153
    67154            .FirstOrDefault(r => r.ReleaseId == id);
    68155
Note: See TracChangeset for help on using the changeset viewer.