Add performer public permalink actions
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
e7e6b375f5
commit
d192705f29
5 changed files with 187 additions and 24 deletions
|
|
@ -113,6 +113,37 @@ namespace Yavsc.Controllers
|
|||
return View(podcasts);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult> PerformerActivity(string id, string activityCode, string? token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(id) || string.IsNullOrWhiteSpace(activityCode)) return NotFound();
|
||||
|
||||
if (!TryValidateToken(id, activityCode, token, out var expireAt))
|
||||
return BadRequest("Ce lien public pour cette activité a expire ou est invalide.");
|
||||
|
||||
var performer = await _context.Performers
|
||||
.Include(p => p.Performer)
|
||||
.FirstOrDefaultAsync(p => p.PerformerId == id);
|
||||
|
||||
var activity = await _context.Activities
|
||||
.FirstOrDefaultAsync(a => a.Code == activityCode);
|
||||
|
||||
if (performer == null || activity == null) return NotFound();
|
||||
|
||||
var profile = (await _context.ListPerformersAsync(_billing, activityCode))
|
||||
.Where(p => string.Equals(p.PerformerId, id, StringComparison.Ordinal))
|
||||
.ToList();
|
||||
|
||||
ViewBag.Activity = activity;
|
||||
ViewBag.PerformerName = performer.Performer?.UserName ?? performer.PerformerId;
|
||||
ViewBag.PerformerId = id;
|
||||
ViewBag.PermalinkExpiry = expireAt;
|
||||
ViewBag.Permalink = Url.Action(nameof(PerformerActivity), "FrontOffice", new { id, activityCode, token = CreateActivityToken(id, activityCode) }, Request.Scheme, Request.Host.Value);
|
||||
|
||||
return View("Profiles", profile);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public IActionResult GeneratePerformerPodcastPermalink(string id)
|
||||
{
|
||||
|
|
@ -124,6 +155,18 @@ namespace Yavsc.Controllers
|
|||
return RedirectToAction(nameof(PerformerPodcasts), new { id, token });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public IActionResult GeneratePerformerActivityPermalink(string id, string activityCode)
|
||||
{
|
||||
var currentUserId = User.GetUserId();
|
||||
if (string.IsNullOrWhiteSpace(currentUserId) || !string.Equals(currentUserId, id, StringComparison.Ordinal))
|
||||
return Forbid();
|
||||
|
||||
var token = CreateActivityToken(id, activityCode);
|
||||
return RedirectToAction(nameof(PerformerActivity), new { id, activityCode, token });
|
||||
}
|
||||
|
||||
private string CreateToken(string performerId)
|
||||
{
|
||||
var expiresAt = DateTimeOffset.UtcNow.AddHours(24);
|
||||
|
|
@ -136,20 +179,45 @@ namespace Yavsc.Controllers
|
|||
return protector.Protect(JsonSerializer.Serialize(payload));
|
||||
}
|
||||
|
||||
private string CreateActivityToken(string performerId, string activityCode)
|
||||
{
|
||||
var expiresAt = DateTimeOffset.UtcNow.AddHours(24);
|
||||
var protector = _dataProtectionProvider.CreateProtector("Yavsc.Org.PerformerActivityLink");
|
||||
var payload = new
|
||||
{
|
||||
performerId,
|
||||
activityCode,
|
||||
exp = expiresAt.ToUnixTimeSeconds(),
|
||||
};
|
||||
return protector.Protect(JsonSerializer.Serialize(payload));
|
||||
}
|
||||
|
||||
private bool TryValidateToken(string performerId, string? token, out DateTimeOffset validUntil)
|
||||
=> TryValidateTokenInternal(performerId, null, token, out validUntil, "Yavsc.Org.PerformerPodcastLink");
|
||||
|
||||
private bool TryValidateToken(string performerId, string activityCode, string? token, out DateTimeOffset validUntil)
|
||||
=> TryValidateTokenInternal(performerId, activityCode, token, out validUntil, "Yavsc.Org.PerformerActivityLink");
|
||||
|
||||
private bool TryValidateTokenInternal(string performerId, string? activityCode, string? token, out DateTimeOffset validUntil, string protectorName)
|
||||
{
|
||||
validUntil = DateTimeOffset.MinValue;
|
||||
if (string.IsNullOrWhiteSpace(token)) return false;
|
||||
|
||||
try
|
||||
{
|
||||
var protector = _dataProtectionProvider.CreateProtector("Yavsc.Org.PerformerPodcastLink");
|
||||
var protector = _dataProtectionProvider.CreateProtector(protectorName);
|
||||
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;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(activityCode))
|
||||
{
|
||||
if (!payload.RootElement.TryGetProperty("activityCode", out var activityPayload)) return false;
|
||||
if (!string.Equals(activityPayload.GetString(), activityCode, StringComparison.Ordinal)) return false;
|
||||
}
|
||||
|
||||
validUntil = DateTimeOffset.FromUnixTimeSeconds(expiry);
|
||||
return validUntil > DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
@{
|
||||
ViewBag.Title = $"Podcasts de {ViewBag.PerformerName}";
|
||||
var performerName = ViewBag.PerformerName ?? "ce performer";
|
||||
var permalinkExpiry = ViewBag.PermalinkExpiry is DateTimeOffset expiry ? expiry : DateTimeOffset.MinValue;
|
||||
}
|
||||
|
||||
<div class="container py-4">
|
||||
|
|
@ -9,10 +10,11 @@
|
|||
<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")).
|
||||
Ce lien public est valable jusqu'au @permalinkExpiry.ToLocalTime().ToString("g").
|
||||
</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-primary" onclick="copyPermalink()">Copier le permalien</button>
|
||||
<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>
|
||||
|
|
@ -57,3 +59,29 @@
|
|||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function copyPermalink() {
|
||||
const url = '@(ViewBag.Permalink ?? string.Empty)';
|
||||
if (!url) return;
|
||||
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(url).catch(() => fallbackCopy(url));
|
||||
return;
|
||||
}
|
||||
|
||||
fallbackCopy(url);
|
||||
}
|
||||
|
||||
function fallbackCopy(value) {
|
||||
const temp = document.createElement('textarea');
|
||||
temp.value = value;
|
||||
temp.setAttribute('readonly', '');
|
||||
temp.style.position = 'fixed';
|
||||
temp.style.left = '-9999px';
|
||||
document.body.appendChild(temp);
|
||||
temp.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(temp);
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -2,10 +2,26 @@
|
|||
|
||||
@{
|
||||
ViewBag.Title = (ViewBag.Activity?.Name ?? "Any") ;
|
||||
var activityCode = ViewBag.Activity?.Code as string;
|
||||
var performerId = ViewBag.PerformerId as string;
|
||||
var permalinkExpiry = ViewBag.PermalinkExpiry is DateTimeOffset expiry ? expiry : DateTimeOffset.MinValue;
|
||||
}
|
||||
<h1>@ViewBag.Title</h1>
|
||||
<em>@ViewBag.Activity.Description</em>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(performerId) && !string.IsNullOrWhiteSpace(activityCode))
|
||||
{
|
||||
<div class="alert alert-info d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3 mt-3" role="alert">
|
||||
<div>
|
||||
<div class="fw-semibold">Permalien public</div>
|
||||
<small class="text-muted">Valable jusqu'au @permalinkExpiry.ToLocalTime().ToString("g")</small>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" onclick="copyPermalink()">
|
||||
Copier le permalien
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@foreach (var profile in Model) {
|
||||
<hr/>
|
||||
@Html.DisplayFor(m=>profile)
|
||||
|
|
@ -15,3 +31,29 @@
|
|||
</a>
|
||||
}
|
||||
|
||||
<script>
|
||||
function copyPermalink() {
|
||||
const url = '@(ViewBag.Permalink ?? string.Empty)';
|
||||
if (!url) return;
|
||||
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(url).catch(() => fallbackCopy(url));
|
||||
return;
|
||||
}
|
||||
|
||||
fallbackCopy(url);
|
||||
}
|
||||
|
||||
function fallbackCopy(value) {
|
||||
const temp = document.createElement('textarea');
|
||||
temp.value = value;
|
||||
temp.setAttribute('readonly', '');
|
||||
temp.style.position = 'fixed';
|
||||
temp.style.left = '-9999px';
|
||||
document.body.appendChild(temp);
|
||||
temp.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(temp);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue