69 lines
2.3 KiB
TypeScript
69 lines
2.3 KiB
TypeScript
/**
|
|
* UserStore — persistence layer for the users table.
|
|
* All queries go through DatabaseConnection for audit + safety.
|
|
*/
|
|
|
|
import { randomUUID } from 'crypto';
|
|
import type { DatabaseConnection } from '../shared/db/DatabaseConnection.js';
|
|
import { USER_QUERIES, RESET_TOKEN_QUERIES } from '../shared/db/queries.constant.js';
|
|
import type { Role, User, UserRow } from './auth.model.js';
|
|
|
|
export class UserStore {
|
|
constructor(private readonly db: DatabaseConnection) {}
|
|
|
|
findByEmail(email: string): UserRow | undefined {
|
|
return this.db.rawGet<UserRow>(USER_QUERIES.SELECT_BY_EMAIL, [email]);
|
|
}
|
|
|
|
findById(id: string): User | undefined {
|
|
const row = this.db.rawGet<UserRow>(USER_QUERIES.SELECT_BY_ID, [id]);
|
|
if (!row) return undefined;
|
|
return this.#toUser(row);
|
|
}
|
|
|
|
create(email: string, passwordHash: string, role: Role = 'viewer'): User {
|
|
const id = randomUUID();
|
|
const createdAt = new Date().toISOString();
|
|
this.db.rawRun(USER_QUERIES.INSERT, [id, email, passwordHash, role, createdAt]);
|
|
return { id, email, role, createdAt, lastLogin: null };
|
|
}
|
|
|
|
touchLogin(id: string): void {
|
|
this.db.rawRun(USER_QUERIES.UPDATE_LAST_LOGIN, [new Date().toISOString(), id]);
|
|
}
|
|
|
|
updatePassword(id: string, passwordHash: string): void {
|
|
this.db.rawRun('UPDATE users SET password_hash = ? WHERE id = ?', [passwordHash, id]);
|
|
}
|
|
|
|
// ── Password reset tokens ──────────────────────────────────────────────────
|
|
|
|
createResetToken(userId: string, token: string, expiresAt: string): void {
|
|
this.db.rawRun(RESET_TOKEN_QUERIES.INSERT, [token, userId, expiresAt]);
|
|
}
|
|
|
|
findResetToken(
|
|
token: string,
|
|
): { token: string; user_id: string; expires_at: string; used: number } | undefined {
|
|
return this.db.rawGet(RESET_TOKEN_QUERIES.FIND, [token]);
|
|
}
|
|
|
|
markTokenUsed(token: string): void {
|
|
this.db.rawRun(RESET_TOKEN_QUERIES.MARK_USED, [token]);
|
|
}
|
|
|
|
purgeExpiredTokens(): void {
|
|
this.db.rawRun(RESET_TOKEN_QUERIES.PURGE, [new Date().toISOString()]);
|
|
}
|
|
|
|
#toUser(row: UserRow): User {
|
|
return {
|
|
id: row.id,
|
|
email: row.email,
|
|
role: row.role,
|
|
createdAt: row.created_at,
|
|
lastLogin: row.last_login,
|
|
};
|
|
}
|
|
}
|