Feed-redirect: 301 for podcast som flyttes til ny host (oppgave 30.8)
Når redirect_feed er satt i podcast-trait, returnerer maskinrommet HTTP 301 Moved Permanently med Location-header i stedet for å serve feeden. iTunes new-feed-url-taggen bevares også i RSS-en for klienter som ikke følger 301. Admin-UI: erstatter det enkle tekstfeltet med tre tilstander: - Inaktiv: knapp "Flytt podcast til annen plattform..." - Bekreftelse: advarsel + URL-felt + rød "Aktiver redirect"-knapp - Aktiv: gul statusindikator med deaktiver-knapp Backend: sjekker redirect_feed tidlig i generate_feed() og returnerer 301 før noe annet arbeid gjøres (DB-oppslag for episodes osv).
This commit is contained in:
parent
ba8d361626
commit
c5239d2923
4 changed files with 98 additions and 17 deletions
|
|
@ -202,8 +202,13 @@ Brukeren eier dataene sine. Flytte bort er enkelt:
|
|||
}
|
||||
```
|
||||
|
||||
Når satt: Caddy returnerer 301 for feed-URL.
|
||||
Apple/Spotify oppdaterer automatisk.
|
||||
Når satt: maskinrommet returnerer HTTP 301 Moved Permanently for
|
||||
`/pub/{slug}/feed.xml` med `Location`-header til ny URL.
|
||||
Apple/Spotify oppdaterer automatisk. I tillegg inkluderes
|
||||
`<itunes:new-feed-url>` i RSS-en for klienter som ikke følger 301.
|
||||
|
||||
Admin-UI har én-klikks aktivering med advarsel. Redirecten kan
|
||||
deaktiveres når som helst fra podcast-trait-innstillingene.
|
||||
|
||||
Brukeren kan også eksportere all data:
|
||||
- RSS-feed med alle episoder
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@
|
|||
let explicit = $state((config.explicit as boolean) ?? false);
|
||||
let language = $state((config.language as string) ?? 'no');
|
||||
let redirectFeed = $state((config.redirect_feed as string) ?? '');
|
||||
let redirectInput = $state('');
|
||||
let showRedirectConfirm = $state(false);
|
||||
|
||||
// iTunes category tree (Apple Podcasts standard)
|
||||
const itunesCategories: Record<string, string[]> = {
|
||||
|
|
@ -182,19 +184,81 @@
|
|||
|
||||
<!-- Redirect Feed -->
|
||||
<div>
|
||||
<label for="podcast-redirect" class="block text-xs font-medium text-gray-600 mb-1">
|
||||
Redirect-feed (valgfritt)
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">
|
||||
Feed-redirect (flytte podcast)
|
||||
</label>
|
||||
|
||||
{#if redirectFeed}
|
||||
<!-- Active redirect indicator -->
|
||||
<div class="rounded border border-amber-300 bg-amber-50 p-3 space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="inline-block h-2 w-2 rounded-full bg-amber-500"></span>
|
||||
<span class="text-xs font-semibold text-amber-800">Redirect aktiv</span>
|
||||
</div>
|
||||
<p class="text-xs text-amber-700 break-all">
|
||||
Feed-URL returnerer 301 til:<br/>
|
||||
<a href={redirectFeed} target="_blank" rel="noopener" class="underline">{redirectFeed}</a>
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => { redirectFeed = ''; emitConfig(); }}
|
||||
class="rounded bg-white border border-amber-400 px-3 py-1 text-xs font-medium text-amber-800 hover:bg-amber-100"
|
||||
>
|
||||
Deaktiver redirect
|
||||
</button>
|
||||
</div>
|
||||
{:else if showRedirectConfirm}
|
||||
<!-- Confirmation dialog -->
|
||||
<div class="rounded border border-red-300 bg-red-50 p-3 space-y-2">
|
||||
<p class="text-xs font-semibold text-red-800">
|
||||
Advarsel: Dette aktiverer en permanent redirect (301)
|
||||
</p>
|
||||
<p class="text-xs text-red-700">
|
||||
Alle som henter feeden vil bli sendt til den nye URL-en.
|
||||
Podcastklienter (Apple, Spotify) oppdaterer automatisk.
|
||||
Du kan deaktivere redirecten nar som helst.
|
||||
</p>
|
||||
<input
|
||||
id="podcast-redirect"
|
||||
type="url"
|
||||
bind:value={redirectFeed}
|
||||
oninput={emitConfig}
|
||||
bind:value={redirectInput}
|
||||
placeholder="https://ny-host.example.com/feed.xml"
|
||||
class="w-full rounded border border-gray-300 px-3 py-1.5 text-sm focus:border-indigo-500 focus:outline-none"
|
||||
class="w-full rounded border border-red-300 px-3 py-1.5 text-sm focus:border-red-500 focus:outline-none"
|
||||
/>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
disabled={!redirectInput.trim()}
|
||||
onclick={() => {
|
||||
redirectFeed = redirectInput.trim();
|
||||
showRedirectConfirm = false;
|
||||
emitConfig();
|
||||
}}
|
||||
class="rounded bg-red-600 px-3 py-1 text-xs font-medium text-white hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Aktiver redirect
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => { showRedirectConfirm = false; redirectInput = ''; }}
|
||||
class="rounded bg-white border border-gray-300 px-3 py-1 text-xs font-medium text-gray-700 hover:bg-gray-50"
|
||||
>
|
||||
Avbryt
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Inactive state — one-click to start -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => { showRedirectConfirm = true; }}
|
||||
class="rounded border border-gray-300 px-3 py-1.5 text-xs font-medium text-gray-600 hover:bg-gray-50"
|
||||
>
|
||||
Flytt podcast til annen plattform...
|
||||
</button>
|
||||
<p class="mt-0.5 text-[11px] text-gray-400">
|
||||
Brukes for a flytte podcasten til en annen plattform. Setter itunes:new-feed-url i RSS.
|
||||
Aktiverer 301-redirect pa feed-URL-en slik at podcastklienter automatisk oppdaterer.
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -105,6 +105,19 @@ pub async fn generate_feed(
|
|||
})?
|
||||
.ok_or(StatusCode::NOT_FOUND)?;
|
||||
|
||||
// 301 Moved Permanently når redirect_feed er satt (podcast flyttet til ny host)
|
||||
if let Some(ref redirect_url) = collection.podcast_config.redirect_feed {
|
||||
if !redirect_url.is_empty() {
|
||||
tracing::info!(slug = %slug, redirect = %redirect_url, "Feed-redirect aktiv");
|
||||
return Ok(Response::builder()
|
||||
.status(StatusCode::MOVED_PERMANENTLY)
|
||||
.header(header::LOCATION, redirect_url.as_str())
|
||||
.header(header::CACHE_CONTROL, "public, max-age=3600")
|
||||
.body("Moved Permanently".into())
|
||||
.unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
let max_items = collection.rss_config.max_items.unwrap_or(50);
|
||||
let items = fetch_feed_items(&state.db, collection.id, max_items, collection.is_podcast)
|
||||
.await
|
||||
|
|
|
|||
3
tasks.md
3
tasks.md
|
|
@ -437,5 +437,4 @@ prøveimport-flyt.
|
|||
- [x] 30.7 Prøveimport-flyt i frontend: "Importer podcast"-wizard i admin. Steg 1: lim inn RSS-URL, vis forhåndsvisning av episoder. Steg 2: importer (kan ta tid for mange episoder). Steg 3: sjekk resultat, sammenlign feeds. Steg 4: re-importer nye episoder når klar. Steg 5: aktiver 301-redirect på gammel host.
|
||||
|
||||
### Eksport og redirect
|
||||
- [~] 30.8 Feed-redirect: `redirect_feed`-felt i podcast-trait. Når satt: Caddy returnerer 301 for feed-URL. Brukeren kan alltid flytte bort. Admin-UI med én-klikks aktivering og advarsel.
|
||||
> Påbegynt: 2026-03-19T00:25
|
||||
- [x] 30.8 Feed-redirect: `redirect_feed`-felt i podcast-trait. Når satt: Caddy returnerer 301 for feed-URL. Brukeren kan alltid flytte bort. Admin-UI med én-klikks aktivering og advarsel.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue