Public Access
- Implemented collection repository with CRUD operations for user collections. - Created routes for managing collections, including listing, creating, updating, and deleting collections. - Added sharing functionality for exams, allowing users to share and clone exams. - Developed a study coach service that analyzes exam results and provides study tips based on weak areas. - Introduced frontend components for managing collections, sharing exams, and displaying study coach insights. - Enhanced user experience with loading states and error handling in shared components.
172 lines
4.8 KiB
TypeScript
172 lines
4.8 KiB
TypeScript
import { getDb } from '../db/database.js'
|
|
|
|
export interface DbResult {
|
|
id: number
|
|
user_id: number
|
|
exam_id: number | null
|
|
title: string
|
|
score_points: number
|
|
total_points: number
|
|
correct_count: number
|
|
graded_count: number
|
|
passing_score: number
|
|
elapsed_ms: number
|
|
state_json: string
|
|
exam_json: string
|
|
completed_at: number
|
|
}
|
|
|
|
export interface ResultListItem {
|
|
id: number
|
|
title: string
|
|
score_points: number
|
|
total_points: number
|
|
correct_count: number
|
|
graded_count: number
|
|
passing_score: number
|
|
elapsed_ms: number
|
|
completed_at: number
|
|
exam_id: number | null
|
|
}
|
|
|
|
export function createResult(data: Omit<DbResult, 'id'>): { id: number } {
|
|
const db = getDb()
|
|
const result = db.prepare(`
|
|
INSERT INTO exam_results (user_id, exam_id, title, score_points, total_points, correct_count, graded_count, passing_score, elapsed_ms, state_json, exam_json)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`).run(
|
|
data.user_id,
|
|
data.exam_id ?? null,
|
|
data.title,
|
|
data.score_points,
|
|
data.total_points,
|
|
data.correct_count,
|
|
data.graded_count,
|
|
data.passing_score ?? 60,
|
|
data.elapsed_ms ?? 0,
|
|
data.state_json,
|
|
data.exam_json
|
|
)
|
|
return { id: result.lastInsertRowid as number }
|
|
}
|
|
|
|
export function getResultById(id: number, userId: number): DbResult | undefined {
|
|
const db = getDb()
|
|
return db.prepare('SELECT * FROM exam_results WHERE id = ? AND user_id = ?').get(id, userId) as DbResult | undefined
|
|
}
|
|
|
|
export function listResultsByUser(userId: number, options: { limit?: number; offset?: number } = {}): { results: ResultListItem[]; total: number } {
|
|
const db = getDb()
|
|
const limit = Math.min(options.limit ?? 30, 100)
|
|
const offset = options.offset ?? 0
|
|
|
|
const results = db.prepare(`
|
|
SELECT id, title, score_points, total_points, correct_count, graded_count,
|
|
passing_score, elapsed_ms, completed_at, exam_id
|
|
FROM exam_results
|
|
WHERE user_id = ?
|
|
ORDER BY completed_at DESC
|
|
LIMIT ? OFFSET ?
|
|
`).all(userId, limit, offset) as ResultListItem[]
|
|
|
|
const countRow = db.prepare('SELECT COUNT(*) AS total FROM exam_results WHERE user_id = ?').get(userId) as { total: number }
|
|
|
|
return { results, total: countRow.total }
|
|
}
|
|
|
|
export function deleteResult(id: number, userId: number): boolean {
|
|
const db = getDb()
|
|
const result = db.prepare('DELETE FROM exam_results WHERE id = ? AND user_id = ?').run(id, userId)
|
|
return result.changes > 0
|
|
}
|
|
|
|
export function countResults(): number {
|
|
const db = getDb()
|
|
const row = db.prepare('SELECT COUNT(*) AS cnt FROM exam_results').get() as { cnt: number }
|
|
return row.cnt
|
|
}
|
|
|
|
export function getAverageScore(): number {
|
|
const db = getDb()
|
|
const row = db.prepare(
|
|
'SELECT COALESCE(AVG(score_points * 100.0 / NULLIF(total_points, 0)), 0) AS avg FROM exam_results'
|
|
).get() as { avg: number }
|
|
return Math.round(row.avg * 10) / 10
|
|
}
|
|
|
|
export function getRecentResults(limit: number): Array<DbResult & { display_name: string; email: string }> {
|
|
const db = getDb()
|
|
return db.prepare(`
|
|
SELECT er.*, u.display_name, u.email
|
|
FROM exam_results er
|
|
JOIN users u ON u.id = er.user_id
|
|
ORDER BY er.completed_at DESC
|
|
LIMIT ?
|
|
`).all(limit) as Array<DbResult & { display_name: string; email: string }>
|
|
}
|
|
|
|
export function countResultsByUser(userId: number): number {
|
|
const db = getDb()
|
|
const row = db.prepare('SELECT COUNT(*) AS cnt FROM exam_results WHERE user_id = ?').get(userId) as { cnt: number }
|
|
return row.cnt
|
|
}
|
|
|
|
export function getResultsWithExamJson(userId: number, limit = 100): Array<{
|
|
title: string
|
|
score_points: number
|
|
total_points: number
|
|
correct_count: number
|
|
graded_count: number
|
|
completed_at: number
|
|
exam_json: string
|
|
exam_id: number | null
|
|
}> {
|
|
const db = getDb()
|
|
return db.prepare(`
|
|
SELECT title, score_points, total_points, correct_count, graded_count, completed_at, exam_json, exam_id
|
|
FROM exam_results
|
|
WHERE user_id = ?
|
|
ORDER BY completed_at DESC
|
|
LIMIT ?
|
|
`).all(userId, limit) as Array<{
|
|
title: string
|
|
score_points: number
|
|
total_points: number
|
|
correct_count: number
|
|
graded_count: number
|
|
completed_at: number
|
|
exam_json: string
|
|
exam_id: number | null
|
|
}>
|
|
}
|
|
|
|
export interface ScoreTrend {
|
|
completed_at: number
|
|
score_pct: number
|
|
title: string
|
|
exam_id: number | null
|
|
}
|
|
|
|
export function getScoreTrends(userId: number, limit = 30): Array<{
|
|
completed_at: number
|
|
score_points: number
|
|
total_points: number
|
|
title: string
|
|
exam_id: number | null
|
|
}> {
|
|
const db = getDb()
|
|
return db.prepare(`
|
|
SELECT completed_at, score_points, total_points, title, exam_id
|
|
FROM exam_results
|
|
WHERE user_id = ? AND total_points > 0
|
|
ORDER BY completed_at ASC
|
|
LIMIT ?
|
|
`).all(userId, limit) as Array<{
|
|
completed_at: number
|
|
score_points: number
|
|
total_points: number
|
|
title: string
|
|
exam_id: number | null
|
|
}>
|
|
}
|