Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ea4669e
updated appsettings with my credentials
MathiasHandeland Aug 22, 2025
a7437be
updated gitignore to ignore migrations
MathiasHandeland Aug 22, 2025
1818439
installed packages
MathiasHandeland Aug 22, 2025
5e1dc8e
updated program.cs
MathiasHandeland Aug 22, 2025
6e768a1
added folder to seperate future code
MathiasHandeland Aug 22, 2025
b829dde
implemented a generic repository
MathiasHandeland Aug 22, 2025
e5eb5ea
impemented customer model and customergetdto, and seeded 3 customers …
MathiasHandeland Aug 22, 2025
53fcf43
implemented getCustomers endpoint, updated program.cs to coinfigure e…
MathiasHandeland Aug 22, 2025
6edd21f
updated add endpoint for customer
MathiasHandeland Aug 22, 2025
7d00ae3
added getcustomerbyid endpoint
MathiasHandeland Aug 22, 2025
fe190de
implemented delete customer endpoint
MathiasHandeland Aug 22, 2025
8fa207b
implemented update customer endpoint and added validator service to c…
MathiasHandeland Aug 22, 2025
104d00f
created movie model
MathiasHandeland Aug 22, 2025
b1b7c15
seeded 3 movies, migrated and updated db
MathiasHandeland Aug 22, 2025
bb19d98
added endpoint getmoviebyid
MathiasHandeland Aug 22, 2025
f0db4f6
implemented endpoint for deleting a movie by id
MathiasHandeland Aug 22, 2025
636f424
implemented endpoint for updating a movie and made it optional which …
MathiasHandeland Aug 22, 2025
e6e2aea
refactored customer and movie endpoints, migrated, update db, updated…
MathiasHandeland Aug 22, 2025
655582c
finished implemetning screening endpoints for movies
MathiasHandeland Aug 22, 2025
622a2cb
implemented extention for customer endpoints: added status message an…
MathiasHandeland Aug 22, 2025
656650c
Updated all movie and screening endpoints to return consistent respon…
MathiasHandeland Aug 22, 2025
e247d60
implemented extention: allow movie creation with optional screenings…
MathiasHandeland Aug 22, 2025
b4d9523
add migration for AddCascadeDeleteToMovieScreenings , update db
MathiasHandeland Aug 22, 2025
4e56c7e
add [JsonIgnore] attribute to the Movie property in your Screening c…
MathiasHandeland Aug 23, 2025
5e23c04
implemented ticket extention, deleted db, run migration, update db, a…
MathiasHandeland Aug 23, 2025
06108f1
updated datacontext with relationships, added navigation prop to cust…
MathiasHandeland Aug 25, 2025
e18c06d
defined jwt settings in appsettings.json and appsetting.development.json
MathiasHandeland Aug 25, 2025
87fe230
added authentication for the endpoints
MathiasHandeland Aug 25, 2025
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -362,9 +362,11 @@ MigrationBackup/
# Fody - auto-generated XML schema
FodyWeavers.xsd

# prevents build directories and sensitive config files (like your database credentials) from being uploaded.
*/**/appsettings.json
*/**/appsettings.Development.json
*/**/bin/Debug
*/**/bin/Release
*/**/obj/Debug
*/**/obj/Release
*/Migrations
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using api_cinema_challenge.Data;
using api_cinema_challenge.DTOs.Requests;
using api_cinema_challenge.DTOs.Response;
using api_cinema_challenge.Enums;
using api_cinema_challenge.Models;
using api_cinema_challenge.Services;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

namespace api_cinema_challenge.Controllers
{
[ApiController]
[Route("/api/[controller]")]
public class UsersController : ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly CinemaContext _context;
private readonly TokenService _tokenService;

public UsersController(UserManager<ApplicationUser> userManager, CinemaContext context,
TokenService tokenService, ILogger<UsersController> logger)
{
_userManager = userManager;
_context = context;
_tokenService = tokenService;
}


[HttpPost]
[Route("register")]
public async Task<IActionResult> Register(RegistrationRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

var result = await _userManager.CreateAsync(
new ApplicationUser { UserName = request.Username, Email = request.Email, Role = request.Role },
request.Password!
);

if (result.Succeeded)
{
request.Password = "";
return CreatedAtAction(nameof(Register), new { email = request.Email, role = Role.User }, request);
}

foreach (var error in result.Errors)
{
ModelState.AddModelError(error.Code, error.Description);
}

return BadRequest(ModelState);
}


[HttpPost]
[Route("login")]
public async Task<ActionResult<AuthResponse>> Authenticate([FromBody] AuthRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

var managedUser = await _userManager.FindByEmailAsync(request.Email!);

if (managedUser == null)
{
return BadRequest("Bad credentials");
}

var isPasswordValid = await _userManager.CheckPasswordAsync(managedUser, request.Password!);

if (!isPasswordValid)
{
return BadRequest("Bad credentials");
}

var userInDb = _context.Users.FirstOrDefault(u => u.Email == request.Email);

if (userInDb is null)
{
return Unauthorized();
}

var accessToken = _tokenService.CreateToken(userInDb);
await _context.SaveChangesAsync();

return Ok(new AuthResponse
{
Username = userInDb.UserName,
Email = userInDb.Email,
Token = accessToken,
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace api_cinema_challenge.DTOs.CustomerDTOs
{
public class CustomerDto
{
public int Id { get; set; }

public required string Name { get; set; }
public required string Email { get; set; }
public required string Phone { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations;

namespace api_cinema_challenge.DTOs.CustomerDTOs
{
public class CustomerPostDto
{
public required string Name { get; set; }
[EmailAddress]
public required string Email { get; set; }
[Phone]
public required string Phone { get; set; }

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations;

namespace api_cinema_challenge.DTOs.CustomerDTOs
{
public class CustomerPutDto
{
public string? Name { get; set; }
[EmailAddress]
public string? Email { get; set; }
[Phone]
public string? Phone { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace api_cinema_challenge.DTOs.MovieDTOs
{
public class MovieDto
{
public int Id { get; set; }
public required string Title { get; set; }
public required string Rating { get; set; }

public required string Description { get; set; }
public required int RuntimeMins { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using api_cinema_challenge.DTOs.ScreeningDTOs;

namespace api_cinema_challenge.DTOs.MovieDTOs
{
public class MoviePostDto
{
public required string Title { get; set; }
public required string Rating { get; set; }
public required string Description { get; set; }
public required int RuntimeMins { get; set; }

// optional screenings for a movie
public List<ScreeningPostDto>? Screenings { get; set; }

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace api_cinema_challenge.DTOs.MovieDTOs
{
public class MoviePutDto
{
public string? Title { get; set; }
public string? Rating { get; set; }
public string? Description { get; set; }
public int? RuntimeMins { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace api_cinema_challenge.DTOs.Requests
{
public class AuthRequest
{
public string? Email { get; set; }
public string? Password { get; set; }

public bool IsValid()
{
return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using api_cinema_challenge.Enums;
using System.ComponentModel.DataAnnotations;

namespace api_cinema_challenge.DTOs.Requests
{
public class RegistrationRequest
{
[Required]
public string? Email { get; set; }

[Required]
public string? Username { get { return this.Email; } set { } }

[Required]
public string? Password { get; set; }

public Role Role { get; set; } = Role.User;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace api_cinema_challenge.DTOs.Response
{
public class AuthResponse
{
public string? Username { get; set; }
public string? Email { get; set; }
public string? Token { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace api_cinema_challenge.DTOs.ScreeningDTOs
{
public class ScreeningDto
{
public int Id { get; set; }
public int ScreenNumber { get; set; }
public int Capacity { get; set; }
public DateTime StartsAt { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace api_cinema_challenge.DTOs.ScreeningDTOs
{
public class ScreeningPostDto
{
public int ScreenNumber { get; set; }
public int Capacity { get; set; }
public DateTime StartsAt { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace api_cinema_challenge.DTOs.TicketDTOs
{
public class TicketDto
{
public int Id { get; set; }
public int NumSeats { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;

namespace api_cinema_challenge.DTOs.TicketDTOs
{
public class TicketPostDto
{
[Required]
public int NumSeats { get; set; }
}
}
44 changes: 30 additions & 14 deletions api-cinema-challenge/api-cinema-challenge/Data/CinemaContext.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,42 @@
using Microsoft.EntityFrameworkCore;
using api_cinema_challenge.Models;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;

namespace api_cinema_challenge.Data
{
public class CinemaContext : DbContext
public class CinemaContext : IdentityUserContext<ApplicationUser>
{
private string _connectionString;
public CinemaContext(DbContextOptions<CinemaContext> options) : base(options)
{
var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
_connectionString = configuration.GetValue<string>("ConnectionStrings:DefaultConnectionString")!;
this.Database.EnsureCreated();
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(_connectionString);
}
public CinemaContext(DbContextOptions<CinemaContext> options) : base(options) { }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);


ModelSeeder.Seed(modelBuilder); // seed initial data

// when a movie is deleted, delete all screenings associated with it
modelBuilder.Entity<Movie>() // movie-screening relationship
.HasMany(m => m.Screenings)
.WithOne(s => s.Movie)
.HasForeignKey(s => s.MovieId)
.OnDelete(DeleteBehavior.Cascade);

modelBuilder.Entity<Ticket>() // ticket-customer relationship
.HasOne(t => t.Customer)
.WithMany(c => c.Tickets)
.HasForeignKey(t => t.CustomerId)
.OnDelete(DeleteBehavior.Cascade);

}

public DbSet<Customer> Customers { get; set; }
public DbSet<Movie> Movies { get; set; }
public DbSet<Screening> Screenings { get; set; }

public DbSet<Ticket> Tickets { get; set; }

}
}
Loading