IC-Computer: Schreibtisch, Fenster und Apps
Ein Computer im Spiel. Man tritt an ein Terminal, drueckt E, und bekommt eine
Oberflaeche mit Mail, Browser, Adressbuch, Kalender und weiteren Anwendungen.
Andere Resources haengen sich als App ein.
Auf ESX umgestellt:
- Fuenf harte Abhaengigkeiten zeigten auf Resources, die es hier nicht gibt.
Eine fehlende Abhaengigkeit verhindert den Start vollstaendig - die
Resource waere nie hochgekommen.
- Die Bruecke zum Framework neu geschrieben; Charakterdaten, Aktenverwaltung
und Immobilien ausgebaut, weil die zugehoerigen Systeme fehlen.
- pc_live_devices fehlte im Schema, wird vom Code aber gelesen. Ohne die
Tabelle ist kein fester PC ansprechbar. Ein PC braucht ausserdem zwei
Eintraege mit derselben Kennung: Standort in der Datenbank, Kamerafahrt in
der config.lua.
Mail ueberarbeitet:
- Postfaecher haengen an einer Anmeldung statt an der Person. Mehrere Leute
koennen dasselbe Firmenpostfach gleichzeitig offen haben.
- Ordner gehoeren zum Postfach, nicht zur Person. Eine Mail an ein geteiltes
Postfach wird je Empfaenger einmal gespeichert; damit das Einsortieren
trotzdem fuer alle gilt, tragen alle Kopien einer Zustellung dieselbe
Kennung. Gelesen und geloescht bleibt persoenlich - das ist keine
Eigenschaft der Nachricht, sondern der Person.
- Signaturen gehoeren ebenfalls zum Postfach.
- Kalender koennen privat, geteilt oder oeffentlich sein. Ein oeffentlicher
Termin haengt bewusst auch an einem Postfach, sonst koennte ihn spaeter
niemand mehr aendern oder absagen.
Neue Apps: Webhosting, Bleeter und dessen Verwaltung.
Behoben:
- prompt(), confirm() und alert() froren das NUI ein. FiveMs CEF hat keinen
Handler fuer die eingebauten Browserdialoge - der Aufruf oeffnet nichts und
kehrt nie zurueck. Ersetzt durch eigene Dialoge.
- Die Fenster tragen data-wid, keine id. Wer sie mit getElementById sucht,
schreibt ins Leere und das Fenster bleibt leer.
Enthaelt README.md mit Einrichtung und PLUGINS.md: eine Anleitung, wie man eine
eigene App baut und einhaengt, mit vollstaendigem Beispiel von der Tabelle bis
zum Schreibtischsymbol.
This commit is contained in:
commit
be502d2834
46 changed files with 13119 additions and 0 deletions
419
server/database.lua
Normal file
419
server/database.lua
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- Database layer – all MySQL calls live here
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
DB = {}
|
||||
|
||||
-- ── Users ──────────────────────────────────────────────────────────────────
|
||||
|
||||
---@param identifier string
|
||||
---@param displayName string
|
||||
---@return table?
|
||||
function DB.GetOrCreateUser(identifier, displayName)
|
||||
local row = MySQL.single.await(
|
||||
'SELECT * FROM pc_live_users WHERE identifier = ?',
|
||||
{ identifier }
|
||||
)
|
||||
if row then return row end
|
||||
|
||||
MySQL.insert.await(
|
||||
'INSERT INTO pc_live_users (identifier, display_name, settings_json) VALUES (?, ?, ?)',
|
||||
{ identifier, displayName, '{}' }
|
||||
)
|
||||
|
||||
return MySQL.single.await(
|
||||
'SELECT * FROM pc_live_users WHERE identifier = ?',
|
||||
{ identifier }
|
||||
)
|
||||
end
|
||||
|
||||
---@param identifier string
|
||||
---@param settings table
|
||||
function DB.SaveSettings(identifier, settings)
|
||||
MySQL.update.await(
|
||||
'UPDATE pc_live_users SET settings_json = ? WHERE identifier = ?',
|
||||
{ json.encode(settings), identifier }
|
||||
)
|
||||
end
|
||||
|
||||
-- ── Installs ───────────────────────────────────────────────────────────────
|
||||
|
||||
---@param userId number
|
||||
---@return table[]
|
||||
function DB.GetInstalls(userId)
|
||||
return MySQL.query.await(
|
||||
'SELECT * FROM pc_live_installs WHERE user_id = ?',
|
||||
{ userId }
|
||||
)
|
||||
end
|
||||
|
||||
---@param userId number
|
||||
---@param appId string
|
||||
---@param version string
|
||||
function DB.InstallApp(userId, appId, version)
|
||||
local existing = MySQL.single.await(
|
||||
'SELECT id FROM pc_live_installs WHERE user_id = ? AND app_id = ?',
|
||||
{ userId, appId }
|
||||
)
|
||||
if existing then
|
||||
MySQL.update.await(
|
||||
'UPDATE pc_live_installs SET version = ?, installed_at = NOW() WHERE id = ?',
|
||||
{ version, existing.id }
|
||||
)
|
||||
else
|
||||
MySQL.insert.await(
|
||||
'INSERT INTO pc_live_installs (user_id, app_id, version, installed_at) VALUES (?, ?, ?, NOW())',
|
||||
{ userId, appId, version }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
---@param userId number
|
||||
---@param appId string
|
||||
function DB.UninstallApp(userId, appId)
|
||||
MySQL.update.await(
|
||||
'DELETE FROM pc_live_installs WHERE user_id = ? AND app_id = ?',
|
||||
{ userId, appId }
|
||||
)
|
||||
end
|
||||
|
||||
-- ── Mail ───────────────────────────────────────────────────────────────────
|
||||
|
||||
---@param toId string
|
||||
---@return table[]
|
||||
function DB.GetInbox(toId)
|
||||
return MySQL.query.await(
|
||||
'SELECT * FROM pc_live_mail WHERE to_identifier = ? ORDER BY sent_at DESC LIMIT 100',
|
||||
{ toId }
|
||||
)
|
||||
end
|
||||
|
||||
---@param id number
|
||||
---@param toId string
|
||||
---@return table?
|
||||
function DB.GetMail(id, toId)
|
||||
return MySQL.single.await(
|
||||
'SELECT * FROM pc_live_mail WHERE id = ? AND to_identifier = ?',
|
||||
{ id, toId }
|
||||
)
|
||||
end
|
||||
|
||||
---@param toId string
|
||||
---@param fromId string
|
||||
---@param subject string
|
||||
---@param body string
|
||||
---@return number inserted id
|
||||
function DB.SendMail(toId, fromId, subject, body)
|
||||
return MySQL.insert.await(
|
||||
'INSERT INTO pc_live_mail (to_identifier, from_identifier, subject, body, is_read, sent_at) VALUES (?, ?, ?, ?, 0, NOW())',
|
||||
{ toId, fromId, subject, body }
|
||||
)
|
||||
end
|
||||
|
||||
---@param toId string Empfänger-Identifier (aufgelöst)
|
||||
---@param fromDisplay string Absender-Anzeige (E-Mail oder Identifier)
|
||||
---@param senderId string Roher Identifier des Absenders (für Gesendet-Abfrage)
|
||||
---@param toAddress string Originale Empfängeradresse
|
||||
---@param subject string
|
||||
---@param body string
|
||||
---@return number
|
||||
--- deliveryId klammert die Kopien einer Zustellung zusammen: eine Mail an ein
|
||||
--- geteiltes Postfach wird je Empfaenger einmal gespeichert, soll aber fuer
|
||||
--- alle im selben Ordner landen.
|
||||
function DB.SendMailExtended(toId, fromDisplay, senderId, toAddress, subject, body, deliveryId)
|
||||
return MySQL.insert.await(
|
||||
'INSERT INTO pc_live_mail'
|
||||
.. ' (to_identifier, from_identifier, sender_id, to_address, subject, body,'
|
||||
.. ' delivery_id, is_read, sent_at)'
|
||||
.. ' VALUES (?, ?, ?, ?, ?, ?, ?, 0, NOW())',
|
||||
{ toId, fromDisplay, senderId, toAddress, subject, body, deliveryId }
|
||||
)
|
||||
end
|
||||
|
||||
---@param id number
|
||||
---@param toId string
|
||||
function DB.MarkRead(id, toId)
|
||||
MySQL.update.await(
|
||||
'UPDATE pc_live_mail SET is_read = 1 WHERE id = ? AND to_identifier = ?',
|
||||
{ id, toId }
|
||||
)
|
||||
end
|
||||
|
||||
---@param id number
|
||||
---@param toId string
|
||||
function DB.DeleteMail(id, toId)
|
||||
MySQL.update.await(
|
||||
'DELETE FROM pc_live_mail WHERE id = ? AND to_identifier = ?',
|
||||
{ id, toId }
|
||||
)
|
||||
end
|
||||
|
||||
---@param toId string
|
||||
---@return number
|
||||
function DB.CountUnread(toId)
|
||||
local row = MySQL.single.await(
|
||||
'SELECT COUNT(*) as cnt FROM pc_live_mail WHERE to_identifier = ? AND is_read = 0',
|
||||
{ toId }
|
||||
)
|
||||
return row and row.cnt or 0
|
||||
end
|
||||
|
||||
---@param fromId string Spieler-Identifier
|
||||
---@return table[]
|
||||
function DB.GetSent(fromId)
|
||||
return MySQL.query.await(
|
||||
'SELECT * FROM pc_live_mail'
|
||||
.. ' WHERE from_identifier = ? OR sender_id = ?'
|
||||
.. ' ORDER BY sent_at DESC LIMIT 100',
|
||||
{ fromId, fromId }
|
||||
)
|
||||
end
|
||||
|
||||
-- ── Mail Folders ───────────────────────────────────────────────────────────
|
||||
--
|
||||
-- Ordner gehoeren zum Postfach, nicht zur Person: ein geteiltes Postfach wird
|
||||
-- von mehreren bedient und braucht dieselbe Struktur fuer alle.
|
||||
--
|
||||
-- address = '' → persoenliche Ordner (Spalte address IS NULL)
|
||||
-- address ~= '' → Ordner dieses Postfachs
|
||||
--
|
||||
-- Der Aufrufer muss vorher pruefen, ob die Person das Postfach benutzen darf.
|
||||
|
||||
---@param identifier string
|
||||
---@param address string|nil
|
||||
---@return table[]
|
||||
function DB.GetFolders(identifier, address)
|
||||
if address and address ~= '' then
|
||||
return MySQL.query.await(
|
||||
'SELECT * FROM pc_live_mail_folders WHERE address = ? ORDER BY name ASC',
|
||||
{ address }
|
||||
) or {}
|
||||
end
|
||||
return MySQL.query.await(
|
||||
'SELECT * FROM pc_live_mail_folders WHERE identifier = ? AND address IS NULL ORDER BY name ASC',
|
||||
{ identifier }
|
||||
) or {}
|
||||
end
|
||||
|
||||
---@param identifier string
|
||||
---@param address string|nil
|
||||
---@param name string
|
||||
---@return number
|
||||
function DB.CreateFolder(identifier, address, name)
|
||||
return MySQL.insert.await(
|
||||
'INSERT INTO pc_live_mail_folders (identifier, address, name) VALUES (?, ?, ?)',
|
||||
{ identifier, (address ~= '' and address or nil), name }
|
||||
)
|
||||
end
|
||||
|
||||
--- Gibt den Ordner zurueck, wenn er zu diesem Postfach gehoert – sonst nil.
|
||||
--- Damit haengt jede Ordneraktion an einer Besitzpruefung statt an der Id
|
||||
--- allein, die der Client frei mitschicken kann.
|
||||
---@param folderId number
|
||||
---@param identifier string
|
||||
---@param address string|nil
|
||||
---@return table|nil
|
||||
function DB.GetFolder(folderId, identifier, address)
|
||||
if address and address ~= '' then
|
||||
return MySQL.single.await(
|
||||
'SELECT * FROM pc_live_mail_folders WHERE id = ? AND address = ?',
|
||||
{ folderId, address }
|
||||
)
|
||||
end
|
||||
return MySQL.single.await(
|
||||
'SELECT * FROM pc_live_mail_folders WHERE id = ? AND identifier = ? AND address IS NULL',
|
||||
{ folderId, identifier }
|
||||
)
|
||||
end
|
||||
|
||||
---@param folderId number
|
||||
---@param identifier string
|
||||
---@param address string|nil
|
||||
function DB.DeleteFolder(folderId, identifier, address)
|
||||
-- Mails zurueck in den Posteingang. Bei einem geteilten Postfach betrifft
|
||||
-- das die Kopien aller Beteiligten, sonst blieben deren Mails in einem
|
||||
-- Ordner haengen, den es nicht mehr gibt.
|
||||
if address and address ~= '' then
|
||||
MySQL.update.await(
|
||||
'UPDATE pc_live_mail SET folder_id = NULL WHERE folder_id = ? AND to_address = ?',
|
||||
{ folderId, address }
|
||||
)
|
||||
else
|
||||
MySQL.update.await(
|
||||
'UPDATE pc_live_mail SET folder_id = NULL WHERE folder_id = ? AND to_identifier = ?',
|
||||
{ folderId, identifier }
|
||||
)
|
||||
end
|
||||
|
||||
MySQL.update.await('DELETE FROM pc_live_mail_folders WHERE id = ?', { folderId })
|
||||
end
|
||||
|
||||
--- Mail einsortieren.
|
||||
--- Im geteilten Postfach wandern alle Kopien derselben Zustellung mit, damit
|
||||
--- die Ablage fuer alle gleich aussieht.
|
||||
---@param mailId number
|
||||
---@param folderId number|nil nil = Posteingang
|
||||
---@param identifier string
|
||||
---@param address string|nil
|
||||
function DB.MoveMailToFolder(mailId, folderId, identifier, address)
|
||||
local mail = MySQL.single.await(
|
||||
'SELECT id, delivery_id, to_address FROM pc_live_mail WHERE id = ? AND to_identifier = ?',
|
||||
{ mailId, identifier }
|
||||
)
|
||||
if not mail then return end
|
||||
|
||||
local shared = address and address ~= '' and mail.to_address == address
|
||||
|
||||
if shared and mail.delivery_id then
|
||||
MySQL.update.await(
|
||||
'UPDATE pc_live_mail SET folder_id = ? WHERE delivery_id = ? AND to_address = ?',
|
||||
{ folderId, mail.delivery_id, address }
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
-- Aeltere Mails haben keine delivery_id – dann bleibt es bei der Kopie.
|
||||
MySQL.update.await(
|
||||
'UPDATE pc_live_mail SET folder_id = ? WHERE id = ? AND to_identifier = ?',
|
||||
{ folderId, mailId, identifier }
|
||||
)
|
||||
end
|
||||
|
||||
---@param folderId number
|
||||
---@param identifier string
|
||||
---@return table[]
|
||||
function DB.GetFolderMails(folderId, identifier)
|
||||
return MySQL.query.await(
|
||||
'SELECT * FROM pc_live_mail WHERE folder_id = ? AND to_identifier = ? ORDER BY sent_at DESC',
|
||||
{ folderId, identifier }
|
||||
) or {}
|
||||
end
|
||||
|
||||
---@param identifier string
|
||||
---@return table[]
|
||||
function DB.GetInboxOnly(identifier)
|
||||
return MySQL.query.await(
|
||||
'SELECT * FROM pc_live_mail WHERE to_identifier = ? AND folder_id IS NULL ORDER BY sent_at DESC LIMIT 100',
|
||||
{ identifier }
|
||||
)
|
||||
end
|
||||
|
||||
-- ── Calendar ───────────────────────────────────────────────────────────────
|
||||
--
|
||||
-- Ein Termin ist entweder privat (address IS NULL) oder gehoert einem Postfach.
|
||||
-- Postfachtermine sind entweder nur fuer dessen Leute sichtbar ('shared') oder
|
||||
-- fuer alle ('public') – bearbeiten darf sie in beiden Faellen nur, wer das
|
||||
-- Postfach bedienen darf.
|
||||
|
||||
--- Alle Termine, die diese Person sehen darf.
|
||||
---@param identifier string
|
||||
---@param addresses string[] Postfaecher, auf die sie Zugriff hat
|
||||
---@return table[]
|
||||
function DB.GetCalendar(identifier, addresses)
|
||||
addresses = addresses or {}
|
||||
|
||||
local params = { identifier }
|
||||
local clauses = { '(c.identifier = ? AND c.address IS NULL)' }
|
||||
|
||||
if #addresses > 0 then
|
||||
local marks = {}
|
||||
for _, addr in ipairs(addresses) do
|
||||
marks[#marks + 1] = '?'
|
||||
params[#params + 1] = addr
|
||||
end
|
||||
clauses[#clauses + 1] = ('(c.address IN (%s))'):format(table.concat(marks, ','))
|
||||
end
|
||||
|
||||
-- Oeffentliche Termine sieht jeder, auch ohne Zugriff auf das Postfach.
|
||||
clauses[#clauses + 1] = "(c.visibility = 'public')"
|
||||
|
||||
return MySQL.query.await(
|
||||
'SELECT c.* FROM pc_live_calendar c WHERE '
|
||||
.. table.concat(clauses, ' OR ') .. ' ORDER BY c.start_at ASC',
|
||||
params
|
||||
) or {}
|
||||
end
|
||||
|
||||
---@param id number
|
||||
---@return table|nil
|
||||
function DB.GetCalendarEvent(id)
|
||||
return MySQL.single.await('SELECT * FROM pc_live_calendar WHERE id = ?', { id })
|
||||
end
|
||||
|
||||
---@param identifier string
|
||||
---@param address string|nil '' oder nil = privat
|
||||
---@param visibility string 'private' | 'shared' | 'public'
|
||||
---@param data table
|
||||
---@return number
|
||||
function DB.AddCalendarEvent(identifier, address, visibility, data)
|
||||
return MySQL.insert.await(
|
||||
'INSERT INTO pc_live_calendar'
|
||||
.. ' (identifier, address, visibility, title, description, start_at, end_at, color)'
|
||||
.. ' VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
{ identifier, (address ~= '' and address or nil), visibility,
|
||||
data.title, data.description or '', data.start_at,
|
||||
data.end_at or nil, data.color or '#00aaff' }
|
||||
)
|
||||
end
|
||||
|
||||
--- Aendern. Die Berechtigung prueft der Aufrufer (server/calendar.lua) –
|
||||
--- hier steht keine Bedingung mehr, weil ein Postfachtermin auch von jemandem
|
||||
--- geaendert werden darf, der ihn nicht angelegt hat.
|
||||
---@param id number
|
||||
---@param address string|nil
|
||||
---@param visibility string
|
||||
---@param data table
|
||||
function DB.UpdateCalendarEvent(id, address, visibility, data)
|
||||
MySQL.update.await(
|
||||
'UPDATE pc_live_calendar SET address=?, visibility=?, title=?, description=?,'
|
||||
.. ' start_at=?, end_at=?, color=? WHERE id=?',
|
||||
{ (address ~= '' and address or nil), visibility,
|
||||
data.title, data.description or '', data.start_at,
|
||||
data.end_at or nil, data.color or '#00aaff', id }
|
||||
)
|
||||
end
|
||||
|
||||
---@param id number
|
||||
function DB.DeleteCalendarEvent(id)
|
||||
MySQL.update.await('DELETE FROM pc_live_calendar WHERE id = ?', { id })
|
||||
end
|
||||
|
||||
-- ── Contacts ───────────────────────────────────────────────────────────────
|
||||
|
||||
---@param identifier string
|
||||
---@return table[]
|
||||
function DB.GetContacts(identifier)
|
||||
return MySQL.query.await(
|
||||
'SELECT * FROM pc_live_contacts WHERE identifier = ? ORDER BY name ASC',
|
||||
{ identifier }
|
||||
)
|
||||
end
|
||||
|
||||
---@param identifier string
|
||||
---@param data table
|
||||
---@return number
|
||||
function DB.AddContact(identifier, data)
|
||||
return MySQL.insert.await(
|
||||
'INSERT INTO pc_live_contacts (identifier, name, email, phone, notes) VALUES (?, ?, ?, ?, ?)',
|
||||
{ identifier, data.name, data.email or '', data.phone or '', data.notes or '' }
|
||||
)
|
||||
end
|
||||
|
||||
---@param id number
|
||||
---@param identifier string
|
||||
---@param data table
|
||||
function DB.UpdateContact(id, identifier, data)
|
||||
MySQL.update.await(
|
||||
'UPDATE pc_live_contacts SET name=?, email=?, phone=?, notes=? WHERE id=? AND identifier=?',
|
||||
{ data.name, data.email or '', data.phone or '', data.notes or '', id, identifier }
|
||||
)
|
||||
end
|
||||
|
||||
---@param id number
|
||||
---@param identifier string
|
||||
function DB.DeleteContact(id, identifier)
|
||||
MySQL.update.await(
|
||||
'DELETE FROM pc_live_contacts WHERE id = ? AND identifier = ?',
|
||||
{ id, identifier }
|
||||
)
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue