@defikitdotnet/education-module-ai
Version:
AI Education Module using Agent Framework
300 lines (299 loc) • 19.8 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SqliteAdapter = void 0;
var better_sqlite3_1 = __importDefault(require("better-sqlite3"));
var path_1 = __importDefault(require("path"));
var fs_1 = __importDefault(require("fs"));
var SqliteAdapter = /** @class */ (function () {
function SqliteAdapter(dbPath) {
// Use provided path or default
var dbFilePath = dbPath || path_1.default.join(__dirname, "../../data/education.db");
// Ensure directory exists
var dir = path_1.default.dirname(dbFilePath);
if (!fs_1.default.existsSync(dir)) {
fs_1.default.mkdirSync(dir, { recursive: true });
}
this.db = new better_sqlite3_1.default(dbFilePath);
// Enable foreign keys
this.db.pragma("foreign_keys = ON");
// Initialize tables
this.initializeTables();
}
SqliteAdapter.prototype.initializeTables = function () {
// Create accounts table (to store agent/user info)
// this.db.exec(`
// CREATE TABLE IF NOT EXISTS accounts (
// id TEXT PRIMARY KEY,
// name TEXT,
// email TEXT UNIQUE,
// created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
// -- Add other relevant account fields as needed
// )
// `);
// // Insert a default test account/agent if it doesn't exist
// // IMPORTANT: This is generally for testing/dev purposes.
// // In production, account creation should be handled properly.
// this.db.exec(`
// INSERT OR IGNORE INTO accounts (id, name)
// VALUES ('8f40db6d-37d1-4a67-a3de-8b21b78ea09a', 'Test Agent2');
// `);
// Create agents table - Commenting out as 'accounts' seems to be the intended table
/*
this.db.exec(`
CREATE TABLE IF NOT EXISTS agents (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT
)
`);
*/
// Create students table
this.db.exec("\n CREATE TABLE IF NOT EXISTS students (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n email TEXT UNIQUE NOT NULL,\n password_hash TEXT NOT NULL,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n )\n ");
// Create teachers table
this.db.exec("\n CREATE TABLE IF NOT EXISTS teachers (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n email TEXT UNIQUE NOT NULL,\n password_hash TEXT NOT NULL,\n is_active BOOLEAN DEFAULT 1,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n )\n ");
// Create categories table (references accounts)
this.db.exec("\n CREATE TABLE IF NOT EXISTS categories (\n id TEXT PRIMARY KEY,\n agent_id TEXT, -- Kept for backward compatibility\n teacher_id TEXT, -- Reference to the teacher who created/manages the category\n name TEXT NOT NULL,\n description TEXT,\n display_order INTEGER DEFAULT 0,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (teacher_id) REFERENCES teachers (id) ON DELETE CASCADE -- New foreign key to teachers\n )\n ");
// Create courses table (references teachers as primary and accounts for backward compatibility)
this.db.exec("\n CREATE TABLE IF NOT EXISTS courses (\n id TEXT PRIMARY KEY,\n agent_id TEXT, -- Kept for backward compatibility\n teacher_id TEXT NOT NULL, -- Reference to the teacher who created/manages the course\n title TEXT NOT NULL,\n description TEXT,\n thumbnail TEXT,\n level TEXT NOT NULL,\n visibility TEXT DEFAULT 'private',\n categories TEXT, -- Stored as JSON array of category IDs\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (teacher_id) REFERENCES teachers (id) ON DELETE CASCADE\n )\n ");
// Create sections table
this.db.exec("\n CREATE TABLE IF NOT EXISTS sections (\n id TEXT PRIMARY KEY,\n course_id TEXT NOT NULL,\n title TEXT NOT NULL,\n description TEXT,\n order_index INTEGER NOT NULL,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (course_id) REFERENCES courses (id) ON DELETE CASCADE\n )\n ");
// Create lectures table
this.db.exec("\n CREATE TABLE IF NOT EXISTS lectures (\n id TEXT PRIMARY KEY,\n section_id TEXT NOT NULL,\n title TEXT NOT NULL,\n content TEXT NOT NULL,\n type TEXT NOT NULL,\n order_index INTEGER NOT NULL,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (section_id) REFERENCES sections (id) ON DELETE CASCADE\n )\n ");
// Create quizzes table
this.db.exec("\n CREATE TABLE IF NOT EXISTS quizzes (\n id TEXT PRIMARY KEY,\n section_id TEXT NOT NULL,\n title TEXT NOT NULL,\n description TEXT,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (section_id) REFERENCES sections (id) ON DELETE CASCADE\n )\n ");
// Create quiz_questions table
this.db.exec("\n CREATE TABLE IF NOT EXISTS quiz_questions (\n id TEXT PRIMARY KEY,\n quiz_id TEXT NOT NULL,\n question TEXT NOT NULL,\n options TEXT NOT NULL, -- Likely JSON string\n answer TEXT NOT NULL, -- Likely JSON string or index\n explanation TEXT,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (quiz_id) REFERENCES quizzes (id) ON DELETE CASCADE\n )\n ");
// Create enrollments table
this.db.exec("\n CREATE TABLE IF NOT EXISTS enrollments (\n id TEXT PRIMARY KEY,\n student_id TEXT NOT NULL,\n course_id TEXT NOT NULL,\n progress REAL DEFAULT 0,\n completed BOOLEAN DEFAULT 0,\n enrolled_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (student_id) REFERENCES students (id) ON DELETE CASCADE,\n FOREIGN KEY (course_id) REFERENCES courses (id) ON DELETE CASCADE,\n UNIQUE(student_id, course_id)\n )\n ");
// Create progress table
this.db.exec("\n CREATE TABLE IF NOT EXISTS progress (\n id TEXT PRIMARY KEY,\n student_id TEXT NOT NULL,\n lecture_id TEXT NOT NULL,\n completed BOOLEAN DEFAULT 0,\n timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (student_id) REFERENCES students (id) ON DELETE CASCADE,\n FOREIGN KEY (lecture_id) REFERENCES lectures (id) ON DELETE CASCADE,\n UNIQUE(student_id, lecture_id)\n )\n ");
// Create lecture_completions table
this.db.exec("\n CREATE TABLE IF NOT EXISTS lecture_completions (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n student_id TEXT NOT NULL,\n course_id TEXT NOT NULL,\n lecture_id TEXT NOT NULL,\n completed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (student_id) REFERENCES students (id) ON DELETE CASCADE,\n FOREIGN KEY (course_id) REFERENCES courses (id) ON DELETE CASCADE,\n FOREIGN KEY (lecture_id) REFERENCES lectures (id) ON DELETE CASCADE,\n UNIQUE(student_id, lecture_id)\n )\n ");
// Create quiz_attempts table
this.db.exec("\n CREATE TABLE IF NOT EXISTS quiz_attempts (\n id TEXT PRIMARY KEY,\n student_id TEXT NOT NULL,\n quiz_id TEXT NOT NULL,\n score REAL NOT NULL,\n answers TEXT NOT NULL, -- Likely JSON string\n completed BOOLEAN DEFAULT 0,\n timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (student_id) REFERENCES students (id) ON DELETE CASCADE,\n FOREIGN KEY (quiz_id) REFERENCES quizzes (id) ON DELETE CASCADE\n )\n ");
};
SqliteAdapter.prototype.all = function (sql_1) {
return __awaiter(this, arguments, void 0, function (sql, params) {
var stmt;
if (params === void 0) { params = []; }
return __generator(this, function (_a) {
try {
stmt = this.db.prepare(sql);
return [2 /*return*/, stmt.all.apply(stmt, params)];
}
catch (error) {
console.error("Database query error (all):", error);
throw error;
}
return [2 /*return*/];
});
});
};
SqliteAdapter.prototype.get = function (sql_1) {
return __awaiter(this, arguments, void 0, function (sql, params) {
var stmt;
if (params === void 0) { params = []; }
return __generator(this, function (_a) {
try {
stmt = this.db.prepare(sql);
return [2 /*return*/, stmt.get.apply(stmt, params)];
}
catch (error) {
console.error("Database query error (get):", error);
throw error;
}
return [2 /*return*/];
});
});
};
SqliteAdapter.prototype.run = function (sql_1) {
return __awaiter(this, arguments, void 0, function (sql, params) {
var stmt, result;
if (params === void 0) { params = []; }
return __generator(this, function (_a) {
try {
stmt = this.db.prepare(sql);
result = stmt.run.apply(stmt, params);
return [2 /*return*/, {
lastID: result.lastInsertRowid,
changes: result.changes
}];
}
catch (error) {
console.error("Database query error (run):", error);
throw error;
}
return [2 /*return*/];
});
});
};
SqliteAdapter.prototype.prepare = function (sql) {
try {
return this.db.prepare(sql);
}
catch (error) {
console.error("Database prepare error:", error);
throw error;
}
};
SqliteAdapter.prototype.exec = function (sql) {
try {
this.db.exec(sql);
}
catch (error) {
console.error("Database exec error:", error);
throw error;
}
};
// Close connection when done
SqliteAdapter.prototype.close = function () {
this.db.close();
};
// The getDb method is now redundant with the db property
SqliteAdapter.prototype.getDb = function () {
return this.db;
};
// --- Student Methods ---
SqliteAdapter.prototype.createStudent = function (id, name, email, passwordHash) {
console.log("[SqliteAdapter] Attempting to create student: ID=".concat(id, ", Email=").concat(email));
var sql = "\n INSERT INTO students (id, name, email, password_hash) \n VALUES (?, ?, ?, ?)\n ";
try {
var stmt = this.db.prepare(sql);
var result = stmt.run(id, name, email, passwordHash);
console.log("[SqliteAdapter] Student creation successful: Changes=".concat(result.changes));
return result;
}
catch (error) {
console.error("[SqliteAdapter] Error creating student (ID=".concat(id, ", Email=").concat(email, "):"), error);
throw error;
}
};
// --- Teacher Methods ---
SqliteAdapter.prototype.createTeacher = function (id, name, email, passwordHash) {
console.log("[SqliteAdapter] Attempting to create teacher: ID=".concat(id, ", Email=").concat(email));
var sql = "\n INSERT INTO teachers (id, name, email, password_hash) \n VALUES (?, ?, ?, ?)\n ";
try {
var stmt = this.db.prepare(sql);
var result = stmt.run(id, name, email, passwordHash);
console.log("[SqliteAdapter] Teacher creation successful: Changes=".concat(result.changes));
return result;
}
catch (error) {
console.error("[SqliteAdapter] Error creating teacher (ID=".concat(id, ", Email=").concat(email, "):"), error);
throw error;
}
};
SqliteAdapter.prototype.findTeacherByEmail = function (email) {
var stmt = this.db.prepare("SELECT * FROM teachers WHERE email = ?");
return stmt.get(email);
};
SqliteAdapter.prototype.findTeacherById = function (id) {
var stmt = this.db.prepare("SELECT id, name, email, created_at FROM teachers WHERE id = ?");
return stmt.get(id);
};
SqliteAdapter.prototype.findStudentByEmail = function (email) {
var stmt = this.db.prepare("SELECT * FROM students WHERE email = ?");
return stmt.get(email);
};
SqliteAdapter.prototype.findStudentById = function (id) {
var stmt = this.db.prepare("SELECT id, name, email, created_at FROM students WHERE id = ?");
return stmt.get(id);
};
// --- Course Methods ---
/**
* @deprecated Use findCoursesByTeacherId instead
*/
SqliteAdapter.prototype.findCoursesByAgentId = function (agentId) {
console.warn('[SqliteAdapter] findCoursesByAgentId is deprecated. Use findCoursesByTeacherId instead.');
// Ensure categories JSON is parsed if needed after retrieval
var stmt = this.db.prepare("SELECT * FROM courses WHERE agent_id = ? ORDER BY updated_at DESC");
return stmt.all(agentId);
};
SqliteAdapter.prototype.findCoursesByTeacherId = function (teacherId) {
var stmt = this.db.prepare("SELECT * FROM courses WHERE teacher_id = ? ORDER BY updated_at DESC");
return stmt.all(teacherId);
};
// --- Category Methods ---
/**
* @deprecated Use findCategoriesByTeacherId instead
*/
SqliteAdapter.prototype.findCategoriesByAgentId = function (agentId) {
console.warn('[SqliteAdapter] findCategoriesByAgentId is deprecated. Use findCategoriesByTeacherId instead.');
var stmt = this.db.prepare("SELECT * FROM categories WHERE agent_id = ? ORDER BY display_order ASC, name ASC");
return stmt.all(agentId);
};
SqliteAdapter.prototype.findCategoriesByTeacherId = function (teacherId) {
// Query categories that reference the teacher directly
var stmt = this.db.prepare("SELECT * FROM categories WHERE teacher_id = ? ORDER BY display_order ASC, name ASC");
return stmt.all(teacherId);
};
/**
* Migrates data from agent_id to teacher_id for courses and categories
* This helps with transitioning from the old model to the new teacher-based model
* @param agentId The agent ID to migrate from
* @param teacherId The teacher ID to migrate to
*/
SqliteAdapter.prototype.migrateAgentToTeacher = function (agentId, teacherId) {
console.log("[SqliteAdapter] Migrating data from agent_id ".concat(agentId, " to teacher_id ").concat(teacherId));
try {
// Start a transaction for atomicity
this.db.exec("BEGIN TRANSACTION");
// Update courses
var updateCoursesStmt = this.db.prepare("\n UPDATE courses \n SET teacher_id = ?, updated_at = CURRENT_TIMESTAMP \n WHERE agent_id = ? AND (teacher_id IS NULL OR teacher_id = '')\n ");
var coursesResult = updateCoursesStmt.run(teacherId, agentId);
// Update categories
var updateCategoriesStmt = this.db.prepare("\n UPDATE categories \n SET teacher_id = ?, updated_at = CURRENT_TIMESTAMP \n WHERE agent_id = ? AND (teacher_id IS NULL OR teacher_id = '')\n ");
var categoriesResult = updateCategoriesStmt.run(teacherId, agentId);
// Commit the transaction
this.db.exec("COMMIT");
console.log("[SqliteAdapter] Migration completed: Updated ".concat(coursesResult.changes, " courses and ").concat(categoriesResult.changes, " categories"));
}
catch (error) {
// Rollback on error
this.db.exec("ROLLBACK");
console.error("[SqliteAdapter] Error migrating data from agent_id ".concat(agentId, " to teacher_id ").concat(teacherId, ":"), error);
throw error;
}
};
return SqliteAdapter;
}());
exports.SqliteAdapter = SqliteAdapter;