fix(plugin): address PR review feedback for sync, auth, and UI hooks
Agent-Logs-Url: https://github.com/devitq/movienight-backend/sessions/fafc7e2d-7464-44d7-a4e1-42f364dd0e37 Co-authored-by: devitq <118541411+devitq@users.noreply.github.com>
This commit is contained in:
co-authored by
devitq
parent
0a90c3eadf
commit
4c39020689
@@ -46,13 +46,22 @@
|
||||
|
||||
function getItemIdFromUrl() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
return params.get('id');
|
||||
return normalizeJellyfinId(params.get('id'));
|
||||
}
|
||||
|
||||
function normalizeJellyfinId(value) {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = value.replace(/-/g, '').toLowerCase();
|
||||
return /^[0-9a-f]{32}$/.test(normalized) ? normalized : null;
|
||||
}
|
||||
|
||||
async function showRecommendation() {
|
||||
const userId = ApiClient.getCurrentUserId();
|
||||
try {
|
||||
const response = await ApiClient.getJSON(ApiClient.getUrl(`MovieNight/Users/${userId}/Recommendations`));
|
||||
const response = await ApiClient.getJSON(ApiClient.getUrl(`MovieNight/Users/${encodeURIComponent(userId)}/Recommendations`));
|
||||
const recommendations = typeof response === 'string' ? JSON.parse(response) : response;
|
||||
|
||||
if (recommendations && recommendations.length > 0) {
|
||||
@@ -77,7 +86,7 @@
|
||||
try {
|
||||
await ApiClient.ajax({
|
||||
type: 'POST',
|
||||
url: ApiClient.getUrl(`MovieNight/Users/${userId}/Ratings/Films/${itemId}`),
|
||||
url: ApiClient.getUrl(`MovieNight/Users/${encodeURIComponent(userId)}/Ratings/Films/${encodeURIComponent(itemId)}`),
|
||||
data: JSON.stringify({ score: parseInt(score), note: 'From Jellyfin UI' }),
|
||||
contentType: 'application/json'
|
||||
});
|
||||
@@ -88,7 +97,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
const observer = new MutationObserver(injectUI);
|
||||
let pendingInjection = false;
|
||||
const observer = new MutationObserver(() => {
|
||||
if (pendingInjection) {
|
||||
return;
|
||||
}
|
||||
|
||||
pendingInjection = true;
|
||||
requestAnimationFrame(() => {
|
||||
pendingInjection = false;
|
||||
injectUI();
|
||||
});
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
// Initial call
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Plugin.MovieNight.Services;
|
||||
@@ -88,7 +89,12 @@ public class MovieNightController : ControllerBase
|
||||
[FromQuery] int limit = 10,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _backendClient.GetRecommendationsAsync(userId, contentType, mood, limit, cancellationToken).ConfigureAwait(false);
|
||||
if (!TryValidateCurrentUserId(userId, out var authenticatedUserId, out var errorResult))
|
||||
{
|
||||
return errorResult!;
|
||||
}
|
||||
|
||||
return await _backendClient.GetRecommendationsAsync(authenticatedUserId, contentType, mood, limit, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -101,7 +107,12 @@ public class MovieNightController : ControllerBase
|
||||
[FromBody] RatingRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await _backendClient.PostRatingAsync(userId, filmId, request.Score, request.Note, cancellationToken).ConfigureAwait(false);
|
||||
if (!TryValidateCurrentUserId(userId, out var authenticatedUserId, out var errorResult))
|
||||
{
|
||||
return errorResult!;
|
||||
}
|
||||
|
||||
await _backendClient.PostRatingAsync(authenticatedUserId, filmId, request.Score, request.Note, cancellationToken).ConfigureAwait(false);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@@ -115,9 +126,42 @@ public class MovieNightController : ControllerBase
|
||||
[FromBody] ViewedRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await _backendClient.MarkViewedAsync(userId, filmId, request.WatchedAt, cancellationToken).ConfigureAwait(false);
|
||||
if (!TryValidateCurrentUserId(userId, out var authenticatedUserId, out var errorResult))
|
||||
{
|
||||
return errorResult!;
|
||||
}
|
||||
|
||||
await _backendClient.MarkViewedAsync(authenticatedUserId, filmId, request.WatchedAt, cancellationToken).ConfigureAwait(false);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
private bool TryValidateCurrentUserId(string routeUserId, out string authenticatedUserId, out ActionResult? errorResult)
|
||||
{
|
||||
authenticatedUserId = NormalizeId(
|
||||
HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier) ??
|
||||
HttpContext.User.FindFirstValue("JellyfinUserId") ??
|
||||
HttpContext.User.FindFirstValue("UserId"));
|
||||
if (string.IsNullOrWhiteSpace(authenticatedUserId))
|
||||
{
|
||||
errorResult = Unauthorized();
|
||||
return false;
|
||||
}
|
||||
|
||||
var normalizedRouteUserId = NormalizeId(routeUserId);
|
||||
if (!string.Equals(authenticatedUserId, normalizedRouteUserId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
errorResult = Forbid();
|
||||
return false;
|
||||
}
|
||||
|
||||
errorResult = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string NormalizeId(string? value)
|
||||
{
|
||||
return Guid.TryParse(value, out var parsedGuid) ? parsedGuid.ToString("N") : string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
||||
@@ -59,9 +59,32 @@ public class MovieNightSyncService
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (item is not Movie movie) continue;
|
||||
|
||||
var jellyfinItemId = movie.Id.ToString("N");
|
||||
var userStates = new List<object>();
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var userData = _userDataManager.GetUserData(user, movie);
|
||||
if (userData is null || (!userData.Played && userData.PlayCount == 0 && userData.LastPlayedDate is null && userData.Rating is null))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
userStates.Add(new
|
||||
{
|
||||
jellyfinUserId = user.Id.ToString("N"),
|
||||
isViewed = userData.Played,
|
||||
playCount = userData.PlayCount,
|
||||
lastPlayedAt = userData.LastPlayedDate,
|
||||
userRating = userData.Rating
|
||||
});
|
||||
}
|
||||
|
||||
var itemData = new Dictionary<string, object?>
|
||||
{
|
||||
@@ -74,16 +97,7 @@ public class MovieNightSyncService
|
||||
["genres"] = movie.Genres,
|
||||
["imdbId"] = movie.GetProviderId(MetadataProvider.Imdb),
|
||||
["tmdbId"] = movie.GetProviderId(MetadataProvider.Tmdb),
|
||||
["userStates"] = users.Select(u => {
|
||||
var userData = _userDataManager.GetUserData(u, movie);
|
||||
return new {
|
||||
jellyfinUserId = u.Id.ToString("N"),
|
||||
isViewed = userData?.Played ?? false,
|
||||
playCount = userData?.PlayCount ?? 0,
|
||||
lastPlayedAt = userData?.LastPlayedDate,
|
||||
userRating = userData?.Rating
|
||||
};
|
||||
}).ToList()
|
||||
["userStates"] = userStates
|
||||
};
|
||||
|
||||
syncItems.Add(itemData);
|
||||
|
||||
Reference in New Issue
Block a user