enhancment complete with phase 10
This commit is contained in:
1 parent
2c4927478a
commit
6adb541967
44 files changed
+7820
-845
No files matched your search
@@ -1,181 +1,547 @@
|
||||
/**
|
||||
* @jest-environment node
|
||||
*/
|
||||
import { DrizzleDatabase } from '../drizzle'
|
||||
import { DatabaseConfig } from '../types'
|
||||
import Database from 'better-sqlite3'
|
||||
import { drizzle } from 'drizzle-orm/better-sqlite3'
|
||||
import * as schema from '@fitai/database'
|
||||
import { DrizzleDatabase } from "../drizzle";
|
||||
import { DatabaseConfig } from "../types";
|
||||
import Database from "better-sqlite3";
|
||||
import { drizzle } from "drizzle-orm/better-sqlite3";
|
||||
import * as schema from "@fitai/database";
|
||||
|
||||
describe('DrizzleDatabase', () => {
|
||||
let db: DrizzleDatabase
|
||||
let sqlite: Database.Database
|
||||
let drizzleDb: ReturnType<typeof drizzle>
|
||||
describe("DrizzleDatabase", () => {
|
||||
let db: DrizzleDatabase;
|
||||
let sqlite: Database.Database;
|
||||
let drizzleDb: ReturnType<typeof drizzle>;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Create in-memory SQLite database
|
||||
sqlite = new Database(':memory:')
|
||||
drizzleDb = drizzle(sqlite, { schema })
|
||||
beforeEach(async () => {
|
||||
// Create in-memory SQLite database
|
||||
sqlite = new Database(":memory:");
|
||||
drizzleDb = drizzle(sqlite, { schema });
|
||||
|
||||
const config: DatabaseConfig = {
|
||||
type: 'sqlite',
|
||||
connection: { filename: ':memory:' },
|
||||
options: { logging: false }
|
||||
const config: DatabaseConfig = {
|
||||
type: "sqlite",
|
||||
connection: { filename: ":memory:" },
|
||||
options: { logging: false },
|
||||
};
|
||||
|
||||
// Initialize DrizzleDatabase with the in-memory db
|
||||
db = new DrizzleDatabase(config, drizzleDb as any);
|
||||
await db.connect();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await db.disconnect();
|
||||
sqlite.close();
|
||||
});
|
||||
|
||||
describe("User Operations", () => {
|
||||
it("should create and retrieve a user", async () => {
|
||||
const newUser = {
|
||||
email: "test@example.com",
|
||||
firstName: "Test",
|
||||
lastName: "User",
|
||||
password: "password123",
|
||||
role: "client" as const,
|
||||
};
|
||||
|
||||
const createdUser = await db.createUser(newUser);
|
||||
expect(createdUser).toBeDefined();
|
||||
expect(createdUser.id).toBeDefined();
|
||||
expect(createdUser.email).toBe(newUser.email);
|
||||
|
||||
const fetchedUser = await db.getUserById(createdUser.id);
|
||||
expect(fetchedUser).toBeDefined();
|
||||
expect(fetchedUser?.id).toBe(createdUser.id);
|
||||
expect(fetchedUser?.email).toBe(createdUser.email);
|
||||
// SQLite/Drizzle might truncate ms or handle dates differently in test env
|
||||
expect(new Date(fetchedUser!.createdAt).getTime()).toBeLessThanOrEqual(
|
||||
createdUser.createdAt.getTime(),
|
||||
);
|
||||
expect(new Date(fetchedUser!.updatedAt).getTime()).toBeLessThanOrEqual(
|
||||
createdUser.updatedAt.getTime(),
|
||||
);
|
||||
});
|
||||
|
||||
it("should update a user", async () => {
|
||||
const user = await db.createUser({
|
||||
email: "update@example.com",
|
||||
firstName: "Update",
|
||||
lastName: "Me",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
|
||||
const updated = await db.updateUser(user.id, { firstName: "Updated" });
|
||||
expect(updated?.firstName).toBe("Updated");
|
||||
|
||||
const fetched = await db.getUserById(user.id);
|
||||
expect(fetched?.firstName).toBe("Updated");
|
||||
});
|
||||
|
||||
it("should delete a user", async () => {
|
||||
const user = await db.createUser({
|
||||
email: "delete@example.com",
|
||||
firstName: "Delete",
|
||||
lastName: "Me",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
|
||||
const deleted = await db.deleteUser(user.id);
|
||||
expect(deleted).toBe(true);
|
||||
|
||||
const fetched = await db.getUserById(user.id);
|
||||
expect(fetched).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Client Operations", () => {
|
||||
it("should create and retrieve a client", async () => {
|
||||
const user = await db.createUser({
|
||||
email: "client@example.com",
|
||||
firstName: "Client",
|
||||
lastName: "User",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
|
||||
const newClient = {
|
||||
userId: user.id,
|
||||
membershipType: "basic" as const,
|
||||
membershipStatus: "active" as const,
|
||||
joinDate: new Date(),
|
||||
};
|
||||
|
||||
const createdClient = await db.createClient(newClient);
|
||||
expect(createdClient).toBeDefined();
|
||||
expect(createdClient.userId).toBe(user.id);
|
||||
|
||||
const fetchedClient = await db.getClientById(createdClient.id);
|
||||
expect(fetchedClient).toBeDefined();
|
||||
expect(fetchedClient?.id).toBe(createdClient.id);
|
||||
expect(fetchedClient?.userId).toBe(createdClient.userId);
|
||||
expect(fetchedClient?.membershipType).toBe(createdClient.membershipType);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Fitness Profile Operations", () => {
|
||||
it("should create and retrieve a fitness profile", async () => {
|
||||
const user = await db.createUser({
|
||||
email: "profile@example.com",
|
||||
firstName: "Profile",
|
||||
lastName: "User",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
|
||||
const newProfile = {
|
||||
userId: user.id,
|
||||
height: 180,
|
||||
weight: 75,
|
||||
age: 30,
|
||||
gender: "male" as const,
|
||||
activityLevel: "moderately_active" as const,
|
||||
fitnessGoals: ["weight_loss"],
|
||||
exerciseHabits: "None",
|
||||
dietHabits: "None",
|
||||
medicalConditions: "None",
|
||||
};
|
||||
|
||||
const createdProfile = await db.createFitnessProfile(newProfile as any);
|
||||
expect(createdProfile).toBeDefined();
|
||||
expect(createdProfile.userId).toBe(user.id);
|
||||
|
||||
const fetchedProfile = await db.getFitnessProfileByUserId(user.id);
|
||||
expect(fetchedProfile).toBeDefined();
|
||||
expect(fetchedProfile?.userId).toBe(user.id);
|
||||
expect(fetchedProfile?.fitnessGoals).toEqual(
|
||||
expect.arrayContaining(["weight_loss"]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Attendance Operations", () => {
|
||||
it("should check in and check out", async () => {
|
||||
const user = await db.createUser({
|
||||
email: "attendance@example.com",
|
||||
firstName: "Attendance",
|
||||
lastName: "User",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
|
||||
const checkIn = await db.checkIn(user.id, "gym");
|
||||
expect(checkIn).toBeDefined();
|
||||
expect(checkIn.checkOutTime).toBeUndefined();
|
||||
|
||||
const activeCheckIn = await db.getActiveCheckIn(user.id);
|
||||
expect(activeCheckIn).toBeDefined();
|
||||
expect(activeCheckIn?.id).toBe(checkIn.id);
|
||||
|
||||
const checkOut = await db.checkOut(checkIn.id);
|
||||
expect(checkOut?.checkOutTime).toBeDefined();
|
||||
|
||||
const activeCheckInAfter = await db.getActiveCheckIn(user.id);
|
||||
expect(activeCheckInAfter).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Batch Query Operations (Phase 9)", () => {
|
||||
describe("getClientsByUserIds", () => {
|
||||
it("should return clients for multiple users", async () => {
|
||||
// Create users
|
||||
const user1 = await db.createUser({
|
||||
email: "batch1@example.com",
|
||||
firstName: "Batch",
|
||||
lastName: "User1",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
const user2 = await db.createUser({
|
||||
email: "batch2@example.com",
|
||||
firstName: "Batch",
|
||||
lastName: "User2",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
const user3 = await db.createUser({
|
||||
email: "batch3@example.com",
|
||||
firstName: "Batch",
|
||||
lastName: "User3",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
|
||||
// Create clients for user1 and user2 only
|
||||
await db.createClient({
|
||||
userId: user1.id,
|
||||
membershipType: "basic",
|
||||
membershipStatus: "active",
|
||||
joinDate: new Date(),
|
||||
});
|
||||
await db.createClient({
|
||||
userId: user2.id,
|
||||
membershipType: "premium",
|
||||
membershipStatus: "active",
|
||||
joinDate: new Date(),
|
||||
});
|
||||
|
||||
// Batch load clients
|
||||
const clients = await db.getClientsByUserIds([
|
||||
user1.id,
|
||||
user2.id,
|
||||
user3.id,
|
||||
]);
|
||||
|
||||
expect(clients).toHaveLength(2);
|
||||
expect(clients.map((c) => c.userId).sort()).toEqual(
|
||||
[user1.id, user2.id].sort(),
|
||||
);
|
||||
expect(clients.find((c) => c.userId === user3.id)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return empty array for empty input", async () => {
|
||||
const clients = await db.getClientsByUserIds([]);
|
||||
expect(clients).toEqual([]);
|
||||
});
|
||||
|
||||
it("should return empty array when no clients exist for users", async () => {
|
||||
const clients = await db.getClientsByUserIds(["nonexistent-id"]);
|
||||
expect(clients).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAttendanceHistoriesByUserIds", () => {
|
||||
it("should return attendance records for multiple users", async () => {
|
||||
const user1 = await db.createUser({
|
||||
email: "attendance1@example.com",
|
||||
firstName: "Attendance",
|
||||
lastName: "User1",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
const user2 = await db.createUser({
|
||||
email: "attendance2@example.com",
|
||||
firstName: "Attendance",
|
||||
lastName: "User2",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
|
||||
// Create attendance records
|
||||
await db.checkIn(user1.id, "gym");
|
||||
await db.checkIn(user1.id, "gym");
|
||||
await db.checkIn(user2.id, "gym");
|
||||
|
||||
const attendances = await db.getAttendanceHistoriesByUserIds([
|
||||
user1.id,
|
||||
user2.id,
|
||||
]);
|
||||
|
||||
expect(attendances.length).toBeGreaterThanOrEqual(3);
|
||||
const user1Records = attendances.filter((a) => a.userId === user1.id);
|
||||
const user2Records = attendances.filter((a) => a.userId === user2.id);
|
||||
expect(user1Records.length).toBeGreaterThanOrEqual(2);
|
||||
expect(user2Records.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("should return records ordered by checkInTime DESC", async () => {
|
||||
const user = await db.createUser({
|
||||
email: "order@example.com",
|
||||
firstName: "Order",
|
||||
lastName: "Test",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
|
||||
// Create multiple check-ins with slight delays
|
||||
const checkIn1 = await db.checkIn(user.id, "gym");
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
const checkIn2 = await db.checkIn(user.id, "gym");
|
||||
|
||||
const attendances = await db.getAttendanceHistoriesByUserIds([user.id]);
|
||||
|
||||
expect(attendances.length).toBeGreaterThanOrEqual(2);
|
||||
// Most recent should be first
|
||||
expect(
|
||||
new Date(attendances[0].checkInTime).getTime(),
|
||||
).toBeGreaterThanOrEqual(
|
||||
new Date(attendances[1].checkInTime).getTime(),
|
||||
);
|
||||
});
|
||||
|
||||
it("should return empty array for empty input", async () => {
|
||||
const attendances = await db.getAttendanceHistoriesByUserIds([]);
|
||||
expect(attendances).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getActiveCheckInsByUserIds", () => {
|
||||
it("should return only active check-ins", async () => {
|
||||
const user1 = await db.createUser({
|
||||
email: "active1@example.com",
|
||||
firstName: "Active",
|
||||
lastName: "User1",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
const user2 = await db.createUser({
|
||||
email: "active2@example.com",
|
||||
firstName: "Active",
|
||||
lastName: "User2",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
|
||||
// User1: check in and check out
|
||||
const checkIn1 = await db.checkIn(user1.id, "gym");
|
||||
await db.checkOut(checkIn1.id);
|
||||
|
||||
// User2: check in only (active)
|
||||
const checkIn2 = await db.checkIn(user2.id, "gym");
|
||||
|
||||
const activeCheckIns = await db.getActiveCheckInsByUserIds([
|
||||
user1.id,
|
||||
user2.id,
|
||||
]);
|
||||
|
||||
expect(activeCheckIns).toHaveLength(1);
|
||||
expect(activeCheckIns[0].userId).toBe(user2.id);
|
||||
expect(activeCheckIns[0].checkOutTime).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return empty array when no active check-ins exist", async () => {
|
||||
const user = await db.createUser({
|
||||
email: "noactive@example.com",
|
||||
firstName: "NoActive",
|
||||
lastName: "User",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
|
||||
const checkIn = await db.checkIn(user.id, "gym");
|
||||
await db.checkOut(checkIn.id);
|
||||
|
||||
const activeCheckIns = await db.getActiveCheckInsByUserIds([user.id]);
|
||||
expect(activeCheckIns).toEqual([]);
|
||||
});
|
||||
|
||||
it("should return empty array for empty input", async () => {
|
||||
const activeCheckIns = await db.getActiveCheckInsByUserIds([]);
|
||||
expect(activeCheckIns).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getRecommendationsByUserIds", () => {
|
||||
it("should return recommendations for multiple users", async () => {
|
||||
const user1 = await db.createUser({
|
||||
email: "rec1@example.com",
|
||||
firstName: "Rec",
|
||||
lastName: "User1",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
const user2 = await db.createUser({
|
||||
email: "rec2@example.com",
|
||||
firstName: "Rec",
|
||||
lastName: "User2",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
|
||||
// Create recommendations
|
||||
// Create fitness profiles (required for recommendations)
|
||||
const profile1 = await db.createFitnessProfile({
|
||||
userId: user1.id,
|
||||
height: 180,
|
||||
weight: 75,
|
||||
age: 30,
|
||||
gender: "male",
|
||||
activityLevel: "moderately_active",
|
||||
} as any);
|
||||
|
||||
const profile2 = await db.createFitnessProfile({
|
||||
userId: user2.id,
|
||||
height: 165,
|
||||
weight: 60,
|
||||
age: 25,
|
||||
gender: "female",
|
||||
activityLevel: "lightly_active",
|
||||
} as any);
|
||||
|
||||
await db.createRecommendation({
|
||||
userId: user1.id,
|
||||
fitnessProfileId: profile1.id,
|
||||
recommendationText: "Test recommendation 1",
|
||||
activityPlan: "Activity plan 1",
|
||||
dietPlan: "Diet plan 1",
|
||||
status: "pending",
|
||||
} as any);
|
||||
await db.createRecommendation({
|
||||
userId: user2.id,
|
||||
fitnessProfileId: profile2.id,
|
||||
recommendationText: "Test recommendation 2",
|
||||
activityPlan: "Activity plan 2",
|
||||
dietPlan: "Diet plan 2",
|
||||
status: "pending",
|
||||
} as any);
|
||||
|
||||
const recommendations = await db.getRecommendationsByUserIds([
|
||||
user1.id,
|
||||
user2.id,
|
||||
]);
|
||||
|
||||
expect(recommendations.length).toBeGreaterThanOrEqual(2);
|
||||
const user1Recs = recommendations.filter((r) => r.userId === user1.id);
|
||||
const user2Recs = recommendations.filter((r) => r.userId === user2.id);
|
||||
expect(user1Recs.length).toBeGreaterThanOrEqual(1);
|
||||
expect(user2Recs.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("should return empty array for empty input", async () => {
|
||||
const recommendations = await db.getRecommendationsByUserIds([]);
|
||||
expect(recommendations).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getFitnessGoalsByUserIds", () => {
|
||||
it("should return fitness goals for multiple users", async () => {
|
||||
const user1 = await db.createUser({
|
||||
email: "goal1@example.com",
|
||||
firstName: "Goal",
|
||||
lastName: "User1",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
const user2 = await db.createUser({
|
||||
email: "goal2@example.com",
|
||||
firstName: "Goal",
|
||||
lastName: "User2",
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
|
||||
// Create fitness goals
|
||||
await db.createFitnessGoal({
|
||||
userId: user1.id,
|
||||
goalType: "weight_target",
|
||||
title: "Lose weight",
|
||||
targetValue: 10,
|
||||
currentValue: 0,
|
||||
unit: "kg",
|
||||
targetDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
|
||||
status: "active",
|
||||
} as any);
|
||||
await db.createFitnessGoal({
|
||||
userId: user2.id,
|
||||
goalType: "strength_milestone",
|
||||
title: "Gain muscle",
|
||||
targetValue: 5,
|
||||
currentValue: 0,
|
||||
unit: "kg",
|
||||
targetDate: new Date(Date.now() + 60 * 24 * 60 * 60 * 1000),
|
||||
status: "active",
|
||||
} as any);
|
||||
|
||||
const goals = await db.getFitnessGoalsByUserIds([user1.id, user2.id]);
|
||||
|
||||
expect(goals.length).toBeGreaterThanOrEqual(2);
|
||||
const user1Goals = goals.filter((g) => g.userId === user1.id);
|
||||
const user2Goals = goals.filter((g) => g.userId === user2.id);
|
||||
expect(user1Goals.length).toBeGreaterThanOrEqual(1);
|
||||
expect(user2Goals.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("should return empty array for empty input", async () => {
|
||||
const goals = await db.getFitnessGoalsByUserIds([]);
|
||||
expect(goals).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Batch Query Performance", () => {
|
||||
it("should be more efficient than individual queries", async () => {
|
||||
// Create 10 users with clients
|
||||
const users = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const user = await db.createUser({
|
||||
email: `perf${i}@example.com`,
|
||||
firstName: "Perf",
|
||||
lastName: `User${i}`,
|
||||
password: "password",
|
||||
role: "client",
|
||||
});
|
||||
await db.createClient({
|
||||
userId: user.id,
|
||||
membershipType: "basic",
|
||||
membershipStatus: "active",
|
||||
joinDate: new Date(),
|
||||
});
|
||||
users.push(user);
|
||||
}
|
||||
|
||||
// Initialize DrizzleDatabase with the in-memory db
|
||||
db = new DrizzleDatabase(config, drizzleDb as any)
|
||||
await db.connect()
|
||||
})
|
||||
const userIds = users.map((u) => u.id);
|
||||
|
||||
afterEach(async () => {
|
||||
await db.disconnect()
|
||||
sqlite.close()
|
||||
})
|
||||
// Batch query (should be 1 query)
|
||||
const batchStart = Date.now();
|
||||
const batchClients = await db.getClientsByUserIds(userIds);
|
||||
const batchTime = Date.now() - batchStart;
|
||||
|
||||
describe('User Operations', () => {
|
||||
it('should create and retrieve a user', async () => {
|
||||
const newUser = {
|
||||
email: 'test@example.com',
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
password: 'password123',
|
||||
role: 'client' as const
|
||||
}
|
||||
// Individual queries (should be 10 queries)
|
||||
const individualStart = Date.now();
|
||||
const individualClients = [];
|
||||
for (const userId of userIds) {
|
||||
const client = await db.getClientByUserId(userId);
|
||||
if (client) individualClients.push(client);
|
||||
}
|
||||
const individualTime = Date.now() - individualStart;
|
||||
|
||||
const createdUser = await db.createUser(newUser)
|
||||
expect(createdUser).toBeDefined()
|
||||
expect(createdUser.id).toBeDefined()
|
||||
expect(createdUser.email).toBe(newUser.email)
|
||||
// Verify same results
|
||||
expect(batchClients.length).toBe(individualClients.length);
|
||||
expect(batchClients.length).toBe(10);
|
||||
|
||||
const fetchedUser = await db.getUserById(createdUser.id)
|
||||
expect(fetchedUser).toBeDefined()
|
||||
expect(fetchedUser?.id).toBe(createdUser.id)
|
||||
expect(fetchedUser?.email).toBe(createdUser.email)
|
||||
// SQLite/Drizzle might truncate ms or handle dates differently in test env
|
||||
expect(new Date(fetchedUser!.createdAt).getTime()).toBeLessThanOrEqual(createdUser.createdAt.getTime())
|
||||
expect(new Date(fetchedUser!.updatedAt).getTime()).toBeLessThanOrEqual(createdUser.updatedAt.getTime())
|
||||
})
|
||||
|
||||
it('should update a user', async () => {
|
||||
const user = await db.createUser({
|
||||
email: 'update@example.com',
|
||||
firstName: 'Update',
|
||||
lastName: 'Me',
|
||||
password: 'password',
|
||||
role: 'client'
|
||||
})
|
||||
|
||||
const updated = await db.updateUser(user.id, { firstName: 'Updated' })
|
||||
expect(updated?.firstName).toBe('Updated')
|
||||
|
||||
const fetched = await db.getUserById(user.id)
|
||||
expect(fetched?.firstName).toBe('Updated')
|
||||
})
|
||||
|
||||
it('should delete a user', async () => {
|
||||
const user = await db.createUser({
|
||||
email: 'delete@example.com',
|
||||
firstName: 'Delete',
|
||||
lastName: 'Me',
|
||||
password: 'password',
|
||||
role: 'client'
|
||||
})
|
||||
|
||||
const deleted = await db.deleteUser(user.id)
|
||||
expect(deleted).toBe(true)
|
||||
|
||||
const fetched = await db.getUserById(user.id)
|
||||
expect(fetched).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Client Operations', () => {
|
||||
it('should create and retrieve a client', async () => {
|
||||
const user = await db.createUser({
|
||||
email: 'client@example.com',
|
||||
firstName: 'Client',
|
||||
lastName: 'User',
|
||||
password: 'password',
|
||||
role: 'client'
|
||||
})
|
||||
|
||||
const newClient = {
|
||||
userId: user.id,
|
||||
membershipType: 'basic' as const,
|
||||
membershipStatus: 'active' as const,
|
||||
joinDate: new Date()
|
||||
}
|
||||
|
||||
const createdClient = await db.createClient(newClient)
|
||||
expect(createdClient).toBeDefined()
|
||||
expect(createdClient.userId).toBe(user.id)
|
||||
|
||||
const fetchedClient = await db.getClientById(createdClient.id)
|
||||
expect(fetchedClient).toBeDefined()
|
||||
expect(fetchedClient?.id).toBe(createdClient.id)
|
||||
expect(fetchedClient?.userId).toBe(createdClient.userId)
|
||||
expect(fetchedClient?.membershipType).toBe(createdClient.membershipType)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Fitness Profile Operations', () => {
|
||||
it('should create and retrieve a fitness profile', async () => {
|
||||
const user = await db.createUser({
|
||||
email: 'profile@example.com',
|
||||
firstName: 'Profile',
|
||||
lastName: 'User',
|
||||
password: 'password',
|
||||
role: 'client'
|
||||
})
|
||||
|
||||
const newProfile = {
|
||||
userId: user.id,
|
||||
height: 180,
|
||||
weight: 75,
|
||||
age: 30,
|
||||
gender: 'male' as const,
|
||||
activityLevel: 'moderately_active' as const,
|
||||
fitnessGoals: ['weight_loss'],
|
||||
exerciseHabits: 'None',
|
||||
dietHabits: 'None',
|
||||
medicalConditions: 'None'
|
||||
}
|
||||
|
||||
const createdProfile = await db.createFitnessProfile(newProfile as any)
|
||||
expect(createdProfile).toBeDefined()
|
||||
expect(createdProfile.userId).toBe(user.id)
|
||||
|
||||
const fetchedProfile = await db.getFitnessProfileByUserId(user.id)
|
||||
expect(fetchedProfile).toBeDefined()
|
||||
expect(fetchedProfile?.userId).toBe(user.id)
|
||||
expect(fetchedProfile?.fitnessGoals).toEqual(expect.arrayContaining(['weight_loss']))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Attendance Operations', () => {
|
||||
it('should check in and check out', async () => {
|
||||
const user = await db.createUser({
|
||||
email: 'attendance@example.com',
|
||||
firstName: 'Attendance',
|
||||
lastName: 'User',
|
||||
password: 'password',
|
||||
role: 'client'
|
||||
})
|
||||
|
||||
const checkIn = await db.checkIn(user.id, 'gym')
|
||||
expect(checkIn).toBeDefined()
|
||||
expect(checkIn.checkOutTime).toBeUndefined()
|
||||
|
||||
const activeCheckIn = await db.getActiveCheckIn(user.id)
|
||||
expect(activeCheckIn).toBeDefined()
|
||||
expect(activeCheckIn?.id).toBe(checkIn.id)
|
||||
|
||||
const checkOut = await db.checkOut(checkIn.id)
|
||||
expect(checkOut?.checkOutTime).toBeDefined()
|
||||
|
||||
const activeCheckInAfter = await db.getActiveCheckIn(user.id)
|
||||
expect(activeCheckInAfter).toBeNull()
|
||||
})
|
||||
})
|
||||
})
|
||||
// Batch should be faster (or at least not significantly slower)
|
||||
// In practice, batch is much faster, but in-memory DB might not show huge difference
|
||||
expect(batchTime).toBeLessThanOrEqual(individualTime * 2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -21,8 +21,13 @@ import {
|
||||
desc,
|
||||
asc,
|
||||
sql,
|
||||
inArray,
|
||||
gte,
|
||||
lte,
|
||||
or,
|
||||
like,
|
||||
} from "@fitai/database";
|
||||
import { SQL, or, like, inArray, gte, lte } from "drizzle-orm";
|
||||
import { SQL } from "drizzle-orm";
|
||||
import type { SortConfig, FilterCondition } from "../filtering";
|
||||
import { buildWhereConditions, buildSearchCondition } from "../filtering";
|
||||
import log from "../logger";
|
||||
@@ -643,6 +648,17 @@ export class DrizzleDatabase implements IDatabase {
|
||||
return result ? this.mapClient(result) : null;
|
||||
}
|
||||
|
||||
async getClientsByUserIds(userIds: string[]): Promise<Client[]> {
|
||||
if (userIds.length === 0) return [];
|
||||
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(clients)
|
||||
.where(inArray(clients.userId, userIds))
|
||||
.all();
|
||||
return results.map((r) => this.mapClient(r));
|
||||
}
|
||||
|
||||
async getAllClients(): Promise<Client[]> {
|
||||
const results = await this.db
|
||||
.select()
|
||||
@@ -1011,6 +1027,36 @@ export class DrizzleDatabase implements IDatabase {
|
||||
return history.find((a) => !a.checkOutTime) || null;
|
||||
}
|
||||
|
||||
async getAttendanceHistoriesByUserIds(
|
||||
userIds: string[],
|
||||
): Promise<Attendance[]> {
|
||||
if (userIds.length === 0) return [];
|
||||
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(attendance)
|
||||
.where(inArray(attendance.userId, userIds))
|
||||
.orderBy(desc(attendance.checkInTime))
|
||||
.all();
|
||||
return results.map((r) => this.mapAttendance(r));
|
||||
}
|
||||
|
||||
async getActiveCheckInsByUserIds(userIds: string[]): Promise<Attendance[]> {
|
||||
if (userIds.length === 0) return [];
|
||||
|
||||
// Get all attendance records for these users and filter for active check-ins
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(attendance)
|
||||
.where(inArray(attendance.userId, userIds))
|
||||
.all();
|
||||
|
||||
// Filter for active check-ins (no checkout time)
|
||||
return results
|
||||
.filter((r) => !r.checkOutTime)
|
||||
.map((r) => this.mapAttendance(r));
|
||||
}
|
||||
|
||||
// Recommendation operations
|
||||
async createRecommendation(
|
||||
data: Omit<Recommendation, "createdAt" | "approvedAt" | "approvedBy">,
|
||||
@@ -1079,6 +1125,20 @@ export class DrizzleDatabase implements IDatabase {
|
||||
return result ? this.mapRecommendation(result) : null;
|
||||
}
|
||||
|
||||
async getRecommendationsByUserIds(
|
||||
userIds: string[],
|
||||
): Promise<Recommendation[]> {
|
||||
if (userIds.length === 0) return [];
|
||||
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(recommendations)
|
||||
.where(inArray(recommendations.userId, userIds))
|
||||
.orderBy(desc(recommendations.createdAt))
|
||||
.all();
|
||||
return results.map((r) => this.mapRecommendation(r));
|
||||
}
|
||||
|
||||
// Fitness Goals operations
|
||||
async createFitnessGoal(
|
||||
goalData: Omit<FitnessGoal, "createdAt" | "updatedAt">,
|
||||
@@ -1190,6 +1250,18 @@ export class DrizzleDatabase implements IDatabase {
|
||||
return this.getFitnessGoalById(id);
|
||||
}
|
||||
|
||||
async getFitnessGoalsByUserIds(userIds: string[]): Promise<FitnessGoal[]> {
|
||||
if (userIds.length === 0) return [];
|
||||
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(fitnessGoals)
|
||||
.where(inArray(fitnessGoals.userId, userIds))
|
||||
.orderBy(desc(fitnessGoals.createdAt))
|
||||
.all();
|
||||
return results.map((r) => this.mapFitnessGoal(r));
|
||||
}
|
||||
|
||||
async getDashboardStats(): Promise<{
|
||||
totalUsers: number;
|
||||
activeClients: number;
|
||||
|
||||
@@ -82,6 +82,7 @@ export interface IDatabase {
|
||||
createClient(client: Omit<Client, "id">): Promise<Client>;
|
||||
getClientById(id: string): Promise<Client | null>;
|
||||
getClientByUserId(userId: string): Promise<Client | null>;
|
||||
getClientsByUserIds(userIds: string[]): Promise<Client[]>;
|
||||
getAllClients(): Promise<Client[]>;
|
||||
getClientsWithPagination(params: {
|
||||
page: number;
|
||||
@@ -113,6 +114,8 @@ export interface IDatabase {
|
||||
): Promise<Attendance>;
|
||||
checkOut(attendanceId: string): Promise<Attendance | null>;
|
||||
getAttendanceHistory(userId: string): Promise<Attendance[]>;
|
||||
getAttendanceHistoriesByUserIds(userIds: string[]): Promise<Attendance[]>;
|
||||
getActiveCheckInsByUserIds(userIds: string[]): Promise<Attendance[]>;
|
||||
getAllAttendance(): Promise<Attendance[]>;
|
||||
getAttendanceWithPagination(params: {
|
||||
page: number;
|
||||
@@ -131,6 +134,7 @@ export interface IDatabase {
|
||||
>,
|
||||
): Promise<Recommendation>;
|
||||
getRecommendationsByUserId(userId: string): Promise<Recommendation[]>;
|
||||
getRecommendationsByUserIds(userIds: string[]): Promise<Recommendation[]>;
|
||||
getAllRecommendations(): Promise<Recommendation[]>;
|
||||
updateRecommendation(
|
||||
id: string,
|
||||
@@ -147,6 +151,7 @@ export interface IDatabase {
|
||||
userId: string,
|
||||
status?: string,
|
||||
): Promise<FitnessGoal[]>;
|
||||
getFitnessGoalsByUserIds(userIds: string[]): Promise<FitnessGoal[]>;
|
||||
updateFitnessGoal(
|
||||
id: string,
|
||||
updates: Partial<FitnessGoal>,
|
||||
|
||||
Reference in new issue
Block a user