Ein IC-Hostinganbieter im Spiel. Er vergibt Domaenen, richtet je Domaene einen Zugang ein, und die Inhaber pflegen darauf ihre Webseite. Die gesamte Verwaltung haengt an einer Anmeldung, nicht an Job oder Serverrechten. Ein Zugang ist zugleich ein Postfach: wer sich anmeldet, bekommt das Postfach dieser Adresse. Mehrere Personen koennen denselben Zugang benutzen und dasselbe Postfach gleichzeitig offen haben - ein Firmenpostfach gehoert der Firma, nicht einer Person. Drei Ebenen: superadmin vergibt Domaenen und setzt je Domaene einen Admin ein admin pflegt die Seiten seiner Domaene und legt dort Zugaenge an editor pflegt nur die Seiten Seiteninhalte sind kein HTML, sondern typisierte Bloecke. Die Seiten werden im NUI des IC-Computers angezeigt, also im selben Kontext wie dieser selbst - wer dort Markup einschleusen kann, kann auch dessen Callbacks aufrufen. Ein Blockmodell schliesst das konstruktiv aus, statt es filtern zu muessen. Passwoerter liegen gesalzen und gehasht in der Datenbank, gerechnet wird in der Datenbank. Fuenf Fehlversuche je Benutzername sperren den Namen kurzzeitig. Enthaelt README.md mit Einrichtung und Anbindung an den IC-Computer sowie ANLEITUNG.md fuer die Bedienung im Spiel.
204 lines
8.1 KiB
Lua
204 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
|