Add files via upload

This commit is contained in:
Serverlele30 authored and GitHub committed 2026-07-23 09:53:08 +02:00
1 parent 1bff4c6eea
commit c3ac407e2e
10 files changed
+697 -1

No files matched your search

+50
View File
@@ -0,0 +1,50 @@
import { sql, ok, fail, auth, clean, ONLINE_SECS, PREVIEW_LEN } from '../lib/db.js';
export default async function handler(req, res) {
try {
const me = await auth(req, res);
if (!me) return;
const now = Math.floor(Date.now() / 1000);
// Alles in EINER Abfrage – auf Serverless zählt jede Round-Trip-Zeit.
const rows = await sql`
SELECT u.name, u.last_seen,
COALESCE(lm.ts, 0) AS last_ts,
lm.text AS last_text,
lm.sender AS last_sender,
COALESCE(un.cnt, 0) AS unread
FROM users u
LEFT JOIN LATERAL (
SELECT m.ts, m.text, m.sender FROM messages m
WHERE (m.sender = ${me.id} AND m.recipient = u.id)
OR (m.sender = u.id AND m.recipient = ${me.id})
ORDER BY m.id DESC LIMIT 1
) lm ON TRUE
LEFT JOIN LATERAL (
SELECT COUNT(*) AS cnt FROM messages m
WHERE m.sender = u.id AND m.recipient = ${me.id}
AND m.id > COALESCE(
(SELECT last_id FROM reads WHERE user_id = ${me.id} AND peer_id = u.id), 0)
) un ON TRUE
WHERE u.id <> ${me.id}
ORDER BY last_ts DESC, LOWER(u.name) ASC`;
const body = rows.map(r => {
const online = (now - Number(r.last_seen) < ONLINE_SECS) ? 1 : 0;
let preview = '';
if (r.last_text) {
let txt = r.last_text.slice(0, PREVIEW_LEN);
if (r.last_text.length > PREVIEW_LEN) txt += '…';
preview = (r.last_sender === me.id ? 'Du: ' : '') + txt;
}
return `USER|${clean(r.name)}|${online}|${r.unread}|${r.last_ts}|${clean(preview)}`;
}).join('\n');
ok(res, body ? body + '\n' : '');
} catch (e) {
fail(res, 'Serverfehler: ' + e.message);
}
}
+21
View File
@@ -0,0 +1,21 @@
import { sql, param, ok, fail, verifyPassword, newToken } from '../lib/db.js';
export default async function handler(req, res) {
const name = param(req, 'user');
const pass = param(req, 'pass');
try {
const rows = await sql`
SELECT id, pass_hash FROM users WHERE LOWER(name) = LOWER(${name})`;
if (!rows.length || !verifyPassword(pass, rows[0].pass_hash))
return fail(res, 'Login fehlgeschlagen');
const token = newToken();
const now = Math.floor(Date.now() / 1000);
await sql`UPDATE users SET token = ${token}, last_seen = ${now} WHERE id = ${rows[0].id}`;
ok(res, `OK|${token}\n`);
} catch (e) {
fail(res, 'Serverfehler: ' + e.message);
}
}
+45
View File
@@ -0,0 +1,45 @@
import { sql, param, ok, fail, auth, clean, MAX_POLL } from '../lib/db.js';
export default async function handler(req, res) {
try {
const me = await auth(req, res);
if (!me) return;
const withName = param(req, 'with');
const since = parseInt(param(req, 'since'), 10) || 0;
const other = await sql`SELECT id FROM users WHERE LOWER(name) = LOWER(${withName})`;
if (!other.length) return fail(res, 'Nutzer unbekannt');
const peer = other[0].id;
const msgs = await sql`
SELECT m.id, u.name AS sender_name, m.ts, m.text
FROM messages m JOIN users u ON u.id = m.sender
WHERE m.id > ${since}
AND ((m.sender = ${me.id} AND m.recipient = ${peer})
OR (m.sender = ${peer} AND m.recipient = ${me.id}))
ORDER BY m.id ASC
LIMIT ${MAX_POLL}`;
const body = msgs
.map(m => `MSG|${m.id}|${clean(m.sender_name)}|${m.ts}|${clean(m.text)}`)
.join('\n');
// Chat als gelesen markieren
const maxRow = await sql`
SELECT COALESCE(MAX(id), 0) AS max_id FROM messages
WHERE (sender = ${me.id} AND recipient = ${peer})
OR (sender = ${peer} AND recipient = ${me.id})`;
const maxId = maxRow[0].max_id;
await sql`
INSERT INTO reads (user_id, peer_id, last_id)
VALUES (${me.id}, ${peer}, ${maxId})
ON CONFLICT (user_id, peer_id)
DO UPDATE SET last_id = GREATEST(reads.last_id, ${maxId})`;
ok(res, body ? body + '\n' : '');
} catch (e) {
fail(res, 'Serverfehler: ' + e.message);
}
}
+21
View File
@@ -0,0 +1,21 @@
import { sql, param, ok, fail, hashPassword } from '../lib/db.js';
export default async function handler(req, res) {
const name = param(req, 'user');
const pass = param(req, 'pass');
if (!/^[A-Za-z0-9_-]{2,20}$/.test(name))
return fail(res, 'Name: 2-20 Zeichen, a-z 0-9 _ -');
if (pass.length < 4)
return fail(res, 'Passwort zu kurz (min. 4)');
try {
const exists = await sql`SELECT id FROM users WHERE LOWER(name) = LOWER(${name})`;
if (exists.length) return fail(res, 'Name bereits vergeben');
await sql`INSERT INTO users (name, pass_hash) VALUES (${name}, ${hashPassword(pass)})`;
ok(res, 'OK|registriert\n');
} catch (e) {
fail(res, 'Serverfehler: ' + e.message);
}
}
+25
View File
@@ -0,0 +1,25 @@
import { sql, param, ok, fail, auth, MAX_TEXT_LEN } from '../lib/db.js';
export default async function handler(req, res) {
try {
const me = await auth(req, res);
if (!me) return;
const to = param(req, 'to');
const text = param(req, 'text').slice(0, MAX_TEXT_LEN);
if (!text) return fail(res, 'Leere Nachricht');
const rcpt = await sql`SELECT id FROM users WHERE LOWER(name) = LOWER(${to})`;
if (!rcpt.length) return fail(res, 'Empfaenger unbekannt');
const now = Math.floor(Date.now() / 1000);
const ins = await sql`
INSERT INTO messages (sender, recipient, ts, text)
VALUES (${me.id}, ${rcpt[0].id}, ${now}, ${text})
RETURNING id`;
ok(res, `OK|${ins[0].id}\n`);
} catch (e) {
fail(res, 'Serverfehler: ' + e.message);
}
}
+37
View File
@@ -0,0 +1,37 @@
// Einmalig aufrufen: https://<projekt>.vercel.app/api/setup
// Legt die Tabellen an. Mehrfaches Aufrufen ist unschädlich.
import { sql, ok, fail } from '../lib/db.js';
export default async function handler(req, res) {
try {
await sql`CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
pass_hash TEXT NOT NULL,
token TEXT,
last_seen BIGINT DEFAULT 0
)`;
// Namen ohne Rücksicht auf Groß-/Kleinschreibung eindeutig halten
await sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_users_name ON users (LOWER(name))`;
await sql`CREATE TABLE IF NOT EXISTS messages (
id SERIAL PRIMARY KEY,
sender INTEGER NOT NULL,
recipient INTEGER NOT NULL,
ts BIGINT NOT NULL,
text TEXT NOT NULL
)`;
await sql`CREATE INDEX IF NOT EXISTS idx_msg ON messages (recipient, sender, id)`;
await sql`CREATE TABLE IF NOT EXISTS reads (
user_id INTEGER NOT NULL,
peer_id INTEGER NOT NULL,
last_id INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (user_id, peer_id)
)`;
ok(res, 'OK|Datenbank bereit\n');
} catch (e) {
fail(res, 'Setup fehlgeschlagen: ' + e.message);
}
}