412 lines
17 KiB
Lua
412 lines
17 KiB
Lua
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|||
|
|
-- ic-web – Netzwerkereignisse, Exports, Konsolenbefehle
|
|||
|
|
--
|
|||
|
|
-- Jedes Ereignis ist frei aufrufbar und deshalb gedrosselt. Rechte werden
|
|||
|
|
-- ausnahmslos serverseitig aus der Anmeldung abgeleitet; der Client erfaehrt
|
|||
|
|
-- nur das Ergebnis.
|
|||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
local S = ICWeb.Sites
|
|||
|
|
local D = ICWeb.Domains
|
|||
|
|
local M = ICWeb.Manage
|
|||
|
|
local A = ICWeb.Auth
|
|||
|
|
local DB = ICWeb.DB
|
|||
|
|
local FW = ICWeb.FW
|
|||
|
|
|
|||
|
|
local function reply(src, reqId, ok, data, err)
|
|||
|
|
TriggerClientEvent('ic-web:client:response', src, reqId,
|
|||
|
|
{ ok = ok == true, data = data, error = err })
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- ── Anmeldung ────────────────────────────────────────────────────────────────
|
|||
|
|
RegisterNetEvent('ic-web:server:login', function(reqId, username, password)
|
|||
|
|
local src = source
|
|||
|
|
-- Eng gedrosselt: die Sperre je Benutzername greift erst nach fuenf
|
|||
|
|
-- Versuchen, diese hier bremst das Durchprobieren vieler Benutzernamen.
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'login', 8, 60000) then
|
|||
|
|
return reply(src, reqId, false, nil, 'Zu viele Versuche, bitte kurz warten.')
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local session, err = A.Login(src, username, password)
|
|||
|
|
reply(src, reqId, session ~= nil, session, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:logout', function(reqId)
|
|||
|
|
local src = source
|
|||
|
|
A.Logout(src)
|
|||
|
|
reply(src, reqId, true)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
-- Wer bin ich gerade? Ohne Anmeldung: data = nil, aber ok = true.
|
|||
|
|
RegisterNetEvent('ic-web:server:session', function(reqId)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'session', 20, 10000) then return end
|
|||
|
|
|
|||
|
|
reply(src, reqId, true, {
|
|||
|
|
session = A.Public(src),
|
|||
|
|
themes = Config.Themes,
|
|||
|
|
tlds = Config.Domain.allowedTlds,
|
|||
|
|
imageHosts = Config.ImageHosts,
|
|||
|
|
provider = Config.MainAdmin.providerName,
|
|||
|
|
selfService = Config.SelfRegistration.enabled,
|
|||
|
|
})
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:changePassword', function(reqId, accountId, password)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'passwd', 5, 60000) then return end
|
|||
|
|
|
|||
|
|
local ok, err = M.SetAccountPassword(src, accountId, password)
|
|||
|
|
if ok then FW.Notify(src, 'Passwort geändert.') end
|
|||
|
|
reply(src, reqId, ok, nil, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
-- ── Lesen: jeder darf ────────────────────────────────────────────────────────
|
|||
|
|
RegisterNetEvent('ic-web:server:getSite', function(reqId, domain, slug)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'get', 30, 10000) then return end
|
|||
|
|
|
|||
|
|
local site, err = S.GetPublic(domain, slug)
|
|||
|
|
reply(src, reqId, site ~= nil, site, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
-- ── Eigene Domaenen ──────────────────────────────────────────────────────────
|
|||
|
|
RegisterNetEvent('ic-web:server:listMine', function(reqId)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'listMine', 10, 10000) then return end
|
|||
|
|
reply(src, reqId, true, S.ListMine(src))
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:getEditable', function(reqId, domain)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'getEditable', 20, 10000) then return end
|
|||
|
|
|
|||
|
|
local site, err = S.GetForEditor(src, domain)
|
|||
|
|
reply(src, reqId, site ~= nil, site, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
-- ── Selbstanmeldung einer Domaene (nur wenn eingeschaltet) ───────────────────
|
|||
|
|
RegisterNetEvent('ic-web:server:register', function(reqId, domain, title)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'register', 3, 60000) then
|
|||
|
|
return reply(src, reqId, false, nil, 'Zu viele Versuche, bitte kurz warten.')
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local site, err, credentials = D.Register(src, domain, title)
|
|||
|
|
if not site then
|
|||
|
|
ICWeb.Log.Write(src, 'domain.rejected', { domain = tostring(domain), reason = err }, 'warn')
|
|||
|
|
return reply(src, reqId, false, nil, err)
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
FW.Notify(src, ('Domäne %s registriert.'):format(site.domain))
|
|||
|
|
reply(src, reqId, true, {
|
|||
|
|
domain = site.domain, title = site.title, credentials = credentials,
|
|||
|
|
})
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
-- ── Bearbeiten ───────────────────────────────────────────────────────────────
|
|||
|
|
RegisterNetEvent('ic-web:server:savePage', function(reqId, domain, slug, title, blocks)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'save', 20, 60000) then
|
|||
|
|
return reply(src, reqId, false, nil, 'Zu viele Speichervorgänge, bitte kurz warten.')
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local ok, err = S.SavePage(src, domain, slug, title, blocks)
|
|||
|
|
reply(src, reqId, ok, nil, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:deletePage', function(reqId, domain, slug)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'delete', 10, 60000) then return end
|
|||
|
|
local ok, err = S.DeletePage(src, domain, slug)
|
|||
|
|
reply(src, reqId, ok, nil, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:updateSite', function(reqId, domain, data)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'update', 20, 60000) then return end
|
|||
|
|
if type(data) ~= 'table' then return reply(src, reqId, false, nil, 'Ungültige Daten.') end
|
|||
|
|
|
|||
|
|
local ok, err = S.UpdateSite(src, domain, data)
|
|||
|
|
reply(src, reqId, ok, nil, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
-- ── Melden ───────────────────────────────────────────────────────────────────
|
|||
|
|
-- Ohne Anmeldung: jeder Spieler soll eine Seite melden koennen.
|
|||
|
|
RegisterNetEvent('ic-web:server:report', function(reqId, domain, slug, reason)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'report', 5, 60000) then return end
|
|||
|
|
|
|||
|
|
local ok, err = S.Report(src, domain, slug, reason)
|
|||
|
|
if ok then FW.Notify(src, 'Meldung eingegangen. Danke.') end
|
|||
|
|
reply(src, reqId, ok, nil, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
-- ── Postfaecher im Mailclient ───────────────────────────────────────────────
|
|||
|
|
-- Ein Postfach freischalten ist eine Passwortpruefung wie die Anmeldung und
|
|||
|
|
-- deshalb genauso eng gedrosselt.
|
|||
|
|
RegisterNetEvent('ic-web:server:openMailbox', function(reqId, username, password)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'openMailbox', 8, 60000) then
|
|||
|
|
return reply(src, reqId, false, nil, 'Zu viele Versuche, bitte kurz warten.')
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local ok, err = A.OpenMailbox(src, username, password)
|
|||
|
|
if ok then FW.Notify(src, 'Postfach hinzugefügt.') end
|
|||
|
|
reply(src, reqId, ok, nil, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:closeMailbox', function(reqId, username)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'closeMailbox', 20, 60000) then return end
|
|||
|
|
|
|||
|
|
local ok, err = A.CloseMailbox(src, username)
|
|||
|
|
reply(src, reqId, ok, nil, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:listMailboxes', function(reqId)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'listMailboxes', 20, 10000) then return end
|
|||
|
|
reply(src, reqId, true, A.ListOpenMailboxes(src))
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
-- ── Zugaenge je Domaene ─────────────────────────────────────────────────────
|
|||
|
|
RegisterNetEvent('ic-web:server:listAccounts', function(reqId, domain)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'accounts', 20, 10000) then return end
|
|||
|
|
|
|||
|
|
local list, err = M.ListAccounts(src, domain)
|
|||
|
|
reply(src, reqId, list ~= nil, list, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:createAccount',
|
|||
|
|
function(reqId, domain, username, password, role, displayName)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'createAccount', 10, 60000) then return end
|
|||
|
|
|
|||
|
|
local account, err = M.CreateAccount(src, domain, username, password, role, displayName)
|
|||
|
|
reply(src, reqId, account ~= nil, account, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:setAccountPassword', function(reqId, accountId, password)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'setPassword', 10, 60000) then return end
|
|||
|
|
|
|||
|
|
local ok, err = M.SetAccountPassword(src, accountId, password)
|
|||
|
|
reply(src, reqId, ok, nil, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:setAccountActive', function(reqId, accountId, active)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'setActive', 20, 60000) then return end
|
|||
|
|
|
|||
|
|
local ok, err = M.SetAccountActive(src, accountId, active == true)
|
|||
|
|
reply(src, reqId, ok, nil, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:setAccountRole', function(reqId, accountId, role)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'setRole', 20, 60000) then return end
|
|||
|
|
|
|||
|
|
local ok, err = M.SetAccountRole(src, accountId, role)
|
|||
|
|
reply(src, reqId, ok, nil, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:deleteAccount', function(reqId, accountId)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'deleteAccount', 10, 60000) then return end
|
|||
|
|
|
|||
|
|
local ok, err = M.DeleteAccount(src, accountId)
|
|||
|
|
reply(src, reqId, ok, nil, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
-- ── Anbieterebene ───────────────────────────────────────────────────────────
|
|||
|
|
RegisterNetEvent('ic-web:server:adminListSites', function(reqId)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'adminList', 20, 10000) then return end
|
|||
|
|
|
|||
|
|
local list, err = M.AdminListSites(src)
|
|||
|
|
reply(src, reqId, list ~= nil, list, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:adminListAccounts', function(reqId)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'adminAccounts', 20, 10000) then return end
|
|||
|
|
|
|||
|
|
local list, err = M.AdminListAccounts(src)
|
|||
|
|
reply(src, reqId, list ~= nil, list, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:adminListJobs', function(reqId)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'adminJobs', 10, 10000) then return end
|
|||
|
|
|
|||
|
|
local list, err = M.AdminListJobs(src)
|
|||
|
|
reply(src, reqId, list ~= nil, list, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:adminRegister',
|
|||
|
|
function(reqId, domain, title, ownerId, adminUser, adminPassword)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'adminRegister', 10, 60000) then return end
|
|||
|
|
|
|||
|
|
local site, err, credentials = M.AdminRegister(src, domain, title, ownerId,
|
|||
|
|
adminUser, adminPassword)
|
|||
|
|
reply(src, reqId, site ~= nil,
|
|||
|
|
site and { domain = site.domain, credentials = credentials } or nil, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:adminSetBlocked', function(reqId, domain, blocked)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'adminBlock', 20, 60000) then return end
|
|||
|
|
|
|||
|
|
local ok, err = M.AdminSetBlocked(src, domain, blocked == true)
|
|||
|
|
reply(src, reqId, ok, nil, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:adminSetOwner', function(reqId, domain, ownerId)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'adminOwner', 10, 60000) then return end
|
|||
|
|
|
|||
|
|
local ok, err = M.AdminSetOwner(src, domain, ownerId)
|
|||
|
|
reply(src, reqId, ok, nil, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:adminDeleteSite', function(reqId, domain)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'adminDelete', 5, 60000) then return end
|
|||
|
|
|
|||
|
|
local ok, err = M.AdminDeleteSite(src, domain)
|
|||
|
|
reply(src, reqId, ok, nil, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:adminListReports', function(reqId)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'adminReports', 20, 10000) then return end
|
|||
|
|
|
|||
|
|
local list, err = M.AdminListReports(src)
|
|||
|
|
reply(src, reqId, list ~= nil, list, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
RegisterNetEvent('ic-web:server:adminSetReportStatus', function(reqId, id, status)
|
|||
|
|
local src = source
|
|||
|
|
if not ICWeb.ThrottleOk(src, 'adminReportStatus', 30, 60000) then return end
|
|||
|
|
|
|||
|
|
local ok, err = M.AdminSetReportStatus(src, id, status)
|
|||
|
|
reply(src, reqId, ok, nil, err)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
-- ── Konsolenbefehle ─────────────────────────────────────────────────────────
|
|||
|
|
-- Kein IC-Weg, sondern die Notbremse des Serverbetreibers. Absichtlich nur in
|
|||
|
|
-- der Serverkonsole und fuer Serveradmins – nicht ueber die Anmeldung.
|
|||
|
|
local function consoleAllowed(src)
|
|||
|
|
if not src or src == 0 then return true end
|
|||
|
|
return FW.IsAdmin(src)
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
RegisterCommand('webpasswd', function(src, args)
|
|||
|
|
if not consoleAllowed(src) then return end
|
|||
|
|
|
|||
|
|
local username = tostring(args[1] or ''):lower()
|
|||
|
|
local password = tostring(args[2] or '')
|
|||
|
|
if username == '' or password == '' then
|
|||
|
|
print('[ic-web] /webpasswd <benutzer@domaene> <neues-passwort>')
|
|||
|
|
return
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local account = DB.GetAccountByUsername(username)
|
|||
|
|
if not account then print('[ic-web] Zugang nicht gefunden.') return end
|
|||
|
|
|
|||
|
|
local ok, err = A.SetPassword(account.id, password)
|
|||
|
|
print(ok and ('[ic-web] Passwort von %s geändert.'):format(username)
|
|||
|
|
or ('[ic-web] Fehlgeschlagen: %s'):format(tostring(err)))
|
|||
|
|
end, true)
|
|||
|
|
|
|||
|
|
RegisterCommand('webaccounts', function(src)
|
|||
|
|
if not consoleAllowed(src) then return end
|
|||
|
|
|
|||
|
|
local rows = DB.GetAllAccounts()
|
|||
|
|
print(('[ic-web] %d Zugänge:'):format(#rows))
|
|||
|
|
for _, a in ipairs(rows) do
|
|||
|
|
print((' %-34s %-11s %-22s %s'):format(
|
|||
|
|
a.username, a.role, a.domain or '—',
|
|||
|
|
ICWeb.Bool(a.active) and 'aktiv' or 'gesperrt'))
|
|||
|
|
end
|
|||
|
|
end, true)
|
|||
|
|
|
|||
|
|
RegisterCommand('webreports', function(src)
|
|||
|
|
if not consoleAllowed(src) then return end
|
|||
|
|
|
|||
|
|
local reports = DB.GetOpenReports()
|
|||
|
|
if #reports == 0 then print('[ic-web] Keine offenen Meldungen.') return end
|
|||
|
|
|
|||
|
|
print(('[ic-web] %d offene Meldungen:'):format(#reports))
|
|||
|
|
for _, r in ipairs(reports) do
|
|||
|
|
print((' #%d %-24s %-10s %s'):format(
|
|||
|
|
r.id, r.domain, r.page_slug or '-', (r.reason or ''):sub(1, 60)))
|
|||
|
|
end
|
|||
|
|
print('[ic-web] Sperren mit /webblock <domain>, erledigen mit /webreport done <id>')
|
|||
|
|
end, true)
|
|||
|
|
|
|||
|
|
RegisterCommand('webblock', function(src, args)
|
|||
|
|
if not consoleAllowed(src) then return end
|
|||
|
|
|
|||
|
|
local domain = tostring(args[1] or ''):lower()
|
|||
|
|
local site = DB.GetSiteByDomain(domain)
|
|||
|
|
if not site then print('[ic-web] Domäne nicht gefunden.') return end
|
|||
|
|
|
|||
|
|
local blocked = not ICWeb.Bool(site.blocked)
|
|||
|
|
DB.SetBlocked(site.id, blocked)
|
|||
|
|
ICWeb.Log.Write(src, blocked and 'site.blocked' or 'site.unblocked', { domain = domain }, 'warn')
|
|||
|
|
print(('[ic-web] %s ist jetzt %s.'):format(domain, blocked and 'gesperrt' or 'frei'))
|
|||
|
|
end, true)
|
|||
|
|
|
|||
|
|
RegisterCommand('webreport', function(src, args)
|
|||
|
|
if not consoleAllowed(src) then return end
|
|||
|
|
|
|||
|
|
local action = tostring(args[1] or '')
|
|||
|
|
local id = tonumber(args[2])
|
|||
|
|
if (action ~= 'done' and action ~= 'dismiss') or not id then
|
|||
|
|
print('[ic-web] /webreport done <id> oder /webreport dismiss <id>')
|
|||
|
|
return
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
DB.SetReportStatus(id, action == 'done' and 'reviewed' or 'dismissed')
|
|||
|
|
print(('[ic-web] Meldung #%d als %s markiert.'):format(id, action))
|
|||
|
|
end, true)
|
|||
|
|
|
|||
|
|
-- ── Exports fuer andere Resources ────────────────────────────────────────────
|
|||
|
|
exports('GetSite', function(domain, slug)
|
|||
|
|
return S.GetPublic(domain, slug)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
exports('DomainExists', function(domain)
|
|||
|
|
return DB.GetSiteByDomain(tostring(domain or ''):lower()) ~= nil
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
exports('GetSitesForJob', function(job)
|
|||
|
|
return DB.GetSitesForOwner('job', job)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
-- Wer ist an diesem Bildschirm angemeldet? pc-live nutzt das, um das Postfach
|
|||
|
|
-- des Zugangs zu oeffnen statt des persoenlichen.
|
|||
|
|
exports('GetSession', function(src)
|
|||
|
|
return A.Public(src)
|
|||
|
|
end)
|
|||
|
|
|
|||
|
|
-- ── Start ────────────────────────────────────────────────────────────────────
|
|||
|
|
AddEventHandler('onResourceStart', function(res)
|
|||
|
|
if res ~= GetCurrentResourceName() then return end
|
|||
|
|
|
|||
|
|
if GetResourceState('ic-mail') ~= 'started' then
|
|||
|
|
print('[ic-web] ^3Hinweis^7: ic-mail läuft nicht – Domänen werden ohne Postfächer angelegt.')
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
A.EnsureMainAdmin()
|
|||
|
|
A.CleanupStaleMailAccess()
|
|||
|
|
DB.PruneLoginFails(86400)
|
|||
|
|
|
|||
|
|
local sites = MySQL.single.await('SELECT COUNT(*) AS n FROM ic_web_sites')
|
|||
|
|
print(('[ic-web] bereit. %d Domänen registriert.'):format((sites and sites.n) or 0))
|
|||
|
|
end)
|