Initial commit
This commit is contained in:
@@ -1,33 +1,44 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
// JsonStore: Dateibasierter Fallback-Store wenn kein MongoDB vorhanden ist.
|
||||
// Alle Daten liegen als JSON-Datei auf der Festplatte — kein externer Datenbankserver nötig.
|
||||
// Nachteil gegenüber MongoDB: Kein Concurrency-Schutz bei mehreren gleichzeitigen Schreiboperationen.
|
||||
class JsonStore {
|
||||
constructor(filePath) {
|
||||
this.filePath = filePath
|
||||
this.state = { users: [], messages: [] }
|
||||
this.state = { users: [], messages: [] } // In-Memory-State — alle Operationen laufen darauf.
|
||||
this._ensureStore()
|
||||
}
|
||||
|
||||
// Stellt sicher dass die JSON-Datei und das Verzeichnis existieren.
|
||||
// Wenn die Datei beschädigt ist (kein gültiges JSON), wird sie neu erstellt.
|
||||
_ensureStore() {
|
||||
const dir = path.dirname(this.filePath)
|
||||
fs.mkdirSync(dir, { recursive: true })
|
||||
fs.mkdirSync(dir, { recursive: true }) // Verzeichnis anlegen falls nicht vorhanden.
|
||||
if (!fs.existsSync(this.filePath)) {
|
||||
this._persist()
|
||||
return
|
||||
}
|
||||
try {
|
||||
const contents = fs.readFileSync(this.filePath, 'utf-8')
|
||||
this.state = JSON.parse(contents)
|
||||
this.state = JSON.parse(contents) // Datei in den In-Memory-State laden.
|
||||
} catch (error) {
|
||||
// Datei vorhanden aber nicht lesbar oder kein gültiges JSON — neu erstellen.
|
||||
console.warn('JSON store damaged, recreating', error)
|
||||
this._persist()
|
||||
}
|
||||
}
|
||||
|
||||
// Schreibt den aktuellen In-Memory-State in die JSON-Datei.
|
||||
// null, 2 formatiert das JSON mit Einrückung — besser lesbar beim Debugging.
|
||||
_persist() {
|
||||
fs.writeFileSync(this.filePath, JSON.stringify(this.state, null, 2), 'utf-8')
|
||||
}
|
||||
|
||||
// Upsert: User aktualisieren wenn vorhanden, sonst neu anlegen.
|
||||
// Spread-Operator {...this.state.users[index], ...} übernimmt alle bestehenden Felder
|
||||
// und überschreibt nur die geänderten — joinedAt bleibt beim Update erhalten.
|
||||
async upsertUser({ id, username, room }) {
|
||||
const trimmed = username?.trim()
|
||||
if (!trimmed) {
|
||||
@@ -60,6 +71,7 @@ class JsonStore {
|
||||
return user
|
||||
}
|
||||
|
||||
// Object.assign mutiert das gefundene Objekt direkt im Array — kein Index-Lookup nötig.
|
||||
async setUserOnline(userId, room) {
|
||||
const user = this.state.users.find((entry) => entry.id === userId)
|
||||
if (!user) {
|
||||
@@ -74,6 +86,7 @@ class JsonStore {
|
||||
return user
|
||||
}
|
||||
|
||||
// lastActiveAt wird auch beim Offline-Setzen aktualisiert — zeigt wann der User zuletzt aktiv war.
|
||||
async setUserOffline(userId) {
|
||||
const user = this.state.users.find((entry) => entry.id === userId)
|
||||
if (!user) {
|
||||
@@ -96,6 +109,8 @@ class JsonStore {
|
||||
return user
|
||||
}
|
||||
|
||||
// splice(index, 1) entfernt genau ein Element an der gefundenen Position.
|
||||
// Destructuring [removed] holt das gelöschte Element aus dem zurückgegebenen Array.
|
||||
async removeUser(userId) {
|
||||
const index = this.state.users.findIndex((entry) => entry.id === userId)
|
||||
if (index === -1) return null
|
||||
@@ -108,6 +123,8 @@ class JsonStore {
|
||||
return this.state.users.find((entry) => entry.id === userId) || null
|
||||
}
|
||||
|
||||
// .map() gibt nur die öffentlichen Felder zurück — interne Felder werden nicht nach außen gegeben.
|
||||
// Sortierung: Online-User zuerst, dann nach letzter Aktivität absteigend.
|
||||
async getParticipants(room) {
|
||||
return this.state.users
|
||||
.filter((entry) => entry.room === room)
|
||||
@@ -126,6 +143,7 @@ class JsonStore {
|
||||
})
|
||||
}
|
||||
|
||||
// createdAt wird serverseitig gesetzt — dem Client-Timestamp wird nicht vertraut.
|
||||
async addMessage({ id, room, userId, username, text }) {
|
||||
const message = {
|
||||
id,
|
||||
@@ -140,11 +158,25 @@ class JsonStore {
|
||||
return message
|
||||
}
|
||||
|
||||
// slice(-limit) gibt die letzten N Nachrichten zurück — neueste am Ende, älteste zuerst.
|
||||
async getMessages(room, limit = 100) {
|
||||
return this.state.messages
|
||||
.filter((message) => message.room === room)
|
||||
.slice(-limit)
|
||||
}
|
||||
|
||||
// roomMap: Objekt das pro Room die Teilnehmerzahl zählt — schneller als mehrere filter()-Aufrufe.
|
||||
// localeCompare: Alphabetische Sortierung mit Sprachunterstützung (Umlaute etc.).
|
||||
async getRooms() {
|
||||
const roomMap = {}
|
||||
for (const user of this.state.users) {
|
||||
if (!roomMap[user.room]) {
|
||||
roomMap[user.room] = { name: user.room, participantCount: 0 }
|
||||
}
|
||||
if (user.isOnline) roomMap[user.room].participantCount++
|
||||
}
|
||||
return Object.values(roomMap).sort((a, b) => a.name.localeCompare(b.name))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JsonStore
|
||||
|
||||
Reference in New Issue
Block a user