chishiki-sqlite 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/index.mjs +1 -0
- package/dist/lib/activities.js +210 -0
- package/dist/lib/actors.js +147 -0
- package/dist/lib/database-manager.js +70 -0
- package/dist/lib/database-reset.js +29 -0
- package/dist/lib/generated-activities.js +240 -0
- package/dist/lib/index.js +13 -0
- package/dist/lib/learning-content.js +230 -0
- package/dist/lib/manual.js +30 -0
- package/dist/lib/statements.js +342 -0
- package/dist/lib/syllst-importer.js +234 -0
- package/dist/lib/types.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { getDatabaseManager } from './database-manager.js';
|
|
2
|
+
import { ManualSqliteClient } from './manual.js';
|
|
3
|
+
const getDb = () => {
|
|
4
|
+
const client = ManualSqliteClient.getInstance();
|
|
5
|
+
return client.getDb();
|
|
6
|
+
};
|
|
7
|
+
const ensureDatabaseInitialized = async () => {
|
|
8
|
+
const dbManager = getDatabaseManager();
|
|
9
|
+
await dbManager.ensureInitialized();
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Initialize the generated_activities table
|
|
13
|
+
*/
|
|
14
|
+
const initializeGeneratedActivitiesTable = async () => {
|
|
15
|
+
const db = getDb();
|
|
16
|
+
if (!db)
|
|
17
|
+
return;
|
|
18
|
+
const tableExists = await db.introspection
|
|
19
|
+
.getTables()
|
|
20
|
+
.then((tables) => tables.some((table) => table.name === 'generated_activities'));
|
|
21
|
+
if (!tableExists) {
|
|
22
|
+
await db.schema
|
|
23
|
+
.createTable('generated_activities')
|
|
24
|
+
.ifNotExists()
|
|
25
|
+
.addColumn('id', 'integer', col => col.primaryKey().autoIncrement())
|
|
26
|
+
.addColumn('content_id', 'integer', col => col.notNull())
|
|
27
|
+
.addColumn('activity_type', 'text', col => col.notNull())
|
|
28
|
+
.addColumn('activity_data', 'text', col => col.notNull())
|
|
29
|
+
.addColumn('generation_prompt', 'text')
|
|
30
|
+
.addColumn('created_at', 'text', col => col.notNull())
|
|
31
|
+
.execute();
|
|
32
|
+
// Create indexes
|
|
33
|
+
await db.schema
|
|
34
|
+
.createIndex('idx_generated_activities_content')
|
|
35
|
+
.ifNotExists()
|
|
36
|
+
.on('generated_activities')
|
|
37
|
+
.column('content_id')
|
|
38
|
+
.execute();
|
|
39
|
+
await db.schema
|
|
40
|
+
.createIndex('idx_generated_activities_type')
|
|
41
|
+
.ifNotExists()
|
|
42
|
+
.on('generated_activities')
|
|
43
|
+
.column('activity_type')
|
|
44
|
+
.execute();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Store a generated activity (flashcards, quiz, etc.)
|
|
49
|
+
*/
|
|
50
|
+
const storeGeneratedActivity = async (activity) => {
|
|
51
|
+
await ensureDatabaseInitialized();
|
|
52
|
+
const db = getDb();
|
|
53
|
+
if (!db) {
|
|
54
|
+
throw new Error('Database not available');
|
|
55
|
+
}
|
|
56
|
+
const now = new Date().toISOString();
|
|
57
|
+
const insertValues = {
|
|
58
|
+
content_id: activity.content_id,
|
|
59
|
+
activity_type: activity.activity_type,
|
|
60
|
+
activity_data: JSON.stringify(activity.activity_data),
|
|
61
|
+
generation_prompt: activity.generation_prompt ?? null,
|
|
62
|
+
created_at: now,
|
|
63
|
+
};
|
|
64
|
+
const result = await db
|
|
65
|
+
.insertInto('generated_activities')
|
|
66
|
+
.values(insertValues)
|
|
67
|
+
.returningAll()
|
|
68
|
+
.executeTakeFirstOrThrow();
|
|
69
|
+
return result;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Get generated activity by ID
|
|
73
|
+
*/
|
|
74
|
+
const getGeneratedActivityById = async (id) => {
|
|
75
|
+
await ensureDatabaseInitialized();
|
|
76
|
+
const db = getDb();
|
|
77
|
+
if (!db)
|
|
78
|
+
return null;
|
|
79
|
+
const result = await db.selectFrom('generated_activities').selectAll().where('id', '=', id).executeTakeFirst();
|
|
80
|
+
return result || null;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Get generated activities for a content item
|
|
84
|
+
*/
|
|
85
|
+
const getGeneratedActivitiesByContentId = async (contentId) => {
|
|
86
|
+
await ensureDatabaseInitialized();
|
|
87
|
+
const db = getDb();
|
|
88
|
+
if (!db)
|
|
89
|
+
return [];
|
|
90
|
+
const results = await db
|
|
91
|
+
.selectFrom('generated_activities')
|
|
92
|
+
.selectAll()
|
|
93
|
+
.where('content_id', '=', contentId)
|
|
94
|
+
.orderBy('created_at', 'desc')
|
|
95
|
+
.execute();
|
|
96
|
+
return results;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Get generated activities by type
|
|
100
|
+
*/
|
|
101
|
+
const getGeneratedActivitiesByType = async (activityType) => {
|
|
102
|
+
await ensureDatabaseInitialized();
|
|
103
|
+
const db = getDb();
|
|
104
|
+
if (!db)
|
|
105
|
+
return [];
|
|
106
|
+
const results = await db
|
|
107
|
+
.selectFrom('generated_activities')
|
|
108
|
+
.selectAll()
|
|
109
|
+
.where('activity_type', '=', activityType)
|
|
110
|
+
.orderBy('created_at', 'desc')
|
|
111
|
+
.execute();
|
|
112
|
+
return results;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Get generated activities for content with specific type
|
|
116
|
+
*/
|
|
117
|
+
const getGeneratedActivityByContentAndType = async (contentId, activityType) => {
|
|
118
|
+
await ensureDatabaseInitialized();
|
|
119
|
+
const db = getDb();
|
|
120
|
+
if (!db)
|
|
121
|
+
return null;
|
|
122
|
+
const result = await db
|
|
123
|
+
.selectFrom('generated_activities')
|
|
124
|
+
.selectAll()
|
|
125
|
+
.where('content_id', '=', contentId)
|
|
126
|
+
.where('activity_type', '=', activityType)
|
|
127
|
+
.orderBy('created_at', 'desc')
|
|
128
|
+
.executeTakeFirst();
|
|
129
|
+
return result || null;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Get all generated activities with pagination
|
|
133
|
+
*/
|
|
134
|
+
const getGeneratedActivities = async (page = 1, limit = 20, filters) => {
|
|
135
|
+
await ensureDatabaseInitialized();
|
|
136
|
+
const db = getDb();
|
|
137
|
+
if (!db)
|
|
138
|
+
return [];
|
|
139
|
+
let query = db.selectFrom('generated_activities').selectAll().orderBy('created_at', 'desc');
|
|
140
|
+
if (filters?.activity_type) {
|
|
141
|
+
query = query.where('activity_type', '=', filters.activity_type);
|
|
142
|
+
}
|
|
143
|
+
const results = await query
|
|
144
|
+
.offset((page - 1) * limit)
|
|
145
|
+
.limit(limit)
|
|
146
|
+
.execute();
|
|
147
|
+
return results;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Get count of generated activities
|
|
151
|
+
*/
|
|
152
|
+
const getGeneratedActivityCount = async (filters) => {
|
|
153
|
+
await ensureDatabaseInitialized();
|
|
154
|
+
const db = getDb();
|
|
155
|
+
if (!db)
|
|
156
|
+
return 0;
|
|
157
|
+
let query = db.selectFrom('generated_activities').select(db.fn.count('id').as('count'));
|
|
158
|
+
if (filters?.activity_type) {
|
|
159
|
+
query = query.where('activity_type', '=', filters.activity_type);
|
|
160
|
+
}
|
|
161
|
+
const result = await query.executeTakeFirst();
|
|
162
|
+
return result?.count || 0;
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* Update generated activity data
|
|
166
|
+
*/
|
|
167
|
+
const updateGeneratedActivityData = async (id, activityData) => {
|
|
168
|
+
await ensureDatabaseInitialized();
|
|
169
|
+
const db = getDb();
|
|
170
|
+
if (!db)
|
|
171
|
+
return;
|
|
172
|
+
await db
|
|
173
|
+
.updateTable('generated_activities')
|
|
174
|
+
.set({ activity_data: JSON.stringify(activityData) })
|
|
175
|
+
.where('id', '=', id)
|
|
176
|
+
.execute();
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Delete a generated activity
|
|
180
|
+
*/
|
|
181
|
+
const deleteGeneratedActivity = async (id) => {
|
|
182
|
+
await ensureDatabaseInitialized();
|
|
183
|
+
const db = getDb();
|
|
184
|
+
if (!db)
|
|
185
|
+
return;
|
|
186
|
+
await db.deleteFrom('generated_activities').where('id', '=', id).execute();
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* Delete generated activities by content ID
|
|
190
|
+
*/
|
|
191
|
+
const deleteGeneratedActivitiesByContentId = async (contentId) => {
|
|
192
|
+
await ensureDatabaseInitialized();
|
|
193
|
+
const db = getDb();
|
|
194
|
+
if (!db)
|
|
195
|
+
return;
|
|
196
|
+
await db.deleteFrom('generated_activities').where('content_id', '=', contentId).execute();
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* Reset the generated activities table
|
|
200
|
+
*/
|
|
201
|
+
const resetGeneratedActivitiesTable = async () => {
|
|
202
|
+
await ensureDatabaseInitialized();
|
|
203
|
+
const db = getDb();
|
|
204
|
+
if (!db)
|
|
205
|
+
return;
|
|
206
|
+
await db.schema.dropTable('generated_activities').ifExists().execute();
|
|
207
|
+
await initializeGeneratedActivitiesTable();
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* Parse activity data from JSON string
|
|
211
|
+
*/
|
|
212
|
+
const parseActivityData = (jsonString) => JSON.parse(jsonString);
|
|
213
|
+
/**
|
|
214
|
+
* Get flashcard activity data
|
|
215
|
+
*/
|
|
216
|
+
const getFlashcardData = async (id) => {
|
|
217
|
+
const activity = await getGeneratedActivityById(id);
|
|
218
|
+
if (!activity || activity.activity_type !== 'flashcard')
|
|
219
|
+
return null;
|
|
220
|
+
return parseActivityData(activity.activity_data);
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* Get multiple choice activity data
|
|
224
|
+
*/
|
|
225
|
+
const getMultipleChoiceData = async (id) => {
|
|
226
|
+
const activity = await getGeneratedActivityById(id);
|
|
227
|
+
if (!activity || activity.activity_type !== 'multiple_choice')
|
|
228
|
+
return null;
|
|
229
|
+
return parseActivityData(activity.activity_data);
|
|
230
|
+
};
|
|
231
|
+
/**
|
|
232
|
+
* Get fill-in-the-blank activity data
|
|
233
|
+
*/
|
|
234
|
+
const getFillBlankData = async (id) => {
|
|
235
|
+
const activity = await getGeneratedActivityById(id);
|
|
236
|
+
if (!activity || activity.activity_type !== 'fill_blank')
|
|
237
|
+
return null;
|
|
238
|
+
return parseActivityData(activity.activity_data);
|
|
239
|
+
};
|
|
240
|
+
export { initializeGeneratedActivitiesTable, storeGeneratedActivity, getGeneratedActivityById, getGeneratedActivitiesByContentId, getGeneratedActivitiesByType, getGeneratedActivityByContentAndType, getGeneratedActivities, getGeneratedActivityCount, updateGeneratedActivityData, deleteGeneratedActivity, deleteGeneratedActivitiesByContentId, resetGeneratedActivitiesTable, parseActivityData, getFlashcardData, getMultipleChoiceData, getFillBlankData, };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './manual.js';
|
|
2
|
+
export * from './database-reset.js';
|
|
3
|
+
export * from './database-manager.js';
|
|
4
|
+
// xAPI LRS exports
|
|
5
|
+
export * from './actors.js';
|
|
6
|
+
export * from './learning-content.js';
|
|
7
|
+
export * from './activities.js';
|
|
8
|
+
export * from './statements.js';
|
|
9
|
+
export * from './generated-activities.js';
|
|
10
|
+
// Content importers
|
|
11
|
+
export * from './syllst-importer.js';
|
|
12
|
+
// SYLLST content importer
|
|
13
|
+
export * from './syllst-importer.js';
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { getDatabaseManager } from './database-manager.js';
|
|
2
|
+
import { ManualSqliteClient } from './manual.js';
|
|
3
|
+
const getDb = () => {
|
|
4
|
+
const client = ManualSqliteClient.getInstance();
|
|
5
|
+
return client.getDb();
|
|
6
|
+
};
|
|
7
|
+
const ensureDatabaseInitialized = async () => {
|
|
8
|
+
const dbManager = getDatabaseManager();
|
|
9
|
+
await dbManager.ensureInitialized();
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Initialize the learning_content table
|
|
13
|
+
*/
|
|
14
|
+
const initializeLearningContentTable = async () => {
|
|
15
|
+
const db = getDb();
|
|
16
|
+
if (!db)
|
|
17
|
+
return;
|
|
18
|
+
const tableExists = await db.introspection
|
|
19
|
+
.getTables()
|
|
20
|
+
.then((tables) => tables.some((table) => table.name === 'learning_content'));
|
|
21
|
+
if (!tableExists) {
|
|
22
|
+
await db.schema
|
|
23
|
+
.createTable('learning_content')
|
|
24
|
+
.ifNotExists()
|
|
25
|
+
.addColumn('id', 'integer', col => col.primaryKey().autoIncrement())
|
|
26
|
+
.addColumn('content_type', 'text', col => col.notNull())
|
|
27
|
+
.addColumn('raw_content', 'text', col => col.notNull())
|
|
28
|
+
.addColumn('metadata', 'text', col => col.notNull().defaultTo('{}'))
|
|
29
|
+
.addColumn('title', 'text', col => col.notNull())
|
|
30
|
+
.addColumn('source_url', 'text')
|
|
31
|
+
.addColumn('language', 'text')
|
|
32
|
+
.addColumn('created_at', 'text', col => col.notNull())
|
|
33
|
+
.addColumn('updated_at', 'text', col => col.notNull())
|
|
34
|
+
.execute();
|
|
35
|
+
// Create indexes
|
|
36
|
+
await db.schema
|
|
37
|
+
.createIndex('idx_learning_content_type')
|
|
38
|
+
.ifNotExists()
|
|
39
|
+
.on('learning_content')
|
|
40
|
+
.column('content_type')
|
|
41
|
+
.execute();
|
|
42
|
+
await db.schema
|
|
43
|
+
.createIndex('idx_learning_content_created')
|
|
44
|
+
.ifNotExists()
|
|
45
|
+
.on('learning_content')
|
|
46
|
+
.column('created_at')
|
|
47
|
+
.execute();
|
|
48
|
+
await db.schema
|
|
49
|
+
.createIndex('idx_learning_content_language')
|
|
50
|
+
.ifNotExists()
|
|
51
|
+
.on('learning_content')
|
|
52
|
+
.column('language')
|
|
53
|
+
.execute();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Add new learning content
|
|
58
|
+
*/
|
|
59
|
+
const addLearningContent = async (content) => {
|
|
60
|
+
await ensureDatabaseInitialized();
|
|
61
|
+
const db = getDb();
|
|
62
|
+
if (!db) {
|
|
63
|
+
throw new Error('Database not available');
|
|
64
|
+
}
|
|
65
|
+
const now = new Date().toISOString();
|
|
66
|
+
const insertValues = {
|
|
67
|
+
content_type: content.content_type,
|
|
68
|
+
raw_content: content.raw_content,
|
|
69
|
+
title: content.title,
|
|
70
|
+
metadata: JSON.stringify(content.metadata || {}),
|
|
71
|
+
source_url: content.source_url ?? null,
|
|
72
|
+
language: content.language ?? null,
|
|
73
|
+
created_at: now,
|
|
74
|
+
updated_at: now,
|
|
75
|
+
};
|
|
76
|
+
const result = await db.insertInto('learning_content').values(insertValues).returningAll().executeTakeFirstOrThrow();
|
|
77
|
+
return result;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Get learning content by ID
|
|
81
|
+
*/
|
|
82
|
+
const getLearningContentById = async (id) => {
|
|
83
|
+
await ensureDatabaseInitialized();
|
|
84
|
+
const db = getDb();
|
|
85
|
+
if (!db)
|
|
86
|
+
return null;
|
|
87
|
+
const result = await db.selectFrom('learning_content').selectAll().where('id', '=', id).executeTakeFirst();
|
|
88
|
+
return result || null;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Get all learning content with pagination
|
|
92
|
+
*/
|
|
93
|
+
const getLearningContent = async (page = 1, limit = 20, filters) => {
|
|
94
|
+
await ensureDatabaseInitialized();
|
|
95
|
+
const db = getDb();
|
|
96
|
+
if (!db)
|
|
97
|
+
return [];
|
|
98
|
+
let query = db.selectFrom('learning_content').selectAll().orderBy('created_at', 'desc');
|
|
99
|
+
if (filters?.content_type) {
|
|
100
|
+
query = query.where('content_type', '=', filters.content_type);
|
|
101
|
+
}
|
|
102
|
+
if (filters?.language) {
|
|
103
|
+
query = query.where('language', '=', filters.language);
|
|
104
|
+
}
|
|
105
|
+
if (filters?.search) {
|
|
106
|
+
query = query.where(eb => eb.or([eb('title', 'like', `%${filters.search}%`), eb('raw_content', 'like', `%${filters.search}%`)]));
|
|
107
|
+
}
|
|
108
|
+
const results = await query
|
|
109
|
+
.offset((page - 1) * limit)
|
|
110
|
+
.limit(limit)
|
|
111
|
+
.execute();
|
|
112
|
+
return results;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Get total count of learning content
|
|
116
|
+
*/
|
|
117
|
+
const getLearningContentCount = async (filters) => {
|
|
118
|
+
await ensureDatabaseInitialized();
|
|
119
|
+
const db = getDb();
|
|
120
|
+
if (!db)
|
|
121
|
+
return 0;
|
|
122
|
+
let query = db.selectFrom('learning_content').select(db.fn.count('id').as('count'));
|
|
123
|
+
if (filters?.content_type) {
|
|
124
|
+
query = query.where('content_type', '=', filters.content_type);
|
|
125
|
+
}
|
|
126
|
+
if (filters?.language) {
|
|
127
|
+
query = query.where('language', '=', filters.language);
|
|
128
|
+
}
|
|
129
|
+
if (filters?.search) {
|
|
130
|
+
query = query.where(eb => eb.or([eb('title', 'like', `%${filters.search}%`), eb('raw_content', 'like', `%${filters.search}%`)]));
|
|
131
|
+
}
|
|
132
|
+
const result = await query.executeTakeFirst();
|
|
133
|
+
return result?.count || 0;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Update learning content
|
|
137
|
+
*/
|
|
138
|
+
const updateLearningContent = async (id, updates) => {
|
|
139
|
+
await ensureDatabaseInitialized();
|
|
140
|
+
const db = getDb();
|
|
141
|
+
if (!db)
|
|
142
|
+
return;
|
|
143
|
+
const now = new Date().toISOString();
|
|
144
|
+
const updateValues = { updated_at: now };
|
|
145
|
+
if (updates.title !== undefined)
|
|
146
|
+
updateValues.title = updates.title;
|
|
147
|
+
if (updates.raw_content !== undefined)
|
|
148
|
+
updateValues.raw_content = updates.raw_content;
|
|
149
|
+
if (updates.metadata !== undefined)
|
|
150
|
+
updateValues.metadata = JSON.stringify(updates.metadata);
|
|
151
|
+
if (updates.language !== undefined)
|
|
152
|
+
updateValues.language = updates.language;
|
|
153
|
+
await db.updateTable('learning_content').set(updateValues).where('id', '=', id).execute();
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Delete learning content
|
|
157
|
+
*/
|
|
158
|
+
const deleteLearningContent = async (id) => {
|
|
159
|
+
await ensureDatabaseInitialized();
|
|
160
|
+
const db = getDb();
|
|
161
|
+
if (!db)
|
|
162
|
+
return;
|
|
163
|
+
await db.deleteFrom('learning_content').where('id', '=', id).execute();
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Delete multiple learning content items
|
|
167
|
+
*/
|
|
168
|
+
const deleteLearningContentItems = async (ids) => {
|
|
169
|
+
await ensureDatabaseInitialized();
|
|
170
|
+
const db = getDb();
|
|
171
|
+
if (!db)
|
|
172
|
+
return;
|
|
173
|
+
await db.deleteFrom('learning_content').where('id', 'in', ids).execute();
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Get recent learning content
|
|
177
|
+
*/
|
|
178
|
+
const getRecentLearningContent = async (days = 7) => {
|
|
179
|
+
await ensureDatabaseInitialized();
|
|
180
|
+
const db = getDb();
|
|
181
|
+
if (!db)
|
|
182
|
+
return [];
|
|
183
|
+
const cutoffDate = new Date();
|
|
184
|
+
cutoffDate.setDate(cutoffDate.getDate() - days);
|
|
185
|
+
const cutoffISO = cutoffDate.toISOString();
|
|
186
|
+
const results = await db
|
|
187
|
+
.selectFrom('learning_content')
|
|
188
|
+
.selectAll()
|
|
189
|
+
.where('created_at', '>=', cutoffISO)
|
|
190
|
+
.orderBy('created_at', 'desc')
|
|
191
|
+
.execute();
|
|
192
|
+
return results;
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* Get learning content by source URL (useful for checking duplicates)
|
|
196
|
+
*/
|
|
197
|
+
const getLearningContentBySourceUrl = async (sourceUrl) => {
|
|
198
|
+
await ensureDatabaseInitialized();
|
|
199
|
+
const db = getDb();
|
|
200
|
+
if (!db)
|
|
201
|
+
return null;
|
|
202
|
+
const result = await db
|
|
203
|
+
.selectFrom('learning_content')
|
|
204
|
+
.selectAll()
|
|
205
|
+
.where('source_url', '=', sourceUrl)
|
|
206
|
+
.executeTakeFirst();
|
|
207
|
+
return result || null;
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* Reset the learning content table
|
|
211
|
+
*/
|
|
212
|
+
const resetLearningContentTable = async () => {
|
|
213
|
+
await ensureDatabaseInitialized();
|
|
214
|
+
const db = getDb();
|
|
215
|
+
if (!db)
|
|
216
|
+
return;
|
|
217
|
+
await db.schema.dropTable('learning_content').ifExists().execute();
|
|
218
|
+
await initializeLearningContentTable();
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Clear all learning content
|
|
222
|
+
*/
|
|
223
|
+
const clearAllLearningContent = async () => {
|
|
224
|
+
await ensureDatabaseInitialized();
|
|
225
|
+
const db = getDb();
|
|
226
|
+
if (!db)
|
|
227
|
+
return;
|
|
228
|
+
await db.deleteFrom('learning_content').execute();
|
|
229
|
+
};
|
|
230
|
+
export { initializeLearningContentTable, addLearningContent, getLearningContentById, getLearningContent, getLearningContentCount, updateLearningContent, deleteLearningContent, deleteLearningContentItems, getRecentLearningContent, getLearningContentBySourceUrl, resetLearningContentTable, clearAllLearningContent, };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Kysely } from 'kysely';
|
|
2
|
+
import { SQLocalKysely } from 'sqlocal/kysely';
|
|
3
|
+
export class ManualSqliteClient {
|
|
4
|
+
static instance = null;
|
|
5
|
+
db = null;
|
|
6
|
+
filename;
|
|
7
|
+
constructor() {
|
|
8
|
+
this.filename = 'database.sqlite3';
|
|
9
|
+
this.initializeDatabase();
|
|
10
|
+
}
|
|
11
|
+
static getInstance() {
|
|
12
|
+
if (!ManualSqliteClient.instance) {
|
|
13
|
+
ManualSqliteClient.instance = new ManualSqliteClient();
|
|
14
|
+
}
|
|
15
|
+
return ManualSqliteClient.instance;
|
|
16
|
+
}
|
|
17
|
+
initializeDatabase() {
|
|
18
|
+
const { dialect } = new SQLocalKysely(this.filename);
|
|
19
|
+
this.db = new Kysely({ dialect });
|
|
20
|
+
}
|
|
21
|
+
getDb() {
|
|
22
|
+
if (!this.db) {
|
|
23
|
+
throw new Error('Database not initialized');
|
|
24
|
+
}
|
|
25
|
+
return this.db;
|
|
26
|
+
}
|
|
27
|
+
static resetInstance() {
|
|
28
|
+
ManualSqliteClient.instance = null;
|
|
29
|
+
}
|
|
30
|
+
}
|