-- ───────────────────────────────────────────────────────────────────────────── -- 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