Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86ab8f2a19 |
@@ -468,8 +468,10 @@ _INDBYGGET = re.compile(r"FBAN|FBAV|FB_IAB|FBIOS|Messenger|Instagram|LinkedInApp
|
|||||||
async def podcast_faelles_side(request: Request, key: str):
|
async def podcast_faelles_side(request: Request, key: str):
|
||||||
ua = request.headers.get("user-agent", "")
|
ua = request.headers.get("user-agent", "")
|
||||||
forhaandsvisning = bool(_FORHAANDSVISNING.search(ua))
|
forhaandsvisning = bool(_FORHAANDSVISNING.search(ua))
|
||||||
if podcast.blokeret(_klient_ip(request)) or not podcast.faelles_slaa_op(key, "" if forhaandsvisning else "side"):
|
delt = None if podcast.blokeret(_klient_ip(request)) else podcast.delt_slaa_op(key, "" if forhaandsvisning else "side")
|
||||||
|
if not delt:
|
||||||
_ikke_fundet(request)
|
_ikke_fundet(request)
|
||||||
|
gaest = delt if delt["slags"] == "gaest" else None
|
||||||
rod = f"/podcast/f/{key}"
|
rod = f"/podcast/f/{key}"
|
||||||
base = str(request.base_url).rstrip("/")
|
base = str(request.base_url).rstrip("/")
|
||||||
udsendelser = _podcast_kontekst(request, rod, med_kladder=False)
|
udsendelser = _podcast_kontekst(request, rod, med_kladder=False)
|
||||||
@@ -478,7 +480,8 @@ async def podcast_faelles_side(request: Request, key: str):
|
|||||||
# stedet for et billede i hele beskedens bredde.
|
# stedet for et billede i hele beskedens bredde.
|
||||||
lille = bool(s.get("slug")) and podcast.cover(s["slug"], lille=True) is not None
|
lille = bool(s.get("slug")) and podcast.cover(s["slug"], lille=True) is not None
|
||||||
og = {"titel": s["titel"], "url": f"{base}{rod}/",
|
og = {"titel": s["titel"], "url": f"{base}{rod}/",
|
||||||
"beskrivelse": "Familiens podcast. Lyt i browseren, eller abonnér i din podcast-app.",
|
"beskrivelse": ("Lyt i browseren, eller abonnér i din podcast-app." if gaest else
|
||||||
|
"Familiens podcast. Lyt i browseren, eller abonnér i din podcast-app."),
|
||||||
"billede": (f"{base}{rod}/{s['slug']}/cover-lille.jpg" if lille else
|
"billede": (f"{base}{rod}/{s['slug']}/cover-lille.jpg" if lille else
|
||||||
f"{base}{rod}/{s['slug']}/cover.jpg" if s.get("har_cover") else ""),
|
f"{base}{rod}/{s['slug']}/cover.jpg" if s.get("har_cover") else ""),
|
||||||
"str": 300 if lille else 3000}
|
"str": 300 if lille else 3000}
|
||||||
@@ -502,12 +505,12 @@ async def podcast_faelles_side(request: Request, key: str):
|
|||||||
indbygget = {"platform": "andet"}
|
indbygget = {"platform": "andet"}
|
||||||
return templates.TemplateResponse(request, "podcast.html", {
|
return templates.TemplateResponse(request, "podcast.html", {
|
||||||
"request": request, "user": None, "is_admin": False, "rod": rod, "delt": True, "og": og,
|
"request": request, "user": None, "is_admin": False, "rod": rod, "delt": True, "og": og,
|
||||||
"indbygget": indbygget, "udsendelser": udsendelser, "saved": ""}, headers=headers)
|
"indbygget": indbygget, "gaest": gaest, "udsendelser": udsendelser, "saved": ""}, headers=headers)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/podcast/f/{key}/{name}.xml")
|
@app.get("/podcast/f/{key}/{name}.xml")
|
||||||
async def podcast_faelles_feed(request: Request, key: str, name: str):
|
async def podcast_faelles_feed(request: Request, key: str, name: str):
|
||||||
if podcast.blokeret(_klient_ip(request)) or not podcast.faelles_slaa_op(key, "feed"):
|
if podcast.blokeret(_klient_ip(request)) or not podcast.delt_slaa_op(key, "feed"):
|
||||||
_ikke_fundet(request)
|
_ikke_fundet(request)
|
||||||
xml = podcast.feed(name, f"/podcast/f/{key}", str(request.base_url).rstrip("/"), med_kladder=False)
|
xml = podcast.feed(name, f"/podcast/f/{key}", str(request.base_url).rstrip("/"), med_kladder=False)
|
||||||
if xml is None:
|
if xml is None:
|
||||||
@@ -519,7 +522,7 @@ async def podcast_faelles_feed(request: Request, key: str, name: str):
|
|||||||
@app.get("/podcast/f/{key}/{name}/cover-lille.jpg")
|
@app.get("/podcast/f/{key}/{name}/cover-lille.jpg")
|
||||||
async def podcast_faelles_cover_lille(request: Request, key: str, name: str):
|
async def podcast_faelles_cover_lille(request: Request, key: str, name: str):
|
||||||
p = podcast.cover(name, lille=True)
|
p = podcast.cover(name, lille=True)
|
||||||
if podcast.blokeret(_klient_ip(request)) or not podcast.faelles_slaa_op(key) or not p:
|
if podcast.blokeret(_klient_ip(request)) or not podcast.delt_slaa_op(key) or not p:
|
||||||
_ikke_fundet(request)
|
_ikke_fundet(request)
|
||||||
return FileResponse(p, media_type="image/jpeg", headers={"Cache-Control": "private, max-age=86400"})
|
return FileResponse(p, media_type="image/jpeg", headers={"Cache-Control": "private, max-age=86400"})
|
||||||
|
|
||||||
@@ -527,7 +530,7 @@ async def podcast_faelles_cover_lille(request: Request, key: str, name: str):
|
|||||||
@app.get("/podcast/f/{key}/{name}/cover.jpg")
|
@app.get("/podcast/f/{key}/{name}/cover.jpg")
|
||||||
async def podcast_faelles_cover(request: Request, key: str, name: str):
|
async def podcast_faelles_cover(request: Request, key: str, name: str):
|
||||||
p = podcast.cover(name)
|
p = podcast.cover(name)
|
||||||
if podcast.blokeret(_klient_ip(request)) or not podcast.faelles_slaa_op(key) or not p:
|
if podcast.blokeret(_klient_ip(request)) or not podcast.delt_slaa_op(key) or not p:
|
||||||
_ikke_fundet(request)
|
_ikke_fundet(request)
|
||||||
return FileResponse(p, media_type="image/jpeg", headers={"Cache-Control": "private, max-age=86400"})
|
return FileResponse(p, media_type="image/jpeg", headers={"Cache-Control": "private, max-age=86400"})
|
||||||
|
|
||||||
@@ -539,7 +542,7 @@ async def podcast_faelles_mp3(request: Request, key: str, name: str, slug: str):
|
|||||||
p = podcast.episode_fil(name, slug)
|
p = podcast.episode_fil(name, slug)
|
||||||
meta = podcast.episode_meta(name, slug) or {}
|
meta = podcast.episode_meta(name, slug) or {}
|
||||||
if (podcast.blokeret(_klient_ip(request)) or not p or meta.get("kladde")
|
if (podcast.blokeret(_klient_ip(request)) or not p or meta.get("kladde")
|
||||||
or not podcast.faelles_slaa_op(key, slug if fra_start else "")):
|
or not podcast.delt_slaa_op(key, slug if fra_start else "")):
|
||||||
_ikke_fundet(request)
|
_ikke_fundet(request)
|
||||||
return FileResponse(p, media_type="audio/mpeg",
|
return FileResponse(p, media_type="audio/mpeg",
|
||||||
headers={"Cache-Control": "private, max-age=86400", "X-Robots-Tag": "noindex, nofollow"})
|
headers={"Cache-Control": "private, max-age=86400", "X-Robots-Tag": "noindex, nofollow"})
|
||||||
@@ -594,7 +597,7 @@ async def admin_podcast(request: Request, saved: str = ""):
|
|||||||
user = require_admin(request)
|
user = require_admin(request)
|
||||||
return templates.TemplateResponse(request, "admin_podcast.html", {
|
return templates.TemplateResponse(request, "admin_podcast.html", {
|
||||||
"request": request, "user": user, "is_admin": True, "saved": saved,
|
"request": request, "user": user, "is_admin": True, "saved": saved,
|
||||||
"noegler": podcast.alle_noegler(), "faelles": podcast.faelles(),
|
"noegler": podcast.alle_noegler(), "faelles": podcast.faelles(), "gaester": podcast.alle_gaester(),
|
||||||
"base": str(request.base_url).rstrip("/"),
|
"base": str(request.base_url).rstrip("/"),
|
||||||
"udsendelser": [{**podcast.show(n), "episoder": podcast.episodes(n, med_kladder=True)} for n in podcast.shows()]})
|
"udsendelser": [{**podcast.show(n), "episoder": podcast.episodes(n, med_kladder=True)} for n in podcast.shows()]})
|
||||||
|
|
||||||
@@ -615,6 +618,27 @@ async def admin_podcast_faelles(request: Request):
|
|||||||
return RedirectResponse(f"/admin/podcast?saved={urllib.parse.quote(besked)}", status_code=303)
|
return RedirectResponse(f"/admin/podcast?saved={urllib.parse.quote(besked)}", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/admin/podcast/gaest")
|
||||||
|
async def admin_podcast_gaest(request: Request):
|
||||||
|
require_admin(request)
|
||||||
|
f = await request.form()
|
||||||
|
handling, key = f.get("handling"), f.get("key") or ""
|
||||||
|
if handling == "ny":
|
||||||
|
try:
|
||||||
|
dage = max(0, min(int(f.get("dage") or 30), 365))
|
||||||
|
except ValueError:
|
||||||
|
dage = 30
|
||||||
|
podcast.gaest_ny(f.get("navn") or "", dage)
|
||||||
|
besked = "Gæstelink lavet. Kopiér det i listen herunder."
|
||||||
|
elif handling == "tilbagekald":
|
||||||
|
besked = "Gæstelinket virker ikke længere" if podcast.gaest_tilbagekald(key) else "Ukendt link"
|
||||||
|
elif handling == "slet":
|
||||||
|
besked = "Gæstelinket er slettet" if podcast.gaest_slet(key) else "Ukendt link"
|
||||||
|
else:
|
||||||
|
besked = "Ukendt handling"
|
||||||
|
return RedirectResponse(f"/admin/podcast?saved={urllib.parse.quote(besked)}", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
@app.post("/admin/podcast/noegle/{key}")
|
@app.post("/admin/podcast/noegle/{key}")
|
||||||
async def admin_podcast_noegle(request: Request, key: str):
|
async def admin_podcast_noegle(request: Request, key: str):
|
||||||
require_admin(request)
|
require_admin(request)
|
||||||
|
|||||||
+72
@@ -12,6 +12,7 @@ Data (ikke i git, persondata):
|
|||||||
DATA/podcast/<show>/episodes/<slug>.json titel, beskrivelse, nr, udgivet, kladde
|
DATA/podcast/<show>/episodes/<slug>.json titel, beskrivelse, nr, udgivet, kladde
|
||||||
DATA/podcast_keys.json nøgle -> bruger, statistik
|
DATA/podcast_keys.json nøgle -> bruger, statistik
|
||||||
DATA/podcast_faelles.json det fælles familielink (én nøgle, kan slås fra)
|
DATA/podcast_faelles.json det fælles familielink (én nøgle, kan slås fra)
|
||||||
|
DATA/podcast_gaester.json gæstelinks til ukendte (navn, udløb, kan trækkes tilbage)
|
||||||
|
|
||||||
En episode med "kladde": true ses kun af administratorer (i deres eget feed og
|
En episode med "kladde": true ses kun af administratorer (i deres eget feed og
|
||||||
på siden), så den kan høres i en rigtig podcast-app før familien får den.
|
på siden), så den kan høres i en rigtig podcast-app før familien får den.
|
||||||
@@ -31,6 +32,7 @@ import config
|
|||||||
ROOT = config.DATA / "podcast"
|
ROOT = config.DATA / "podcast"
|
||||||
KEYS = config.DATA / "podcast_keys.json"
|
KEYS = config.DATA / "podcast_keys.json"
|
||||||
DELT = config.DATA / "podcast_faelles.json"
|
DELT = config.DATA / "podcast_faelles.json"
|
||||||
|
GAEST = config.DATA / "podcast_gaester.json"
|
||||||
SHOW = re.compile(r"^[a-z0-9][a-z0-9-]{0,63}$")
|
SHOW = re.compile(r"^[a-z0-9][a-z0-9-]{0,63}$")
|
||||||
SLUG = re.compile(r"^[a-z0-9][a-z0-9._-]{0,120}$")
|
SLUG = re.compile(r"^[a-z0-9][a-z0-9._-]{0,120}$")
|
||||||
KEY = re.compile(r"^[A-Za-z0-9_-]{32,64}$")
|
KEY = re.compile(r"^[A-Za-z0-9_-]{32,64}$")
|
||||||
@@ -303,6 +305,76 @@ def faelles_slaa_op(key: str, hvad: str = "") -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- del med ukendte ----------------
|
||||||
|
# Gæstelinks til folk uden for familien (fx en jobsamtale): hver har sit eget navn,
|
||||||
|
# sin egen nøgle og en udløbsdato, og kan trækkes tilbage uden at røre familiens
|
||||||
|
# link eller de personlige adresser. Samme side som det fælles link, aldrig kladder.
|
||||||
|
|
||||||
|
def gaester() -> dict:
|
||||||
|
try:
|
||||||
|
return json.loads(GAEST.read_text())
|
||||||
|
except (OSError, ValueError):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def alle_gaester() -> list[dict]:
|
||||||
|
nu = time.time()
|
||||||
|
ud = [{"key": key, **v, "udloebet": bool(v.get("udloeber") and v["udloeber"] < nu)}
|
||||||
|
for key, v in gaester().items()]
|
||||||
|
return sorted(ud, key=lambda v: (bool(v.get("tilbagekaldt") or v["udloebet"]), -v.get("oprettet", 0)))
|
||||||
|
|
||||||
|
|
||||||
|
def gaest_ny(navn: str, dage: int) -> str:
|
||||||
|
g = gaester()
|
||||||
|
key = secrets.token_urlsafe(32)
|
||||||
|
nu = int(time.time())
|
||||||
|
g[key] = {"navn": navn.strip()[:80] or "Gæst", "oprettet": nu,
|
||||||
|
"udloeber": nu + dage * 86400 if dage > 0 else None, "hentninger": 0, "hentet": {}}
|
||||||
|
_skriv(GAEST, g)
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
def gaest_tilbagekald(key: str) -> bool:
|
||||||
|
g = gaester()
|
||||||
|
if key not in g:
|
||||||
|
return False
|
||||||
|
g[key]["tilbagekaldt"] = int(time.time())
|
||||||
|
_skriv(GAEST, g)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def gaest_slet(key: str) -> bool:
|
||||||
|
g = gaester()
|
||||||
|
if g.pop(key, None) is None:
|
||||||
|
return False
|
||||||
|
_skriv(GAEST, g)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def gaest_slaa_op(key: str, hvad: str = "") -> dict | None:
|
||||||
|
if not KEY.match(key or ""):
|
||||||
|
return None
|
||||||
|
g = gaester()
|
||||||
|
v = g.get(key)
|
||||||
|
if not v or v.get("tilbagekaldt") or (v.get("udloeber") and v["udloeber"] < time.time()):
|
||||||
|
return None
|
||||||
|
if hvad:
|
||||||
|
v["hentninger"] = v.get("hentninger", 0) + 1
|
||||||
|
v["sidst"] = int(time.time())
|
||||||
|
v.setdefault("hentet", {})
|
||||||
|
v["hentet"][hvad] = v["hentet"].get(hvad, 0) + 1
|
||||||
|
_skriv(GAEST, g)
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
def delt_slaa_op(key: str, hvad: str = "") -> dict | None:
|
||||||
|
"""Fælles familielink eller gæstelink -> {"slags": "familie"|"gaest", ...}, ellers None."""
|
||||||
|
if faelles_slaa_op(key, hvad):
|
||||||
|
return {"slags": "familie"}
|
||||||
|
v = gaest_slaa_op(key, hvad)
|
||||||
|
return {"slags": "gaest", **v} if v else None
|
||||||
|
|
||||||
|
|
||||||
# ---------------- gæt på nøgler ----------------
|
# ---------------- gæt på nøgler ----------------
|
||||||
# Nøglerne er 256 bit og kan ikke gættes, men en klient der bliver ved med at
|
# Nøglerne er 256 bit og kan ikke gættes, men en klient der bliver ved med at
|
||||||
# ramme forkerte nøgler, får 404 uden at vi overhovedet slår op (og samme svar
|
# ramme forkerte nøgler, får 404 uden at vi overhovedet slår op (og samme svar
|
||||||
|
|||||||
@@ -63,6 +63,54 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h2 style="font-size:1.15rem;margin-top:2rem">Del med ukendte</h2>
|
||||||
|
<p class="wt-muted" style="margin:.3rem 0 .8rem">Gæstelinks til folk uden for familien, fx til en jobsamtale. Hvert link har sin egen nøgle og udløber af sig selv; du kan også trække det tilbage før tid. Det har intet med familiens link eller de personlige adresser at gøre. Kladder vises aldrig.</p>
|
||||||
|
<form method="post" action="/admin/podcast/gaest" class="wt-card" style="padding:1rem 1.2rem;max-width:48rem;display:flex;gap:.5rem;flex-wrap:wrap;align-items:flex-end">
|
||||||
|
<input type="hidden" name="handling" value="ny">
|
||||||
|
<label style="flex:1 1 16rem">Hvem er det til<br>
|
||||||
|
<input class="wt-input" name="navn" required maxlength="80" placeholder="fx Jobsamtale, Firma A/S" style="width:100%"></label>
|
||||||
|
<label>Udløber efter<br>
|
||||||
|
<select class="wt-input" name="dage">
|
||||||
|
<option value="7">7 dage</option><option value="14">14 dage</option>
|
||||||
|
<option value="30" selected>30 dage</option><option value="90">90 dage</option>
|
||||||
|
<option value="0">Aldrig (træk selv tilbage)</option>
|
||||||
|
</select></label>
|
||||||
|
<button class="wt-btn wt-btn--primary" type="submit">Lav gæstelink</button>
|
||||||
|
</form>
|
||||||
|
<table class="wt-table" style="margin-top:.8rem">
|
||||||
|
<thead><tr><th>Til</th><th>Link</th><th>Udløber</th><th>Sidst brugt</th><th>Hentninger</th><th></th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for g in gaester %}
|
||||||
|
{% set doed = g.tilbagekaldt or g.udloebet %}
|
||||||
|
<tr{% if doed %} style="opacity:.55"{% endif %}>
|
||||||
|
<td>{{ g.navn }}</td>
|
||||||
|
<td>{% if doed %}<span class="wt-muted">{{ 'Trukket tilbage' if g.tilbagekaldt else 'Udløbet' }}</span>{% else %}
|
||||||
|
<div style="display:flex;gap:.4rem;min-width:14rem">
|
||||||
|
<input class="wt-input" readonly id="g-{{ loop.index }}" value="{{ base }}/podcast/f/{{ g.key }}/"
|
||||||
|
style="flex:1;font-family:ui-monospace,monospace;font-size:.8rem" onclick="this.select()">
|
||||||
|
<button class="wt-btn" type="button" onclick="var i=document.getElementById('g-{{ loop.index }}');i.select();navigator.clipboard&&navigator.clipboard.writeText(i.value);this.textContent='Kopieret'">Kopiér</button>
|
||||||
|
</div>{% endif %}</td>
|
||||||
|
<td>{% if g.udloeber %}<span data-ts="{{ g.udloeber }}"></span>{% else %}Aldrig{% endif %}</td>
|
||||||
|
<td data-ts="{{ g.sidst or '' }}"></td>
|
||||||
|
<td title="{% for h, n in (g.hentet or {}).items() %}{{ h }}: {{ n }} {% endfor %}">{{ g.hentninger or 0 }}</td>
|
||||||
|
<td>
|
||||||
|
<form method="post" action="/admin/podcast/gaest" style="margin:0">
|
||||||
|
<input type="hidden" name="key" value="{{ g.key }}">
|
||||||
|
{% if doed %}
|
||||||
|
<button class="wt-btn" type="submit" name="handling" value="slet">Slet</button>
|
||||||
|
{% else %}
|
||||||
|
<button class="wt-btn" type="submit" name="handling" value="tilbagekald"
|
||||||
|
onclick="return confirm('Træk gæstelinket tilbage? Det holder op med at virke med det samme.')">Træk tilbage</button>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr><td colspan="6" class="wt-muted">Ingen gæstelinks.</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<h2 style="font-size:1.15rem;margin-top:2rem">Lytternes adresser</h2>
|
<h2 style="font-size:1.15rem;margin-top:2rem">Lytternes adresser</h2>
|
||||||
<table class="wt-table">
|
<table class="wt-table">
|
||||||
<thead><tr><th>Bruger</th><th>Oprettet</th><th>Sidst hentet</th><th>Hentninger</th><th>App</th><th></th></tr></thead>
|
<thead><tr><th>Bruger</th><th>Oprettet</th><th>Sidst hentet</th><th>Hentninger</th><th>App</th><th></th></tr></thead>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="wt-muted" style="margin:.2rem 0 .8rem">{% if delt %}Tryk på din app, så kommer nye episoder af sig selv. Linket er familiens fælles, så del det kun inden for familien.{% else %}Tryk på din app. Nye episoder kommer så af sig selv. Adressen er din egen, så del den ikke; familien får hver sin.{% endif %}</p>
|
<p class="wt-muted" style="margin:.2rem 0 .8rem">{% if gaest %}Tryk på din app, så kommer nye episoder af sig selv. Linket er delt med dig personligt{% if gaest.udloeber %} og virker til <span data-ts="{{ gaest.udloeber }}"></span>{% endif %}, så del det ikke videre.{% elif delt %}Tryk på din app, så kommer nye episoder af sig selv. Linket er familiens fælles, så del det kun inden for familien.{% else %}Tryk på din app. Nye episoder kommer så af sig selv. Adressen er din egen, så del den ikke; familien får hver sin.{% endif %}</p>
|
||||||
<div style="display:flex;flex-wrap:wrap;gap:.5rem">
|
<div style="display:flex;flex-wrap:wrap;gap:.5rem">
|
||||||
<a class="wt-btn wt-btn--primary" href="podcast://{{ u.uden_skema }}">Apple Podcasts</a>
|
<a class="wt-btn wt-btn--primary" href="podcast://{{ u.uden_skema }}">Apple Podcasts</a>
|
||||||
<a class="wt-btn" href="overcast://x-callback-url/add?url={{ u.feed_enc }}">Overcast</a>
|
<a class="wt-btn" href="overcast://x-callback-url/add?url={{ u.feed_enc }}">Overcast</a>
|
||||||
@@ -108,6 +108,9 @@ function kopierTekst(t, knap) {
|
|||||||
};
|
};
|
||||||
if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(t).then(ok, fallback); else fallback();
|
if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(t).then(ok, fallback); else fallback();
|
||||||
}
|
}
|
||||||
|
document.querySelectorAll('[data-ts]').forEach(function (s) {
|
||||||
|
s.textContent = new Date(parseInt(s.dataset.ts, 10) * 1000).toLocaleDateString('da-DK', { dateStyle: 'long' });
|
||||||
|
});
|
||||||
document.querySelectorAll('[data-kopier-tekst]').forEach(function (b) {
|
document.querySelectorAll('[data-kopier-tekst]').forEach(function (b) {
|
||||||
b.addEventListener('click', function () { kopierTekst(b.dataset.kopierTekst, b); });
|
b.addEventListener('click', function () { kopierTekst(b.dataset.kopierTekst, b); });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Røgtest af "Del med ukendte" (gæstelinks) med FastAPI TestClient, uden OIDC.
|
||||||
|
# Kør i CT151: PORTAL_DATA=/tmp/pg-test/data + env fra /etc/portal.env. Rører ikke prod-data.
|
||||||
|
import json, time
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
import main, podcast
|
||||||
|
|
||||||
|
d = podcast.ROOT / "test"
|
||||||
|
(d / "episodes").mkdir(parents=True, exist_ok=True)
|
||||||
|
(d / "show.json").write_text(json.dumps({"titel": "Testshow", "beskrivelse": "b"}))
|
||||||
|
for slug, kladde in (("e1", False), ("e2", True)):
|
||||||
|
(d / "episodes" / f"{slug}.mp3").write_bytes(b"\xff\xfb" + b"\0" * 2000)
|
||||||
|
(d / "episodes" / f"{slug}.json").write_text(json.dumps({"titel": slug, "nr": 1, "kladde": kladde}))
|
||||||
|
|
||||||
|
c = TestClient(main.app, base_url="https://weircon.dk")
|
||||||
|
admin = {"username": "agw", "name": "Asger", "groups": [main.config.ADMIN_GROUP, "Familie"]}
|
||||||
|
main.current_user = lambda request: admin
|
||||||
|
|
||||||
|
r = c.post("/admin/podcast/gaest", data={"handling": "ny", "navn": "Jobsamtale X", "dage": "14"}, follow_redirects=False)
|
||||||
|
assert r.status_code == 303, r.text
|
||||||
|
g = podcast.alle_gaester()[0]; key = g["key"]
|
||||||
|
assert 13.9 * 86400 < g["udloeber"] - time.time() < 14.1 * 86400
|
||||||
|
r = c.get("/admin/podcast"); assert r.status_code == 200 and "Jobsamtale X" in r.text and key in r.text
|
||||||
|
|
||||||
|
main.current_user = lambda request: None
|
||||||
|
r = c.get(f"/podcast/f/{key}/"); assert r.status_code == 200 and "delt med dig personligt" in r.text, r.status_code
|
||||||
|
assert "familiens fælles" not in r.text and "e2.mp3" not in r.text
|
||||||
|
assert c.get(f"/podcast/f/{key}/test.xml").status_code == 200
|
||||||
|
assert c.get(f"/podcast/f/{key}/test/e1.mp3").status_code == 200
|
||||||
|
assert c.get(f"/podcast/f/{key}/test/e2.mp3").status_code == 404, "kladde må ikke kunne hentes"
|
||||||
|
assert podcast.gaester()[key]["hentninger"] == 3
|
||||||
|
assert not podcast.faelles().get("key"), "gæstelinket må ikke røre familielinket"
|
||||||
|
|
||||||
|
# udløbet
|
||||||
|
gg = podcast.gaester(); gg[key]["udloeber"] = int(time.time()) - 1; podcast._skriv(podcast.GAEST, gg)
|
||||||
|
assert c.get(f"/podcast/f/{key}/").status_code == 404
|
||||||
|
gg[key]["udloeber"] = int(time.time()) + 3600; podcast._skriv(podcast.GAEST, gg)
|
||||||
|
assert c.get(f"/podcast/f/{key}/").status_code == 200
|
||||||
|
|
||||||
|
# trukket tilbage, slettet
|
||||||
|
main.current_user = lambda request: admin
|
||||||
|
assert c.post("/admin/podcast/gaest", data={"handling": "tilbagekald", "key": key}, follow_redirects=False).status_code == 303
|
||||||
|
assert c.get(f"/podcast/f/{key}/test/e1.mp3").status_code == 404
|
||||||
|
assert c.post("/admin/podcast/gaest", data={"handling": "slet", "key": key}, follow_redirects=False).status_code == 303
|
||||||
|
assert key not in podcast.gaester()
|
||||||
|
|
||||||
|
# "aldrig" + familielinket virker stadig ved siden af
|
||||||
|
c.post("/admin/podcast/gaest", data={"handling": "ny", "navn": "Uden udløb", "dage": "0"})
|
||||||
|
assert podcast.alle_gaester()[0]["udloeber"] is None
|
||||||
|
fk = podcast.faelles_skift()
|
||||||
|
r = c.get(f"/podcast/f/{fk}/"); assert r.status_code == 200 and "familiens fælles" in r.text
|
||||||
|
print("OK")
|
||||||
Reference in New Issue
Block a user