UNPKG

@defikitdotnet/education-module-ai

Version:
300 lines (299 loc) 19.8 kB
"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;