Feature-Voting, Giveaways und Contribution-Heatmap
- /wunsch: Voting-Embed mit 👍, Reaction-Tracking (add/remove) in SQLite, Top-20-Rangliste öffentlich auf der Roadmap-Seite - /giveaway (Admin): Preis/Dauer/Gewinnerzahl, 🎉-Teilnahme-Button (toggle), Minuten-Scheduler zieht Gewinner, schließt das Embed ab und pingt sie - Contribution-Heatmap: 52-Wochen-Grid in Brand-Gelb aus dem Commit-Archiv (/api/heatmap) auf der Roadmap-Seite - Setting voting_channel_id auf der Setup-Seite Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -178,6 +178,80 @@ export const listGallery = (limit, offset) => ({
|
||||
total: countGalleryStmt.get().n,
|
||||
});
|
||||
|
||||
// Feature-Voting (/wunsch) + Giveaways
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS wishes (
|
||||
message_id TEXT PRIMARY KEY,
|
||||
author TEXT,
|
||||
idea TEXT NOT NULL,
|
||||
score INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS giveaways (
|
||||
message_id TEXT PRIMARY KEY,
|
||||
channel_id TEXT NOT NULL,
|
||||
prize TEXT NOT NULL,
|
||||
winners INTEGER NOT NULL DEFAULT 1,
|
||||
ends_at TEXT NOT NULL,
|
||||
ended INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS giveaway_entries (
|
||||
giveaway_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
PRIMARY KEY (giveaway_id, user_id)
|
||||
);
|
||||
`);
|
||||
|
||||
const insertWish = db.prepare('INSERT INTO wishes (message_id, author, idea) VALUES (?, ?, ?)');
|
||||
const wishScoreStmt = db.prepare('UPDATE wishes SET score = MAX(0, score + ?) WHERE message_id = ?');
|
||||
const hasWishStmt = db.prepare('SELECT 1 FROM wishes WHERE message_id = ?');
|
||||
const topWishesStmt = db.prepare(
|
||||
'SELECT idea, author, score, created_at FROM wishes ORDER BY score DESC, created_at DESC LIMIT ?'
|
||||
);
|
||||
export const saveWish = (messageId, author, idea) => insertWish.run(messageId, author, idea);
|
||||
export const isWish = (messageId) => Boolean(hasWishStmt.get(messageId));
|
||||
export const bumpWish = (messageId, delta) => wishScoreStmt.run(delta, messageId);
|
||||
export const topWishes = (limit = 20) => topWishesStmt.all(limit);
|
||||
|
||||
const insertGiveaway = db.prepare(`
|
||||
INSERT INTO giveaways (message_id, channel_id, prize, winners, ends_at)
|
||||
VALUES (@message_id, @channel_id, @prize, @winners, @ends_at)
|
||||
`);
|
||||
const dueGiveawaysStmt = db.prepare(
|
||||
`SELECT * FROM giveaways WHERE ended = 0 AND ends_at <= datetime('now')`
|
||||
);
|
||||
const endGiveawayStmt = db.prepare('UPDATE giveaways SET ended = 1 WHERE message_id = ?');
|
||||
const giveawayStmt = db.prepare('SELECT * FROM giveaways WHERE message_id = ?');
|
||||
const toggleEntryInsert = db.prepare(
|
||||
'INSERT OR IGNORE INTO giveaway_entries (giveaway_id, user_id) VALUES (?, ?)'
|
||||
);
|
||||
const toggleEntryDelete = db.prepare(
|
||||
'DELETE FROM giveaway_entries WHERE giveaway_id = ? AND user_id = ?'
|
||||
);
|
||||
const entriesStmt = db.prepare('SELECT user_id FROM giveaway_entries WHERE giveaway_id = ?');
|
||||
export const saveGiveaway = (g) => insertGiveaway.run(g);
|
||||
export const getGiveaway = (id) => giveawayStmt.get(id) ?? null;
|
||||
export const dueGiveaways = () => dueGiveawaysStmt.all();
|
||||
export const markGiveawayEnded = (id) => endGiveawayStmt.run(id);
|
||||
export const giveawayEntries = (id) => entriesStmt.all(id).map((r) => r.user_id);
|
||||
/** Teilnahme togglen — true = jetzt drin, false = ausgetragen */
|
||||
export function toggleGiveawayEntry(giveawayId, userId) {
|
||||
if (toggleEntryInsert.run(giveawayId, userId).changes > 0) return true;
|
||||
toggleEntryDelete.run(giveawayId, userId);
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Commits pro Tag der letzten 365 Tage (Contribution-Heatmap) */
|
||||
export function commitHeatmap() {
|
||||
return db
|
||||
.prepare(`
|
||||
SELECT date(committed_at) AS day, count(*) AS n
|
||||
FROM commits WHERE committed_at >= datetime('now', '-365 days')
|
||||
GROUP BY day
|
||||
`)
|
||||
.all();
|
||||
}
|
||||
|
||||
// Modmail: DM-Konversationen ↔ Staff-Threads
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS modmail (
|
||||
|
||||
Reference in New Issue
Block a user