From 2e529bb61ce64a8ad1e9e22af48a1b64d9a72235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cmit=20Tun=C3=A7?= Date: Tue, 28 Apr 2026 20:16:28 +0300 Subject: [PATCH] feat: implement database management service layer with MySQL driver and API controllers --- .../app/Contracts/DatabaseDriverInterface.php | 5 + .../Http/Controllers/Api/SchemaController.php | 21 +++ backend/app/Services/Database/MySqlDriver.php | 6 + backend/app/Services/DatabaseService.php | 8 ++ backend/routes/api.php | 1 + frontend/src/components/Sidebar.tsx | 126 +++++++++++++++--- frontend/src/services/api.ts | 1 + 7 files changed, 153 insertions(+), 15 deletions(-) diff --git a/backend/app/Contracts/DatabaseDriverInterface.php b/backend/app/Contracts/DatabaseDriverInterface.php index 1271cc4..03f6896 100644 --- a/backend/app/Contracts/DatabaseDriverInterface.php +++ b/backend/app/Contracts/DatabaseDriverInterface.php @@ -78,4 +78,9 @@ interface DatabaseDriverInterface * Perform batch update on a table. */ public function batchUpdate(string $table, array $changes): bool; + + /** + * Create a new database. + */ + public function createDatabase(string $name, string $charset = 'utf8mb4', string $collation = 'utf8mb4_unicode_ci'): bool; } diff --git a/backend/app/Http/Controllers/Api/SchemaController.php b/backend/app/Http/Controllers/Api/SchemaController.php index 02e14e9..9a7245d 100644 --- a/backend/app/Http/Controllers/Api/SchemaController.php +++ b/backend/app/Http/Controllers/Api/SchemaController.php @@ -266,4 +266,25 @@ class SchemaController extends Controller return Response::json(['error' => $e->getMessage()], 400); } } + + public function createDatabase(Request $request) + { + $request->validate([ + 'name' => 'required|string|max:64', + 'charset' => 'nullable|string|max:32', + 'collation' => 'nullable|string|max:64', + ]); + + try { + $this->initializeDriver($request); + $name = $request->get('name'); + $charset = $request->get('charset', 'utf8mb4'); + $collation = $request->get('collation', 'utf8mb4_unicode_ci'); + + $this->databaseService->createDatabase($name, $charset, $collation); + return Response::json(['message' => "Database '{$name}' created successfully"]); + } catch (\Exception $e) { + return Response::json(['error' => $e->getMessage()], 400); + } + } } diff --git a/backend/app/Services/Database/MySqlDriver.php b/backend/app/Services/Database/MySqlDriver.php index 18ab252..ed84f9a 100644 --- a/backend/app/Services/Database/MySqlDriver.php +++ b/backend/app/Services/Database/MySqlDriver.php @@ -448,4 +448,10 @@ class MySqlDriver implements DatabaseDriverInterface, SchemaDiscoveryInterface fclose($handle); return $path; } + + public function createDatabase(string $name, string $charset = 'utf8mb4', string $collation = 'utf8mb4_unicode_ci'): bool + { + DB::connection($this->connectionName)->statement("CREATE DATABASE `{$name}` CHARACTER SET {$charset} COLLATE {$collation}"); + return true; + } } diff --git a/backend/app/Services/DatabaseService.php b/backend/app/Services/DatabaseService.php index 758e502..77f0019 100644 --- a/backend/app/Services/DatabaseService.php +++ b/backend/app/Services/DatabaseService.php @@ -164,4 +164,12 @@ class DatabaseService { return $this->getDriver()->batchUpdate($table, $changes); } + + /** + * Create a new database. + */ + public function createDatabase(string $name, string $charset = 'utf8mb4', string $collation = 'utf8mb4_unicode_ci'): bool + { + return $this->getDriver()->createDatabase($name, $charset, $collation); + } } diff --git a/backend/routes/api.php b/backend/routes/api.php index 200e428..7a790c6 100644 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -10,6 +10,7 @@ Route::get('/user', function (Request $request) { Route::prefix('schema')->group(function () { Route::get('/databases', [SchemaController::class, 'databases']); + Route::post('/databases', [SchemaController::class, 'createDatabase']); Route::get('/tables/{database}', [SchemaController::class, 'tables']); Route::get('/metadata/{database}', [SchemaController::class, 'metadata']); Route::get('/metadata/{database}/tables', [SchemaController::class, 'tablesMetadata']); diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 110a94e..96ec1a7 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -1,8 +1,9 @@ import React, { useEffect, useState, useMemo } from 'react'; -import { Box, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Typography, Divider, CircularProgress, Stack, TextField, InputAdornment } from '@mui/material'; -import { Storage, Search, FilterList, ChevronRight, ExpandMore, TableChart, Folder } from '@mui/icons-material'; +import { Box, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Typography, Divider, CircularProgress, Stack, TextField, InputAdornment, IconButton, Tooltip, Dialog, DialogTitle, DialogContent, DialogActions, Button, Alert, Snackbar, AlertTitle } from '@mui/material'; +import { Storage, Search, FilterList, ChevronRight, ExpandMore, TableChart, Folder, Add } from '@mui/icons-material'; import { useAppStore } from '../store/useAppStore'; import { SchemaService } from '../services/api'; +import type { MainNotification } from '../types/database'; const Sidebar: React.FC = () => { const { activeDatabase, setActiveDatabase, setActiveTable, activeTable } = useAppStore(); @@ -16,18 +17,30 @@ const Sidebar: React.FC = () => { const [tablesLoading, setTablesLoading] = useState(false); + const [openNewDbDialog, setOpenNewDbDialog] = useState(false); + const [newDbName, setNewDbName] = useState(''); + const [isCreatingDb, setIsCreatingDb] = useState(false); + const [notification, setNotification] = useState({ + open: false, + message: '', + title: '', + severity: 'success' + }); + + const fetchDatabases = async () => { + setLoading(true); + try { + const response = await SchemaService.getDatabases(); + setDatabases(response.data); + } catch (error) { + console.error('Failed to fetch databases', error); + } finally { + setLoading(false); + } + }; + // Fetch databases on mount useEffect(() => { - const fetchDatabases = async () => { - try { - const response = await SchemaService.getDatabases(); - setDatabases(response.data); - } catch (error) { - console.error('Failed to fetch databases', error); - } finally { - setLoading(false); - } - }; fetchDatabases(); }, []); @@ -86,11 +99,45 @@ const Sidebar: React.FC = () => { return tables.filter(table => table.toLowerCase().includes(tableSearch.toLowerCase())); }, [tables, tableSearch]); + const handleCreateDatabase = async () => { + if (!newDbName.trim()) return; + + setIsCreatingDb(true); + try { + await SchemaService.createDatabase(newDbName); + setNotification({ + open: true, + title: 'Success', + message: `Database '${newDbName}' created successfully.`, + severity: 'success' + }); + setOpenNewDbDialog(false); + setNewDbName(''); + fetchDatabases(); + } catch (error: any) { + setNotification({ + open: true, + title: 'Error', + message: error.response?.data?.error || error.message, + severity: 'error' + }); + } finally { + setIsCreatingDb(false); + } + }; + return ( - - - Explorer + + + + Explorer + + + setOpenNewDbDialog(true)} sx={{ color: 'primary.main' }}> + + + @@ -229,6 +276,55 @@ const Sidebar: React.FC = () => { + + {/* New Database Dialog */} + setOpenNewDbDialog(false)} maxWidth="xs" fullWidth> + Create New Database + + setNewDbName(e.target.value)} + disabled={isCreatingDb} + onKeyDown={(e) => { + if (e.key === 'Enter') handleCreateDatabase(); + }} + /> + + + + + + + + {/* Local Notification */} + setNotification({ ...notification, open: false })} + anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} + > + setNotification({ ...notification, open: false })} + sx={{ width: '100%', borderRadius: 2 }} + > + {notification.title} + {notification.message} + + ); }; diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 8a72d80..cd79057 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -24,6 +24,7 @@ export default api; export const SchemaService = { getDatabases: () => api.get('/schema/databases'), + createDatabase: (name: string, charset?: string, collation?: string) => api.post('/schema/databases', { name, charset, collation }), getTables: (db: string) => api.get(`/schema/tables/${db}`, { params: { database: db } }), getDatabaseMetadata: (db: string) => api.get(`/schema/metadata/${db}`, { params: { database: db } }), getTablesMetadata: (db: string) => api.get(`/schema/metadata/${db}/tables`, { params: { database: db } }),