sqlite-hub 0.8.7 → 0.9.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/changelog.md +11 -0
- package/frontend/index.html +1 -1
- package/frontend/js/api.js +67 -0
- package/frontend/js/app.js +1398 -999
- package/frontend/js/components/modal.js +208 -3
- package/frontend/js/components/queryEditor.js +9 -4
- package/frontend/js/components/queryHistoryDetail.js +20 -10
- package/frontend/js/components/queryHistoryPanel.js +4 -3
- package/frontend/js/components/rowEditorPanel.js +26 -5
- package/frontend/js/components/sidebar.js +49 -5
- package/frontend/js/components/structureGraph.js +614 -632
- package/frontend/js/components/tableDesignerSqlPreview.js +24 -25
- package/frontend/js/lib/mediaTaggingDefaults.js +27 -0
- package/frontend/js/router.js +81 -71
- package/frontend/js/store.js +3091 -2184
- package/frontend/js/views/charts.js +2 -2
- package/frontend/js/views/data.js +28 -14
- package/frontend/js/views/editor.js +172 -177
- package/frontend/js/views/mediaTagging.js +861 -0
- package/frontend/styles/base.css +11 -1
- package/frontend/styles/components.css +48 -6
- package/frontend/styles/views.css +796 -0
- package/package.json +1 -1
- package/server/routes/charts.js +4 -1
- package/server/routes/data.js +14 -0
- package/server/routes/mediaTagging.js +166 -0
- package/server/routes/sql.js +1 -0
- package/server/server.js +4 -0
- package/server/services/sqlite/dataBrowserService.js +25 -0
- package/server/services/sqlite/exportService.js +31 -1
- package/server/services/sqlite/mediaTaggingService.js +1689 -0
- package/server/services/storage/appStateStore.js +321 -2
|
@@ -46,6 +46,63 @@ const CONNECTION_LOGO_EXTENSION_BY_FILE_EXTENSION = {
|
|
|
46
46
|
".webp": "webp",
|
|
47
47
|
};
|
|
48
48
|
const QUERY_HISTORY_MIGRATION_KEY = "queryHistoryV1Migrated";
|
|
49
|
+
const MEDIA_TAGGING_CONFIG_FIELDS = [
|
|
50
|
+
{
|
|
51
|
+
column: "tag_table",
|
|
52
|
+
property: "tagTable",
|
|
53
|
+
definition: "tag_table TEXT NOT NULL DEFAULT ''",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
column: "media_table",
|
|
57
|
+
property: "mediaTable",
|
|
58
|
+
definition: "media_table TEXT NOT NULL DEFAULT ''",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
column: "path_column",
|
|
62
|
+
property: "pathColumn",
|
|
63
|
+
definition: "path_column TEXT NOT NULL DEFAULT ''",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
column: "tagged_column",
|
|
67
|
+
property: "taggedColumn",
|
|
68
|
+
definition: "tagged_column TEXT NOT NULL DEFAULT ''",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
column: "untagged_query",
|
|
72
|
+
property: "untaggedQuery",
|
|
73
|
+
definition: "untagged_query TEXT NOT NULL DEFAULT ''",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
column: "tagged_query",
|
|
77
|
+
property: "taggedQuery",
|
|
78
|
+
definition: "tagged_query TEXT NOT NULL DEFAULT ''",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
column: "mapping_table",
|
|
82
|
+
property: "mappingTable",
|
|
83
|
+
definition: "mapping_table TEXT NOT NULL DEFAULT ''",
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
function normalizeMediaTaggingConfigValue(value) {
|
|
88
|
+
return String(value ?? "").trim();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function normalizeMediaTaggingConfigRecord(config = {}) {
|
|
92
|
+
return {
|
|
93
|
+
tagTable: normalizeMediaTaggingConfigValue(config.tagTable),
|
|
94
|
+
mediaTable: normalizeMediaTaggingConfigValue(config.mediaTable),
|
|
95
|
+
pathColumn: normalizeMediaTaggingConfigValue(config.pathColumn),
|
|
96
|
+
taggedColumn: normalizeMediaTaggingConfigValue(config.taggedColumn),
|
|
97
|
+
untaggedQuery: String(config.untaggedQuery ?? "").trim(),
|
|
98
|
+
taggedQuery: String(config.taggedQuery ?? "").trim(),
|
|
99
|
+
mappingTable: normalizeMediaTaggingConfigValue(config.mappingTable),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isChartCompatibleQueryType(queryType) {
|
|
104
|
+
return String(queryType ?? "").trim().toLowerCase() === "select";
|
|
105
|
+
}
|
|
49
106
|
|
|
50
107
|
class AppStateStore {
|
|
51
108
|
constructor(filePath, options = {}) {
|
|
@@ -200,6 +257,18 @@ class AppStateStore {
|
|
|
200
257
|
|
|
201
258
|
CREATE INDEX IF NOT EXISTS idx_query_history_chart_chart_type
|
|
202
259
|
ON query_history_chart(chart_type);
|
|
260
|
+
|
|
261
|
+
CREATE TABLE IF NOT EXISTS media_tagging_config (
|
|
262
|
+
database_key TEXT PRIMARY KEY,
|
|
263
|
+
tag_table TEXT NOT NULL DEFAULT '',
|
|
264
|
+
media_table TEXT NOT NULL DEFAULT '',
|
|
265
|
+
path_column TEXT NOT NULL DEFAULT '',
|
|
266
|
+
tagged_column TEXT NOT NULL DEFAULT '',
|
|
267
|
+
untagged_query TEXT NOT NULL DEFAULT '',
|
|
268
|
+
tagged_query TEXT NOT NULL DEFAULT '',
|
|
269
|
+
mapping_table TEXT NOT NULL DEFAULT '',
|
|
270
|
+
updated_at TEXT NOT NULL
|
|
271
|
+
);
|
|
203
272
|
`);
|
|
204
273
|
|
|
205
274
|
const recentConnectionColumns = new Set(
|
|
@@ -212,6 +281,8 @@ class AppStateStore {
|
|
|
212
281
|
if (!recentConnectionColumns.has("logoPath")) {
|
|
213
282
|
this.db.exec("ALTER TABLE recent_connections ADD COLUMN logoPath TEXT");
|
|
214
283
|
}
|
|
284
|
+
|
|
285
|
+
this.ensureMediaTaggingConfigSchema();
|
|
215
286
|
}
|
|
216
287
|
|
|
217
288
|
seedDefaultSettings() {
|
|
@@ -481,6 +552,85 @@ class AppStateStore {
|
|
|
481
552
|
}
|
|
482
553
|
}
|
|
483
554
|
|
|
555
|
+
ensureMediaTaggingConfigSchema() {
|
|
556
|
+
const columns = this.db
|
|
557
|
+
.prepare("PRAGMA table_info(media_tagging_config)")
|
|
558
|
+
.all()
|
|
559
|
+
.map((column) => column.name);
|
|
560
|
+
const columnSet = new Set(columns);
|
|
561
|
+
|
|
562
|
+
this.mediaTaggingConfigHasLegacyJsonColumn = columnSet.has("config_json");
|
|
563
|
+
|
|
564
|
+
for (const field of MEDIA_TAGGING_CONFIG_FIELDS) {
|
|
565
|
+
if (!columnSet.has(field.column)) {
|
|
566
|
+
this.db.exec(`ALTER TABLE media_tagging_config ADD COLUMN ${field.definition}`);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (this.mediaTaggingConfigHasLegacyJsonColumn) {
|
|
571
|
+
this.backfillMediaTaggingConfigColumnsFromLegacyJson();
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
backfillMediaTaggingConfigColumnsFromLegacyJson() {
|
|
576
|
+
const rows = this.db
|
|
577
|
+
.prepare(
|
|
578
|
+
`
|
|
579
|
+
SELECT database_key, config_json
|
|
580
|
+
FROM media_tagging_config
|
|
581
|
+
WHERE config_json IS NOT NULL
|
|
582
|
+
`
|
|
583
|
+
)
|
|
584
|
+
.all();
|
|
585
|
+
|
|
586
|
+
if (!rows.length) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const updateStatement = this.db.prepare(`
|
|
591
|
+
UPDATE media_tagging_config
|
|
592
|
+
SET
|
|
593
|
+
tag_table = COALESCE(NULLIF(tag_table, ''), ?),
|
|
594
|
+
media_table = COALESCE(NULLIF(media_table, ''), ?),
|
|
595
|
+
path_column = COALESCE(NULLIF(path_column, ''), ?),
|
|
596
|
+
tagged_column = COALESCE(NULLIF(tagged_column, ''), ?),
|
|
597
|
+
untagged_query = COALESCE(NULLIF(untagged_query, ''), ?),
|
|
598
|
+
tagged_query = COALESCE(NULLIF(tagged_query, ''), ?),
|
|
599
|
+
mapping_table = COALESCE(NULLIF(mapping_table, ''), ?)
|
|
600
|
+
WHERE database_key = ?
|
|
601
|
+
`);
|
|
602
|
+
|
|
603
|
+
this.db.transaction(() => {
|
|
604
|
+
for (const row of rows) {
|
|
605
|
+
const parsedConfig = this.parseStoredValue(row.config_json);
|
|
606
|
+
const normalizedConfig = normalizeMediaTaggingConfigRecord(
|
|
607
|
+
parsedConfig && typeof parsedConfig === "object" && !Array.isArray(parsedConfig)
|
|
608
|
+
? parsedConfig
|
|
609
|
+
: {}
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
updateStatement.run(
|
|
613
|
+
normalizedConfig.tagTable,
|
|
614
|
+
normalizedConfig.mediaTable,
|
|
615
|
+
normalizedConfig.pathColumn,
|
|
616
|
+
normalizedConfig.taggedColumn,
|
|
617
|
+
normalizedConfig.untaggedQuery,
|
|
618
|
+
normalizedConfig.taggedQuery,
|
|
619
|
+
normalizedConfig.mappingTable,
|
|
620
|
+
row.database_key
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
})();
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
buildMediaTaggingConfigRecord(row = {}) {
|
|
627
|
+
return normalizeMediaTaggingConfigRecord(
|
|
628
|
+
Object.fromEntries(
|
|
629
|
+
MEDIA_TAGGING_CONFIG_FIELDS.map((field) => [field.property, row[field.column] ?? null])
|
|
630
|
+
)
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
|
|
484
634
|
getMetaValue(key) {
|
|
485
635
|
const row = this.db
|
|
486
636
|
.prepare("SELECT value FROM app_meta WHERE key = ?")
|
|
@@ -601,6 +751,9 @@ class AppStateStore {
|
|
|
601
751
|
}),
|
|
602
752
|
previewSql: buildSqlPreview(row.raw_sql ?? row.rawSql),
|
|
603
753
|
lastRun,
|
|
754
|
+
chartsEligible:
|
|
755
|
+
isChartCompatibleQueryType(queryType) &&
|
|
756
|
+
(!lastRun || String(lastRun.status ?? "").trim().toLowerCase() !== "error"),
|
|
604
757
|
};
|
|
605
758
|
}
|
|
606
759
|
|
|
@@ -661,6 +814,7 @@ class AppStateStore {
|
|
|
661
814
|
search,
|
|
662
815
|
queryType,
|
|
663
816
|
onlySaved = false,
|
|
817
|
+
onlyUnsaved = false,
|
|
664
818
|
onlyFavorites = false,
|
|
665
819
|
latestStatus = null,
|
|
666
820
|
} = {}) {
|
|
@@ -702,6 +856,10 @@ class AppStateStore {
|
|
|
702
856
|
clauses.push("q.is_saved = 1");
|
|
703
857
|
}
|
|
704
858
|
|
|
859
|
+
if (onlyUnsaved) {
|
|
860
|
+
clauses.push("q.is_saved = 0");
|
|
861
|
+
}
|
|
862
|
+
|
|
705
863
|
if (onlyFavorites) {
|
|
706
864
|
clauses.push("q.is_favorite = 1");
|
|
707
865
|
}
|
|
@@ -736,6 +894,7 @@ class AppStateStore {
|
|
|
736
894
|
search = "",
|
|
737
895
|
queryType = null,
|
|
738
896
|
onlySaved = false,
|
|
897
|
+
onlyUnsaved = false,
|
|
739
898
|
onlyFavorites = false,
|
|
740
899
|
latestStatus = null,
|
|
741
900
|
} = {}) {
|
|
@@ -768,6 +927,7 @@ class AppStateStore {
|
|
|
768
927
|
search,
|
|
769
928
|
queryType,
|
|
770
929
|
onlySaved,
|
|
930
|
+
onlyUnsaved,
|
|
771
931
|
onlyFavorites,
|
|
772
932
|
latestStatus,
|
|
773
933
|
});
|
|
@@ -1092,6 +1252,7 @@ class AppStateStore {
|
|
|
1092
1252
|
LIMIT 1
|
|
1093
1253
|
)
|
|
1094
1254
|
WHERE q.database_key = ?
|
|
1255
|
+
AND q.query_type = 'select'
|
|
1095
1256
|
AND COALESCE(latest.status, 'success') != 'error'
|
|
1096
1257
|
ORDER BY q.last_used_at DESC, q.id DESC
|
|
1097
1258
|
`)
|
|
@@ -1144,6 +1305,54 @@ class AppStateStore {
|
|
|
1144
1305
|
return this.decorateQueryHistoryRow(row);
|
|
1145
1306
|
}
|
|
1146
1307
|
|
|
1308
|
+
findQueryHistoryItemBySql(databaseKey, rawSql) {
|
|
1309
|
+
const normalizedDatabaseKey = this.normalizeQueryHistoryText(databaseKey);
|
|
1310
|
+
const normalizedSql = normalizeSql(rawSql);
|
|
1311
|
+
|
|
1312
|
+
if (!normalizedDatabaseKey || !normalizedSql) {
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
const row = this.db
|
|
1317
|
+
.prepare(`
|
|
1318
|
+
SELECT
|
|
1319
|
+
q.id,
|
|
1320
|
+
q.database_key,
|
|
1321
|
+
q.normalized_sql,
|
|
1322
|
+
q.raw_sql,
|
|
1323
|
+
q.title,
|
|
1324
|
+
q.notes,
|
|
1325
|
+
q.query_type,
|
|
1326
|
+
q.tables_detected,
|
|
1327
|
+
q.is_favorite,
|
|
1328
|
+
q.is_saved,
|
|
1329
|
+
q.is_destructive,
|
|
1330
|
+
q.use_count,
|
|
1331
|
+
q.first_executed_at,
|
|
1332
|
+
q.last_used_at,
|
|
1333
|
+
latest.id AS last_run_id,
|
|
1334
|
+
latest.executed_at AS last_run_executed_at,
|
|
1335
|
+
latest.duration_ms AS last_run_duration_ms,
|
|
1336
|
+
latest.row_count AS last_run_row_count,
|
|
1337
|
+
latest.status AS last_run_status,
|
|
1338
|
+
latest.error_message AS last_run_error_message,
|
|
1339
|
+
latest.affected_rows AS last_run_affected_rows
|
|
1340
|
+
FROM query_history q
|
|
1341
|
+
LEFT JOIN query_runs latest
|
|
1342
|
+
ON latest.id = (
|
|
1343
|
+
SELECT runs.id
|
|
1344
|
+
FROM query_runs runs
|
|
1345
|
+
WHERE runs.history_id = q.id
|
|
1346
|
+
ORDER BY runs.executed_at DESC, runs.id DESC
|
|
1347
|
+
LIMIT 1
|
|
1348
|
+
)
|
|
1349
|
+
WHERE q.database_key = ? AND q.normalized_sql = ?
|
|
1350
|
+
`)
|
|
1351
|
+
.get(normalizedDatabaseKey, normalizedSql);
|
|
1352
|
+
|
|
1353
|
+
return row ? this.decorateQueryHistoryRow(row) : null;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1147
1356
|
getQueryHistoryItemForDatabase(historyId, databaseKey) {
|
|
1148
1357
|
const item = this.getQueryHistoryItemById(historyId);
|
|
1149
1358
|
const normalizedDatabaseKey = this.normalizeQueryHistoryText(databaseKey);
|
|
@@ -1155,6 +1364,16 @@ class AppStateStore {
|
|
|
1155
1364
|
return item;
|
|
1156
1365
|
}
|
|
1157
1366
|
|
|
1367
|
+
getChartQueryHistoryItemForDatabase(historyId, databaseKey) {
|
|
1368
|
+
const item = this.getQueryHistoryItemForDatabase(historyId, databaseKey);
|
|
1369
|
+
|
|
1370
|
+
if (!isChartCompatibleQueryType(item.queryType)) {
|
|
1371
|
+
throw new ValidationError("Only SELECT queries can be opened in Charts.");
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
return item;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1158
1377
|
getQueryRunsByHistoryId(historyId, limit = 8) {
|
|
1159
1378
|
const normalizedLimit = Math.max(1, Math.min(50, Number(limit) || 8));
|
|
1160
1379
|
|
|
@@ -1259,7 +1478,7 @@ class AppStateStore {
|
|
|
1259
1478
|
}
|
|
1260
1479
|
|
|
1261
1480
|
getQueryHistoryChartsDetail(historyId, databaseKey) {
|
|
1262
|
-
const item = this.
|
|
1481
|
+
const item = this.getChartQueryHistoryItemForDatabase(historyId, databaseKey);
|
|
1263
1482
|
|
|
1264
1483
|
return {
|
|
1265
1484
|
item,
|
|
@@ -1318,7 +1537,7 @@ class AppStateStore {
|
|
|
1318
1537
|
tableVisible = true,
|
|
1319
1538
|
databaseKey = null,
|
|
1320
1539
|
} = {}) {
|
|
1321
|
-
const item = this.
|
|
1540
|
+
const item = this.getChartQueryHistoryItemForDatabase(queryHistoryId, databaseKey);
|
|
1322
1541
|
const normalizedChartType = normalizeChartType(chartType);
|
|
1323
1542
|
const normalizedConfig = normalizeChartConfig(normalizedChartType, config);
|
|
1324
1543
|
const normalizedResultColumns = normalizeResultColumns(resultColumns);
|
|
@@ -1860,6 +2079,106 @@ class AppStateStore {
|
|
|
1860
2079
|
|
|
1861
2080
|
return this.getSettings();
|
|
1862
2081
|
}
|
|
2082
|
+
|
|
2083
|
+
getMediaTaggingConfig(databaseKey) {
|
|
2084
|
+
const normalizedDatabaseKey = String(databaseKey ?? "").trim();
|
|
2085
|
+
|
|
2086
|
+
if (!normalizedDatabaseKey) {
|
|
2087
|
+
return null;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
const row = this.db
|
|
2091
|
+
.prepare(
|
|
2092
|
+
`
|
|
2093
|
+
SELECT
|
|
2094
|
+
tag_table,
|
|
2095
|
+
media_table,
|
|
2096
|
+
path_column,
|
|
2097
|
+
tagged_column,
|
|
2098
|
+
untagged_query,
|
|
2099
|
+
tagged_query,
|
|
2100
|
+
mapping_table,
|
|
2101
|
+
updated_at
|
|
2102
|
+
FROM media_tagging_config
|
|
2103
|
+
WHERE database_key = ?
|
|
2104
|
+
`
|
|
2105
|
+
)
|
|
2106
|
+
.get(normalizedDatabaseKey);
|
|
2107
|
+
|
|
2108
|
+
if (!row) {
|
|
2109
|
+
return null;
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
return {
|
|
2113
|
+
config: structuredClone(this.buildMediaTaggingConfigRecord(row)),
|
|
2114
|
+
updatedAt: row.updated_at ?? null,
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
setMediaTaggingConfig(databaseKey, config) {
|
|
2119
|
+
const normalizedDatabaseKey = String(databaseKey ?? "").trim();
|
|
2120
|
+
|
|
2121
|
+
if (!normalizedDatabaseKey) {
|
|
2122
|
+
throw new ValidationError("Media tagging configuration requires a database key.");
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
const updatedAt = new Date().toISOString();
|
|
2126
|
+
const normalizedConfig = normalizeMediaTaggingConfigRecord(config);
|
|
2127
|
+
const values = [
|
|
2128
|
+
normalizedDatabaseKey,
|
|
2129
|
+
normalizedConfig.tagTable,
|
|
2130
|
+
normalizedConfig.mediaTable,
|
|
2131
|
+
normalizedConfig.pathColumn,
|
|
2132
|
+
normalizedConfig.taggedColumn,
|
|
2133
|
+
normalizedConfig.untaggedQuery,
|
|
2134
|
+
normalizedConfig.taggedQuery,
|
|
2135
|
+
normalizedConfig.mappingTable,
|
|
2136
|
+
];
|
|
2137
|
+
const explicitColumns = [
|
|
2138
|
+
"database_key",
|
|
2139
|
+
...MEDIA_TAGGING_CONFIG_FIELDS.map((field) => field.column),
|
|
2140
|
+
];
|
|
2141
|
+
const assignments = MEDIA_TAGGING_CONFIG_FIELDS.map(
|
|
2142
|
+
(field) => `${field.column} = excluded.${field.column}`
|
|
2143
|
+
);
|
|
2144
|
+
|
|
2145
|
+
if (this.mediaTaggingConfigHasLegacyJsonColumn) {
|
|
2146
|
+
explicitColumns.push("config_json");
|
|
2147
|
+
assignments.push("config_json = excluded.config_json");
|
|
2148
|
+
values.push(JSON.stringify(normalizedConfig));
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
explicitColumns.push("updated_at");
|
|
2152
|
+
assignments.push("updated_at = excluded.updated_at");
|
|
2153
|
+
values.push(updatedAt);
|
|
2154
|
+
|
|
2155
|
+
this.db
|
|
2156
|
+
.prepare(
|
|
2157
|
+
`
|
|
2158
|
+
INSERT INTO media_tagging_config (${explicitColumns.join(", ")})
|
|
2159
|
+
VALUES (${explicitColumns.map(() => "?").join(", ")})
|
|
2160
|
+
ON CONFLICT(database_key) DO UPDATE SET
|
|
2161
|
+
${assignments.join(",\n ")}
|
|
2162
|
+
`
|
|
2163
|
+
)
|
|
2164
|
+
.run(...values);
|
|
2165
|
+
|
|
2166
|
+
return this.getMediaTaggingConfig(normalizedDatabaseKey);
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
clearMediaTaggingConfig(databaseKey) {
|
|
2170
|
+
const normalizedDatabaseKey = String(databaseKey ?? "").trim();
|
|
2171
|
+
|
|
2172
|
+
if (!normalizedDatabaseKey) {
|
|
2173
|
+
return false;
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
const result = this.db
|
|
2177
|
+
.prepare("DELETE FROM media_tagging_config WHERE database_key = ?")
|
|
2178
|
+
.run(normalizedDatabaseKey);
|
|
2179
|
+
|
|
2180
|
+
return result.changes > 0;
|
|
2181
|
+
}
|
|
1863
2182
|
}
|
|
1864
2183
|
|
|
1865
2184
|
module.exports = {
|