ic-web/server/framework.lua
Bjoern Flessing a2ce3a69fd IC-Webhosting: Domaenen, Webseiten und Postfaecher ueber Anmeldungen
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.
2026-08-09 11:50:53 +00:00

137 lines
5 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- ─────────────────────────────────────────────────────────────────────────────
-- ic-web ESX-Bridge, Rechte und Logging
--
-- Alle Framework-Zugriffe zentral. ESX wird bei Bedarf geholt, nicht beim
-- Laden: beim Start ist es je nach Reihenfolge noch nicht bereit, und ein
-- einmal fehlgeschlagener Versuch haette die Bridge dauerhaft lahmgelegt.
-- ─────────────────────────────────────────────────────────────────────────────
ICWeb = ICWeb or {}
ICWeb.FW = {}
local FW = ICWeb.FW
local ESX
local function esx()
if ESX then return ESX end
local ok, obj = pcall(function() return exports['es_extended']:getSharedObject() end)
if ok and obj then ESX = obj end
return ESX
end
local function player(src)
local api = esx()
return api and api.GetPlayerFromId(src) or nil
end
function FW.GetIdentifier(src)
local xPlayer = player(src)
return xPlayer and xPlayer.identifier or nil
end
function FW.GetName(src)
local xPlayer = player(src)
if not xPlayer then return GetPlayerName(src) or 'Unbekannt' end
if xPlayer.getName then return xPlayer.getName() end
return xPlayer.name or 'Unbekannt'
end
-- Job und Dienstgrad. Beides wird fuer die Registrierung gebraucht.
function FW.GetJob(src)
local xPlayer = player(src)
if not xPlayer or not xPlayer.job then return nil, 0 end
return xPlayer.job.name, tonumber(xPlayer.job.grade) or 0
end
-- Jobliste fuer die Adminansicht. Der Job entscheidet nicht mehr ueber Rechte,
-- er steht nur noch als Besitzervermerk an einer Domaene.
function FW.GetJobs()
local api = esx()
if not api or not api.GetJobs then return {} end
local out = {}
for name, job in pairs(api.GetJobs()) do
out[#out + 1] = { name = name, label = job.label or name }
end
table.sort(out, function(a, b) return a.label < b.label end)
return out
end
function FW.Notify(src, message)
local xPlayer = player(src)
if xPlayer and xPlayer.showNotification then
xPlayer.showNotification(message)
return
end
TriggerClientEvent('esx:showNotification', src, message)
end
-- ── Adminrechte ─────────────────────────────────────────────────────────────
function FW.IsAdmin(src)
if not src or src == 0 then return true end -- Serverkonsole
local mode = Config.Admin.mode or 'either'
local okEsx = false
local xPlayer = player(src)
if xPlayer and xPlayer.getGroup then
okEsx = Config.Admin.esxGroups[xPlayer.getGroup()] == true
end
local okAce = IsPlayerAceAllowed(src, Config.Admin.acePermission) == true
if mode == 'esx' then return okEsx end
if mode == 'ace' then return okAce end
if mode == 'both' then return okEsx and okAce end
return okEsx or okAce
end
-- ── Logging ─────────────────────────────────────────────────────────────────
-- Ausschliesslich ueber fmsdk. Fehlt es, wird verworfen statt zu crashen.
ICWeb.Log = {}
function ICWeb.Log.Write(src, action, details, level)
if GetResourceState('fmsdk') ~= 'started' then
if Config.Debug then
print(('[ic-web] fmsdk fehlt, Log verworfen: %s'):format(tostring(action)))
end
return false
end
local meta = details or {}
meta.action = (Config.Logging.actionPrefix or 'ic_web.') .. tostring(action)
meta.resource = GetCurrentResourceName()
meta.playerSource = src or nil
meta.identifier = src and FW.GetIdentifier(src) or nil
local ok = pcall(function()
exports.fmsdk:Log(Config.Logging.dataset or 'ic_web', level or 'info',
('Webhosting: %s'):format(meta.action), meta)
end)
return ok
end
-- ── Drossel ─────────────────────────────────────────────────────────────────
-- Jedes RegisterNetEvent ist frei aufrufbar.
local buckets = {}
function ICWeb.ThrottleOk(src, name, maxPerWindow, windowMs)
local now = GetGameTimer()
local key = ('%s:%s'):format(src, name)
local entry = buckets[key]
if not entry or (now - entry.start) > windowMs then
buckets[key] = { start = now, count = 1 }
return true
end
entry.count = entry.count + 1
return entry.count <= maxPerWindow
end
AddEventHandler('playerDropped', function()
local prefix = tostring(source) .. ':'
for key in pairs(buckets) do
if key:sub(1, #prefix) == prefix then buckets[key] = nil end
end
end)