db layer implemented
using sqlite
This commit is contained in:
1 parent
e287e55f1f
commit
df668927f5
14 files changed
+2541
-70
No files matched your search
Binary file not shown.
Generated
+1469
-19
File diff suppressed because it is too large.
Load diff
@@ -15,6 +15,11 @@
|
||||
"@hookform/resolvers": "^3.3.0",
|
||||
"@tanstack/react-query": "^5.0.0",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/sqlite3": "^3.1.11",
|
||||
"ag-charts-community": "^9.0.0",
|
||||
"ag-charts-react": "^9.0.0",
|
||||
"ag-grid-community": "^32.0.0",
|
||||
"ag-grid-react": "^32.0.0",
|
||||
"autoprefixer": "^10.4.0",
|
||||
"axios": "^1.6.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
@@ -25,12 +30,10 @@
|
||||
"react-dom": "^18.0.0",
|
||||
"react-hook-form": "^7.47.0",
|
||||
"recharts": "^2.8.0",
|
||||
"sqlite": "^5.1.1",
|
||||
"sqlite3": "^5.1.7",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"zod": "^3.22.0",
|
||||
"ag-grid-community": "^32.0.0",
|
||||
"ag-grid-react": "^32.0.0",
|
||||
"ag-charts-community": "^9.0.0",
|
||||
"ag-charts-react": "^9.0.0"
|
||||
"zod": "^3.22.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^6.1.0",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import { users } from '../../../../lib/database'
|
||||
import { getDatabase } from '../../../../lib/database/index'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const db = await getDatabase()
|
||||
const { email, password } = await request.json()
|
||||
|
||||
if (!email || !password) {
|
||||
@@ -13,7 +14,7 @@ export async function POST(request: NextRequest) {
|
||||
)
|
||||
}
|
||||
|
||||
const user = users.find(u => u.email === email)
|
||||
const user = await db.getUserByEmail(email)
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import { users, clients } from '../../../../lib/database'
|
||||
import { getDatabase } from '../../../../lib/database/index'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const db = await getDatabase()
|
||||
const { email, password, firstName, lastName, phone } = await request.json()
|
||||
|
||||
if (!email || !password || !firstName || !lastName) {
|
||||
@@ -13,7 +14,7 @@ export async function POST(request: NextRequest) {
|
||||
)
|
||||
}
|
||||
|
||||
const existingUser = users.find(u => u.email === email)
|
||||
const existingUser = await db.getUserByEmail(email)
|
||||
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
@@ -23,31 +24,22 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, 10)
|
||||
const userId = Math.random().toString(36).substr(2, 9)
|
||||
|
||||
const newUser = {
|
||||
id: userId,
|
||||
const newUser = await db.createUser({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password: hashedPassword,
|
||||
phone,
|
||||
role: 'client',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}
|
||||
role: 'client'
|
||||
})
|
||||
|
||||
users.push(newUser)
|
||||
|
||||
const newClient = {
|
||||
id: Math.random().toString(36).substr(2, 9),
|
||||
userId,
|
||||
const newClient = await db.createClient({
|
||||
userId: newUser.id,
|
||||
membershipType: 'basic',
|
||||
membershipStatus: 'active',
|
||||
joinDate: new Date(),
|
||||
}
|
||||
|
||||
clients.push(newClient)
|
||||
joinDate: new Date()
|
||||
})
|
||||
|
||||
const { password: _, ...userWithoutPassword } = newUser
|
||||
|
||||
@@ -68,6 +60,16 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const usersWithoutPassword = users.map(({ password: _, ...user }) => user)
|
||||
return NextResponse.json({ users: usersWithoutPassword })
|
||||
try {
|
||||
const db = await getDatabase()
|
||||
const allUsers = await db.getAllUsers()
|
||||
const usersWithoutPassword = allUsers.map(({ password: _, ...user }) => user)
|
||||
return NextResponse.json({ users: usersWithoutPassword })
|
||||
} catch (error) {
|
||||
console.error('Get users error:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { users, fitnessProfiles } from '../../../../lib/database'
|
||||
import { getDatabase } from '../../../../lib/database/index'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const profileData: FitnessProfile = await request.json()
|
||||
const db = await getDatabase()
|
||||
const profileData = await request.json()
|
||||
|
||||
if (!profileData.userId || !profileData.height || !profileData.weight || !profileData.age) {
|
||||
return NextResponse.json(
|
||||
@@ -12,21 +13,29 @@ export async function POST(request: NextRequest) {
|
||||
)
|
||||
}
|
||||
|
||||
// For demo purposes, we'll allow profile creation without strict user validation
|
||||
// In production, you'd validate against a persistent database
|
||||
console.log('Creating fitness profile for user ID:', profileData.userId)
|
||||
// Check if user exists
|
||||
const user = await db.getUserById(profileData.userId)
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ error: 'User not found' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
const existingProfile = fitnessProfiles.find(p => p.userId === profileData.userId)
|
||||
// Check if profile already exists
|
||||
const existingProfile = await db.getFitnessProfileByUserId(profileData.userId)
|
||||
|
||||
let profile
|
||||
if (existingProfile) {
|
||||
Object.assign(existingProfile, profileData)
|
||||
profile = await db.updateFitnessProfile(profileData.userId, profileData)
|
||||
} else {
|
||||
fitnessProfiles.push(profileData)
|
||||
profile = await db.createFitnessProfile(profileData)
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
message: 'Fitness profile saved successfully',
|
||||
profile: profileData
|
||||
profile
|
||||
},
|
||||
{ status: 201 }
|
||||
)
|
||||
@@ -41,11 +50,12 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const db = await getDatabase()
|
||||
const { searchParams } = new URL(request.url)
|
||||
const userId = searchParams.get('userId')
|
||||
|
||||
if (userId) {
|
||||
const profile = fitnessProfiles.find(p => p.userId === userId)
|
||||
const profile = await db.getFitnessProfileByUserId(userId)
|
||||
if (!profile) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Profile not found' },
|
||||
@@ -55,7 +65,8 @@ export async function GET(request: NextRequest) {
|
||||
return NextResponse.json({ profile })
|
||||
}
|
||||
|
||||
return NextResponse.json({ profiles: fitnessProfiles })
|
||||
const profiles = await db.getAllFitnessProfiles()
|
||||
return NextResponse.json({ profiles })
|
||||
} catch (error) {
|
||||
console.error('Get fitness profiles error:', error)
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { users, clients } from '../../../lib/database'
|
||||
import { getDatabase } from '../../../lib/database/index'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const db = await getDatabase()
|
||||
const { searchParams } = new URL(request.url)
|
||||
const role = searchParams.get('role')
|
||||
|
||||
let filteredUsers = users
|
||||
let users = await db.getAllUsers()
|
||||
|
||||
if (role) {
|
||||
filteredUsers = users.filter(user => user.role === role)
|
||||
users = users.filter(user => user.role === role)
|
||||
}
|
||||
|
||||
const usersWithClients = filteredUsers.map(({ password: _, ...user }) => {
|
||||
const client = clients.find(c => c.userId === user.id)
|
||||
return { ...user, client }
|
||||
})
|
||||
const usersWithClients = await Promise.all(
|
||||
users.map(async (user) => {
|
||||
const { password: _, ...userWithoutPassword } = user
|
||||
const client = await db.getClientByUserId(user.id)
|
||||
return { ...userWithoutPassword, client }
|
||||
})
|
||||
)
|
||||
|
||||
return NextResponse.json({ users: usersWithClients })
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { IDatabase, DatabaseConfig } from './types'
|
||||
import { SQLiteDatabase } from './sqlite'
|
||||
|
||||
// Database factory - creates appropriate database instance based on config
|
||||
export class DatabaseFactory {
|
||||
private static instance: IDatabase | null = null
|
||||
private static config: DatabaseConfig | null = null
|
||||
|
||||
static async initialize(config: DatabaseConfig): Promise<IDatabase> {
|
||||
if (this.instance && this.config && this.isSameConfig(config)) {
|
||||
return this.instance
|
||||
}
|
||||
|
||||
// Close existing connection if different config
|
||||
if (this.instance) {
|
||||
await this.instance.disconnect()
|
||||
this.instance = null
|
||||
}
|
||||
|
||||
// Create new database instance based on type
|
||||
switch (config.type) {
|
||||
case 'sqlite':
|
||||
this.instance = new SQLiteDatabase(config)
|
||||
break
|
||||
|
||||
case 'postgresql':
|
||||
// TODO: Implement PostgreSQLDatabase
|
||||
throw new Error('PostgreSQL implementation not yet available')
|
||||
|
||||
case 'mysql':
|
||||
// TODO: Implement MySQLDatabase
|
||||
throw new Error('MySQL implementation not yet available')
|
||||
|
||||
case 'mongodb':
|
||||
// TODO: Implement MongoDBDatabase
|
||||
throw new Error('MongoDB implementation not yet available')
|
||||
|
||||
default:
|
||||
throw new Error(`Unsupported database type: ${config.type}`)
|
||||
}
|
||||
|
||||
await this.instance.connect()
|
||||
this.config = config
|
||||
|
||||
return this.instance
|
||||
}
|
||||
|
||||
static getInstance(): IDatabase {
|
||||
if (!this.instance) {
|
||||
throw new Error('Database not initialized. Call initialize() first.')
|
||||
}
|
||||
return this.instance
|
||||
}
|
||||
|
||||
static async disconnect(): Promise<void> {
|
||||
if (this.instance) {
|
||||
await this.instance.disconnect()
|
||||
this.instance = null
|
||||
this.config = null
|
||||
}
|
||||
}
|
||||
|
||||
private static isSameConfig(config: DatabaseConfig): boolean {
|
||||
if (!this.config) return false
|
||||
|
||||
return (
|
||||
this.config.type === config.type &&
|
||||
JSON.stringify(this.config.connection) === JSON.stringify(config.connection)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Default configuration
|
||||
export const defaultDatabaseConfig: DatabaseConfig = {
|
||||
type: 'sqlite',
|
||||
connection: {
|
||||
filename: './data/fitai.db'
|
||||
},
|
||||
options: {
|
||||
logging: process.env.NODE_ENV === 'development'
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience function to get database instance
|
||||
export async function getDatabase(): Promise<IDatabase> {
|
||||
return await DatabaseFactory.initialize(defaultDatabaseConfig)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Database Entity Types
|
||||
export interface User {
|
||||
id: string
|
||||
email: string
|
||||
firstName: string
|
||||
lastName: string
|
||||
password: string
|
||||
phone?: string
|
||||
role: 'admin' | 'client'
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
export interface Client {
|
||||
id: string
|
||||
userId: string
|
||||
membershipType: 'basic' | 'premium' | 'vip'
|
||||
membershipStatus: 'active' | 'inactive' | 'expired'
|
||||
joinDate: Date
|
||||
}
|
||||
|
||||
export interface FitnessProfile {
|
||||
userId: string
|
||||
height: string
|
||||
weight: string
|
||||
age: string
|
||||
gender: 'male' | 'female' | 'other'
|
||||
activityLevel: 'sedentary' | 'light' | 'moderate' | 'active' | 'very_active'
|
||||
fitnessGoals: string[]
|
||||
exerciseHabits: string
|
||||
dietHabits: string
|
||||
medicalConditions: string
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
// Database Interface - allows us to swap implementations
|
||||
export interface IDatabase {
|
||||
// Connection management
|
||||
connect(): Promise<void>
|
||||
disconnect(): Promise<void>
|
||||
|
||||
// User operations
|
||||
createUser(user: Omit<User, 'id' | 'createdAt' | 'updatedAt'>): Promise<User>
|
||||
getUserById(id: string): Promise<User | null>
|
||||
getUserByEmail(email: string): Promise<User | null>
|
||||
getAllUsers(): Promise<User[]>
|
||||
updateUser(id: string, updates: Partial<User>): Promise<User | null>
|
||||
deleteUser(id: string): Promise<boolean>
|
||||
|
||||
// Client operations
|
||||
createClient(client: Omit<Client, 'id'>): Promise<Client>
|
||||
getClientById(id: string): Promise<Client | null>
|
||||
getClientByUserId(userId: string): Promise<Client | null>
|
||||
getAllClients(): Promise<Client[]>
|
||||
updateClient(id: string, updates: Partial<Client>): Promise<Client | null>
|
||||
deleteClient(id: string): Promise<boolean>
|
||||
|
||||
// Fitness Profile operations
|
||||
createFitnessProfile(profile: Omit<FitnessProfile, 'createdAt' | 'updatedAt'>): Promise<FitnessProfile>
|
||||
getFitnessProfileByUserId(userId: string): Promise<FitnessProfile | null>
|
||||
getAllFitnessProfiles(): Promise<FitnessProfile[]>
|
||||
updateFitnessProfile(userId: string, updates: Partial<FitnessProfile>): Promise<FitnessProfile | null>
|
||||
deleteFitnessProfile(userId: string): Promise<boolean>
|
||||
}
|
||||
|
||||
// Database configuration
|
||||
export interface DatabaseConfig {
|
||||
type: 'sqlite' | 'postgresql' | 'mysql' | 'mongodb'
|
||||
connection: {
|
||||
filename?: string // for SQLite
|
||||
host?: string // for SQL databases
|
||||
port?: number
|
||||
database?: string
|
||||
username?: string
|
||||
password?: string
|
||||
}
|
||||
options?: {
|
||||
logging?: boolean
|
||||
poolSize?: number
|
||||
timeout?: number
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { getDatabase } from './src/lib/database/index.js'
|
||||
|
||||
async function testDatabase() {
|
||||
try {
|
||||
console.log('Testing database connection...')
|
||||
const db = await getDatabase()
|
||||
|
||||
console.log('Creating test user...')
|
||||
const user = await db.createUser({
|
||||
email: 'test@example.com',
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
password: 'hashedpassword',
|
||||
role: 'client'
|
||||
})
|
||||
|
||||
console.log('Created user:', user)
|
||||
|
||||
console.log('Getting user by email...')
|
||||
const foundUser = await db.getUserByEmail('test@example.com')
|
||||
console.log('Found user:', foundUser)
|
||||
|
||||
console.log('Database test completed successfully!')
|
||||
} catch (error) {
|
||||
console.error('Database test failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
testDatabase()
|
||||
@@ -1,5 +1,5 @@
|
||||
export const API_BASE_URL = __DEV__
|
||||
? 'http://192.168.0.100:3000'
|
||||
? 'http://192.168.1.24:3000'
|
||||
: 'https://your-production-url.com'
|
||||
|
||||
export const API_ENDPOINTS = {
|
||||
|
||||
Reference in new issue
Block a user