diff --git a/SimpleServer/SimpleServer.sln b/SimpleServer/SimpleServer.sln index 9ac80e2..375db30 100644 --- a/SimpleServer/SimpleServer.sln +++ b/SimpleServer/SimpleServer.sln @@ -49,6 +49,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Payment.API", "src\Services EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity.Grpc", "src\Services\Identity\Identity.Grpc\Identity.Grpc.csproj", "{D4025862-FCFB-4FC4-BDDA-42127817C5B0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Watchlist.API", "src\Services\WatchlistService\Watchlist.API\Watchlist.API.csproj", "{19FC313F-7235-41E9-9B3E-46D8736051D2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -103,6 +105,10 @@ Global {D4025862-FCFB-4FC4-BDDA-42127817C5B0}.Debug|Any CPU.Build.0 = Debug|Any CPU {D4025862-FCFB-4FC4-BDDA-42127817C5B0}.Release|Any CPU.ActiveCfg = Release|Any CPU {D4025862-FCFB-4FC4-BDDA-42127817C5B0}.Release|Any CPU.Build.0 = Release|Any CPU + {19FC313F-7235-41E9-9B3E-46D8736051D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19FC313F-7235-41E9-9B3E-46D8736051D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19FC313F-7235-41E9-9B3E-46D8736051D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19FC313F-7235-41E9-9B3E-46D8736051D2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -129,6 +135,7 @@ Global {B28B8B14-5D5E-4F71-8D29-8F4FA518E63B} = {5DB32E24-821A-4265-8866-5FA3DD7E5E54} {A2136994-728F-4D71-87F9-E11E6DD78790} = {D6B1FD89-8AD1-46A3-A89C-6A73DEFE7D81} {D4025862-FCFB-4FC4-BDDA-42127817C5B0} = {96C41B3F-C50A-499A-8341-6BDC36B37389} + {19FC313F-7235-41E9-9B3E-46D8736051D2} = {EBA29E1D-1833-43D8-9B09-A2D80787B656} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C9B02C29-4D8B-4687-AB7D-9DF5642002D9} diff --git a/SimpleServer/src/ApiGateways/Web.Bff.SimpleNetflix/OcelotApiGw/ocelot.Development.json b/SimpleServer/src/ApiGateways/Web.Bff.SimpleNetflix/OcelotApiGw/ocelot.Development.json index 32d0ad6..85df62e 100644 --- a/SimpleServer/src/ApiGateways/Web.Bff.SimpleNetflix/OcelotApiGw/ocelot.Development.json +++ b/SimpleServer/src/ApiGateways/Web.Bff.SimpleNetflix/OcelotApiGw/ocelot.Development.json @@ -261,6 +261,28 @@ "movies" ] } + }, + { + "DownstreamPathTemplate": "/api/watchList/{everything}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5013 + } + ], + "UpstreamPathTemplate": "/watchList/{everything}", + "UpstreamHttpMethod": [ + "GET", + "POST", + "DELETE" + ], + "AuthenticationOptions": { + "AuthenticationProviderKey": "IdentityApiKey", + "AllowedScopes": [ + "watchlist" + ] + } } ] } \ No newline at end of file diff --git a/SimpleServer/src/Services/Movie/Movie.API/Properties/launchSettings.json b/SimpleServer/src/Services/Movie/Movie.API/Properties/launchSettings.json index 061fdc8..01913fd 100644 --- a/SimpleServer/src/Services/Movie/Movie.API/Properties/launchSettings.json +++ b/SimpleServer/src/Services/Movie/Movie.API/Properties/launchSettings.json @@ -42,8 +42,8 @@ "launchBrowser": true, "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", "environmentVariables": {}, - "useSSL": true, - "publishAllPorts": true + "publishAllPorts": true, + "useSSL": true } } } \ No newline at end of file diff --git a/SimpleServer/src/Services/WatchlistService/Watchlist.API/Controllers/SavedWatchListController.cs b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Controllers/SavedWatchListController.cs new file mode 100644 index 0000000..bb0dcf4 --- /dev/null +++ b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Controllers/SavedWatchListController.cs @@ -0,0 +1,53 @@ +using System; +using System.Security.Claims; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Watchlist.API.Repository.WatchList; + +namespace Watchlist.API.Controllers +{ + [Authorize(Roles = "Member")] + [Route("api/watchList")] + public class SavedWatchListController : Controller + { + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IWatchListRepository _watchListService; + private readonly ILogger _logger; + + public SavedWatchListController(IWatchListRepository watchListService, ILogger logger, IHttpContextAccessor httpContextAccessor) + { + _watchListService = watchListService; + _logger = logger; + _httpContextAccessor = httpContextAccessor; + } + + [HttpGet] + public async Task>> GetWatchListAsync() + { + var userId = GetUserId(); + return Ok(await _watchListService.GetWatchListAsync(userId)); + } + + private string GetUserId() + { + return _httpContextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value + ?? throw new ArgumentNullException("User not found"); + } + + [HttpPost] + public async Task AddMovieToWatchListAsync([FromBody] string movieId) + { + var userId = GetUserId(); + await _watchListService.InsertToWatchedListAsync(userId, movieId); + return Ok(); + } + + [HttpDelete("{movieId}")] + public async Task RemoveMovieFromWatchListAsync(string movieId) + { + var userId = GetUserId(); + await _watchListService.RemoveFromWatchedListAsync(userId, movieId); + return Ok(); + } + } +} diff --git a/SimpleServer/src/Services/WatchlistService/Watchlist.API/Controllers/UnfinishedWatchListController.cs b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Controllers/UnfinishedWatchListController.cs new file mode 100644 index 0000000..787485e --- /dev/null +++ b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Controllers/UnfinishedWatchListController.cs @@ -0,0 +1,13 @@ +using System; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Watchlist.API.Controllers +{ + [Authorize(Roles = "Member")] + [Route("api/unfinishedWatchList")] + public class UnfinishedWatchListController : Controller + { + + } +} diff --git a/SimpleServer/src/Services/WatchlistService/Watchlist.API/Entity/UnfinishedMovies.cs b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Entity/UnfinishedMovies.cs new file mode 100644 index 0000000..83e2eb7 --- /dev/null +++ b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Entity/UnfinishedMovies.cs @@ -0,0 +1,14 @@ + +namespace Watchlist.API.Entity +{ + public class UnfinishedMovies + { + public string MovieId { get; set; } = string.Empty; + + // total duration (in minutes) + public int ProgressDuarion { get; set; } + + // Last watched + public DateTime LastWatched { get; set; } + } +} diff --git a/SimpleServer/src/Services/WatchlistService/Watchlist.API/Entity/UserUnfinishedMovies.cs b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Entity/UserUnfinishedMovies.cs new file mode 100644 index 0000000..d60d74d --- /dev/null +++ b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Entity/UserUnfinishedMovies.cs @@ -0,0 +1,9 @@ +namespace Watchlist.API.Entity +{ + public class UserUnfinishedMovies + { + public string UserId { get; set; } = string.Empty; + + public List UnfinishedMoviesList { get; set; } = new(); + } +} diff --git a/SimpleServer/src/Services/WatchlistService/Watchlist.API/Entity/UserWatchList.cs b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Entity/UserWatchList.cs new file mode 100644 index 0000000..a737f70 --- /dev/null +++ b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Entity/UserWatchList.cs @@ -0,0 +1,9 @@ +namespace Watchlist.API.Entity +{ + public class UserWatchList + { + public string UserId { get; set; } = string.Empty; + + public List SavedMovieIds { get; set; } = new(); + }; +} diff --git a/SimpleServer/src/Services/WatchlistService/Watchlist.API/Infrastructure/Data/IWatchListContext.cs b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Infrastructure/Data/IWatchListContext.cs new file mode 100644 index 0000000..11b532a --- /dev/null +++ b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Infrastructure/Data/IWatchListContext.cs @@ -0,0 +1,11 @@ +using MongoDB.Driver; +using Watchlist.API.Entity; + +namespace Watchlist.API.Infrastructure.Data +{ + public interface IWatchListContext + { + IMongoCollection SavedMovies { get; } + IMongoCollection UnfinishedMovies { get; } + } +} diff --git a/SimpleServer/src/Services/WatchlistService/Watchlist.API/Infrastructure/Data/WatchListContext.cs b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Infrastructure/Data/WatchListContext.cs new file mode 100644 index 0000000..c132bdb --- /dev/null +++ b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Infrastructure/Data/WatchListContext.cs @@ -0,0 +1,26 @@ +using System; +using MongoDB.Driver; +using Watchlist.API.Entity; + +namespace Watchlist.API.Infrastructure.Data +{ + public class WatchListContext : IWatchListContext + { + public WatchListContext(IConfiguration configuration) + { + var client = new MongoClient(configuration.GetValue("DatabaseSettings:ConnectionString")); + var database = client.GetDatabase(configuration.GetValue("DatabaseSettings:DatabaseName")); + SavedMovies = database.GetCollection("userSavedList"); + UnfinishedMovies = database.GetCollection("userUnfinishedMovies"); + } + + public WatchListContext() + { + } + + + public IMongoCollection SavedMovies { get; } + + public IMongoCollection UnfinishedMovies { get; } + } +} diff --git a/SimpleServer/src/Services/WatchlistService/Watchlist.API/Program.cs b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Program.cs new file mode 100644 index 0000000..e4fcc1d --- /dev/null +++ b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Program.cs @@ -0,0 +1,46 @@ +using Watchlist.API.Infrastructure.Data; +using Watchlist.API.Repository.UnfinishedList; +using Watchlist.API.Repository.WatchList; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +builder.Services.AddAuthentication("Bearer") + .AddJwtBearer("Bearer", options => + { + options.RequireHttpsMetadata = false; + options.Authority = builder.Configuration["IdentityUrl"]; + options.Audience = "watchlist"; + }); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +// DI for DbContext and Repository +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); + +public partial class Program { } \ No newline at end of file diff --git a/SimpleServer/src/Services/WatchlistService/Watchlist.API/Properties/launchSettings.json b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Properties/launchSettings.json new file mode 100644 index 0000000..1bb99d7 --- /dev/null +++ b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:12245", + "sslPort": 44377 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5013", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true + }, + "https": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7085;http://localhost:5013", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/SimpleServer/src/Services/WatchlistService/Watchlist.API/Repository/SavedMovies/ISavedWatchListRepository.cs b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Repository/SavedMovies/ISavedWatchListRepository.cs new file mode 100644 index 0000000..e18dc41 --- /dev/null +++ b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Repository/SavedMovies/ISavedWatchListRepository.cs @@ -0,0 +1,14 @@ +using System; + +namespace Watchlist.API.Repository.WatchList +{ + public interface ISavedWatchListRepository + { + // Add movie to watchlist + Task InsertToSavedWatchedListAsync(string userId, string movieId); + // Remove movie from watchlist + Task RemoveFromSavedWatchedListAsync(string userId, string movieId); + // Get watchlist + Task> GetSavedWatchListAsync(string userId); + } +} diff --git a/SimpleServer/src/Services/WatchlistService/Watchlist.API/Repository/SavedMovies/SavedWatchListRepository.cs b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Repository/SavedMovies/SavedWatchListRepository.cs new file mode 100644 index 0000000..81a81dd --- /dev/null +++ b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Repository/SavedMovies/SavedWatchListRepository.cs @@ -0,0 +1,38 @@ +using MongoDB.Driver; +using Watchlist.API.Entity; +using Watchlist.API.Infrastructure.Data; + +namespace Watchlist.API.Repository.WatchList +{ + public class SavedWatchListRepository : ISavedWatchListRepository + { + private readonly IWatchListContext _context; + + public SavedWatchListRepository(IWatchListContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + } + + public async Task> GetSavedWatchListAsync(string userId) + { + // Get all watch lists from database where userId == userId + return (await _context.SavedMovies.FindAsync(x => x.UserId == userId)) + .FirstOrDefault()?.SavedMovieIds ?? new List(); + } + + public async Task InsertToSavedWatchedListAsync(string userId, string movieId) + { + await _context.SavedMovies.UpdateOneAsync( + x => x.UserId == userId, + Builders.Update.AddToSet(x => x.SavedMovieIds, movieId), + new UpdateOptions { IsUpsert = true }); + } + + public async Task RemoveFromSavedWatchedListAsync(string userId, string movieId) + { + await _context.SavedMovies.UpdateOneAsync( + x => x.UserId == userId, + Builders.Update.Pull(x => x.SavedMovieIds, movieId)); + } + } +} diff --git a/SimpleServer/src/Services/WatchlistService/Watchlist.API/Repository/UnfinishedList/FinishedListRepository.cs b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Repository/UnfinishedList/FinishedListRepository.cs new file mode 100644 index 0000000..3ea0a63 --- /dev/null +++ b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Repository/UnfinishedList/FinishedListRepository.cs @@ -0,0 +1,9 @@ +using System; + +namespace Watchlist.API.Repository.UnfinishedList +{ + public class FinishedListRepository : IFinishedListRepository + { + + } +} diff --git a/SimpleServer/src/Services/WatchlistService/Watchlist.API/Repository/UnfinishedList/IFinishedListRepository.cs b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Repository/UnfinishedList/IFinishedListRepository.cs new file mode 100644 index 0000000..99a24e3 --- /dev/null +++ b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Repository/UnfinishedList/IFinishedListRepository.cs @@ -0,0 +1,9 @@ +using System; + +namespace Watchlist.API.Repository.UnfinishedList +{ + public interface IFinishedListRepository + { + + } +} diff --git a/SimpleServer/src/Services/WatchlistService/Watchlist.API/Watchlist.API.csproj b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Watchlist.API.csproj new file mode 100644 index 0000000..60cd906 --- /dev/null +++ b/SimpleServer/src/Services/WatchlistService/Watchlist.API/Watchlist.API.csproj @@ -0,0 +1,25 @@ + + + + net7.0 + enable + enable + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + diff --git a/SimpleServer/src/Services/WatchlistService/Watchlist.API/appsettings.Development.json b/SimpleServer/src/Services/WatchlistService/Watchlist.API/appsettings.Development.json new file mode 100644 index 0000000..9ab341d --- /dev/null +++ b/SimpleServer/src/Services/WatchlistService/Watchlist.API/appsettings.Development.json @@ -0,0 +1,14 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "IdentityUrl":"https://localhost:7096", + "DatabaseSettings": { + "ConnectionString": "mongodb://localhost:27017", + "DatabaseName": "userWwatchListDB" + } +} + diff --git a/SimpleServer/src/Services/WatchlistService/Watchlist.API/appsettings.json b/SimpleServer/src/Services/WatchlistService/Watchlist.API/appsettings.json new file mode 100644 index 0000000..af0538f --- /dev/null +++ b/SimpleServer/src/Services/WatchlistService/Watchlist.API/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} +