Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 63 additions & 10 deletions GameBox/Controllers/GameModelsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,42 @@

namespace GameBox.Controllers
{
/// <summary>
/// MVC controller responsible for CRUD operations on <see cref="GameModel"/> entities.
/// Contains actions for listing, viewing details, creating, editing and deleting games.
/// </summary>
public class GameModelsController : Controller
{
private readonly ApplicationDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;

/// <summary>
/// Initializes a new instance of the <see cref="GameModelsController"/> class.
/// </summary>
/// <param name="context">The application's database context.</param>
/// <param name="userManager">User manager used to obtain information about the current user.</param>
public GameModelsController(ApplicationDbContext context, UserManager<ApplicationUser> userManager)
{
_context = context;
_userManager = userManager;
}

// GET: GameModels
/// <summary>
/// GET: GameModels
/// Returns a view with all game models.
/// </summary>
/// <returns>A task that resolves to an <see cref="IActionResult"/> rendering the index view.</returns>
public async Task<IActionResult> Index()
{
return View(await _context.GameModels.ToListAsync());
}

// GET: GameModels/Details/5
/// <summary>
/// GET: GameModels/Details/{id}
/// Shows details for a single game model.
/// </summary>
/// <param name="id">The id of the game to display details for.</param>
/// <returns>NotFound if id is null or the game does not exist; otherwise the details view.</returns>
public async Task<IActionResult> Details(int? id)
{
if (id == null)
Expand All @@ -47,15 +65,25 @@ public async Task<IActionResult> Details(int? id)
return View(gameModel);
}

// GET: GameModels/Create
/// <summary>
/// GET: GameModels/Create
/// Returns the form to create a new game. Requires authentication.
/// </summary>
/// <returns>The create view.</returns>
[Authorize]
public IActionResult Create()
{
return View();
}

// POST: GameModels/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
/// <summary>
/// POST: GameModels/Create
/// Creates a new game and assigns the currently authenticated user as the owner.
/// Prevents overposting by binding only allowed properties.
/// Requires a valid antiforgery token and authentication.
/// </summary>
/// <param name="gameModel">The incoming model bound from the form.</param>
/// <returns>Redirects to Index on success; otherwise returns the create view with validation errors.</returns>
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
Expand Down Expand Up @@ -85,7 +113,12 @@ public async Task<IActionResult> Create([Bind("Id,Name,Description")] GameModel
return View(gameModel);
}

// GET: GameModels/Edit/5
/// <summary>
/// GET: GameModels/Edit/{id}
/// Returns the edit form for the specified game.
/// </summary>
/// <param name="id">The id of the game to edit.</param>
/// <returns>NotFound if id is null or the game does not exist; otherwise the edit view.</returns>
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
Expand All @@ -101,8 +134,13 @@ public async Task<IActionResult> Edit(int? id)
return View(gameModel);
}

// POST: GameModels/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
/// <summary>
/// POST: GameModels/Edit/{id}
/// Updates an existing game. Protects against overposting by binding allowed properties only.
/// </summary>
/// <param name="id">The id of the game being edited.</param>
/// <param name="gameModel">The model values submitted from the form.</param>
/// <returns>Redirects to Index on success; otherwise returns the edit view with validation errors.</returns>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Name,Description,Rating")] GameModel gameModel)
Expand Down Expand Up @@ -135,7 +173,12 @@ public async Task<IActionResult> Edit(int id, [Bind("Id,Name,Description,Rating"
return View(gameModel);
}

// GET: GameModels/Delete/5
/// <summary>
/// GET: GameModels/Delete/{id}
/// Shows a confirmation view to delete the specified game.
/// </summary>
/// <param name="id">The id of the game to delete.</param>
/// <returns>NotFound if id is null or the game does not exist; otherwise the delete confirmation view.</returns>
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
Expand All @@ -153,7 +196,12 @@ public async Task<IActionResult> Delete(int? id)
return View(gameModel);
}

// POST: GameModels/Delete/5
/// <summary>
/// POST: GameModels/Delete/{id}
/// Deletes the specified game after confirmation.
/// </summary>
/// <param name="id">The id of the game to delete.</param>
/// <returns>Redirects to the index view after deletion.</returns>
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
Expand All @@ -168,6 +216,11 @@ public async Task<IActionResult> DeleteConfirmed(int id)
return RedirectToAction(nameof(Index));
}

/// <summary>
/// Checks whether a game exists in the database.
/// </summary>
/// <param name="id">The id of the game to check.</param>
/// <returns>True if the game exists; otherwise false.</returns>
private bool GameModelExists(int id)
{
return _context.GameModels.Any(e => e.Id == id);
Expand Down
17 changes: 17 additions & 0 deletions GameBox/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,35 @@

namespace GameBox.Controllers
{
/// <summary>
/// Controller responsible for rendering the application's home pages such as the index,
/// privacy page, and the global error page.
/// </summary>
public class HomeController : Controller
{
/// <summary>
/// Renders the application home page (index).
/// </summary>
/// <returns>An <see cref="IActionResult"/> that renders the Index view.</returns>
public IActionResult Index()
{
return View();
}

/// <summary>
/// Renders the privacy information page.
/// </summary>
/// <returns>An <see cref="IActionResult"/> that renders the Privacy view.</returns>
public IActionResult Privacy()
{
return View();
}

/// <summary>
/// Displays the error view with request tracing information.
/// The response is not cached to ensure fresh error details are shown.
/// </summary>
/// <returns>An <see cref="IActionResult"/> that renders the Error view with an <see cref="ErrorViewModel"/>.</returns>
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
Expand Down
7 changes: 7 additions & 0 deletions GameBox/Models/ApplicationUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@

namespace GameBox.Models
{
/// <summary>
/// Represents an application user with extended profile and social features.
/// Extends the base IdentityUser with additional GameBox-specific properties.
/// </summary>
public class ApplicationUser : IdentityUser
{
/// <summary>
/// Gets or sets the collection of users who follow this user.
/// </summary>
public ICollection<ApplicationUser> Followers { get; set; } = new List<ApplicationUser>();
}
}
10 changes: 10 additions & 0 deletions GameBox/Models/ErrorViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
namespace GameBox.Models
{
/// <summary>
/// View model for displaying error information to users.
/// </summary>
public class ErrorViewModel
{
/// <summary>
/// Gets or sets the unique request ID associated with the error.
/// </summary>
public string? RequestId { get; set; }

/// <summary>
/// Gets a value indicating whether the request ID should be displayed to the user.
/// Returns true if RequestId is not null or empty.
/// </summary>
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
}
49 changes: 47 additions & 2 deletions GameBox/Models/GameModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,83 @@

namespace GameBox.Models
{

/// <summary>
/// Represents a game in the GameBox application.
/// </summary>
public class GameModel
{
/// <summary>
/// Gets or sets the unique identifier for the game.
/// </summary>
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }

/// <summary>
/// Gets or sets the name of the game.
/// </summary>
[StringLength(100, MinimumLength = 1, ErrorMessage = "Game name cannot exceed 100 characters.")]
public required string Name { get; set; }

/// <summary>
/// Gets or sets the description of the game.
/// </summary>
[StringLength(1000, ErrorMessage = "Description cannot exceed 1000 characters.")]
public required string Description { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the rating for the game on a scale of 1 to 10.
/// </summary>
[Range(1, 10, ErrorMessage = "Ratings must be between 1 and 10")]
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

[nitpick] Inconsistent punctuation in validation error messages. The error message on line 23 ends with a period ("Game name cannot exceed 100 characters."), while the error message on line 35 does not ("Ratings must be between 1 and 10"). For consistency, all error messages should either include or exclude ending punctuation.

Suggested change
[Range(1, 10, ErrorMessage = "Ratings must be between 1 and 10")]
[Range(1, 10, ErrorMessage = "Ratings must be between 1 and 10.")]

Copilot uses AI. Check for mistakes.
public int? Rating { get; set; }

/// <summary>
/// Gets or sets the ID of the user who owns this game.
/// </summary>
// Shouldn't this work either way?
[ForeignKey("ApplicationUser")]
public string? OwnerId { get; set; }

/// <summary>
/// Gets or sets the navigation property to the application user who owns this game.
/// </summary>
[ForeignKey(nameof(OwnerId))]
public ApplicationUser? Owner { get; set; }
}
}

/// <summary>
/// View model for displaying a paginated list of games with search capabilities.
/// </summary>
public class GameListViewModel
{
/// <summary>
/// Gets or sets the collection of games to display.
/// </summary>
public required IEnumerable<GameModel> Games { get; set; }

/// <summary>
/// Gets or sets the current page number in the pagination.
/// </summary>
public int CurrentPage { get; set; }

/// <summary>
/// Gets or sets the total number of pages available.
/// </summary>
public int TotalPages { get; set; }

/// <summary>
/// Gets or sets the number of items to display per page.
/// </summary>
public int PageSize { get; set; }

/// <summary>
/// Gets or sets the total number of items across all pages.
/// </summary>
public int TotalItems { get; set; }

/// <summary>
/// Gets or sets the search term used to filter the games list.
/// </summary>
public string? SearchTerm { get; set; }
Comment on lines 51 to 86
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The GameListViewModel class and its documentation are placed outside the GameBox.Models namespace. This class should be defined within the namespace block (before the closing brace on line 51) to maintain consistency with the GameModel class and follow standard C# organizational practices.

Suggested change
}
/// <summary>
/// View model for displaying a paginated list of games with search capabilities.
/// </summary>
public class GameListViewModel
{
/// <summary>
/// Gets or sets the collection of games to display.
/// </summary>
public required IEnumerable<GameModel> Games { get; set; }
/// <summary>
/// Gets or sets the current page number in the pagination.
/// </summary>
public int CurrentPage { get; set; }
/// <summary>
/// Gets or sets the total number of pages available.
/// </summary>
public int TotalPages { get; set; }
/// <summary>
/// Gets or sets the number of items to display per page.
/// </summary>
public int PageSize { get; set; }
/// <summary>
/// Gets or sets the total number of items across all pages.
/// </summary>
public int TotalItems { get; set; }
/// <summary>
/// Gets or sets the search term used to filter the games list.
/// </summary>
public string? SearchTerm { get; set; }
/// <summary>
/// View model for displaying a paginated list of games with search capabilities.
/// </summary>
public class GameListViewModel
{
/// <summary>
/// Gets or sets the collection of games to display.
/// </summary>
public required IEnumerable<GameModel> Games { get; set; }
/// <summary>
/// Gets or sets the current page number in the pagination.
/// </summary>
public int CurrentPage { get; set; }
/// <summary>
/// Gets or sets the total number of pages available.
/// </summary>
public int TotalPages { get; set; }
/// <summary>
/// Gets or sets the number of items to display per page.
/// </summary>
public int PageSize { get; set; }
/// <summary>
/// Gets or sets the total number of items across all pages.
/// </summary>
public int TotalItems { get; set; }
/// <summary>
/// Gets or sets the search term used to filter the games list.
/// </summary>
public string? SearchTerm { get; set; }
}

Copilot uses AI. Check for mistakes.

}