Organized the differences between server and docker versions.
We are launching a docker version (server version) today so we want to just organize the repo so its easier to navigate.
This commit is contained in:
186
desktop/src/api/user/user.service.ts
Normal file
186
desktop/src/api/user/user.service.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
import {queryAll, queryOne, run} from '../../shared/database';
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
const USER_DB_NAME = 'userdata';
|
||||
const SALT_ROUNDS = 10;
|
||||
|
||||
interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
profile_picture_url: string | null;
|
||||
has_password: boolean;
|
||||
}
|
||||
|
||||
export async function userExists(id: number): Promise<boolean> {
|
||||
const sql = 'SELECT 1 FROM User WHERE id = ?';
|
||||
const row = await queryOne(sql, [id], USER_DB_NAME);
|
||||
return !!row;
|
||||
}
|
||||
|
||||
export async function createUser(username: string, profilePictureUrl?: string, password?: string): Promise<{ lastID: number }> {
|
||||
let passwordHash = null;
|
||||
|
||||
if (password && password.trim()) {
|
||||
passwordHash = await bcrypt.hash(password.trim(), SALT_ROUNDS);
|
||||
}
|
||||
|
||||
const sql = `
|
||||
INSERT INTO User (username, profile_picture_url, password_hash)
|
||||
VALUES (?, ?, ?)
|
||||
`;
|
||||
const params = [username, profilePictureUrl || null, passwordHash];
|
||||
|
||||
const result = await run(sql, params, USER_DB_NAME);
|
||||
|
||||
return { lastID: result.lastID };
|
||||
}
|
||||
|
||||
export async function updateUser(userId: number, updates: any): Promise<any> {
|
||||
const fields: string[] = [];
|
||||
const values: (string | number | null)[] = [];
|
||||
|
||||
if (updates.username !== undefined) {
|
||||
fields.push('username = ?');
|
||||
values.push(updates.username);
|
||||
}
|
||||
|
||||
if (updates.profilePictureUrl !== undefined) {
|
||||
fields.push('profile_picture_url = ?');
|
||||
values.push(updates.profilePictureUrl);
|
||||
}
|
||||
|
||||
if (updates.password !== undefined) {
|
||||
if (updates.password === null || updates.password === '') {
|
||||
// Eliminar contraseña
|
||||
fields.push('password_hash = ?');
|
||||
values.push(null);
|
||||
} else {
|
||||
// Actualizar contraseña
|
||||
const hash = await bcrypt.hash(updates.password.trim(), SALT_ROUNDS);
|
||||
fields.push('password_hash = ?');
|
||||
values.push(hash);
|
||||
}
|
||||
}
|
||||
|
||||
if (fields.length === 0) {
|
||||
return { changes: 0, lastID: userId };
|
||||
}
|
||||
|
||||
const setClause = fields.join(', ');
|
||||
const sql = `UPDATE User SET ${setClause} WHERE id = ?`;
|
||||
values.push(userId);
|
||||
|
||||
return await run(sql, values, USER_DB_NAME);
|
||||
}
|
||||
|
||||
export async function deleteUser(userId: number): Promise<any> {
|
||||
await run(
|
||||
`DELETE FROM ListEntry WHERE user_id = ?`,
|
||||
[userId],
|
||||
USER_DB_NAME
|
||||
);
|
||||
|
||||
await run(
|
||||
`DELETE FROM UserIntegration WHERE user_id = ?`,
|
||||
[userId],
|
||||
USER_DB_NAME
|
||||
);
|
||||
|
||||
await run(
|
||||
`DELETE FROM favorites WHERE user_id = ?`,
|
||||
[userId],
|
||||
'favorites'
|
||||
);
|
||||
|
||||
const result = await run(
|
||||
`DELETE FROM User WHERE id = ?`,
|
||||
[userId],
|
||||
USER_DB_NAME
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getAllUsers(): Promise<User[]> {
|
||||
const sql = `
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
profile_picture_url,
|
||||
CASE WHEN password_hash IS NOT NULL THEN 1 ELSE 0 END as has_password
|
||||
FROM User
|
||||
ORDER BY id
|
||||
`;
|
||||
|
||||
const users = await queryAll(sql, [], USER_DB_NAME);
|
||||
|
||||
return users.map((user: any) => ({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
profile_picture_url: user.profile_picture_url || null,
|
||||
has_password: !!user.has_password
|
||||
})) as User[];
|
||||
}
|
||||
|
||||
export async function getUserById(id: number): Promise<User | null> {
|
||||
const sql = `
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
profile_picture_url,
|
||||
CASE WHEN password_hash IS NOT NULL THEN 1 ELSE 0 END as has_password
|
||||
FROM User
|
||||
WHERE id = ?
|
||||
`;
|
||||
|
||||
const user = await queryOne(sql, [id], USER_DB_NAME);
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
profile_picture_url: user.profile_picture_url || null,
|
||||
has_password: !!user.has_password
|
||||
};
|
||||
}
|
||||
|
||||
export async function verifyPassword(userId: number, password: string): Promise<boolean> {
|
||||
const sql = 'SELECT password_hash FROM User WHERE id = ?';
|
||||
const user = await queryOne(sql, [userId], USER_DB_NAME);
|
||||
|
||||
if (!user || !user.password_hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return await bcrypt.compare(password, user.password_hash);
|
||||
}
|
||||
|
||||
export async function getAniListIntegration(userId: number) {
|
||||
const sql = `
|
||||
SELECT anilist_user_id, expires_at
|
||||
FROM UserIntegration
|
||||
WHERE user_id = ? AND platform = ?
|
||||
`;
|
||||
|
||||
const row = await queryOne(sql, [userId, "AniList"], USER_DB_NAME);
|
||||
|
||||
if (!row) {
|
||||
return { connected: false };
|
||||
}
|
||||
|
||||
return {
|
||||
connected: true,
|
||||
anilistUserId: row.anilist_user_id,
|
||||
expiresAt: row.expires_at
|
||||
};
|
||||
}
|
||||
|
||||
export async function removeAniListIntegration(userId: number) {
|
||||
const sql = `
|
||||
DELETE FROM UserIntegration
|
||||
WHERE user_id = ? AND platform = ?
|
||||
`;
|
||||
|
||||
return run(sql, [userId, "AniList"], USER_DB_NAME);
|
||||
}
|
||||
Reference in New Issue
Block a user