diff --git a/.env.sample b/.env.sample
index c87806f..7662c93 100644
--- a/.env.sample
+++ b/.env.sample
@@ -1 +1,7 @@
-VITE_BACKEND_URL=http://backend.com
\ No newline at end of file
+VITE_BACKEND_URL=http://backend.com
+REACT_APP_GOOGLE_CLIENT_ID=your_google_client_id_here
+REACT_APP_GOOGLE_API_KEY=your_google_api_key_here
+# .env
+VITE_API_BASE_URL=http://localhost:3001/api
+REACT_APP_GOOGLE_CLIENT_ID=your_google_client_id_here
+REACT_APP_GOOGLE_API_KEY=your_google_api_key_here
\ No newline at end of file
diff --git a/src/App.jsx b/src/App.jsx
index fcb0981..b803208 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,25 +1,37 @@
-import { BrowserRouter, Routes, Route, useLocation } from "react-router-dom";
-import { useLayoutEffect } from "react";
-import Editor from "./pages/Editor";
-import BugReport from "./pages/BugReport";
-import Templates from "./pages/Templates";
-import LandingPage from "./pages/LandingPage";
-import SettingsContextProvider from "./context/SettingsContext";
-import NotFound from "./pages/NotFound";
+// App.jsx (Final Updated Version)
+import React, { useEffect } from 'react';
+import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom';
+import { useLayoutEffect } from 'react';
+import Editor from './pages/Editor';
+import BugReport from './pages/BugReport';
+import Templates from './pages/Templates';
+import LandingPage from './pages/LandingPage';
+import SettingsContextProvider from './context/SettingsContext';
+import { GoogleDriveProvider } from './context/GoogleDriveContext';
+import { SyncProvider } from './context/synccontext';
+import SyncToolbar from './components/synctoolbar';
+import NotFound from './pages/NotFound';
export default function App() {
return (
-
-
-
- } />
- } />
- } />
- } />
- } />
-
-
+
+
+
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+
);
}
@@ -30,4 +42,4 @@ function RestoreScroll() {
window.scroll(0, 0);
}, [location.pathname]);
return null;
-}
+}
\ No newline at end of file
diff --git a/src/components/authmodal.jsx b/src/components/authmodal.jsx
new file mode 100644
index 0000000..9866eca
--- /dev/null
+++ b/src/components/authmodal.jsx
@@ -0,0 +1,137 @@
+// components/AuthModal.jsx
+import React, { useState } from 'react';
+import { useSync } from '../context/synccontext';
+import './AuthModal.css';
+
+const AuthModal = ({ isOpen, onClose, onSuccess }) => {
+ const [activeTab, setActiveTab] = useState('login');
+ const [formData, setFormData] = useState({
+ email: '',
+ password: '',
+ username: '',
+ });
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState('');
+
+ const { register, login } = useSync();
+
+ const handleInputChange = (e) => {
+ setFormData({
+ ...formData,
+ [e.target.name]: e.target.value,
+ });
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setLoading(true);
+ setError('');
+
+ try {
+ if (activeTab === 'register') {
+ await register(formData.email, formData.password, formData.username);
+ } else {
+ await login(formData.email, formData.password);
+ }
+
+ onSuccess?.();
+ onClose();
+ } catch (err) {
+ setError(err.message);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleTabChange = (tab) => {
+ setActiveTab(tab);
+ setError('');
+ setFormData({ email: '', password: '', username: '' });
+ };
+
+ if (!isOpen) return null;
+
+ return (
+
+
+
+
Sync Your Work
+
+
+
+
+
+
+
+
+
+
+
+
With Sync You Can:
+
+ - ✨ Save projects to the cloud
+ - 🔄 Continue work on any device
+ - 🔒 Automatic backups
+ - 📱 Access your diagrams anywhere
+
+
+
+
+ );
+};
+
+export default AuthModal;
\ No newline at end of file
diff --git a/src/components/datatypeselector.jsx b/src/components/datatypeselector.jsx
new file mode 100644
index 0000000..048feed
--- /dev/null
+++ b/src/components/datatypeselector.jsx
@@ -0,0 +1,160 @@
+// components/DataTypeSelector.jsx
+import React, { useState } from 'react';
+import {
+ BASE_DATA_TYPES,
+ DATA_TYPE_CATEGORIES,
+ ENUM_TYPE,
+ CUSTOM_TYPE_TEMPLATES,
+ getDataTypeDefinition
+} from '../utils/dataTypes';
+import './DataTypeSelector.css';
+
+const DataTypeSelector = ({ value, onChange, onEnumCreate }) => {
+ const [showCustomTypes, setShowCustomTypes] = useState(false);
+ const [customTypeName, setCustomTypeName] = useState('');
+ const [enumValues, setEnumValues] = useState(['']);
+ const [showEnumCreator, setShowEnumCreator] = useState(false);
+
+ const handleDataTypeChange = (dataType) => {
+ if (dataType === ENUM_TYPE) {
+ setShowEnumCreator(true);
+ } else {
+ onChange({
+ type: dataType,
+ definition: getDataTypeDefinition(dataType),
+ });
+ }
+ };
+
+ const handleEnumCreate = () => {
+ const validValues = enumValues.filter(val => val.trim() !== '');
+ if (validValues.length > 0) {
+ onEnumCreate?.(validValues);
+ onChange({
+ type: ENUM_TYPE,
+ definition: `ENUM('${validValues.join("','")}')`,
+ enumValues: validValues,
+ });
+ setShowEnumCreator(false);
+ setEnumValues(['']);
+ }
+ };
+
+ const addEnumValue = () => {
+ setEnumValues([...enumValues, '']);
+ };
+
+ const updateEnumValue = (index, value) => {
+ const newValues = [...enumValues];
+ newValues[index] = value;
+ setEnumValues(newValues);
+ };
+
+ const removeEnumValue = (index) => {
+ if (enumValues.length > 1) {
+ setEnumValues(enumValues.filter((_, i) => i !== index));
+ }
+ };
+
+ const addCustomType = () => {
+ if (customTypeName.trim()) {
+ // Here you would save the custom type to user preferences or context
+ console.log('Adding custom type:', customTypeName);
+ setCustomTypeName('');
+ setShowCustomTypes(false);
+ }
+ };
+
+ return (
+
+
+ {Object.entries(DATA_TYPE_CATEGORIES).map(([categoryKey, category]) => (
+
+
{category.label}
+
+ {category.types.map((dataType) => (
+
+ ))}
+
+
+ ))}
+
+
+ {/* Enum Creator Modal */}
+ {showEnumCreator && (
+
+
+
Create ENUM Type
+
Define the possible values for your ENUM type:
+
+
+ {enumValues.map((value, index) => (
+
+ updateEnumValue(index, e.target.value)}
+ placeholder={`Value ${index + 1}`}
+ />
+ {enumValues.length > 1 && (
+
+ )}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ {/* Custom Type Creator */}
+ {showCustomTypes && (
+
+
+
Add Custom Data Type
+
setCustomTypeName(e.target.value)}
+ placeholder="Enter custom type name (e.g., status_type)"
+ />
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default DataTypeSelector;
\ No newline at end of file
diff --git a/src/components/gdmodal.css b/src/components/gdmodal.css
new file mode 100644
index 0000000..4f76bf2
--- /dev/null
+++ b/src/components/gdmodal.css
@@ -0,0 +1,191 @@
+/* components/GoogleDriveModal.css */
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 1000;
+}
+
+.modal-content {
+ background: white;
+ border-radius: 8px;
+ padding: 0;
+ width: 90%;
+ max-width: 600px;
+ max-height: 80vh;
+ overflow: hidden;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+}
+
+.modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20px;
+ border-bottom: 1px solid #e1e5e9;
+}
+
+.modal-header h2 {
+ margin: 0;
+ color: #333;
+}
+
+.close-button {
+ background: none;
+ border: none;
+ font-size: 24px;
+ cursor: pointer;
+ color: #666;
+}
+
+.close-button:hover {
+ color: #333;
+}
+
+.auth-section {
+ padding: 40px 20px;
+ text-align: center;
+}
+
+.sign-in-button {
+ background: #4285f4;
+ color: white;
+ border: none;
+ padding: 12px 24px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 16px;
+ margin-top: 16px;
+}
+
+.sign-in-button:hover {
+ background: #3367d6;
+}
+
+.tab-navigation {
+ display: flex;
+ border-bottom: 1px solid #e1e5e9;
+}
+
+.tab-button {
+ flex: 1;
+ padding: 16px;
+ background: none;
+ border: none;
+ cursor: pointer;
+ font-size: 16px;
+ color: #666;
+}
+
+.tab-button.active {
+ color: #4285f4;
+ border-bottom: 2px solid #4285f4;
+}
+
+.error-message {
+ background: #ffeaea;
+ color: #d32f2f;
+ padding: 12px 20px;
+ margin: 20px;
+ border-radius: 4px;
+ border-left: 4px solid #d32f2f;
+}
+
+.file-list-section, .save-section {
+ padding: 20px;
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 16px;
+}
+
+.refresh-button {
+ background: #f8f9fa;
+ border: 1px solid #dadce0;
+ padding: 8px 16px;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.refresh-button:hover {
+ background: #e8f0fe;
+}
+
+.loading, .empty-state {
+ text-align: center;
+ padding: 40px;
+ color: #666;
+}
+
+.file-list {
+ max-height: 400px;
+ overflow-y: auto;
+}
+
+.file-item {
+ padding: 16px;
+ border: 1px solid #e1e5e9;
+ border-radius: 4px;
+ margin-bottom: 8px;
+ cursor: pointer;
+}
+
+.file-item:hover {
+ background: #f8f9fa;
+ border-color: #4285f4;
+}
+
+.file-name {
+ font-weight: 500;
+ color: #333;
+}
+
+.file-modified {
+ font-size: 14px;
+ color: #666;
+ margin-top: 4px;
+}
+
+.input-group {
+ margin-bottom: 20px;
+}
+
+.input-group label {
+ display: block;
+ margin-bottom: 8px;
+ color: #333;
+ font-weight: 500;
+}
+
+.input-group input {
+ width: 100%;
+ padding: 12px;
+ border: 1px solid #dadce0;
+ border-radius: 4px;
+ font-size: 16px;
+ box-sizing: border-box;
+}
+
+.save-button {
+ background: #34a853;
+ color: white;
+ border: none;
+ padding: 12px 24px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 16px;
+ width: 100%;
+}
+
+.save-button:hover {
+ background: #2e8b47;
+}
\ No newline at end of file
diff --git a/src/components/gdmodal.js b/src/components/gdmodal.js
new file mode 100644
index 0000000..124e16f
--- /dev/null
+++ b/src/components/gdmodal.js
@@ -0,0 +1,164 @@
+// components/GoogleDriveModal.js
+import React, { useState, useEffect } from 'react';
+import { useGoogleDrive } from '../context/GoogleDriveContext';
+import './GoogleDriveModal.css';
+
+const GoogleDriveModal = ({ isOpen, onClose, onFileSelect, onSave }) => {
+ const [files, setFiles] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState('');
+ const [activeTab, setActiveTab] = useState('open'); // 'open' or 'save'
+ const [fileName, setFileName] = useState('');
+
+ const {
+ isAuthenticated,
+ signIn,
+ listFiles,
+ getFileContent,
+ saveToDrive
+ } = useGoogleDrive();
+
+ useEffect(() => {
+ if (isOpen && isAuthenticated) {
+ loadFiles();
+ }
+ }, [isOpen, isAuthenticated]);
+
+ const loadFiles = async () => {
+ setLoading(true);
+ setError('');
+ try {
+ const fileList = await listFiles();
+ setFiles(fileList);
+ } catch (err) {
+ setError('Failed to load files from Google Drive');
+ console.error(err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleSignIn = async () => {
+ try {
+ await signIn();
+ } catch (err) {
+ setError('Failed to sign in to Google Drive');
+ }
+ };
+
+ const handleFileSelect = async (file) => {
+ try {
+ const content = await getFileContent(file.id);
+ onFileSelect(content, file.name);
+ onClose();
+ } catch (err) {
+ setError('Failed to load file content');
+ }
+ };
+
+ const handleSave = async () => {
+ if (!fileName.trim()) {
+ setError('Please enter a file name');
+ return;
+ }
+
+ try {
+ await onSave(fileName);
+ onClose();
+ } catch (err) {
+ setError('Failed to save file to Google Drive');
+ }
+ };
+
+ if (!isOpen) return null;
+
+ return (
+
+
+
+
Google Drive
+
+
+
+ {!isAuthenticated ? (
+
+
Connect to Google Drive to save and open your diagrams
+
+
+ ) : (
+ <>
+
+
+
+
+
+ {error &&
{error}
}
+
+ {activeTab === 'open' && (
+
+
+
Your Diagrams
+
+
+ {loading ? (
+
Loading files...
+ ) : files.length === 0 ? (
+
No diagram files found in your Google Drive
+ ) : (
+
+ {files.map((file) => (
+
handleFileSelect(file)}
+ >
+
{file.name}
+
+ Modified: {new Date(file.modifiedTime).toLocaleDateString()}
+
+
+ ))}
+
+ )}
+
+ )}
+
+ {activeTab === 'save' && (
+
+
+
+ setFileName(e.target.value)}
+ placeholder="Enter file name (e.g., my-diagram.json)"
+ />
+
+
+
+ )}
+ >
+ )}
+
+
+ );
+};
+
+export default GoogleDriveModal;
\ No newline at end of file
diff --git a/src/components/partitionconfigure.jsx b/src/components/partitionconfigure.jsx
new file mode 100644
index 0000000..8629ffd
--- /dev/null
+++ b/src/components/partitionconfigure.jsx
@@ -0,0 +1,244 @@
+// components/PartitionConfigurator.jsx
+import React, { useState, useEffect } from 'react';
+import {
+ PARTITION_METHODS,
+ PARTITION_STRATEGIES,
+ createPartitionConfig,
+ generateDefaultPartitions,
+ validatePartitionConfig,
+} from '../utils/partitioning';
+import './PartitionConfigurator.css';
+
+const PartitionConfigurator = ({ tableColumns, onConfigChange }) => {
+ const [isPartitioned, setIsPartitioned] = useState(false);
+ const [partitionMethod, setPartitionMethod] = useState(PARTITION_METHODS.RANGE);
+ const [partitionColumns, setPartitionColumns] = useState([]);
+ const [partitions, setPartitions] = useState([]);
+ const [errors, setErrors] = useState([]);
+
+ useEffect(() => {
+ if (isPartitioned) {
+ const config = createPartitionConfig(partitionMethod, partitionColumns);
+ const validationErrors = validatePartitionConfig(config, partitions);
+ setErrors(validationErrors);
+
+ onConfigChange({
+ enabled: isPartitioned,
+ config: validationErrors.length === 0 ? config : null,
+ partitions: validationErrors.length === 0 ? partitions : [],
+ errors: validationErrors,
+ });
+ } else {
+ onConfigChange({ enabled: false, config: null, partitions: [], errors: [] });
+ }
+ }, [isPartitioned, partitionMethod, partitionColumns, partitions, onConfigChange]);
+
+ const handleMethodChange = (method) => {
+ setPartitionMethod(method);
+ setPartitions(generateDefaultPartitions(method));
+ };
+
+ const handleAddPartition = () => {
+ const newPartition = { name: `p${partitions.length + 1}` };
+
+ switch (partitionMethod) {
+ case PARTITION_METHODS.RANGE:
+ newPartition.from = '';
+ newPartition.to = '';
+ break;
+ case PARTITION_METHODS.LIST:
+ newPartition.values = [''];
+ break;
+ case PARTITION_METHODS.HASH:
+ newPartition.modulus = partitions.length + 1;
+ newPartition.remainder = partitions.length;
+ break;
+ }
+
+ setPartitions([...partitions, newPartition]);
+ };
+
+ const handlePartitionChange = (index, field, value) => {
+ const updatedPartitions = [...partitions];
+
+ if (field === 'values' && partitionMethod === PARTITION_METHODS.LIST) {
+ // For list values, split by comma and trim
+ updatedPartitions[index].values = value.split(',').map(v => v.trim()).filter(v => v);
+ } else {
+ updatedPartitions[index][field] = value;
+ }
+
+ setPartitions(updatedPartitions);
+ };
+
+ const handleRemovePartition = (index) => {
+ setPartitions(partitions.filter((_, i) => i !== index));
+ };
+
+ const addListValue = (partitionIndex) => {
+ const updatedPartitions = [...partitions];
+ updatedPartitions[partitionIndex].values.push('');
+ setPartitions(updatedPartitions);
+ };
+
+ const removeListValue = (partitionIndex, valueIndex) => {
+ const updatedPartitions = [...partitions];
+ updatedPartitions[partitionIndex].values = updatedPartitions[partitionIndex].values.filter((_, i) => i !== valueIndex);
+ setPartitions(updatedPartitions);
+ };
+
+ return (
+
+
+
+
+
+ {isPartitioned && (
+
+
+
+
+
+
+
+
+
+ Hold Ctrl/Cmd to select multiple columns
+
+
+
+
+ {partitions.map((partition, index) => (
+
+
handlePartitionChange(index, 'name', e.target.value)}
+ placeholder="Partition name"
+ />
+
+ {partitionMethod === PARTITION_METHODS.RANGE && (
+ <>
+
handlePartitionChange(index, 'from', e.target.value)}
+ placeholder="FROM value"
+ />
+
handlePartitionChange(index, 'to', e.target.value)}
+ placeholder="TO value"
+ />
+ >
+ )}
+
+ {partitionMethod === PARTITION_METHODS.LIST && (
+
+ {partition.values.map((value, valueIndex) => (
+
+ {
+ const newValues = [...partition.values];
+ newValues[valueIndex] = e.target.value;
+ handlePartitionChange(index, 'values', newValues.join(','));
+ }}
+ placeholder="Value"
+ />
+ {partition.values.length > 1 && (
+
+ )}
+
+ ))}
+
+
+ )}
+
+ {partitionMethod === PARTITION_METHODS.HASH && (
+ <>
+
handlePartitionChange(index, 'modulus', parseInt(e.target.value))}
+ placeholder="Modulus"
+ />
+
handlePartitionChange(index, 'remainder', parseInt(e.target.value))}
+ placeholder="Remainder"
+ />
+ >
+ )}
+
+
+
+ ))}
+
+
+
+
+ {errors.length > 0 && (
+
+
Configuration Errors:
+
+ {errors.map((error, index) => (
+ - {error}
+ ))}
+
+
+ )}
+
+ )}
+
+ );
+};
+
+export default PartitionConfigurator;
\ No newline at end of file
diff --git a/src/components/synctoolbar.jsx b/src/components/synctoolbar.jsx
new file mode 100644
index 0000000..c52b450
--- /dev/null
+++ b/src/components/synctoolbar.jsx
@@ -0,0 +1,94 @@
+// components/SyncToolbar.jsx
+import React, { useState, useEffect } from 'react';
+import { useSync } from '../context/synccontext';
+import AuthModal from './authmodal';
+import ProjectModal from './ProjectModal';
+import './SyncToolbar.css';
+
+const SyncToolbar = () => {
+ const [showAuthModal, setShowAuthModal] = useState(false);
+ const [showProjectModal, setShowProjectModal] = useState(false);
+ const [lastSaved, setLastSaved] = useState(null);
+
+ const { isAuthenticated, user, logout, autoSave } = useSync();
+
+ const handleSave = async () => {
+ // Get current project data from your editor state
+ const projectData = getCurrentProjectData(); // Implement this based on your editor
+
+ try {
+ await autoSave('current-project-id', projectData);
+ setLastSaved(new Date());
+ } catch (error) {
+ console.error('Save failed:', error);
+ }
+ };
+
+ const getCurrentProjectData = () => {
+ // This should return the current diagram/schema data from your editor
+ // Replace with actual implementation
+ return {
+ name: 'Current Project',
+ schema: {}, // Your diagram schema
+ metadata: {
+ lastModified: new Date().toISOString(),
+ },
+ };
+ };
+
+ return (
+
+ {isAuthenticated ? (
+
+ Hello, {user?.username}
+
+
+ {lastSaved && (
+
+ Last saved: {lastSaved.toLocaleTimeString()}
+
+ )}
+
+
+ ) : (
+
+ )}
+
+
setShowAuthModal(false)}
+ onSuccess={() => setShowAuthModal(false)}
+ />
+
+ setShowProjectModal(false)}
+ />
+
+ );
+};
+
+export default SyncToolbar;
\ No newline at end of file
diff --git a/src/components/tableeditor.jsx b/src/components/tableeditor.jsx
new file mode 100644
index 0000000..e8df30f
--- /dev/null
+++ b/src/components/tableeditor.jsx
@@ -0,0 +1,117 @@
+// components/TableEditor.jsx (Updated)
+import React, { useState } from 'react';
+import PartitionConfigurator from './PartitionConfigurator';
+import DataTypeSelector from './datatypeselector';
+
+const TableEditor = ({ table, onSave, onCancel }) => {
+ const [tableData, setTableData] = useState(table || { name: '', columns: [] });
+ const [showPartitioning, setShowPartitioning] = useState(false);
+ const [partitionConfig, setPartitionConfig] = useState(null);
+
+ const handleAddColumn = () => {
+ setTableData({
+ ...tableData,
+ columns: [
+ ...tableData.columns,
+ { name: '', type: 'VARCHAR', nullable: false, primaryKey: false }
+ ]
+ });
+ };
+
+ const handleColumnChange = (index, field, value) => {
+ const updatedColumns = [...tableData.columns];
+ updatedColumns[index][field] = value;
+ setTableData({ ...tableData, columns: updatedColumns });
+ };
+
+ const handleRemoveColumn = (index) => {
+ setTableData({
+ ...tableData,
+ columns: tableData.columns.filter((_, i) => i !== index)
+ });
+ };
+
+ return (
+
+
+
{table ? 'Edit Table' : 'Create Table'}
+
+
+
+
+ setTableData({ ...tableData, name: e.target.value })}
+ placeholder="Enter table name"
+ />
+
+
+
+
+
+
+
+ {showPartitioning && (
+
+ )}
+
+
+
+
+
+
+
+ );
+};
+
+export default TableEditor;
\ No newline at end of file
diff --git a/src/context/gdcontext.js b/src/context/gdcontext.js
new file mode 100644
index 0000000..2857939
--- /dev/null
+++ b/src/context/gdcontext.js
@@ -0,0 +1,227 @@
+// context/GoogleDriveContext.js
+import React, { createContext, useContext, useState, useCallback } from 'react';
+import { google } from 'googleapis';
+
+const GoogleDriveContext = createContext();
+
+export const useGoogleDrive = () => {
+ const context = useContext(GoogleDriveContext);
+ if (!context) {
+ throw new Error('useGoogleDrive must be used within a GoogleDriveProvider');
+ }
+ return context;
+};
+
+export const GoogleDriveProvider = ({ children }) => {
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+ const [gapiClient, setGapiClient] = useState(null);
+ const [tokenClient, setTokenClient] = useState(null);
+
+ // Initialize Google API client
+ const initializeGapi = useCallback(async () => {
+ try {
+ // Load the Google API client library
+ await new Promise((resolve) => {
+ if (window.gapi) {
+ resolve();
+ return;
+ }
+ const script = document.createElement('script');
+ script.src = 'https://apis.google.com/js/api.js';
+ script.onload = resolve;
+ document.head.appendChild(script);
+ });
+
+ await window.gapi.load('client', async () => {
+ await window.gapi.client.init({
+ apiKey: process.env.REACT_APP_GOOGLE_API_KEY,
+ discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'],
+ });
+ setGapiClient(window.gapi);
+ });
+ } catch (error) {
+ console.error('Error initializing Google API:', error);
+ }
+ }, []);
+
+ // Initialize Google Identity Services
+ const initializeGIS = useCallback(() => {
+ return new Promise((resolve) => {
+ if (window.google) {
+ resolve();
+ return;
+ }
+ const script = document.createElement('script');
+ script.src = 'https://accounts.google.com/gsi/client';
+ script.onload = resolve;
+ document.head.appendChild(script);
+ });
+ }, []);
+
+ // Sign in to Google
+ const signIn = useCallback(async () => {
+ try {
+ await initializeGapi();
+ await initializeGIS();
+
+ const client = window.google.accounts.oauth2.initTokenClient({
+ client_id: process.env.REACT_APP_GOOGLE_CLIENT_ID,
+ scope: 'https://www.googleapis.com/auth/drive.file',
+ callback: (response) => {
+ if (response.access_token) {
+ window.gapi.client.setToken({
+ access_token: response.access_token,
+ });
+ setIsAuthenticated(true);
+ }
+ },
+ });
+
+ client.requestAccessToken();
+ } catch (error) {
+ console.error('Error signing in:', error);
+ throw error;
+ }
+ }, [initializeGapi, initializeGIS]);
+
+ // Sign out from Google
+ const signOut = useCallback(() => {
+ if (window.gapi?.client?.getToken()) {
+ const token = window.gapi.client.getToken();
+ window.gapi.client.setToken(null);
+ setIsAuthenticated(false);
+
+ if (token) {
+ window.google.accounts.oauth2.revoke(token.access_token);
+ }
+ }
+ }, []);
+
+ // Save file to Google Drive
+ const saveToDrive = useCallback(async (fileName, content, mimeType = 'application/json') => {
+ if (!gapiClient || !isAuthenticated) {
+ throw new Error('Not authenticated with Google Drive');
+ }
+
+ try {
+ const file = new Blob([content], { type: mimeType });
+ const metadata = {
+ name: fileName,
+ mimeType: mimeType,
+ };
+
+ const form = new FormData();
+ form.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
+ form.append('file', file);
+
+ const response = await fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart', {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${gapiClient.client.getToken().access_token}`,
+ },
+ body: form,
+ });
+
+ if (!response.ok) {
+ throw new Error(`Failed to save file: ${response.statusText}`);
+ }
+
+ const result = await response.json();
+ return result;
+ } catch (error) {
+ console.error('Error saving to Google Drive:', error);
+ throw error;
+ }
+ }, [gapiClient, isAuthenticated]);
+
+ // Update existing file in Google Drive
+ const updateFile = useCallback(async (fileId, content, mimeType = 'application/json') => {
+ if (!gapiClient || !isAuthenticated) {
+ throw new Error('Not authenticated with Google Drive');
+ }
+
+ try {
+ const file = new Blob([content], { type: mimeType });
+
+ const response = await fetch(`https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=multipart`, {
+ method: 'PATCH',
+ headers: {
+ Authorization: `Bearer ${gapiClient.client.getToken().access_token}`,
+ },
+ body: file,
+ });
+
+ if (!response.ok) {
+ throw new Error(`Failed to update file: ${response.statusText}`);
+ }
+
+ const result = await response.json();
+ return result;
+ } catch (error) {
+ console.error('Error updating file in Google Drive:', error);
+ throw error;
+ }
+ }, [gapiClient, isAuthenticated]);
+
+ // List files from Google Drive
+ const listFiles = useCallback(async () => {
+ if (!gapiClient || !isAuthenticated) {
+ throw new Error('Not authenticated with Google Drive');
+ }
+
+ try {
+ const response = await gapiClient.client.drive.files.list({
+ pageSize: 100,
+ fields: 'files(id, name, mimeType, modifiedTime, createdTime)',
+ q: "mimeType='application/json' or mimeType='application/xml'",
+ orderBy: 'modifiedTime desc',
+ });
+
+ return response.result.files;
+ } catch (error) {
+ console.error('Error listing files:', error);
+ throw error;
+ }
+ }, [gapiClient, isAuthenticated]);
+
+ // Get file content from Google Drive
+ const getFileContent = useCallback(async (fileId) => {
+ if (!gapiClient || !isAuthenticated) {
+ throw new Error('Not authenticated with Google Drive');
+ }
+
+ try {
+ const response = await fetch(`https://www.googleapis.com/drive/v3/files/${fileId}?alt=media`, {
+ headers: {
+ Authorization: `Bearer ${gapiClient.client.getToken().access_token}`,
+ },
+ });
+
+ if (!response.ok) {
+ throw new Error(`Failed to get file content: ${response.statusText}`);
+ }
+
+ const content = await response.text();
+ return content;
+ } catch (error) {
+ console.error('Error getting file content:', error);
+ throw error;
+ }
+ }, [gapiClient, isAuthenticated]);
+
+ const value = {
+ isAuthenticated,
+ signIn,
+ signOut,
+ saveToDrive,
+ updateFile,
+ listFiles,
+ getFileContent,
+ };
+
+ return (
+
+ {children}
+
+ );
+};
\ No newline at end of file
diff --git a/src/context/synccontext.js b/src/context/synccontext.js
new file mode 100644
index 0000000..889f064
--- /dev/null
+++ b/src/context/synccontext.js
@@ -0,0 +1,239 @@
+// context/SyncContext.js
+import React, { createContext, useContext, useState, useCallback } from 'react';
+
+const SyncContext = createContext();
+
+export const useSync = () => {
+ const context = useContext(SyncContext);
+ if (!context) {
+ throw new Error('useSync must be used within a SyncProvider');
+ }
+ return context;
+};
+
+export const SyncProvider = ({ children }) => {
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+ const [user, setUser] = useState(null);
+ const [syncToken, setSyncToken] = useState(localStorage.getItem('drawdb_sync_token'));
+
+ // API base URL - adjust based on your environment
+ const API_BASE = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3001/api';
+
+ // Register new account
+ const register = useCallback(async (email, password, username) => {
+ try {
+ const response = await fetch(`${API_BASE}/auth/register`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ email, password, username }),
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.message || 'Registration failed');
+ }
+
+ const data = await response.json();
+ setUser(data.user);
+ setSyncToken(data.token);
+ setIsAuthenticated(true);
+ localStorage.setItem('drawdb_sync_token', data.token);
+
+ return data;
+ } catch (error) {
+ console.error('Registration error:', error);
+ throw error;
+ }
+ }, [API_BASE]);
+
+ // Login to existing account
+ const login = useCallback(async (email, password) => {
+ try {
+ const response = await fetch(`${API_BASE}/auth/login`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ email, password }),
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.message || 'Login failed');
+ }
+
+ const data = await response.json();
+ setUser(data.user);
+ setSyncToken(data.token);
+ setIsAuthenticated(true);
+ localStorage.setItem('drawdb_sync_token', data.token);
+
+ return data;
+ } catch (error) {
+ console.error('Login error:', error);
+ throw error;
+ }
+ }, [API_BASE]);
+
+ // Login with sync token
+ const loginWithToken = useCallback(async (token) => {
+ try {
+ const response = await fetch(`${API_BASE}/auth/me`, {
+ method: 'GET',
+ headers: {
+ 'Authorization': `Bearer ${token}`,
+ },
+ });
+
+ if (!response.ok) {
+ throw new Error('Token authentication failed');
+ }
+
+ const data = await response.json();
+ setUser(data.user);
+ setSyncToken(token);
+ setIsAuthenticated(true);
+
+ return data;
+ } catch (error) {
+ console.error('Token login error:', error);
+ localStorage.removeItem('drawdb_sync_token');
+ throw error;
+ }
+ }, [API_BASE]);
+
+ // Logout
+ const logout = useCallback(() => {
+ setUser(null);
+ setSyncToken(null);
+ setIsAuthenticated(false);
+ localStorage.removeItem('drawdb_sync_token');
+ }, []);
+
+ // Save project to server
+ const saveProject = useCallback(async (projectId, projectData) => {
+ if (!syncToken) {
+ throw new Error('Not authenticated');
+ }
+
+ try {
+ const response = await fetch(`${API_BASE}/projects/${projectId || ''}`, {
+ method: projectId ? 'PUT' : 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${syncToken}`,
+ },
+ body: JSON.stringify({
+ name: projectData.name || 'Untitled Project',
+ schema: projectData.schema,
+ metadata: projectData.metadata,
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to save project');
+ }
+
+ return await response.json();
+ } catch (error) {
+ console.error('Save project error:', error);
+ throw error;
+ }
+ }, [syncToken, API_BASE]);
+
+ // Load project from server
+ const loadProject = useCallback(async (projectId) => {
+ if (!syncToken) {
+ throw new Error('Not authenticated');
+ }
+
+ try {
+ const response = await fetch(`${API_BASE}/projects/${projectId}`, {
+ headers: {
+ 'Authorization': `Bearer ${syncToken}`,
+ },
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to load project');
+ }
+
+ return await response.json();
+ } catch (error) {
+ console.error('Load project error:', error);
+ throw error;
+ }
+ }, [syncToken, API_BASE]);
+
+ // List user's projects
+ const listProjects = useCallback(async () => {
+ if (!syncToken) {
+ throw new Error('Not authenticated');
+ }
+
+ try {
+ const response = await fetch(`${API_BASE}/projects`, {
+ headers: {
+ 'Authorization': `Bearer ${syncToken}`,
+ },
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to load projects');
+ }
+
+ return await response.json();
+ } catch (error) {
+ console.error('List projects error:', error);
+ throw error;
+ }
+ }, [syncToken, API_BASE]);
+
+ // Auto-save project
+ const autoSave = useCallback(async (projectId, projectData) => {
+ if (!syncToken) return null;
+
+ try {
+ const response = await fetch(`${API_BASE}/projects/${projectId}/autosave`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${syncToken}`,
+ },
+ body: JSON.stringify({
+ schema: projectData.schema,
+ lastModified: new Date().toISOString(),
+ }),
+ });
+
+ if (response.ok) {
+ return await response.json();
+ }
+ } catch (error) {
+ console.error('Auto-save error:', error);
+ }
+ return null;
+ }, [syncToken, API_BASE]);
+
+ const value = {
+ isAuthenticated,
+ user,
+ syncToken,
+ register,
+ login,
+ loginWithToken,
+ logout,
+ saveProject,
+ loadProject,
+ listProjects,
+ autoSave,
+ };
+
+ return (
+
+ {children}
+
+ );
+};
\ No newline at end of file
diff --git a/src/pages/Editor.jsx b/src/pages/Editor.jsx
index 24b5afe..e2348e6 100644
--- a/src/pages/Editor.jsx
+++ b/src/pages/Editor.jsx
@@ -41,3 +41,64 @@ export default function Editor() {
);
}
+
+// pages/Editor.js
+import React, { useState, useCallback } from 'react';
+import { useGoogleDrive } from '../context/GoogleDriveContext';
+import GoogleDriveModal from '../components/GoogleDriveModal';
+
+const Editor = () => {
+ const [isDriveModalOpen, setIsDriveModalOpen] = useState(false);
+ const { saveToDrive } = useGoogleDrive();
+
+ // Your existing editor state and functions...
+ const [diagramData, setDiagramData] = useState(''); // Your actual diagram data
+
+ const handleSaveToDrive = useCallback(async (fileName) => {
+ // Convert your diagram data to the appropriate format
+ const content = JSON.stringify({
+ diagramData: diagramData,
+ version: '1.0',
+ timestamp: new Date().toISOString(),
+ });
+
+ await saveToDrive(fileName, content);
+ // Show success message or handle completion
+ }, [diagramData, saveToDrive]);
+
+ const handleOpenFromDrive = useCallback((content, fileName) => {
+ // Parse the content and load it into your editor
+ try {
+ const data = JSON.parse(content);
+ setDiagramData(data.diagramData);
+ // Show success message
+ } catch (error) {
+ console.error('Error parsing file content:', error);
+ }
+ }, []);
+
+ return (
+
+ {/* Your existing editor UI */}
+
+ {/* Add Google Drive buttons to your toolbar */}
+
+
+
+ {/* Your other toolbar buttons */}
+
+
+
setIsDriveModalOpen(false)}
+ onFileSelect={handleOpenFromDrive}
+ onSave={handleSaveToDrive}
+ />
+
+ );
+};
\ No newline at end of file
diff --git a/src/utils/dataType.js b/src/utils/dataType.js
new file mode 100644
index 0000000..8895c7b
--- /dev/null
+++ b/src/utils/dataType.js
@@ -0,0 +1,168 @@
+// utils/dataTypes.js
+export const BASE_DATA_TYPES = {
+ // PostgreSQL standard types
+ INTEGER: 'INTEGER',
+ BIGINT: 'BIGINT',
+ SMALLINT: 'SMALLINT',
+ SERIAL: 'SERIAL',
+ BIGSERIAL: 'BIGSERIAL',
+ NUMERIC: 'NUMERIC',
+ DECIMAL: 'DECIMAL',
+ REAL: 'REAL',
+ DOUBLE_PRECISION: 'DOUBLE_PRECISION',
+ VARCHAR: 'VARCHAR',
+ CHAR: 'CHAR',
+ TEXT: 'TEXT',
+ BOOLEAN: 'BOOLEAN',
+ DATE: 'DATE',
+ TIME: 'TIME',
+ TIMESTAMP: 'TIMESTAMP',
+ TIMESTAMPTZ: 'TIMESTAMPTZ',
+ INTERVAL: 'INTERVAL',
+ JSON: 'JSON',
+ JSONB: 'JSONB',
+ UUID: 'UUID',
+ BYTEA: 'BYTEA',
+};
+
+export const CUSTOM_DATA_TYPES = {
+ // Popular custom types
+ ULID: 'ULID',
+ EMAIL: 'EMAIL',
+ URL: 'URL',
+ IP_ADDRESS: 'IP_ADDRESS',
+ MAC_ADDR: 'MAC_ADDR',
+ INET: 'INET',
+ CIDR: 'CIDR',
+ TSVECTOR: 'TSVECTOR',
+ LTREE: 'LTREE',
+};
+
+export const ENUM_TYPE = 'USER_DEFINED_ENUM';
+
+// Data type categories for organization
+export const DATA_TYPE_CATEGORIES = {
+ NUMERIC: {
+ label: 'Numeric',
+ types: [
+ BASE_DATA_TYPES.INTEGER,
+ BASE_DATA_TYPES.BIGINT,
+ BASE_DATA_TYPES.SMALLINT,
+ BASE_DATA_TYPES.SERIAL,
+ BASE_DATA_TYPES.BIGSERIAL,
+ BASE_DATA_TYPES.NUMERIC,
+ BASE_DATA_TYPES.DECIMAL,
+ BASE_DATA_TYPES.REAL,
+ BASE_DATA_TYPES.DOUBLE_PRECISION,
+ ]
+ },
+ STRING: {
+ label: 'String',
+ types: [
+ BASE_DATA_TYPES.VARCHAR,
+ BASE_DATA_TYPES.CHAR,
+ BASE_DATA_TYPES.TEXT,
+ ]
+ },
+ TEMPORAL: {
+ label: 'Date/Time',
+ types: [
+ BASE_DATA_TYPES.DATE,
+ BASE_DATA_TYPES.TIME,
+ BASE_DATA_TYPES.TIMESTAMP,
+ BASE_DATA_TYPES.TIMESTAMPTZ,
+ BASE_DATA_TYPES.INTERVAL,
+ ]
+ },
+ BOOLEAN: {
+ label: 'Boolean',
+ types: [BASE_DATA_TYPES.BOOLEAN]
+ },
+ JSON: {
+ label: 'JSON',
+ types: [
+ BASE_DATA_TYPES.JSON,
+ BASE_DATA_TYPES.JSONB,
+ ]
+ },
+ NETWORK: {
+ label: 'Network',
+ types: [
+ CUSTOM_DATA_TYPES.INET,
+ CUSTOM_DATA_TYPES.CIDR,
+ CUSTOM_DATA_TYPES.MAC_ADDR,
+ CUSTOM_DATA_TYPES.IP_ADDRESS,
+ ]
+ },
+ CUSTOM: {
+ label: 'Custom',
+ types: [
+ CUSTOM_DATA_TYPES.ULID,
+ CUSTOM_DATA_TYPES.EMAIL,
+ CUSTOM_DATA_TYPES.URL,
+ ENUM_TYPE,
+ ]
+ },
+ OTHER: {
+ label: 'Other',
+ types: [
+ BASE_DATA_TYPES.UUID,
+ BASE_DATA_TYPES.BYTEA,
+ CUSTOM_DATA_TYPES.TSVECTOR,
+ CUSTOM_DATA_TYPES.LTREE,
+ ]
+ }
+};
+
+// Data type templates for custom types
+export const CUSTOM_TYPE_TEMPLATES = {
+ [CUSTOM_DATA_TYPES.ULID]: {
+ name: 'ULID',
+ description: 'Universally Unique Lexicographically Sortable Identifier',
+ definition: 'ULID',
+ requiresExtension: false,
+ },
+ [CUSTOM_DATA_TYPES.EMAIL]: {
+ name: 'EMAIL',
+ description: 'Email address with validation',
+ definition: 'VARCHAR(255) CHECK (value ~* \'^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$\')',
+ requiresExtension: false,
+ },
+ [ENUM_TYPE]: {
+ name: 'ENUM',
+ description: 'User-defined enumerated type',
+ definition: null, // Will be defined by user
+ requiresExtension: false,
+ isEnum: true,
+ },
+};
+
+// Get SQL definition for a data type
+export const getDataTypeDefinition = (dataType, enumValues = [], length = null) => {
+ if (dataType === ENUM_TYPE) {
+ return enumValues.length > 0 ? `ENUM('${enumValues.join("','")}')` : 'TEXT';
+ }
+
+ const template = CUSTOM_TYPE_TEMPLATES[dataType];
+ if (template) {
+ return template.definition;
+ }
+
+ // Handle length for string types
+ if ((dataType === BASE_DATA_TYPES.VARCHAR || dataType === BASE_DATA_TYPES.CHAR) && length) {
+ return `${dataType}(${length})`;
+ }
+
+ if (dataType === BASE_DATA_TYPES.NUMERIC && length) {
+ return `${dataType}(${length})`;
+ }
+
+ return dataType;
+};
+
+// Validate data type
+export const isValidDataType = (dataType) => {
+ return Object.values(BASE_DATA_TYPES).includes(dataType) ||
+ Object.values(CUSTOM_DATA_TYPES).includes(dataType) ||
+ dataType === ENUM_TYPE;
+};
\ No newline at end of file
diff --git a/src/utils/partitioning.js b/src/utils/partitioning.js
new file mode 100644
index 0000000..40da8fe
--- /dev/null
+++ b/src/utils/partitioning.js
@@ -0,0 +1,151 @@
+// utils/partitioning.js
+export const PARTITION_METHODS = {
+ RANGE: 'RANGE',
+ LIST: 'LIST',
+ HASH: 'HASH',
+};
+
+export const PARTITION_STRATEGIES = {
+ BY_RANGE: 'BY RANGE',
+ BY_LIST: 'BY LIST',
+ BY_HASH: 'BY HASH',
+};
+
+// Partition configuration template
+export const createPartitionConfig = (method, columns, options = {}) => {
+ const config = {
+ method,
+ columns: Array.isArray(columns) ? columns : [columns],
+ options,
+ };
+
+ switch (method) {
+ case PARTITION_METHODS.RANGE:
+ config.definition = `PARTITION BY RANGE (${config.columns.join(', ')})`;
+ break;
+ case PARTITION_METHODS.LIST:
+ config.definition = `PARTITION BY LIST (${config.columns.join(', ')})`;
+ break;
+ case PARTITION_METHODS.HASH:
+ config.definition = `PARTITION BY HASH (${config.columns.join(', ')})`;
+ break;
+ default:
+ config.definition = '';
+ }
+
+ return config;
+};
+
+// Generate partition DDL
+export const generatePartitionDDL = (tableName, partitionConfig, partitions = []) => {
+ const baseDDL = `CREATE TABLE ${tableName} (\n -- your columns here\n) ${partitionConfig.definition};\n\n`;
+
+ const partitionDDL = partitions.map(partition => {
+ switch (partitionConfig.method) {
+ case PARTITION_METHODS.RANGE:
+ return `CREATE TABLE ${tableName}_${partition.name}\nPARTITION OF ${tableName}\nFOR VALUES FROM ('${partition.from}') TO ('${partition.to}');`;
+
+ case PARTITION_METHODS.LIST:
+ const values = partition.values.map(v => `'${v}'`).join(', ');
+ return `CREATE TABLE ${tableName}_${partition.name}\nPARTITION OF ${tableName}\nFOR VALUES IN (${values});`;
+
+ case PARTITION_METHODS.HASH:
+ return `CREATE TABLE ${tableName}_${partition.name}\nPARTITION OF ${tableName}\nFOR VALUES WITH (MODULUS ${partition.modulus}, REMAINDER ${partition.remainder});`;
+
+ default:
+ return '';
+ }
+ }).join('\n\n');
+
+ return baseDDL + partitionDDL;
+};
+
+// Validate partition configuration
+export const validatePartitionConfig = (partitionConfig, partitions) => {
+ const errors = [];
+
+ if (!partitionConfig.columns || partitionConfig.columns.length === 0) {
+ errors.push('At least one partition column is required');
+ }
+
+ if (partitions.length === 0) {
+ errors.push('At least one partition is required');
+ }
+
+ switch (partitionConfig.method) {
+ case PARTITION_METHODS.RANGE:
+ partitions.forEach((partition, index) => {
+ if (!partition.from || !partition.to) {
+ errors.push(`Partition ${partition.name} must have both FROM and TO values`);
+ }
+ });
+ break;
+
+ case PARTITION_METHODS.LIST:
+ partitions.forEach((partition, index) => {
+ if (!partition.values || partition.values.length === 0) {
+ errors.push(`Partition ${partition.name} must have at least one value`);
+ }
+ });
+ break;
+
+ case PARTITION_METHODS.HASH:
+ partitions.forEach((partition, index) => {
+ if (partition.modulus === undefined || partition.remainder === undefined) {
+ errors.push(`Partition ${partition.name} must have modulus and remainder`);
+ }
+ if (partition.remainder >= partition.modulus) {
+ errors.push(`Partition ${partition.name} remainder must be less than modulus`);
+ }
+ });
+
+ // Check that all remainders are unique and cover the range
+ const remainders = partitions.map(p => p.remainder);
+ const uniqueRemainders = new Set(remainders);
+ if (uniqueRemainders.size !== remainders.length) {
+ errors.push('All partition remainders must be unique');
+ }
+ break;
+ }
+
+ return errors;
+};
+
+// Generate default partitions based on method
+export const generateDefaultPartitions = (method, count = 4) => {
+ switch (method) {
+ case PARTITION_METHODS.RANGE:
+ // Generate monthly partitions for the next year
+ const ranges = [];
+ const now = new Date();
+ for (let i = 0; i < count; i++) {
+ const month = new Date(now.getFullYear(), now.getMonth() + i, 1);
+ const nextMonth = new Date(now.getFullYear(), now.getMonth() + i + 1, 1);
+ ranges.push({
+ name: `p${(month.getMonth() + 1).toString().padStart(2, '0')}_${month.getFullYear()}`,
+ from: month.toISOString().split('T')[0],
+ to: nextMonth.toISOString().split('T')[0],
+ });
+ }
+ return ranges;
+
+ case PARTITION_METHODS.LIST:
+ // Generate partitions for common categories
+ return [
+ { name: 'active', values: ['active', 'enabled'] },
+ { name: 'inactive', values: ['inactive', 'disabled'] },
+ { name: 'pending', values: ['pending', 'draft'] },
+ ];
+
+ case PARTITION_METHODS.HASH:
+ // Generate hash partitions
+ return Array.from({ length: count }, (_, i) => ({
+ name: `p${i}`,
+ modulus: count,
+ remainder: i,
+ }));
+
+ default:
+ return [];
+ }
+};
\ No newline at end of file