Add performer podcast public permalink
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
0893805ac4
commit
e7e6b375f5
3 changed files with 147 additions and 3 deletions
|
|
@ -4,6 +4,10 @@
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
* [Yavsc.Org] Permalien public de podcasts performer avec jeton a duree
|
||||||
|
limitee, page Razor de presentation et bandeau de prospection vers
|
||||||
|
l'inscription Yavsc et le telechargement de PostIt.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace Yavsc.Controllers
|
namespace Yavsc.Controllers
|
||||||
{
|
{
|
||||||
|
|
@ -10,6 +12,7 @@ namespace Yavsc.Controllers
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using Models;
|
using Models;
|
||||||
using ViewModels.FrontOffice;
|
using ViewModels.FrontOffice;
|
||||||
|
using Yavsc.Abstract.Streaming;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
using Yavsc.Services;
|
using Yavsc.Services;
|
||||||
|
|
||||||
|
|
@ -20,18 +23,24 @@ namespace Yavsc.Controllers
|
||||||
readonly ILogger _logger;
|
readonly ILogger _logger;
|
||||||
readonly IStringLocalizer _SR;
|
readonly IStringLocalizer _SR;
|
||||||
private readonly IBillingService _billing;
|
private readonly IBillingService _billing;
|
||||||
|
private readonly IDataProtectionProvider _dataProtectionProvider;
|
||||||
|
private readonly ILiveProcessor _liveProcessor;
|
||||||
|
|
||||||
public FrontOfficeController(ApplicationDbContext context,
|
public FrontOfficeController(ApplicationDbContext context,
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
IBillingService billing,
|
IBillingService billing,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IStringLocalizer<FrontOfficeController> SR)
|
IDataProtectionProvider dataProtectionProvider,
|
||||||
|
ILiveProcessor liveProcessor,
|
||||||
|
IStringLocalizer<FrontOfficeController> SR)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_logger = loggerFactory.CreateLogger<FrontOfficeController>();
|
_logger = loggerFactory.CreateLogger<FrontOfficeController>();
|
||||||
_SR = SR;
|
_SR = SR;
|
||||||
_billing = billing;
|
_billing = billing;
|
||||||
|
_dataProtectionProvider = dataProtectionProvider;
|
||||||
|
_liveProcessor = liveProcessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActionResult Index()
|
public ActionResult Index()
|
||||||
|
|
@ -77,5 +86,77 @@ namespace Yavsc.Controllers
|
||||||
return View(result);
|
return View(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult> PerformerPodcasts(string id, string? token)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(id)) return NotFound();
|
||||||
|
|
||||||
|
if (!TryValidateToken(id, token, out var expireAt))
|
||||||
|
return BadRequest("Ce lien public de podcast a expire ou est invalide.");
|
||||||
|
|
||||||
|
var performer = await _context.Performers
|
||||||
|
.Include(p => p.Performer)
|
||||||
|
.FirstOrDefaultAsync(p => p.PerformerId == id);
|
||||||
|
|
||||||
|
if (performer == null) return NotFound();
|
||||||
|
|
||||||
|
var podcasts = (await _liveProcessor.ListAsync(HttpContext.RequestAborted))
|
||||||
|
.Where(p => string.Equals(p.OwnerId, id, StringComparison.Ordinal))
|
||||||
|
.OrderByDescending(p => p.CreatedAt)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
ViewBag.PerformerName = performer.Performer?.UserName ?? performer.PerformerId;
|
||||||
|
ViewBag.PerformerId = id;
|
||||||
|
ViewBag.PermalinkExpiry = expireAt;
|
||||||
|
ViewBag.Permalink = Url.Action(nameof(PerformerPodcasts), "FrontOffice", new { id, token = CreateToken(id) }, Request.Scheme, Request.Host.Value);
|
||||||
|
|
||||||
|
return View(podcasts);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
public IActionResult GeneratePerformerPodcastPermalink(string id)
|
||||||
|
{
|
||||||
|
var currentUserId = User.GetUserId();
|
||||||
|
if (string.IsNullOrWhiteSpace(currentUserId) || !string.Equals(currentUserId, id, StringComparison.Ordinal))
|
||||||
|
return Forbid();
|
||||||
|
|
||||||
|
var token = CreateToken(id);
|
||||||
|
return RedirectToAction(nameof(PerformerPodcasts), new { id, token });
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CreateToken(string performerId)
|
||||||
|
{
|
||||||
|
var expiresAt = DateTimeOffset.UtcNow.AddHours(24);
|
||||||
|
var protector = _dataProtectionProvider.CreateProtector("Yavsc.Org.PerformerPodcastLink");
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
performerId,
|
||||||
|
exp = expiresAt.ToUnixTimeSeconds(),
|
||||||
|
};
|
||||||
|
return protector.Protect(JsonSerializer.Serialize(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryValidateToken(string performerId, string? token, out DateTimeOffset validUntil)
|
||||||
|
{
|
||||||
|
validUntil = DateTimeOffset.MinValue;
|
||||||
|
if (string.IsNullOrWhiteSpace(token)) return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var protector = _dataProtectionProvider.CreateProtector("Yavsc.Org.PerformerPodcastLink");
|
||||||
|
var payload = JsonDocument.Parse(protector.Unprotect(token));
|
||||||
|
var ownerId = payload.RootElement.GetProperty("performerId").GetString();
|
||||||
|
var expiry = payload.RootElement.GetProperty("exp").GetInt64();
|
||||||
|
|
||||||
|
if (!string.Equals(ownerId, performerId, StringComparison.Ordinal)) return false;
|
||||||
|
|
||||||
|
validUntil = DateTimeOffset.FromUnixTimeSeconds(expiry);
|
||||||
|
return validUntil > DateTimeOffset.UtcNow;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
59
src/Yavsc.Org/Views/FrontOffice/PerformerPodcasts.cshtml
Normal file
59
src/Yavsc.Org/Views/FrontOffice/PerformerPodcasts.cshtml
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
@model IReadOnlyList<Yavsc.Abstract.Streaming.PodcastResponse>
|
||||||
|
@{
|
||||||
|
ViewBag.Title = $"Podcasts de {ViewBag.PerformerName}";
|
||||||
|
var performerName = ViewBag.PerformerName ?? "ce performer";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container py-4">
|
||||||
|
<div class="alert alert-info d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3" role="alert">
|
||||||
|
<div>
|
||||||
|
<h1 class="h3 mb-1">Découvrez les podcasts de @performerName</h1>
|
||||||
|
<p class="mb-0 text-muted">
|
||||||
|
Ce lien public est valable jusqu'au @(((DateTimeOffset)ViewBag.PermalinkExpiry).ToLocalTime().ToString("g")).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<a class="btn btn-primary" href="https://www.yavsc.fr/" target="_blank" rel="noreferrer">S'inscrire</a>
|
||||||
|
<a class="btn btn-outline-primary" href="https://postit.yavsc.fr/" target="_blank" rel="noreferrer">Installer PostIt</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (Model == null || Model.Count == 0)
|
||||||
|
{
|
||||||
|
<div class="alert alert-light border">
|
||||||
|
Aucun podcast public n'est encore disponible pour @performerName.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="row g-3">
|
||||||
|
@foreach (var podcast in Model)
|
||||||
|
{
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-start gap-3">
|
||||||
|
<div>
|
||||||
|
<h2 class="h5 mb-1">@podcast.Title</h2>
|
||||||
|
<small class="text-muted">
|
||||||
|
@podcast.CreatedAt.ToLocalTime().ToString("g")
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-secondary">@podcast.State</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-3 mb-2 text-muted">
|
||||||
|
@podcast.DurationSeconds.ToString("0.##") s · @podcast.SegmentCount segments · @((podcast.Bytes / 1024.0 / 1024.0).ToString("0.##")) Mo
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<audio controls class="w-100 mt-2" preload="metadata">
|
||||||
|
<source src="@Url.Action("Playlist", "Podcast", new { id = podcast.Id })" type="application/vnd.apple.mpegurl" />
|
||||||
|
Votre navigateur ne prend pas en charge la lecture audio.
|
||||||
|
</audio>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
Loading…
Reference in a new issue