205 lines
8.1 KiB
Lua
205 lines
8.1 KiB
Lua
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|||
|
|
-- ic-web – Seiten lesen und schreiben, Meldungen
|
|||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
ICWeb = ICWeb or {}
|
|||
|
|
ICWeb.Sites = {}
|
|||
|
|
|
|||
|
|
local S = ICWeb.Sites
|
|||
|
|
local DB = ICWeb.DB
|
|||
|
|
local FW = ICWeb.FW
|
|||
|
|
local D = ICWeb.Domains
|
|||
|
|
local B = ICWeb.Blocks
|
|||
|
|
|
|||
|
|
-- Bloecke aus der Datenbank lesen. Kaputtes JSON darf keine Seite zerlegen.
|
|||
|
|
local function decodeBlocks(raw)
|
|||
|
|
if type(raw) ~= 'string' or raw == '' then return {} end
|
|||
|
|
local ok, decoded = pcall(json.decode, raw)
|
|||
|
|
if not ok or type(decoded) ~= 'table' then return {} end
|
|||
|
|
return decoded
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- ── Lesen ───────────────────────────────────────────────────────────────────
|
|||
|
|
-- Was der Browser bekommt. Gibt (site, nil) oder (nil, fehlertext) zurueck.
|
|||
|
|
function S.GetPublic(domain, slug)
|
|||
|
|
local site = DB.GetSiteByDomain(tostring(domain or ''):lower())
|
|||
|
|
if not site then return nil, 'Diese Adresse existiert nicht.' end
|
|||
|
|
if ICWeb.Bool(site.blocked) then return nil, 'Diese Seite wurde gesperrt.' end
|
|||
|
|
if not ICWeb.Bool(site.published) then
|
|||
|
|
return nil, 'Diese Seite ist noch nicht veröffentlicht.'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local pages = DB.GetPages(site.id)
|
|||
|
|
if #pages == 0 then return nil, 'Diese Seite hat noch keinen Inhalt.' end
|
|||
|
|
|
|||
|
|
slug = tostring(slug or 'home')
|
|||
|
|
local page
|
|||
|
|
for _, p in ipairs(pages) do
|
|||
|
|
if p.slug == slug then page = p break end
|
|||
|
|
end
|
|||
|
|
page = page or pages[1]
|
|||
|
|
|
|||
|
|
-- Navigation: nur Titel und Slug, keine Inhalte.
|
|||
|
|
local nav = {}
|
|||
|
|
for _, p in ipairs(pages) do
|
|||
|
|
nav[#nav + 1] = { slug = p.slug, title = p.title }
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
domain = site.domain,
|
|||
|
|
title = site.title,
|
|||
|
|
theme = site.theme,
|
|||
|
|
nav = nav,
|
|||
|
|
page = { slug = page.slug, title = page.title, blocks = decodeBlocks(page.blocks) },
|
|||
|
|
}, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Was der Bearbeiter bekommt: auch unveroeffentlichte Seiten.
|
|||
|
|
function S.GetForEditor(src, domain)
|
|||
|
|
local site = DB.GetSiteByDomain(tostring(domain or ''):lower())
|
|||
|
|
if not site then return nil, 'Domäne nicht gefunden.' end
|
|||
|
|
if not D.CanEdit(src, site) then return nil, 'Keine Berechtigung für diese Domäne.' end
|
|||
|
|
|
|||
|
|
local pages = {}
|
|||
|
|
for _, p in ipairs(DB.GetPages(site.id)) do
|
|||
|
|
pages[#pages + 1] = {
|
|||
|
|
slug = p.slug, title = p.title,
|
|||
|
|
sort_order = p.sort_order, blocks = decodeBlocks(p.blocks),
|
|||
|
|
}
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
id = site.id, domain = site.domain, title = site.title,
|
|||
|
|
theme = site.theme, published = ICWeb.Bool(site.published),
|
|||
|
|
blocked = ICWeb.Bool(site.blocked), pages = pages,
|
|||
|
|
}, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Alle Domaenen, die der angemeldete Zugang bearbeiten darf.
|
|||
|
|
function S.ListMine(src)
|
|||
|
|
local session = ICWeb.Auth.Get(src)
|
|||
|
|
if not session then return {} end
|
|||
|
|
|
|||
|
|
local function entry(site)
|
|||
|
|
return { domain = site.domain, title = site.title,
|
|||
|
|
published = ICWeb.Bool(site.published),
|
|||
|
|
blocked = ICWeb.Bool(site.blocked) }
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Der Anbieter sieht alles, alle anderen genau ihre Domaene.
|
|||
|
|
if session.role == 'superadmin' then
|
|||
|
|
local out = {}
|
|||
|
|
for _, site in ipairs(DB.GetAllSites()) do out[#out + 1] = entry(site) end
|
|||
|
|
return out
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local site = session.domain and DB.GetSiteByDomain(session.domain)
|
|||
|
|
return site and { entry(site) } or {}
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- ── Schreiben ───────────────────────────────────────────────────────────────
|
|||
|
|
local function checkSlug(slug)
|
|||
|
|
slug = tostring(slug or ''):lower():gsub('%s', '')
|
|||
|
|
if not slug:match('^[a-z0-9%-]+$') then return nil end
|
|||
|
|
if #slug < 1 or #slug > 60 then return nil end
|
|||
|
|
return slug
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Seite speichern. Gibt (true, nil) oder (false, fehlertext) zurueck.
|
|||
|
|
function S.SavePage(src, domain, slug, title, blocks)
|
|||
|
|
local site = DB.GetSiteByDomain(tostring(domain or ''):lower())
|
|||
|
|
if not site then return false, 'Domäne nicht gefunden.' end
|
|||
|
|
if not D.CanEdit(src, site) then return false, 'Keine Berechtigung.' end
|
|||
|
|
if ICWeb.Bool(site.blocked) then return false, 'Diese Seite ist gesperrt.' end
|
|||
|
|
|
|||
|
|
local safeSlug = checkSlug(slug)
|
|||
|
|
if not safeSlug then return false, 'Ungültiger Seitenname.' end
|
|||
|
|
|
|||
|
|
-- Neue Seite? Dann Obergrenze pruefen.
|
|||
|
|
if not DB.GetPage(site.id, safeSlug) then
|
|||
|
|
if DB.CountPages(site.id) >= (Config.MaxPagesPerSite or 8) then
|
|||
|
|
return false, ('Höchstens %d Seiten je Domäne.'):format(Config.MaxPagesPerSite or 8)
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Hier faellt alles weg, was nicht ins Blockschema passt.
|
|||
|
|
local safeBlocks, err = B.ValidatePage(blocks)
|
|||
|
|
if not safeBlocks then return false, err end
|
|||
|
|
|
|||
|
|
local safeTitle = tostring(title or safeSlug):sub(1, 120)
|
|||
|
|
DB.SavePage(site.id, safeSlug, safeTitle, json.encode(safeBlocks), 0)
|
|||
|
|
|
|||
|
|
ICWeb.Log.Write(src, 'page.saved', {
|
|||
|
|
domain = site.domain, slug = safeSlug, blocks = #safeBlocks,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return true, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function S.DeletePage(src, domain, slug)
|
|||
|
|
local site = DB.GetSiteByDomain(tostring(domain or ''):lower())
|
|||
|
|
if not site then return false, 'Domäne nicht gefunden.' end
|
|||
|
|
if not D.CanEdit(src, site) then return false, 'Keine Berechtigung.' end
|
|||
|
|
|
|||
|
|
local safeSlug = checkSlug(slug)
|
|||
|
|
if not safeSlug then return false, 'Ungültiger Seitenname.' end
|
|||
|
|
if safeSlug == 'home' then return false, 'Die Startseite lässt sich nicht löschen.' end
|
|||
|
|
|
|||
|
|
DB.DeletePage(site.id, safeSlug)
|
|||
|
|
ICWeb.Log.Write(src, 'page.deleted', { domain = site.domain, slug = safeSlug })
|
|||
|
|
return true, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Titel, Theme und Veroeffentlichung.
|
|||
|
|
function S.UpdateSite(src, domain, data)
|
|||
|
|
local site = DB.GetSiteByDomain(tostring(domain or ''):lower())
|
|||
|
|
if not site then return false, 'Domäne nicht gefunden.' end
|
|||
|
|
if not D.CanEdit(src, site) then return false, 'Keine Berechtigung.' end
|
|||
|
|
if ICWeb.Bool(site.blocked) then return false, 'Diese Seite ist gesperrt.' end
|
|||
|
|
|
|||
|
|
local theme = tostring(data.theme or site.theme)
|
|||
|
|
local themeOk = false
|
|||
|
|
for _, t in ipairs(Config.Themes) do
|
|||
|
|
if t == theme then themeOk = true break end
|
|||
|
|
end
|
|||
|
|
if not themeOk then theme = Config.DefaultTheme end
|
|||
|
|
|
|||
|
|
DB.UpdateSite(site.id, {
|
|||
|
|
title = tostring(data.title or site.title):sub(1, 120),
|
|||
|
|
theme = theme,
|
|||
|
|
published = data.published == true,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
ICWeb.Log.Write(src, 'site.updated', {
|
|||
|
|
domain = site.domain, published = data.published == true, theme = theme,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return true, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- ── Meldungen ───────────────────────────────────────────────────────────────
|
|||
|
|
-- Bilder werden extern verlinkt; die Hostliste faengt nur den groben Rahmen ab.
|
|||
|
|
-- Deshalb muss jeder Spieler eine Seite melden koennen.
|
|||
|
|
function S.Report(src, domain, slug, reason)
|
|||
|
|
local site = DB.GetSiteByDomain(tostring(domain or ''):lower())
|
|||
|
|
if not site then return false, 'Domäne nicht gefunden.' end
|
|||
|
|
|
|||
|
|
local identifier = FW.GetIdentifier(src)
|
|||
|
|
if not identifier then return false, 'Spieler nicht geladen.' end
|
|||
|
|
|
|||
|
|
-- Eine Meldung je Spieler und Seite pro Stunde: sonst laesst sich die
|
|||
|
|
-- Meldeliste durch Wiederholung unbrauchbar machen.
|
|||
|
|
if DB.RecentReportBy(site.id, identifier, 3600) then
|
|||
|
|
return false, 'Du hast diese Seite bereits gemeldet.'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local safeReason = tostring(reason or ''):gsub('[^\32-\126\128-\255]', ''):sub(1, 500)
|
|||
|
|
DB.AddReport(site.id, checkSlug(slug), safeReason, identifier)
|
|||
|
|
|
|||
|
|
ICWeb.Log.Write(src, 'site.reported', {
|
|||
|
|
domain = site.domain, slug = slug, reason = safeReason,
|
|||
|
|
}, 'warn')
|
|||
|
|
|
|||
|
|
return true, nil
|
|||
|
|
end
|