feat: Michess – Initiales Setup auf Basis von chessu
- Rebranding: chessu → Michess (Header, Footer, Metadata, Session-Cookie) - Stockfish KI-Integration: 6 Schwierigkeitsgrade (Anfänger bis Meister) - Admin-Panel: Nutzerverwaltung, Sperren/Entsperren, Git-Pull-Update - Freundessystem: Anfragen, Freundesliste, Nutzersuche - DB-Schema: role, banned, friend_request, friendship Tabellen - Docker: NAS-optimiertes docker-compose.yml mit PostgreSQL Healthcheck - Stockfish binary via apk im Alpine-Image installiert - .env.example für einfaches Deployment - scripts/update.sh für manuelles NAS-Update - Brain.md + Plan.md als Projekt-Gedächtnis Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,11 +6,13 @@ export const INIT_TABLES = /* sql */ `
|
||||
CREATE TABLE IF NOT EXISTS "user" (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(128) UNIQUE NOT NULL,
|
||||
email VARCHAR(128),
|
||||
email VARCHAR(128) UNIQUE,
|
||||
password TEXT,
|
||||
role VARCHAR(16) DEFAULT 'user',
|
||||
wins INTEGER DEFAULT 0,
|
||||
losses INTEGER DEFAULT 0,
|
||||
draws INTEGER DEFAULT 0,
|
||||
banned BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "game" (
|
||||
@@ -22,7 +24,28 @@ export const INIT_TABLES = /* sql */ `
|
||||
white_name VARCHAR(32),
|
||||
black_id INT REFERENCES "user",
|
||||
black_name VARCHAR(32),
|
||||
vs_ai BOOLEAN DEFAULT FALSE,
|
||||
ai_level INTEGER,
|
||||
started_at TIMESTAMP NOT NULL,
|
||||
ended_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "friend_request" (
|
||||
id SERIAL PRIMARY KEY,
|
||||
from_id INT REFERENCES "user"(id) ON DELETE CASCADE,
|
||||
to_id INT REFERENCES "user"(id) ON DELETE CASCADE,
|
||||
status VARCHAR(16) DEFAULT 'pending',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(from_id, to_id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "friendship" (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id_1 INT REFERENCES "user"(id) ON DELETE CASCADE,
|
||||
user_id_2 INT REFERENCES "user"(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id_1, user_id_2)
|
||||
);
|
||||
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS role VARCHAR(16) DEFAULT 'user';
|
||||
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS banned BOOLEAN DEFAULT FALSE;
|
||||
ALTER TABLE "game" ADD COLUMN IF NOT EXISTS vs_ai BOOLEAN DEFAULT FALSE;
|
||||
ALTER TABLE "game" ADD COLUMN IF NOT EXISTS ai_level INTEGER;
|
||||
`;
|
||||
|
||||
@@ -8,7 +8,7 @@ export const create = async (user: User, password: string) => {
|
||||
|
||||
try {
|
||||
const res = await db.query(
|
||||
`INSERT INTO "user"(name, email, password) VALUES($1, $2, $3) RETURNING id, name, email, wins, losses, draws`,
|
||||
`INSERT INTO "user"(name, email, password) VALUES($1, $2, $3) RETURNING id, name, email, wins, losses, draws, role, banned`,
|
||||
[user.name, user.email || null, password]
|
||||
);
|
||||
return res.rows[0] as User;
|
||||
@@ -24,11 +24,11 @@ export const findById = async (id: number) => {
|
||||
}
|
||||
try {
|
||||
const res = await db.query(
|
||||
`SELECT id, name, email, wins, losses, draws FROM "user" WHERE id=$1`,
|
||||
`SELECT id, name, email, wins, losses, draws, role, banned FROM "user" WHERE id=$1`,
|
||||
[id]
|
||||
);
|
||||
if (res.rowCount) {
|
||||
return res.rows[0] as User;
|
||||
return res.rows[0] as User & { banned?: boolean };
|
||||
} else return null;
|
||||
} catch (err: unknown) {
|
||||
console.log(err);
|
||||
@@ -37,14 +37,13 @@ export const findById = async (id: number) => {
|
||||
};
|
||||
|
||||
export const findByNameEmail = async (user: User, includePassword = false, limit?: number) => {
|
||||
// if user is not specified, get all users
|
||||
if (!user) {
|
||||
try {
|
||||
const res = await db.query(
|
||||
`SELECT id, name, email, wins, losses, draws FROM "user" LIMIT $1`,
|
||||
`SELECT id, name, email, wins, losses, draws, role, banned FROM "user" LIMIT $1`,
|
||||
[limit ?? 10]
|
||||
);
|
||||
return res.rows as (User & { password?: string })[];
|
||||
return res.rows as (User & { password?: string; banned?: boolean })[];
|
||||
} catch (err: unknown) {
|
||||
console.log(err);
|
||||
return null;
|
||||
@@ -53,12 +52,25 @@ export const findByNameEmail = async (user: User, includePassword = false, limit
|
||||
|
||||
try {
|
||||
const res = await db.query(
|
||||
`SELECT id, name, email, wins, losses, draws${
|
||||
`SELECT id, name, email, wins, losses, draws, role, banned${
|
||||
includePassword ? `, password` : ""
|
||||
} FROM "user" WHERE name=$1 OR email=$2 LIMIT $3`,
|
||||
[user.name, user.email, limit ?? 1]
|
||||
);
|
||||
return res.rows as (User & { password?: string })[];
|
||||
return res.rows as (User & { password?: string; banned?: boolean })[];
|
||||
} catch (err: unknown) {
|
||||
console.log(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const searchByName = async (query: string, limit = 10) => {
|
||||
try {
|
||||
const res = await db.query(
|
||||
`SELECT id, name, wins, losses, draws FROM "user" WHERE name ILIKE $1 LIMIT $2`,
|
||||
[`%${query}%`, limit]
|
||||
);
|
||||
return res.rows as User[];
|
||||
} catch (err: unknown) {
|
||||
console.log(err);
|
||||
return null;
|
||||
@@ -71,11 +83,11 @@ export const update = async (id: number, updatedUser: User & { password?: string
|
||||
}
|
||||
|
||||
try {
|
||||
let query = `UPDATE "user" SET name=$1, email=$2 WHERE id=$3 RETURNING id, name, email, wins, losses, draws`;
|
||||
let values = [updatedUser.name, updatedUser.email, id];
|
||||
let query = `UPDATE "user" SET name=$1, email=$2 WHERE id=$3 RETURNING id, name, email, wins, losses, draws, role, banned`;
|
||||
let values: (string | number | undefined)[] = [updatedUser.name, updatedUser.email, id];
|
||||
|
||||
if (updatedUser.password) {
|
||||
query = `UPDATE "user" SET name=$1, email=$2, password=$3 WHERE id=$4 RETURNING id, name, email, wins, losses, draws`;
|
||||
query = `UPDATE "user" SET name=$1, email=$2, password=$3 WHERE id=$4 RETURNING id, name, email, wins, losses, draws, role, banned`;
|
||||
values = [updatedUser.name, updatedUser.email, updatedUser.password, id];
|
||||
}
|
||||
const res = await db.query(query, values);
|
||||
@@ -86,6 +98,58 @@ export const update = async (id: number, updatedUser: User & { password?: string
|
||||
}
|
||||
};
|
||||
|
||||
export const adminUpdate = async (id: number, fields: { role?: string; banned?: boolean }) => {
|
||||
try {
|
||||
const setClauses: string[] = [];
|
||||
const values: (string | boolean | number)[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
if (fields.role !== undefined) {
|
||||
setClauses.push(`role=$${paramIndex++}`);
|
||||
values.push(fields.role);
|
||||
}
|
||||
if (fields.banned !== undefined) {
|
||||
setClauses.push(`banned=$${paramIndex++}`);
|
||||
values.push(fields.banned);
|
||||
}
|
||||
|
||||
if (!setClauses.length) return null;
|
||||
|
||||
values.push(id);
|
||||
const res = await db.query(
|
||||
`UPDATE "user" SET ${setClauses.join(", ")} WHERE id=$${paramIndex} RETURNING id, name, email, role, banned`,
|
||||
values
|
||||
);
|
||||
return res.rows[0] as User & { banned?: boolean };
|
||||
} catch (err: unknown) {
|
||||
console.log(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const getAllUsers = async (limit = 50, offset = 0, search?: string) => {
|
||||
try {
|
||||
let query = `SELECT id, name, email, wins, losses, draws, role, banned, created_at FROM "user"`;
|
||||
const values: (string | number)[] = [];
|
||||
|
||||
if (search) {
|
||||
query += ` WHERE name ILIKE $1 OR email ILIKE $1`;
|
||||
values.push(`%${search}%`);
|
||||
query += ` ORDER BY created_at DESC LIMIT $${values.length + 1} OFFSET $${values.length + 2}`;
|
||||
values.push(limit, offset);
|
||||
} else {
|
||||
query += ` ORDER BY created_at DESC LIMIT $1 OFFSET $2`;
|
||||
values.push(limit, offset);
|
||||
}
|
||||
|
||||
const res = await db.query(query, values);
|
||||
return res.rows as (User & { banned?: boolean; created_at?: string })[];
|
||||
} catch (err: unknown) {
|
||||
console.log(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const remove = async (id: number) => {
|
||||
if (id === 0) {
|
||||
return null;
|
||||
@@ -106,7 +170,10 @@ const UserModel = {
|
||||
create,
|
||||
findById,
|
||||
findByNameEmail,
|
||||
searchByName,
|
||||
update,
|
||||
adminUpdate,
|
||||
getAllUsers,
|
||||
remove
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user