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
|
||||
|
||||
* [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
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
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
|
||||
{
|
||||
|
|
@ -10,6 +12,7 @@ namespace Yavsc.Controllers
|
|||
using Microsoft.Extensions.Localization;
|
||||
using Models;
|
||||
using ViewModels.FrontOffice;
|
||||
using Yavsc.Abstract.Streaming;
|
||||
using Yavsc.Server.Helpers;
|
||||
using Yavsc.Services;
|
||||
|
||||
|
|
@ -20,18 +23,24 @@ namespace Yavsc.Controllers
|
|||
readonly ILogger _logger;
|
||||
readonly IStringLocalizer _SR;
|
||||
private readonly IBillingService _billing;
|
||||
private readonly IDataProtectionProvider _dataProtectionProvider;
|
||||
private readonly ILiveProcessor _liveProcessor;
|
||||
|
||||
public FrontOfficeController(ApplicationDbContext context,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
IBillingService billing,
|
||||
ILoggerFactory loggerFactory,
|
||||
IStringLocalizer<FrontOfficeController> SR)
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
ILiveProcessor liveProcessor,
|
||||
IStringLocalizer<FrontOfficeController> SR)
|
||||
{
|
||||
_context = context;
|
||||
_userManager = userManager;
|
||||
_logger = loggerFactory.CreateLogger<FrontOfficeController>();
|
||||
_SR = SR;
|
||||
_billing = billing;
|
||||
_dataProtectionProvider = dataProtectionProvider;
|
||||
_liveProcessor = liveProcessor;
|
||||
}
|
||||
|
||||
public ActionResult Index()
|
||||
|
|
@ -77,5 +86,77 @@ namespace Yavsc.Controllers
|
|||
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