@sonicjs-cms/core 2.0.3 → 2.0.5
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/dist/{chunk-LEG4KNFP.cjs → chunk-3JMOWGUU.cjs} +20 -2
- package/dist/chunk-3JMOWGUU.cjs.map +1 -0
- package/dist/{chunk-LH4Z7QID.js → chunk-6FR25MPC.js} +111 -3
- package/dist/chunk-6FR25MPC.js.map +1 -0
- package/dist/{chunk-3NVJ6W27.cjs → chunk-DOR2IU73.cjs} +111 -2
- package/dist/chunk-DOR2IU73.cjs.map +1 -0
- package/dist/{chunk-M6FPVS7E.js → chunk-G5KY3WJV.js} +16 -29
- package/dist/chunk-G5KY3WJV.js.map +1 -0
- package/dist/{chunk-CDBVZEWR.js → chunk-HSRPDEQQ.js} +20 -2
- package/dist/chunk-HSRPDEQQ.js.map +1 -0
- package/dist/{chunk-4BJGEGX5.cjs → chunk-IM5SDXOE.cjs} +19 -32
- package/dist/chunk-IM5SDXOE.cjs.map +1 -0
- package/dist/{chunk-PPUKPNTP.js → chunk-LGC3TNCY.js} +293 -101
- package/dist/chunk-LGC3TNCY.js.map +1 -0
- package/dist/{chunk-5B3VMVEX.cjs → chunk-NPWWR6RI.cjs} +400 -208
- package/dist/chunk-NPWWR6RI.cjs.map +1 -0
- package/dist/{chunk-UL32L2KV.cjs → chunk-TRSHFTF6.cjs} +123 -3
- package/dist/chunk-TRSHFTF6.cjs.map +1 -0
- package/dist/{chunk-XJETEIRU.js → chunk-VSLEA22M.js} +123 -4
- package/dist/chunk-VSLEA22M.js.map +1 -0
- package/dist/index.cjs +876 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +759 -13
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +23 -23
- package/dist/middleware.js +2 -2
- package/dist/routes.cjs +25 -25
- package/dist/routes.js +5 -5
- package/dist/services.cjs +25 -21
- package/dist/services.js +2 -2
- package/dist/utils.cjs +11 -11
- package/dist/utils.js +1 -1
- package/migrations/006_plugin_system.sql +2 -2
- package/migrations/011_config_managed_collections.sql +1 -0
- package/migrations/018_settings_table.sql +23 -0
- package/package.json +1 -1
- package/dist/chunk-3NVJ6W27.cjs.map +0 -1
- package/dist/chunk-4BJGEGX5.cjs.map +0 -1
- package/dist/chunk-5B3VMVEX.cjs.map +0 -1
- package/dist/chunk-CDBVZEWR.js.map +0 -1
- package/dist/chunk-LEG4KNFP.cjs.map +0 -1
- package/dist/chunk-LH4Z7QID.js.map +0 -1
- package/dist/chunk-M6FPVS7E.js.map +0 -1
- package/dist/chunk-PPUKPNTP.js.map +0 -1
- package/dist/chunk-UL32L2KV.cjs.map +0 -1
- package/dist/chunk-XJETEIRU.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,21 +1,743 @@
|
|
|
1
|
-
import { api_default, api_media_default, api_system_default, admin_api_default, router, adminCollectionsRoutes, adminSettingsRoutes, admin_content_default, adminMediaRoutes, adminPluginRoutes, adminLogsRoutes, userRoutes, auth_default } from './chunk-
|
|
2
|
-
export { ROUTES_INFO, admin_api_default as adminApiRoutes, adminCheckboxRoutes, admin_code_examples_default as adminCodeExamplesRoutes, adminCollectionsRoutes, admin_content_default as adminContentRoutes, router as adminDashboardRoutes, adminDesignRoutes, admin_faq_default as adminFAQRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_testimonials_default as adminTestimonialsRoutes, userRoutes as adminUsersRoutes, api_content_crud_default as apiContentCrudRoutes, api_media_default as apiMediaRoutes, api_default as apiRoutes, api_system_default as apiSystemRoutes, auth_default as authRoutes } from './chunk-
|
|
3
|
-
import { schema_exports } from './chunk-
|
|
4
|
-
export { Logger, apiTokens, collections, content, contentVersions, getLogger, initLogger, insertCollectionSchema, insertContentSchema, insertLogConfigSchema, insertMediaSchema, insertPluginActivityLogSchema, insertPluginAssetSchema, insertPluginHookSchema, insertPluginRouteSchema, insertPluginSchema, insertSystemLogSchema, insertUserSchema, insertWorkflowHistorySchema, logConfig, media, pluginActivityLog, pluginAssets, pluginHooks, pluginRoutes, plugins, selectCollectionSchema, selectContentSchema, selectLogConfigSchema, selectMediaSchema, selectPluginActivityLogSchema, selectPluginAssetSchema, selectPluginHookSchema, selectPluginRouteSchema, selectPluginSchema, selectSystemLogSchema, selectUserSchema, selectWorkflowHistorySchema, systemLogs, users, workflowHistory } from './chunk-
|
|
5
|
-
import { metricsMiddleware, bootstrapMiddleware } from './chunk-
|
|
6
|
-
export { AuthManager, PermissionManager, bootstrapMiddleware, cacheHeaders, compressionMiddleware, detailedLoggingMiddleware, getActivePlugins, isPluginActive, logActivity, loggingMiddleware, optionalAuth, performanceLoggingMiddleware, requireActivePlugin, requireActivePlugins, requireAnyPermission, requireAuth, requirePermission, requireRole, securityHeaders, securityLoggingMiddleware } from './chunk-
|
|
7
|
-
export { MigrationService, PluginBootstrapService, PluginService as PluginServiceClass, cleanupRemovedCollections, fullCollectionSync, getAvailableCollectionNames, getManagedCollections, isCollectionManaged, loadCollectionConfig, loadCollectionConfigs, syncCollection, syncCollections, validateCollectionConfig } from './chunk-
|
|
1
|
+
import { api_default, api_media_default, api_system_default, admin_api_default, router, adminCollectionsRoutes, adminSettingsRoutes, admin_content_default, adminMediaRoutes, adminPluginRoutes, adminLogsRoutes, userRoutes, auth_default } from './chunk-LGC3TNCY.js';
|
|
2
|
+
export { ROUTES_INFO, admin_api_default as adminApiRoutes, adminCheckboxRoutes, admin_code_examples_default as adminCodeExamplesRoutes, adminCollectionsRoutes, admin_content_default as adminContentRoutes, router as adminDashboardRoutes, adminDesignRoutes, admin_faq_default as adminFAQRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_testimonials_default as adminTestimonialsRoutes, userRoutes as adminUsersRoutes, api_content_crud_default as apiContentCrudRoutes, api_media_default as apiMediaRoutes, api_default as apiRoutes, api_system_default as apiSystemRoutes, auth_default as authRoutes } from './chunk-LGC3TNCY.js';
|
|
3
|
+
import { schema_exports } from './chunk-6FR25MPC.js';
|
|
4
|
+
export { Logger, apiTokens, collections, content, contentVersions, getLogger, initLogger, insertCollectionSchema, insertContentSchema, insertLogConfigSchema, insertMediaSchema, insertPluginActivityLogSchema, insertPluginAssetSchema, insertPluginHookSchema, insertPluginRouteSchema, insertPluginSchema, insertSystemLogSchema, insertUserSchema, insertWorkflowHistorySchema, logConfig, media, pluginActivityLog, pluginAssets, pluginHooks, pluginRoutes, plugins, selectCollectionSchema, selectContentSchema, selectLogConfigSchema, selectMediaSchema, selectPluginActivityLogSchema, selectPluginAssetSchema, selectPluginHookSchema, selectPluginRouteSchema, selectPluginSchema, selectSystemLogSchema, selectUserSchema, selectWorkflowHistorySchema, systemLogs, users, workflowHistory } from './chunk-6FR25MPC.js';
|
|
5
|
+
import { metricsMiddleware, bootstrapMiddleware, requireAuth } from './chunk-G5KY3WJV.js';
|
|
6
|
+
export { AuthManager, PermissionManager, bootstrapMiddleware, cacheHeaders, compressionMiddleware, detailedLoggingMiddleware, getActivePlugins, isPluginActive, logActivity, loggingMiddleware, optionalAuth, performanceLoggingMiddleware, requireActivePlugin, requireActivePlugins, requireAnyPermission, requireAuth, requirePermission, requireRole, securityHeaders, securityLoggingMiddleware } from './chunk-G5KY3WJV.js';
|
|
7
|
+
export { MigrationService, PluginBootstrapService, PluginService as PluginServiceClass, cleanupRemovedCollections, fullCollectionSync, getAvailableCollectionNames, getManagedCollections, isCollectionManaged, loadCollectionConfig, loadCollectionConfigs, syncCollection, syncCollections, validateCollectionConfig } from './chunk-HSRPDEQQ.js';
|
|
8
8
|
export { renderFilterBar } from './chunk-RYQCT2IV.js';
|
|
9
|
+
import { init_admin_layout_catalyst_template, renderAdminLayoutCatalyst } from './chunk-3LZ6TLPC.js';
|
|
9
10
|
export { getConfirmationDialogScript, renderAlert, renderConfirmationDialog, renderForm, renderFormField, renderPagination, renderTable } from './chunk-3LZ6TLPC.js';
|
|
10
11
|
export { HookSystemImpl, HookUtils, PluginManager as PluginManagerClass, PluginRegistryImpl, PluginValidator as PluginValidatorClass, ScopedHookSystem as ScopedHookSystemClass } from './chunk-EAELJXRV.js';
|
|
11
|
-
import { getCoreVersion } from './chunk-
|
|
12
|
-
export { QueryFilterBuilder, SONICJS_VERSION, TemplateRenderer, buildQuery, escapeHtml, getCoreVersion, renderTemplate, sanitizeInput, sanitizeObject, templateRenderer } from './chunk-
|
|
12
|
+
import { package_default, getCoreVersion } from './chunk-VSLEA22M.js';
|
|
13
|
+
export { QueryFilterBuilder, SONICJS_VERSION, TemplateRenderer, buildQuery, escapeHtml, getCoreVersion, renderTemplate, sanitizeInput, sanitizeObject, templateRenderer } from './chunk-VSLEA22M.js';
|
|
13
14
|
export { metricsTracker } from './chunk-FICTAGD4.js';
|
|
14
15
|
export { HOOKS } from './chunk-LOUJRBXV.js';
|
|
15
16
|
import './chunk-V4OQ3NZ2.js';
|
|
16
17
|
import { Hono } from 'hono';
|
|
17
18
|
import { drizzle } from 'drizzle-orm/d1';
|
|
18
19
|
|
|
20
|
+
// src/plugins/core-plugins/database-tools-plugin/services/database-service.ts
|
|
21
|
+
var DatabaseToolsService = class {
|
|
22
|
+
constructor(db) {
|
|
23
|
+
this.db = db;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Get database statistics
|
|
27
|
+
*/
|
|
28
|
+
async getDatabaseStats() {
|
|
29
|
+
const tables = await this.getTables();
|
|
30
|
+
const stats = {
|
|
31
|
+
tables: [],
|
|
32
|
+
totalRows: 0
|
|
33
|
+
};
|
|
34
|
+
for (const tableName of tables) {
|
|
35
|
+
try {
|
|
36
|
+
const result = await this.db.prepare(`SELECT COUNT(*) as count FROM ${tableName}`).first();
|
|
37
|
+
const rowCount = result?.count || 0;
|
|
38
|
+
stats.tables.push({
|
|
39
|
+
name: tableName,
|
|
40
|
+
rowCount
|
|
41
|
+
});
|
|
42
|
+
stats.totalRows += rowCount;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.warn(`Could not count rows in table ${tableName}:`, error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return stats;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get all tables in the database
|
|
51
|
+
*/
|
|
52
|
+
async getTables() {
|
|
53
|
+
const result = await this.db.prepare(`
|
|
54
|
+
SELECT name FROM sqlite_master
|
|
55
|
+
WHERE type='table'
|
|
56
|
+
AND name NOT LIKE 'sqlite_%'
|
|
57
|
+
ORDER BY name
|
|
58
|
+
`).all();
|
|
59
|
+
return result.results?.map((row) => row.name) || [];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Truncate all data except admin user
|
|
63
|
+
*/
|
|
64
|
+
async truncateAllData(adminEmail) {
|
|
65
|
+
const errors = [];
|
|
66
|
+
const tablesCleared = [];
|
|
67
|
+
let adminUserPreserved = false;
|
|
68
|
+
try {
|
|
69
|
+
const adminUser = await this.db.prepare(
|
|
70
|
+
"SELECT * FROM users WHERE email = ? AND role = ?"
|
|
71
|
+
).bind(adminEmail, "admin").first();
|
|
72
|
+
if (!adminUser) {
|
|
73
|
+
return {
|
|
74
|
+
success: false,
|
|
75
|
+
message: "Admin user not found. Operation cancelled for safety.",
|
|
76
|
+
tablesCleared: [],
|
|
77
|
+
adminUserPreserved: false,
|
|
78
|
+
errors: ["Admin user not found"]
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const tablesToTruncate = [
|
|
82
|
+
"content",
|
|
83
|
+
"content_versions",
|
|
84
|
+
"content_workflow_status",
|
|
85
|
+
"collections",
|
|
86
|
+
"media",
|
|
87
|
+
"sessions",
|
|
88
|
+
"notifications",
|
|
89
|
+
"api_tokens",
|
|
90
|
+
"workflow_history",
|
|
91
|
+
"scheduled_content",
|
|
92
|
+
"faqs",
|
|
93
|
+
"faq_categories",
|
|
94
|
+
"plugins",
|
|
95
|
+
"plugin_settings",
|
|
96
|
+
"email_templates",
|
|
97
|
+
"email_themes"
|
|
98
|
+
];
|
|
99
|
+
const existingTables = await this.getTables();
|
|
100
|
+
const tablesToClear = tablesToTruncate.filter(
|
|
101
|
+
(table) => existingTables.includes(table)
|
|
102
|
+
);
|
|
103
|
+
for (const tableName of tablesToClear) {
|
|
104
|
+
try {
|
|
105
|
+
await this.db.prepare(`DELETE FROM ${tableName}`).run();
|
|
106
|
+
tablesCleared.push(tableName);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
errors.push(`Failed to clear table ${tableName}: ${error}`);
|
|
109
|
+
console.error(`Error clearing table ${tableName}:`, error);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
await this.db.prepare("DELETE FROM users WHERE email != ? OR role != ?").bind(adminEmail, "admin").run();
|
|
114
|
+
const verifyAdmin = await this.db.prepare(
|
|
115
|
+
"SELECT id FROM users WHERE email = ? AND role = ?"
|
|
116
|
+
).bind(adminEmail, "admin").first();
|
|
117
|
+
adminUserPreserved = !!verifyAdmin;
|
|
118
|
+
tablesCleared.push("users (non-admin)");
|
|
119
|
+
} catch (error) {
|
|
120
|
+
errors.push(`Failed to clear non-admin users: ${error}`);
|
|
121
|
+
console.error("Error clearing non-admin users:", error);
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
await this.db.prepare("DELETE FROM sqlite_sequence").run();
|
|
125
|
+
} catch (error) {
|
|
126
|
+
}
|
|
127
|
+
const message = errors.length > 0 ? `Truncation completed with ${errors.length} errors. ${tablesCleared.length} tables cleared.` : `Successfully truncated database. ${tablesCleared.length} tables cleared.`;
|
|
128
|
+
return {
|
|
129
|
+
success: errors.length === 0,
|
|
130
|
+
message,
|
|
131
|
+
tablesCleared,
|
|
132
|
+
adminUserPreserved,
|
|
133
|
+
errors: errors.length > 0 ? errors : void 0
|
|
134
|
+
};
|
|
135
|
+
} catch (error) {
|
|
136
|
+
return {
|
|
137
|
+
success: false,
|
|
138
|
+
message: `Database truncation failed: ${error}`,
|
|
139
|
+
tablesCleared,
|
|
140
|
+
adminUserPreserved,
|
|
141
|
+
errors: [String(error)]
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Create a backup of current data (simplified version)
|
|
147
|
+
*/
|
|
148
|
+
async createBackup() {
|
|
149
|
+
try {
|
|
150
|
+
const backupId = `backup_${Date.now()}`;
|
|
151
|
+
const stats = await this.getDatabaseStats();
|
|
152
|
+
console.log(`Backup ${backupId} created with ${stats.totalRows} total rows`);
|
|
153
|
+
return {
|
|
154
|
+
success: true,
|
|
155
|
+
message: `Backup created successfully (${stats.totalRows} rows)`,
|
|
156
|
+
backupId
|
|
157
|
+
};
|
|
158
|
+
} catch (error) {
|
|
159
|
+
return {
|
|
160
|
+
success: false,
|
|
161
|
+
message: `Backup failed: ${error}`
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get table data with optional pagination and sorting
|
|
167
|
+
*/
|
|
168
|
+
async getTableData(tableName, limit = 100, offset = 0, sortColumn, sortDirection = "asc") {
|
|
169
|
+
try {
|
|
170
|
+
const tables = await this.getTables();
|
|
171
|
+
if (!tables.includes(tableName)) {
|
|
172
|
+
throw new Error(`Table ${tableName} not found`);
|
|
173
|
+
}
|
|
174
|
+
const pragmaResult = await this.db.prepare(`PRAGMA table_info(${tableName})`).all();
|
|
175
|
+
const columns = pragmaResult.results?.map((col) => col.name) || [];
|
|
176
|
+
if (sortColumn && !columns.includes(sortColumn)) {
|
|
177
|
+
sortColumn = void 0;
|
|
178
|
+
}
|
|
179
|
+
const countResult = await this.db.prepare(`SELECT COUNT(*) as count FROM ${tableName}`).first();
|
|
180
|
+
const totalRows = countResult?.count || 0;
|
|
181
|
+
let query = `SELECT * FROM ${tableName}`;
|
|
182
|
+
if (sortColumn) {
|
|
183
|
+
query += ` ORDER BY ${sortColumn} ${sortDirection.toUpperCase()}`;
|
|
184
|
+
}
|
|
185
|
+
query += ` LIMIT ${limit} OFFSET ${offset}`;
|
|
186
|
+
const dataResult = await this.db.prepare(query).all();
|
|
187
|
+
return {
|
|
188
|
+
tableName,
|
|
189
|
+
columns,
|
|
190
|
+
rows: dataResult.results || [],
|
|
191
|
+
totalRows
|
|
192
|
+
};
|
|
193
|
+
} catch (error) {
|
|
194
|
+
throw new Error(`Failed to fetch table data: ${error}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Validate database integrity
|
|
199
|
+
*/
|
|
200
|
+
async validateDatabase() {
|
|
201
|
+
const issues = [];
|
|
202
|
+
try {
|
|
203
|
+
const requiredTables = ["users", "content", "collections"];
|
|
204
|
+
const existingTables = await this.getTables();
|
|
205
|
+
for (const table of requiredTables) {
|
|
206
|
+
if (!existingTables.includes(table)) {
|
|
207
|
+
issues.push(`Critical table missing: ${table}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const adminCount = await this.db.prepare(
|
|
211
|
+
"SELECT COUNT(*) as count FROM users WHERE role = ?"
|
|
212
|
+
).bind("admin").first();
|
|
213
|
+
if (adminCount?.count === 0) {
|
|
214
|
+
issues.push("No admin users found");
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
const integrityResult = await this.db.prepare("PRAGMA integrity_check").first();
|
|
218
|
+
if (integrityResult && integrityResult.integrity_check !== "ok") {
|
|
219
|
+
issues.push(`Database integrity check failed: ${integrityResult.integrity_check}`);
|
|
220
|
+
}
|
|
221
|
+
} catch (error) {
|
|
222
|
+
issues.push(`Could not run integrity check: ${error}`);
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
issues.push(`Validation error: ${error}`);
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
valid: issues.length === 0,
|
|
229
|
+
issues
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// src/templates/pages/admin-database-table.template.ts
|
|
235
|
+
init_admin_layout_catalyst_template();
|
|
236
|
+
function renderDatabaseTablePage(data) {
|
|
237
|
+
const totalPages = Math.ceil(data.totalRows / data.pageSize);
|
|
238
|
+
const startRow = (data.currentPage - 1) * data.pageSize + 1;
|
|
239
|
+
const endRow = Math.min(data.currentPage * data.pageSize, data.totalRows);
|
|
240
|
+
const pageContent = `
|
|
241
|
+
<div class="space-y-6">
|
|
242
|
+
<!-- Header -->
|
|
243
|
+
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between">
|
|
244
|
+
<div>
|
|
245
|
+
<div class="flex items-center space-x-3">
|
|
246
|
+
<a
|
|
247
|
+
href="/admin/settings/database-tools"
|
|
248
|
+
class="inline-flex items-center text-sm/6 text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-300"
|
|
249
|
+
>
|
|
250
|
+
<svg class="w-5 h-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
251
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
|
|
252
|
+
</svg>
|
|
253
|
+
Back to Database Tools
|
|
254
|
+
</a>
|
|
255
|
+
</div>
|
|
256
|
+
<h1 class="mt-2 text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8">Table: ${data.tableName}</h1>
|
|
257
|
+
<p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">
|
|
258
|
+
Showing ${startRow.toLocaleString()} - ${endRow.toLocaleString()} of ${data.totalRows.toLocaleString()} rows
|
|
259
|
+
</p>
|
|
260
|
+
</div>
|
|
261
|
+
<div class="mt-4 sm:mt-0 flex items-center space-x-3">
|
|
262
|
+
<div class="flex items-center space-x-2">
|
|
263
|
+
<label for="pageSize" class="text-sm font-medium text-zinc-700 dark:text-zinc-300">
|
|
264
|
+
Rows per page:
|
|
265
|
+
</label>
|
|
266
|
+
<select
|
|
267
|
+
id="pageSize"
|
|
268
|
+
onchange="changePageSize(this.value)"
|
|
269
|
+
class="rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors shadow-sm cursor-pointer"
|
|
270
|
+
>
|
|
271
|
+
<option value="10" ${data.pageSize === 10 ? "selected" : ""}>10</option>
|
|
272
|
+
<option value="20" ${data.pageSize === 20 ? "selected" : ""}>20</option>
|
|
273
|
+
<option value="50" ${data.pageSize === 50 ? "selected" : ""}>50</option>
|
|
274
|
+
<option value="100" ${data.pageSize === 100 ? "selected" : ""}>100</option>
|
|
275
|
+
<option value="200" ${data.pageSize === 200 ? "selected" : ""}>200</option>
|
|
276
|
+
</select>
|
|
277
|
+
</div>
|
|
278
|
+
<button
|
|
279
|
+
onclick="refreshTableData()"
|
|
280
|
+
class="inline-flex items-center justify-center rounded-lg bg-white dark:bg-zinc-800 px-3.5 py-2.5 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors shadow-sm"
|
|
281
|
+
>
|
|
282
|
+
<svg class="-ml-0.5 mr-1.5 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
283
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
|
284
|
+
</svg>
|
|
285
|
+
Refresh
|
|
286
|
+
</button>
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
<!-- Table Card -->
|
|
291
|
+
<div class="rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 overflow-hidden">
|
|
292
|
+
<!-- Table -->
|
|
293
|
+
<div class="overflow-x-auto">
|
|
294
|
+
<table class="min-w-full divide-y divide-zinc-950/10 dark:divide-white/10">
|
|
295
|
+
<thead class="bg-zinc-50 dark:bg-white/5">
|
|
296
|
+
<tr>
|
|
297
|
+
${data.columns.map((col) => `
|
|
298
|
+
<th
|
|
299
|
+
scope="col"
|
|
300
|
+
class="px-4 py-3.5 text-left text-xs font-semibold text-zinc-950 dark:text-white uppercase tracking-wider cursor-pointer hover:bg-zinc-100 dark:hover:bg-white/10 transition-colors"
|
|
301
|
+
onclick="sortTable('${col}')"
|
|
302
|
+
>
|
|
303
|
+
<div class="flex items-center space-x-1">
|
|
304
|
+
<span>${col}</span>
|
|
305
|
+
${data.sortColumn === col ? `
|
|
306
|
+
<svg class="w-4 h-4 ${data.sortDirection === "asc" ? "" : "rotate-180"}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
307
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"/>
|
|
308
|
+
</svg>
|
|
309
|
+
` : `
|
|
310
|
+
<svg class="w-4 h-4 text-zinc-400 dark:text-zinc-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
311
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"/>
|
|
312
|
+
</svg>
|
|
313
|
+
`}
|
|
314
|
+
</div>
|
|
315
|
+
</th>
|
|
316
|
+
`).join("")}
|
|
317
|
+
</tr>
|
|
318
|
+
</thead>
|
|
319
|
+
<tbody class="divide-y divide-zinc-950/5 dark:divide-white/5">
|
|
320
|
+
${data.rows.length > 0 ? data.rows.map((row, idx) => `
|
|
321
|
+
<tr class="${idx % 2 === 0 ? "bg-white dark:bg-zinc-900" : "bg-zinc-50 dark:bg-zinc-900/50"}">
|
|
322
|
+
${data.columns.map((col) => `
|
|
323
|
+
<td class="px-4 py-3 text-sm text-zinc-700 dark:text-zinc-300 whitespace-nowrap max-w-xs overflow-hidden text-ellipsis" title="${escapeHtml2(String(row[col] ?? ""))}">
|
|
324
|
+
${formatCellValue(row[col])}
|
|
325
|
+
</td>
|
|
326
|
+
`).join("")}
|
|
327
|
+
</tr>
|
|
328
|
+
`).join("") : `
|
|
329
|
+
<tr>
|
|
330
|
+
<td colspan="${data.columns.length}" class="px-4 py-12 text-center text-sm text-zinc-500 dark:text-zinc-400">
|
|
331
|
+
<svg class="w-12 h-12 mx-auto mb-4 text-zinc-400 dark:text-zinc-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
332
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"/>
|
|
333
|
+
</svg>
|
|
334
|
+
<p>No data in this table</p>
|
|
335
|
+
</td>
|
|
336
|
+
</tr>
|
|
337
|
+
`}
|
|
338
|
+
</tbody>
|
|
339
|
+
</table>
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<!-- Pagination -->
|
|
343
|
+
${totalPages > 1 ? `
|
|
344
|
+
<div class="flex items-center justify-between border-t border-zinc-950/10 dark:border-white/10 bg-zinc-50 dark:bg-zinc-900/50 px-4 py-3 sm:px-6">
|
|
345
|
+
<div class="flex flex-1 justify-between sm:hidden">
|
|
346
|
+
<button
|
|
347
|
+
onclick="goToPage(${data.currentPage - 1})"
|
|
348
|
+
${data.currentPage === 1 ? "disabled" : ""}
|
|
349
|
+
class="relative inline-flex items-center rounded-lg px-4 py-2 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-100 dark:hover:bg-zinc-800 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
350
|
+
>
|
|
351
|
+
Previous
|
|
352
|
+
</button>
|
|
353
|
+
<button
|
|
354
|
+
onclick="goToPage(${data.currentPage + 1})"
|
|
355
|
+
${data.currentPage === totalPages ? "disabled" : ""}
|
|
356
|
+
class="relative ml-3 inline-flex items-center rounded-lg px-4 py-2 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-100 dark:hover:bg-zinc-800 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
357
|
+
>
|
|
358
|
+
Next
|
|
359
|
+
</button>
|
|
360
|
+
</div>
|
|
361
|
+
<div class="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
|
|
362
|
+
<div>
|
|
363
|
+
<p class="text-sm text-zinc-700 dark:text-zinc-300">
|
|
364
|
+
Page <span class="font-medium">${data.currentPage}</span> of <span class="font-medium">${totalPages}</span>
|
|
365
|
+
</p>
|
|
366
|
+
</div>
|
|
367
|
+
<div>
|
|
368
|
+
<nav class="isolate inline-flex -space-x-px rounded-lg shadow-sm" aria-label="Pagination">
|
|
369
|
+
<button
|
|
370
|
+
onclick="goToPage(${data.currentPage - 1})"
|
|
371
|
+
${data.currentPage === 1 ? "disabled" : ""}
|
|
372
|
+
class="relative inline-flex items-center rounded-l-lg px-2 py-2 text-zinc-400 ring-1 ring-inset ring-zinc-300 dark:ring-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-800 focus:z-20 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
373
|
+
>
|
|
374
|
+
<span class="sr-only">Previous</span>
|
|
375
|
+
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
376
|
+
<path fill-rule="evenodd" d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z" clip-rule="evenodd" />
|
|
377
|
+
</svg>
|
|
378
|
+
</button>
|
|
379
|
+
|
|
380
|
+
${generatePageNumbers(data.currentPage, totalPages)}
|
|
381
|
+
|
|
382
|
+
<button
|
|
383
|
+
onclick="goToPage(${data.currentPage + 1})"
|
|
384
|
+
${data.currentPage === totalPages ? "disabled" : ""}
|
|
385
|
+
class="relative inline-flex items-center rounded-r-lg px-2 py-2 text-zinc-400 ring-1 ring-inset ring-zinc-300 dark:ring-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-800 focus:z-20 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
386
|
+
>
|
|
387
|
+
<span class="sr-only">Next</span>
|
|
388
|
+
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
389
|
+
<path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" />
|
|
390
|
+
</svg>
|
|
391
|
+
</button>
|
|
392
|
+
</nav>
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
` : ""}
|
|
397
|
+
</div>
|
|
398
|
+
</div>
|
|
399
|
+
|
|
400
|
+
<script>
|
|
401
|
+
const currentTableName = '${data.tableName}';
|
|
402
|
+
let currentPage = ${data.currentPage};
|
|
403
|
+
let currentPageSize = ${data.pageSize};
|
|
404
|
+
let currentSort = '${data.sortColumn || ""}';
|
|
405
|
+
let currentSortDir = '${data.sortDirection || "asc"}';
|
|
406
|
+
|
|
407
|
+
function goToPage(page) {
|
|
408
|
+
if (page < 1 || page > ${totalPages}) return;
|
|
409
|
+
const params = new URLSearchParams();
|
|
410
|
+
params.set('page', page);
|
|
411
|
+
params.set('pageSize', currentPageSize);
|
|
412
|
+
if (currentSort) {
|
|
413
|
+
params.set('sort', currentSort);
|
|
414
|
+
params.set('dir', currentSortDir);
|
|
415
|
+
}
|
|
416
|
+
window.location.href = \`/admin/database-tools/tables/\${currentTableName}?\${params}\`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function sortTable(column) {
|
|
420
|
+
let newDir = 'asc';
|
|
421
|
+
if (currentSort === column && currentSortDir === 'asc') {
|
|
422
|
+
newDir = 'desc';
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const params = new URLSearchParams();
|
|
426
|
+
params.set('page', '1');
|
|
427
|
+
params.set('pageSize', currentPageSize);
|
|
428
|
+
params.set('sort', column);
|
|
429
|
+
params.set('dir', newDir);
|
|
430
|
+
window.location.href = \`/admin/database-tools/tables/\${currentTableName}?\${params}\`;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function changePageSize(newSize) {
|
|
434
|
+
const params = new URLSearchParams();
|
|
435
|
+
params.set('page', '1');
|
|
436
|
+
params.set('pageSize', newSize);
|
|
437
|
+
if (currentSort) {
|
|
438
|
+
params.set('sort', currentSort);
|
|
439
|
+
params.set('dir', currentSortDir);
|
|
440
|
+
}
|
|
441
|
+
window.location.href = \`/admin/database-tools/tables/\${currentTableName}?\${params}\`;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function refreshTableData() {
|
|
445
|
+
window.location.reload();
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function formatCellValue(value) {
|
|
449
|
+
if (value === null || value === undefined) {
|
|
450
|
+
return '<span class="text-zinc-400 dark:text-zinc-500 italic">null</span>';
|
|
451
|
+
}
|
|
452
|
+
if (typeof value === 'boolean') {
|
|
453
|
+
return \`<span class="px-2 py-0.5 rounded text-xs font-medium \${value ? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400' : 'bg-zinc-100 text-zinc-800 dark:bg-zinc-800 dark:text-zinc-400'}">\${value}</span>\`;
|
|
454
|
+
}
|
|
455
|
+
if (typeof value === 'object') {
|
|
456
|
+
return '<span class="text-xs font-mono text-zinc-600 dark:text-zinc-400">' + JSON.stringify(value).substring(0, 50) + (JSON.stringify(value).length > 50 ? '...' : '') + '</span>';
|
|
457
|
+
}
|
|
458
|
+
const str = String(value);
|
|
459
|
+
if (str.length > 100) {
|
|
460
|
+
return escapeHtml(str.substring(0, 100)) + '...';
|
|
461
|
+
}
|
|
462
|
+
return escapeHtml(str);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function escapeHtml(text) {
|
|
466
|
+
const map = {
|
|
467
|
+
'&': '&',
|
|
468
|
+
'<': '<',
|
|
469
|
+
'>': '>',
|
|
470
|
+
'"': '"',
|
|
471
|
+
"'": '''
|
|
472
|
+
};
|
|
473
|
+
return String(text).replace(/[&<>"']/g, m => map[m]);
|
|
474
|
+
}
|
|
475
|
+
</script>
|
|
476
|
+
`;
|
|
477
|
+
const layoutData = {
|
|
478
|
+
title: `Table: ${data.tableName}`,
|
|
479
|
+
pageTitle: `Database: ${data.tableName}`,
|
|
480
|
+
currentPath: `/admin/database-tools/tables/${data.tableName}`,
|
|
481
|
+
user: data.user,
|
|
482
|
+
content: pageContent
|
|
483
|
+
};
|
|
484
|
+
return renderAdminLayoutCatalyst(layoutData);
|
|
485
|
+
}
|
|
486
|
+
function generatePageNumbers(currentPage, totalPages) {
|
|
487
|
+
const pages = [];
|
|
488
|
+
const maxVisible = 7;
|
|
489
|
+
if (totalPages <= maxVisible) {
|
|
490
|
+
for (let i = 1; i <= totalPages; i++) {
|
|
491
|
+
pages.push(i);
|
|
492
|
+
}
|
|
493
|
+
} else {
|
|
494
|
+
if (currentPage <= 4) {
|
|
495
|
+
for (let i = 1; i <= 5; i++) pages.push(i);
|
|
496
|
+
pages.push(-1);
|
|
497
|
+
pages.push(totalPages);
|
|
498
|
+
} else if (currentPage >= totalPages - 3) {
|
|
499
|
+
pages.push(1);
|
|
500
|
+
pages.push(-1);
|
|
501
|
+
for (let i = totalPages - 4; i <= totalPages; i++) pages.push(i);
|
|
502
|
+
} else {
|
|
503
|
+
pages.push(1);
|
|
504
|
+
pages.push(-1);
|
|
505
|
+
for (let i = currentPage - 1; i <= currentPage + 1; i++) pages.push(i);
|
|
506
|
+
pages.push(-1);
|
|
507
|
+
pages.push(totalPages);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return pages.map((page) => {
|
|
511
|
+
if (page === -1) {
|
|
512
|
+
return `
|
|
513
|
+
<span class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-zinc-700 dark:text-zinc-300 ring-1 ring-inset ring-zinc-300 dark:ring-zinc-700">
|
|
514
|
+
...
|
|
515
|
+
</span>
|
|
516
|
+
`;
|
|
517
|
+
}
|
|
518
|
+
const isActive = page === currentPage;
|
|
519
|
+
return `
|
|
520
|
+
<button
|
|
521
|
+
onclick="goToPage(${page})"
|
|
522
|
+
class="relative inline-flex items-center px-4 py-2 text-sm font-semibold ${isActive ? "z-10 bg-indigo-600 text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" : "text-zinc-900 dark:text-zinc-100 ring-1 ring-inset ring-zinc-300 dark:ring-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-800"}"
|
|
523
|
+
>
|
|
524
|
+
${page}
|
|
525
|
+
</button>
|
|
526
|
+
`;
|
|
527
|
+
}).join("");
|
|
528
|
+
}
|
|
529
|
+
function escapeHtml2(text) {
|
|
530
|
+
const map = {
|
|
531
|
+
"&": "&",
|
|
532
|
+
"<": "<",
|
|
533
|
+
">": ">",
|
|
534
|
+
'"': """,
|
|
535
|
+
"'": "'"
|
|
536
|
+
};
|
|
537
|
+
return String(text).replace(/[&<>"']/g, (m) => map[m] || m);
|
|
538
|
+
}
|
|
539
|
+
function formatCellValue(value) {
|
|
540
|
+
if (value === null || value === void 0) {
|
|
541
|
+
return '<span class="text-zinc-400 dark:text-zinc-500 italic">null</span>';
|
|
542
|
+
}
|
|
543
|
+
if (typeof value === "boolean") {
|
|
544
|
+
return `<span class="px-2 py-0.5 rounded text-xs font-medium ${value ? "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400" : "bg-zinc-100 text-zinc-800 dark:bg-zinc-800 dark:text-zinc-400"}">${value}</span>`;
|
|
545
|
+
}
|
|
546
|
+
if (typeof value === "object") {
|
|
547
|
+
return '<span class="text-xs font-mono text-zinc-600 dark:text-zinc-400">' + JSON.stringify(value).substring(0, 50) + (JSON.stringify(value).length > 50 ? "..." : "") + "</span>";
|
|
548
|
+
}
|
|
549
|
+
const str = String(value);
|
|
550
|
+
if (str.length > 100) {
|
|
551
|
+
return escapeHtml2(str.substring(0, 100)) + "...";
|
|
552
|
+
}
|
|
553
|
+
return escapeHtml2(str);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/plugins/core-plugins/database-tools-plugin/admin-routes.ts
|
|
557
|
+
function createDatabaseToolsAdminRoutes() {
|
|
558
|
+
const router2 = new Hono();
|
|
559
|
+
router2.use("*", requireAuth());
|
|
560
|
+
router2.get("/api/stats", async (c) => {
|
|
561
|
+
try {
|
|
562
|
+
const user = c.get("user");
|
|
563
|
+
if (!user || user.role !== "admin") {
|
|
564
|
+
return c.json({
|
|
565
|
+
success: false,
|
|
566
|
+
error: "Unauthorized. Admin access required."
|
|
567
|
+
}, 403);
|
|
568
|
+
}
|
|
569
|
+
const db = c.env.DB;
|
|
570
|
+
const service = new DatabaseToolsService(db);
|
|
571
|
+
const stats = await service.getDatabaseStats();
|
|
572
|
+
return c.json({
|
|
573
|
+
success: true,
|
|
574
|
+
data: stats
|
|
575
|
+
});
|
|
576
|
+
} catch (error) {
|
|
577
|
+
console.error("Error fetching database stats:", error);
|
|
578
|
+
return c.json({
|
|
579
|
+
success: false,
|
|
580
|
+
error: "Failed to fetch database statistics"
|
|
581
|
+
}, 500);
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
router2.post("/api/truncate", async (c) => {
|
|
585
|
+
try {
|
|
586
|
+
const user = c.get("user");
|
|
587
|
+
if (!user || user.role !== "admin") {
|
|
588
|
+
return c.json({
|
|
589
|
+
success: false,
|
|
590
|
+
error: "Unauthorized. Admin access required."
|
|
591
|
+
}, 403);
|
|
592
|
+
}
|
|
593
|
+
const body = await c.req.json();
|
|
594
|
+
const { confirmText } = body;
|
|
595
|
+
if (confirmText !== "TRUNCATE ALL DATA") {
|
|
596
|
+
return c.json({
|
|
597
|
+
success: false,
|
|
598
|
+
error: "Invalid confirmation text. Operation cancelled."
|
|
599
|
+
}, 400);
|
|
600
|
+
}
|
|
601
|
+
const db = c.env.DB;
|
|
602
|
+
const service = new DatabaseToolsService(db);
|
|
603
|
+
const result = await service.truncateAllData(user.email);
|
|
604
|
+
return c.json({
|
|
605
|
+
success: result.success,
|
|
606
|
+
message: result.message,
|
|
607
|
+
data: {
|
|
608
|
+
tablesCleared: result.tablesCleared,
|
|
609
|
+
adminUserPreserved: result.adminUserPreserved,
|
|
610
|
+
errors: result.errors
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
} catch (error) {
|
|
614
|
+
console.error("Error truncating database:", error);
|
|
615
|
+
return c.json({
|
|
616
|
+
success: false,
|
|
617
|
+
error: "Failed to truncate database"
|
|
618
|
+
}, 500);
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
router2.post("/api/backup", async (c) => {
|
|
622
|
+
try {
|
|
623
|
+
const user = c.get("user");
|
|
624
|
+
if (!user || user.role !== "admin") {
|
|
625
|
+
return c.json({
|
|
626
|
+
success: false,
|
|
627
|
+
error: "Unauthorized. Admin access required."
|
|
628
|
+
}, 403);
|
|
629
|
+
}
|
|
630
|
+
const db = c.env.DB;
|
|
631
|
+
const service = new DatabaseToolsService(db);
|
|
632
|
+
const result = await service.createBackup();
|
|
633
|
+
return c.json({
|
|
634
|
+
success: result.success,
|
|
635
|
+
message: result.message,
|
|
636
|
+
data: {
|
|
637
|
+
backupId: result.backupId
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
} catch (error) {
|
|
641
|
+
console.error("Error creating backup:", error);
|
|
642
|
+
return c.json({
|
|
643
|
+
success: false,
|
|
644
|
+
error: "Failed to create backup"
|
|
645
|
+
}, 500);
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
router2.get("/api/validate", async (c) => {
|
|
649
|
+
try {
|
|
650
|
+
const user = c.get("user");
|
|
651
|
+
if (!user || user.role !== "admin") {
|
|
652
|
+
return c.json({
|
|
653
|
+
success: false,
|
|
654
|
+
error: "Unauthorized. Admin access required."
|
|
655
|
+
}, 403);
|
|
656
|
+
}
|
|
657
|
+
const db = c.env.DB;
|
|
658
|
+
const service = new DatabaseToolsService(db);
|
|
659
|
+
const validation = await service.validateDatabase();
|
|
660
|
+
return c.json({
|
|
661
|
+
success: true,
|
|
662
|
+
data: validation
|
|
663
|
+
});
|
|
664
|
+
} catch (error) {
|
|
665
|
+
console.error("Error validating database:", error);
|
|
666
|
+
return c.json({
|
|
667
|
+
success: false,
|
|
668
|
+
error: "Failed to validate database"
|
|
669
|
+
}, 500);
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
router2.get("/api/tables/:tableName", async (c) => {
|
|
673
|
+
try {
|
|
674
|
+
const user = c.get("user");
|
|
675
|
+
if (!user || user.role !== "admin") {
|
|
676
|
+
return c.json({
|
|
677
|
+
success: false,
|
|
678
|
+
error: "Unauthorized. Admin access required."
|
|
679
|
+
}, 403);
|
|
680
|
+
}
|
|
681
|
+
const tableName = c.req.param("tableName");
|
|
682
|
+
const limit = parseInt(c.req.query("limit") || "100");
|
|
683
|
+
const offset = parseInt(c.req.query("offset") || "0");
|
|
684
|
+
const sortColumn = c.req.query("sort");
|
|
685
|
+
const sortDirection = c.req.query("dir") || "asc";
|
|
686
|
+
const db = c.env.DB;
|
|
687
|
+
const service = new DatabaseToolsService(db);
|
|
688
|
+
const tableData = await service.getTableData(tableName, limit, offset, sortColumn, sortDirection);
|
|
689
|
+
return c.json({
|
|
690
|
+
success: true,
|
|
691
|
+
data: tableData
|
|
692
|
+
});
|
|
693
|
+
} catch (error) {
|
|
694
|
+
console.error("Error fetching table data:", error);
|
|
695
|
+
return c.json({
|
|
696
|
+
success: false,
|
|
697
|
+
error: `Failed to fetch table data: ${error}`
|
|
698
|
+
}, 500);
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
router2.get("/tables/:tableName", async (c) => {
|
|
702
|
+
try {
|
|
703
|
+
const user = c.get("user");
|
|
704
|
+
if (!user || user.role !== "admin") {
|
|
705
|
+
return c.redirect("/admin/login");
|
|
706
|
+
}
|
|
707
|
+
const tableName = c.req.param("tableName");
|
|
708
|
+
const page = parseInt(c.req.query("page") || "1");
|
|
709
|
+
const pageSize = parseInt(c.req.query("pageSize") || "20");
|
|
710
|
+
const sortColumn = c.req.query("sort");
|
|
711
|
+
const sortDirection = c.req.query("dir") || "asc";
|
|
712
|
+
const offset = (page - 1) * pageSize;
|
|
713
|
+
const db = c.env.DB;
|
|
714
|
+
const service = new DatabaseToolsService(db);
|
|
715
|
+
const tableData = await service.getTableData(tableName, pageSize, offset, sortColumn, sortDirection);
|
|
716
|
+
const pageData = {
|
|
717
|
+
user: {
|
|
718
|
+
name: user.email.split("@")[0] || "Unknown",
|
|
719
|
+
email: user.email,
|
|
720
|
+
role: user.role
|
|
721
|
+
},
|
|
722
|
+
tableName: tableData.tableName,
|
|
723
|
+
columns: tableData.columns,
|
|
724
|
+
rows: tableData.rows,
|
|
725
|
+
totalRows: tableData.totalRows,
|
|
726
|
+
currentPage: page,
|
|
727
|
+
pageSize,
|
|
728
|
+
sortColumn,
|
|
729
|
+
sortDirection
|
|
730
|
+
};
|
|
731
|
+
return c.html(renderDatabaseTablePage(pageData));
|
|
732
|
+
} catch (error) {
|
|
733
|
+
console.error("Error rendering table page:", error);
|
|
734
|
+
return c.text(`Error: ${error}`, 500);
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
return router2;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// src/app.ts
|
|
19
741
|
function createSonicJSApp(config = {}) {
|
|
20
742
|
const app = new Hono();
|
|
21
743
|
const appVersion = config.version || getCoreVersion();
|
|
@@ -49,12 +771,40 @@ function createSonicJSApp(config = {}) {
|
|
|
49
771
|
app.route("/admin/dashboard", router);
|
|
50
772
|
app.route("/admin/collections", adminCollectionsRoutes);
|
|
51
773
|
app.route("/admin/settings", adminSettingsRoutes);
|
|
774
|
+
app.route("/admin/database-tools", createDatabaseToolsAdminRoutes());
|
|
52
775
|
app.route("/admin/content", admin_content_default);
|
|
53
776
|
app.route("/admin/media", adminMediaRoutes);
|
|
54
777
|
app.route("/admin/plugins", adminPluginRoutes);
|
|
55
778
|
app.route("/admin/logs", adminLogsRoutes);
|
|
56
779
|
app.route("/admin", userRoutes);
|
|
57
780
|
app.route("/auth", auth_default);
|
|
781
|
+
app.get("/files/*", async (c) => {
|
|
782
|
+
try {
|
|
783
|
+
const url = new URL(c.req.url);
|
|
784
|
+
const pathname = url.pathname;
|
|
785
|
+
const objectKey = pathname.replace(/^\/files\//, "");
|
|
786
|
+
if (!objectKey) {
|
|
787
|
+
return c.notFound();
|
|
788
|
+
}
|
|
789
|
+
const object = await c.env.MEDIA_BUCKET.get(objectKey);
|
|
790
|
+
if (!object) {
|
|
791
|
+
return c.notFound();
|
|
792
|
+
}
|
|
793
|
+
const headers = new Headers();
|
|
794
|
+
object.httpMetadata?.contentType && headers.set("Content-Type", object.httpMetadata.contentType);
|
|
795
|
+
object.httpMetadata?.contentDisposition && headers.set("Content-Disposition", object.httpMetadata.contentDisposition);
|
|
796
|
+
headers.set("Cache-Control", "public, max-age=31536000");
|
|
797
|
+
headers.set("Access-Control-Allow-Origin", "*");
|
|
798
|
+
headers.set("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
|
|
799
|
+
headers.set("Access-Control-Allow-Headers", "Content-Type");
|
|
800
|
+
return new Response(object.body, {
|
|
801
|
+
headers
|
|
802
|
+
});
|
|
803
|
+
} catch (error) {
|
|
804
|
+
console.error("Error serving file:", error);
|
|
805
|
+
return c.notFound();
|
|
806
|
+
}
|
|
807
|
+
});
|
|
58
808
|
if (config.routes) {
|
|
59
809
|
for (const route of config.routes) {
|
|
60
810
|
app.route(route.path, route.handler);
|
|
@@ -90,10 +840,6 @@ function createDb(d1) {
|
|
|
90
840
|
return drizzle(d1, { schema: schema_exports });
|
|
91
841
|
}
|
|
92
842
|
|
|
93
|
-
// package.json
|
|
94
|
-
var package_default = {
|
|
95
|
-
version: "2.0.3"};
|
|
96
|
-
|
|
97
843
|
// src/index.ts
|
|
98
844
|
var VERSION = package_default.version;
|
|
99
845
|
|