coldown fix
This commit is contained in:
220
server/sqlite-storage.ts
Normal file
220
server/sqlite-storage.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
import Database from 'better-sqlite3';
|
||||
import { type Pixel, type InsertPixel, type CanvasConfig, type InsertCanvasConfig, type UserCooldown, type InsertUserCooldown } from "@shared/schema";
|
||||
import { randomUUID } from "crypto";
|
||||
import { config } from "./config";
|
||||
import { IStorage } from "./storage";
|
||||
|
||||
export class SQLiteStorage implements IStorage {
|
||||
private db: Database.Database;
|
||||
|
||||
constructor(dbPath: string = ':memory:') {
|
||||
this.db = new Database(dbPath);
|
||||
this.initTables();
|
||||
this.initDefaultConfig();
|
||||
}
|
||||
|
||||
private initTables() {
|
||||
// Pixels table
|
||||
this.db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS pixels (
|
||||
id TEXT PRIMARY KEY DEFAULT (hex(randomblob(16))),
|
||||
x INTEGER NOT NULL,
|
||||
y INTEGER NOT NULL,
|
||||
color TEXT NOT NULL,
|
||||
userId TEXT NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
// Canvas config table
|
||||
this.db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS canvas_config (
|
||||
id TEXT PRIMARY KEY DEFAULT (hex(randomblob(16))),
|
||||
canvasWidth INTEGER NOT NULL DEFAULT 100,
|
||||
canvasHeight INTEGER NOT NULL DEFAULT 100,
|
||||
defaultCooldown INTEGER NOT NULL DEFAULT 5,
|
||||
enableAutomaticEvents BOOLEAN NOT NULL DEFAULT 0,
|
||||
eventDuration INTEGER NOT NULL DEFAULT 30,
|
||||
eventInterval INTEGER NOT NULL DEFAULT 6,
|
||||
updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
// User cooldowns table
|
||||
this.db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS user_cooldowns (
|
||||
id TEXT PRIMARY KEY DEFAULT (hex(randomblob(16))),
|
||||
userId TEXT NOT NULL UNIQUE,
|
||||
lastPlacement DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
cooldownEnds DATETIME NOT NULL
|
||||
)
|
||||
`);
|
||||
|
||||
// Create indexes
|
||||
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_pixels_xy ON pixels(x, y)`);
|
||||
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_pixels_created ON pixels(createdAt DESC)`);
|
||||
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_cooldowns_user ON user_cooldowns(userId)`);
|
||||
}
|
||||
|
||||
private initDefaultConfig() {
|
||||
const existingConfig = this.db.prepare('SELECT * FROM canvas_config LIMIT 1').get();
|
||||
if (!existingConfig) {
|
||||
this.db.prepare(`
|
||||
INSERT INTO canvas_config (canvasWidth, canvasHeight, defaultCooldown, enableAutomaticEvents, eventDuration, eventInterval)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
config.canvasWidth,
|
||||
config.canvasHeight,
|
||||
config.defaultCooldown,
|
||||
config.enableAutomaticEvents ? 1 : 0,
|
||||
config.eventDurationMinutes,
|
||||
config.eventIntervalHours
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getPixel(x: number, y: number): Promise<Pixel | undefined> {
|
||||
const row = this.db.prepare('SELECT * FROM pixels WHERE x = ? AND y = ? ORDER BY createdAt DESC LIMIT 1').get(x, y) as any;
|
||||
if (!row) return undefined;
|
||||
|
||||
return {
|
||||
...row,
|
||||
createdAt: new Date(row.createdAt),
|
||||
enableAutomaticEvents: Boolean(row.enableAutomaticEvents)
|
||||
};
|
||||
}
|
||||
|
||||
async getAllPixels(): Promise<Pixel[]> {
|
||||
const rows = this.db.prepare(`
|
||||
SELECT p1.* FROM pixels p1
|
||||
INNER JOIN (
|
||||
SELECT x, y, MAX(createdAt) as maxCreated
|
||||
FROM pixels
|
||||
GROUP BY x, y
|
||||
) p2 ON p1.x = p2.x AND p1.y = p2.y AND p1.createdAt = p2.maxCreated
|
||||
`).all() as any[];
|
||||
|
||||
return rows.map(row => ({
|
||||
...row,
|
||||
createdAt: new Date(row.createdAt)
|
||||
}));
|
||||
}
|
||||
|
||||
async placePixel(insertPixel: InsertPixel): Promise<Pixel> {
|
||||
const id = randomUUID();
|
||||
const now = new Date();
|
||||
|
||||
this.db.prepare(`
|
||||
INSERT INTO pixels (id, x, y, color, userId, username, createdAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(id, insertPixel.x, insertPixel.y, insertPixel.color, insertPixel.userId, insertPixel.username, now.toISOString());
|
||||
|
||||
return {
|
||||
id,
|
||||
...insertPixel,
|
||||
createdAt: now
|
||||
};
|
||||
}
|
||||
|
||||
async getCanvasConfig(): Promise<CanvasConfig> {
|
||||
const row = this.db.prepare('SELECT * FROM canvas_config ORDER BY updatedAt DESC LIMIT 1').get() as any;
|
||||
|
||||
return {
|
||||
...row,
|
||||
enableAutomaticEvents: Boolean(row.enableAutomaticEvents),
|
||||
updatedAt: new Date(row.updatedAt)
|
||||
};
|
||||
}
|
||||
|
||||
async updateCanvasConfig(configUpdate: InsertCanvasConfig): Promise<CanvasConfig> {
|
||||
const currentConfig = await this.getCanvasConfig();
|
||||
const now = new Date();
|
||||
|
||||
this.db.prepare(`
|
||||
UPDATE canvas_config
|
||||
SET canvasWidth = ?, canvasHeight = ?, defaultCooldown = ?,
|
||||
enableAutomaticEvents = ?, eventDuration = ?, eventInterval = ?,
|
||||
updatedAt = ?
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
configUpdate.canvasWidth ?? currentConfig.canvasWidth,
|
||||
configUpdate.canvasHeight ?? currentConfig.canvasHeight,
|
||||
configUpdate.defaultCooldown ?? currentConfig.defaultCooldown,
|
||||
configUpdate.enableAutomaticEvents ? 1 : 0,
|
||||
configUpdate.eventDuration ?? currentConfig.eventDuration,
|
||||
configUpdate.eventInterval ?? currentConfig.eventInterval,
|
||||
now.toISOString(),
|
||||
currentConfig.id
|
||||
);
|
||||
|
||||
return this.getCanvasConfig();
|
||||
}
|
||||
|
||||
async getUserCooldown(userId: string): Promise<UserCooldown | undefined> {
|
||||
const row = this.db.prepare('SELECT * FROM user_cooldowns WHERE userId = ?').get(userId) as any;
|
||||
if (!row) return undefined;
|
||||
|
||||
return {
|
||||
...row,
|
||||
lastPlacement: new Date(row.lastPlacement),
|
||||
cooldownEnds: new Date(row.cooldownEnds)
|
||||
};
|
||||
}
|
||||
|
||||
async setUserCooldown(insertCooldown: InsertUserCooldown): Promise<UserCooldown> {
|
||||
const id = randomUUID();
|
||||
const now = new Date();
|
||||
|
||||
this.db.prepare(`
|
||||
INSERT OR REPLACE INTO user_cooldowns (id, userId, lastPlacement, cooldownEnds)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`).run(id, insertCooldown.userId, now.toISOString(), insertCooldown.cooldownEnds.toISOString());
|
||||
|
||||
return {
|
||||
id,
|
||||
userId: insertCooldown.userId,
|
||||
lastPlacement: now,
|
||||
cooldownEnds: insertCooldown.cooldownEnds
|
||||
};
|
||||
}
|
||||
|
||||
async getRecentPlacements(limit: number = 10): Promise<Pixel[]> {
|
||||
const rows = this.db.prepare(`
|
||||
SELECT * FROM pixels
|
||||
ORDER BY createdAt DESC
|
||||
LIMIT ?
|
||||
`).all(limit) as any[];
|
||||
|
||||
return rows.map(row => ({
|
||||
...row,
|
||||
createdAt: new Date(row.createdAt)
|
||||
}));
|
||||
}
|
||||
|
||||
// Canvas-Erweiterungsmethode
|
||||
async expandCanvas(newWidth: number, newHeight: number): Promise<void> {
|
||||
const currentConfig = await this.getCanvasConfig();
|
||||
|
||||
if (newWidth < currentConfig.canvasWidth || newHeight < currentConfig.canvasHeight) {
|
||||
throw new Error("Canvas kann nur erweitert werden, nicht verkleinert");
|
||||
}
|
||||
|
||||
await this.updateCanvasConfig({
|
||||
canvasWidth: newWidth,
|
||||
canvasHeight: newHeight,
|
||||
defaultCooldown: currentConfig.defaultCooldown,
|
||||
enableAutomaticEvents: currentConfig.enableAutomaticEvents,
|
||||
eventDuration: currentConfig.eventDuration,
|
||||
eventInterval: currentConfig.eventInterval
|
||||
});
|
||||
}
|
||||
|
||||
async deletePixel(pixelId: string): Promise<void> {
|
||||
this.db.prepare("DELETE FROM pixels WHERE id = ?").run(pixelId);
|
||||
}
|
||||
|
||||
async clearCanvas(): Promise<void> {
|
||||
this.db.prepare("DELETE FROM pixels").run();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user