db layer implemented

using sqlite
This commit is contained in:
dimitar committed 2025-11-07 23:46:46 +01:00
1 parent e287e55f1f
commit df668927f5
14 files changed
+2541 -70

No files matched your search

+369
View File
@@ -0,0 +1,369 @@
import Database from 'better-sqlite3'
import path from 'path'
import fs from 'fs'
import { IDatabase, User, Client, FitnessProfile, DatabaseConfig } from './types'
export class SQLiteDatabase implements IDatabase {
private db: Database.Database | null = null
private config: DatabaseConfig
constructor(config: DatabaseConfig) {
this.config = config
}
async connect(): Promise<void> {
try {
const dbPath = this.config.connection.filename || path.join(process.cwd(), 'data', 'fitai.db')
// Ensure data directory exists
const dataDir = path.dirname(dbPath)
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true })
}
this.db = new Database(dbPath)
// Enable foreign keys
this.db.exec('PRAGMA foreign_keys = ON')
// Create tables
await this.createTables()
if (this.config.options?.logging) {
console.log('SQLite database connected successfully at:', dbPath)
}
} catch (error) {
console.error('Failed to connect to SQLite database:', error)
throw error
}
}
async disconnect(): Promise<void> {
if (this.db) {
this.db.close()
this.db = null
if (this.config.options?.logging) {
console.log('SQLite database disconnected')
}
}
}
private async createTables(): Promise<void> {
if (!this.db) throw new Error('Database not connected')
// Users table
this.db.exec(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
firstName TEXT NOT NULL,
lastName TEXT NOT NULL,
password TEXT NOT NULL,
phone TEXT,
role TEXT NOT NULL CHECK (role IN ('admin', 'client')),
createdAt DATETIME NOT NULL,
updatedAt DATETIME NOT NULL
)
`)
// Clients table
this.db.exec(`
CREATE TABLE IF NOT EXISTS clients (
id TEXT PRIMARY KEY,
userId TEXT NOT NULL,
membershipType TEXT NOT NULL CHECK (membershipType IN ('basic', 'premium', 'vip')),
membershipStatus TEXT NOT NULL CHECK (membershipStatus IN ('active', 'inactive', 'expired')),
joinDate DATETIME NOT NULL,
FOREIGN KEY (userId) REFERENCES users (id) ON DELETE CASCADE
)
`)
// Fitness profiles table
this.db.exec(`
CREATE TABLE IF NOT EXISTS fitness_profiles (
userId TEXT PRIMARY KEY,
height TEXT NOT NULL,
weight TEXT NOT NULL,
age TEXT NOT NULL,
gender TEXT NOT NULL CHECK (gender IN ('male', 'female', 'other')),
activityLevel TEXT NOT NULL CHECK (activityLevel IN ('sedentary', 'light', 'moderate', 'active', 'very_active')),
fitnessGoals TEXT NOT NULL, -- JSON array
exerciseHabits TEXT,
dietHabits TEXT,
medicalConditions TEXT,
createdAt DATETIME NOT NULL,
updatedAt DATETIME NOT NULL,
FOREIGN KEY (userId) REFERENCES users (id) ON DELETE CASCADE
)
`)
// Create indexes for better performance
this.db.exec(`
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_clients_userId ON clients(userId);
CREATE INDEX IF NOT EXISTS idx_fitness_profiles_userId ON fitness_profiles(userId);
`)
}
// User operations
async createUser(userData: Omit<User, 'id' | 'createdAt' | 'updatedAt'>): Promise<User> {
if (!this.db) throw new Error('Database not connected')
const id = Math.random().toString(36).substr(2, 9)
const now = new Date()
const user: User = {
id,
...userData,
createdAt: now,
updatedAt: now
}
const stmt = this.db.prepare(
`INSERT INTO users (id, email, firstName, lastName, password, phone, role, createdAt, updatedAt)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
)
stmt.run(
user.id, user.email, user.firstName, user.lastName, user.password,
user.phone, user.role, user.createdAt.toISOString(), user.updatedAt.toISOString()
)
return user
}
async getUserById(id: string): Promise<User | null> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('SELECT * FROM users WHERE id = ?')
const row = stmt.get(id)
return row ? this.mapRowToUser(row) : null
}
async getUserByEmail(email: string): Promise<User | null> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('SELECT * FROM users WHERE email = ?')
const row = stmt.get(email)
return row ? this.mapRowToUser(row) : null
}
async getAllUsers(): Promise<User[]> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('SELECT * FROM users ORDER BY createdAt DESC')
const rows = stmt.all()
return rows.map(row => this.mapRowToUser(row))
}
async updateUser(id: string, updates: Partial<User>): Promise<User | null> {
if (!this.db) throw new Error('Database not connected')
const fields = Object.keys(updates).filter(key => key !== 'id')
if (fields.length === 0) return this.getUserById(id)
const setClause = fields.map(field => `${field} = ?`).join(', ')
const values = fields.map(field => (updates as any)[field])
values.push(new Date().toISOString()) // updatedAt
values.push(id)
const stmt = this.db.prepare(`UPDATE users SET ${setClause}, updatedAt = ? WHERE id = ?`)
stmt.run(values)
return this.getUserById(id)
}
async deleteUser(id: string): Promise<boolean> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('DELETE FROM users WHERE id = ?')
const result = stmt.run(id)
return (result.changes || 0) > 0
}
// Client operations
async createClient(clientData: Omit<Client, 'id'>): Promise<Client> {
if (!this.db) throw new Error('Database not connected')
const id = Math.random().toString(36).substr(2, 9)
const client: Client = { id, ...clientData }
const stmt = this.db.prepare(
`INSERT INTO clients (id, userId, membershipType, membershipStatus, joinDate)
VALUES (?, ?, ?, ?, ?)`
)
stmt.run(
client.id, client.userId, client.membershipType,
client.membershipStatus, client.joinDate.toISOString()
)
return client
}
async getClientById(id: string): Promise<Client | null> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('SELECT * FROM clients WHERE id = ?')
const row = stmt.get(id)
return row ? this.mapRowToClient(row) : null
}
async getClientByUserId(userId: string): Promise<Client | null> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('SELECT * FROM clients WHERE userId = ?')
const row = stmt.get(userId)
return row ? this.mapRowToClient(row) : null
}
async getAllClients(): Promise<Client[]> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('SELECT * FROM clients ORDER BY joinDate DESC')
const rows = stmt.all()
return rows.map(row => this.mapRowToClient(row))
}
async updateClient(id: string, updates: Partial<Client>): Promise<Client | null> {
if (!this.db) throw new Error('Database not connected')
const fields = Object.keys(updates).filter(key => key !== 'id')
if (fields.length === 0) return this.getClientById(id)
const setClause = fields.map(field => `${field} = ?`).join(', ')
const values = fields.map(field => (updates as any)[field])
values.push(id)
const stmt = this.db.prepare(`UPDATE clients SET ${setClause} WHERE id = ?`)
stmt.run(values)
return this.getClientById(id)
}
async deleteClient(id: string): Promise<boolean> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('DELETE FROM clients WHERE id = ?')
const result = stmt.run(id)
return (result.changes || 0) > 0
}
// Fitness Profile operations
async createFitnessProfile(profileData: Omit<FitnessProfile, 'createdAt' | 'updatedAt'>): Promise<FitnessProfile> {
if (!this.db) throw new Error('Database not connected')
const now = new Date()
const profile: FitnessProfile = {
...profileData,
createdAt: now,
updatedAt: now
}
const stmt = this.db.prepare(
`INSERT INTO fitness_profiles
(userId, height, weight, age, gender, activityLevel, fitnessGoals,
exerciseHabits, dietHabits, medicalConditions, createdAt, updatedAt)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
)
stmt.run(
profile.userId, profile.height, profile.weight, profile.age, profile.gender,
profile.activityLevel, JSON.stringify(profile.fitnessGoals), profile.exerciseHabits,
profile.dietHabits, profile.medicalConditions, profile.createdAt.toISOString(),
profile.updatedAt.toISOString()
)
return profile
}
async getFitnessProfileByUserId(userId: string): Promise<FitnessProfile | null> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('SELECT * FROM fitness_profiles WHERE userId = ?')
const row = stmt.get(userId)
return row ? this.mapRowToFitnessProfile(row) : null
}
async getAllFitnessProfiles(): Promise<FitnessProfile[]> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('SELECT * FROM fitness_profiles ORDER BY createdAt DESC')
const rows = stmt.all()
return rows.map(row => this.mapRowToFitnessProfile(row))
}
async updateFitnessProfile(userId: string, updates: Partial<FitnessProfile>): Promise<FitnessProfile | null> {
if (!this.db) throw new Error('Database not connected')
const fields = Object.keys(updates).filter(key => key !== 'userId' && key !== 'createdAt')
if (fields.length === 0) return this.getFitnessProfileByUserId(userId)
const setClause = fields.map(field => `${field} = ?`).join(', ')
const values = fields.map(field => {
const value = (updates as any)[field]
return field === 'fitnessGoals' ? JSON.stringify(value) : value
})
values.push(new Date().toISOString()) // updatedAt
values.push(userId)
const stmt = this.db.prepare(`UPDATE fitness_profiles SET ${setClause}, updatedAt = ? WHERE userId = ?`)
stmt.run(values)
return this.getFitnessProfileByUserId(userId)
}
async deleteFitnessProfile(userId: string): Promise<boolean> {
if (!this.db) throw new Error('Database not connected')
const stmt = this.db.prepare('DELETE FROM fitness_profiles WHERE userId = ?')
const result = stmt.run(userId)
return (result.changes || 0) > 0
}
// Helper methods to map database rows to entities
private mapRowToUser(row: any): User {
return {
id: row.id,
email: row.email,
firstName: row.firstName,
lastName: row.lastName,
password: row.password,
phone: row.phone,
role: row.role,
createdAt: new Date(row.createdAt),
updatedAt: new Date(row.updatedAt)
}
}
private mapRowToClient(row: any): Client {
return {
id: row.id,
userId: row.userId,
membershipType: row.membershipType,
membershipStatus: row.membershipStatus,
joinDate: new Date(row.joinDate)
}
}
private mapRowToFitnessProfile(row: any): FitnessProfile {
return {
userId: row.userId,
height: row.height,
weight: row.weight,
age: row.age,
gender: row.gender,
activityLevel: row.activityLevel,
fitnessGoals: JSON.parse(row.fitnessGoals || '[]'),
exerciseHabits: row.exerciseHabits,
dietHabits: row.dietHabits,
medicalConditions: row.medicalConditions,
createdAt: new Date(row.createdAt),
updatedAt: new Date(row.updatedAt)
}
}
}