feat: implement database management service layer and frontend SQL import/export utility

This commit is contained in:
Ümit Tunç
2026-04-28 21:12:20 +03:00
parent 01ddb81aa9
commit ab5a12f8f2
6 changed files with 48 additions and 24 deletions
@@ -37,7 +37,7 @@ interface DatabaseDriverInterface
/**
* Export the database.
*/
public function export(array $config, array $filters = []): string;
public function export(array $config, array $filters = [], array $options = []): string;
/**
* Import the database.
@@ -112,8 +112,11 @@ class SchemaController extends Controller
$this->initializeDriver($request);
$config = $request->only(['host', 'username', 'password', 'database', 'port', 'table']);
$filters = json_decode($request->get('filters', '[]'), true);
$options = [
'structureOnly' => filter_var($request->get('structureOnly'), FILTER_VALIDATE_BOOLEAN)
];
$filePath = $this->databaseService->export($config, $filters);
$filePath = $this->databaseService->export($config, $filters, $options);
return Response::download($filePath)->deleteFileAfterSend(true);
} catch (\Exception $e) {
@@ -147,7 +147,7 @@ class MySqlDriver implements DatabaseDriverInterface, SchemaDiscoveryInterface
return $this->query($sql, [$table, $dbName]);
}
public function export(array $config, array $filters = []): string
public function export(array $config, array $filters = [], array $options = []): string
{
$database = $config['database'] ?? '';
$table = $config['table'] ?? '';
@@ -157,9 +157,11 @@ class MySqlDriver implements DatabaseDriverInterface, SchemaDiscoveryInterface
return $this->exportFilteredTable($database, $table, $filters);
}
$isStructureOnly = $options['structureOnly'] ?? false;
$filename = !empty($table)
? "{$table}-" . date('Y-m-d') . ".sql"
: "backup-" . ($database ?: 'all') . "-" . date('Y-m-d') . ".sql";
? "{$table}-" . ($isStructureOnly ? 'schema-' : '') . date('Y-m-d') . ".sql"
: "backup-" . ($database ?: 'all') . "-" . ($isStructureOnly ? 'schema-' : '') . date('Y-m-d') . ".sql";
$directory = storage_path('app/backups');
if (!is_dir($directory)) {
@@ -191,6 +193,10 @@ class MySqlDriver implements DatabaseDriverInterface, SchemaDiscoveryInterface
// --quick: useful for large tables
$flags = "--single-transaction --skip-lock-tables --routines --triggers --events --quick";
if ($isStructureOnly) {
$flags .= " --no-data";
}
$command = sprintf(
'%s -u %s %s -h %s -P %s %s %s > %s 2> %s',
$mysqldumpPath === 'mysqldump' ? 'mysqldump' : escapeshellarg($mysqldumpPath),
+2 -2
View File
@@ -102,9 +102,9 @@ class DatabaseService
/**
* Export the database.
*/
public function export(array $config, array $filters = []): string
public function export(array $config, array $filters = [], array $options = []): string
{
return $this->getDriver()->export($config, $filters);
return $this->getDriver()->export($config, $filters, $options);
}
/**
+29 -15
View File
@@ -38,22 +38,23 @@ const TransferContent: React.FC<TransferContentProps> = ({ mode = 'both' }) => {
const [error, setError] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const handleExport = async () => {
const handleExport = async (onlyStructure: boolean = false) => {
setLoading(true);
setError(null);
setSuccess(null);
try {
const response = await SchemaService.exportDatabase(activeDatabase || undefined, activeTable || undefined);
const response = await SchemaService.exportDatabase(activeDatabase || undefined, activeTable || undefined, undefined, onlyStructure);
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
const suffix = onlyStructure ? '-schema' : '';
const filename = activeTable
? `${activeTable}-${new Date().toISOString().split('T')[0]}.sql`
: `backup-${activeDatabase || 'all'}-${new Date().toISOString().split('T')[0]}.sql`;
? `${activeTable}${suffix}-${new Date().toISOString().split('T')[0]}.sql`
: `backup-${activeDatabase || 'all'}${suffix}-${new Date().toISOString().split('T')[0]}.sql`;
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
setSuccess(`${activeTable ? 'Table' : 'Database'} exported successfully!`);
setSuccess(`${activeTable ? 'Table' : 'Database'} ${onlyStructure ? 'structure' : ''} exported successfully!`);
} catch (err: any) {
setError('Export failed: ' + (err.response?.data?.error || err.message));
} finally {
@@ -138,22 +139,35 @@ const TransferContent: React.FC<TransferContentProps> = ({ mode = 'both' }) => {
<TableCell sx={{ py: 3 }}>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 0.5 }}>Export {activeTable ? 'Table' : 'Database'}</Typography>
<Typography variant="body2" color="text.secondary">
Create a full backup of the current {activeTable ? 'table' : 'database'}: <br/>
Create a backup of the current {activeTable ? 'table' : 'database'}: <br/>
<Box component="span" sx={{ color: 'primary.main', fontWeight: 600 }}>
{activeTable ? `${activeDatabase}.${activeTable}` : (activeDatabase || 'All Databases')}
</Box>
</Typography>
</TableCell>
<TableCell align="right" sx={{ py: 3 }}>
<Button
variant="contained"
startIcon={<CloudDownload />}
onClick={handleExport}
disabled={loading}
sx={{ borderRadius: 2, px: 3, py: 1, fontWeight: 700, minWidth: 160 }}
>
Start Export
</Button>
<Stack direction="row" spacing={1} justifyContent="flex-end">
<Button
variant="outlined"
color="primary"
startIcon={<CloudDownload />}
onClick={() => handleExport(true)}
disabled={loading}
sx={{ borderRadius: 2, px: 2, py: 1, fontWeight: 700 }}
>
Export Structure
</Button>
<Button
variant="contained"
color="primary"
startIcon={<CloudDownload />}
onClick={() => handleExport(false)}
disabled={loading}
sx={{ borderRadius: 2, px: 2, py: 1, fontWeight: 700 }}
>
Full Export
</Button>
</Stack>
</TableCell>
</TableRow>
)}
+3 -2
View File
@@ -39,10 +39,11 @@ export const SchemaService = {
bulkAction: (data: { tables: string[], action: string, database: string }) => api.post('/schema/bulk-action', data),
batchUpdate: (table: string, changes: any[]) => api.post(`/schema/${table}/batch-update`, { changes }),
executeQuery: (query: string) => api.post('/schema/execute', { query }),
exportDatabase: (database?: string, table?: string, filters?: any) => api.post('/schema/export', {
exportDatabase: (database?: string, table?: string, filters?: any, structureOnly: boolean = false) => api.post('/schema/export', {
database,
table,
filters: filters ? JSON.stringify(filters) : undefined
filters: filters ? JSON.stringify(filters) : undefined,
structureOnly
}, { responseType: 'blob' }),
importDatabase: (formData: FormData) => api.post('/schema/import', formData, {
headers: { 'Content-Type': 'multipart/form-data' }