Skip to content

Commit d84e5cd

Browse files
committed
feat: embed WordPress plugin template and improve file handling
- Embed WordPress plugin template directly in code instead of external file - Add more file extensions to .gitignore (.zip, .tar.gz, .sql, .sql.gz) - Add plugin path field to profile configuration - Set default export/plugin paths to executable directory - Implement WordPress plugin with export/import functionality
1 parent 56f649a commit d84e5cd

File tree

5 files changed

+190
-23
lines changed

5 files changed

+190
-23
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
logs.json
22
profiles.json
33
dback-linux
4-
dback-windows.exe
4+
dback-windows.exe
5+
*.zip
6+
*.tar.gz
7+
*.sql
8+
*.sql.gz

backend/wordpress/generator.go

Lines changed: 150 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,157 @@ import (
55
"crypto/rand"
66
"encoding/hex"
77
"fmt"
8-
"io/ioutil"
98
"os"
109
"strings"
1110
)
1211

12+
const pluginTemplate = `<?php
13+
14+
/**
15+
* Plugin Name: DB Sync Connector
16+
* Description: generated by DB Sync Manager App. Handles database export/import securely.
17+
* Version: 1.0.0
18+
* Author: DB Sync Manager
19+
*/
20+
21+
if (!defined('ABSPATH')) {
22+
exit;
23+
}
24+
25+
define('DBACK_API_KEY', '{{API_KEY}}');
26+
27+
class DBack_Sync_API {
28+
29+
public function __construct() {
30+
add_action('rest_api_init', array($this, 'register_routes'));
31+
}
32+
33+
public function register_routes() {
34+
register_rest_route('dback/v1', '/export', array(
35+
'methods' => 'GET',
36+
'callback' => array($this, 'handle_export'),
37+
'permission_callback' => array($this, 'check_permission'),
38+
));
39+
40+
register_rest_route('dback/v1', '/import', array(
41+
'methods' => 'POST',
42+
'callback' => array($this, 'handle_import'),
43+
'permission_callback' => array($this, 'check_permission'),
44+
));
45+
}
46+
47+
public function check_permission($request) {
48+
$auth_header = $request->get_header('X-DBACK-KEY');
49+
return $auth_header === DBACK_API_KEY;
50+
}
51+
52+
public function handle_export($request) {
53+
// Increase limits
54+
set_time_limit(0);
55+
ini_set('memory_limit', '512M');
56+
57+
global $wpdb;
58+
$db_name = DB_NAME;
59+
$db_user = DB_USER;
60+
$db_pass = DB_PASSWORD;
61+
$db_host = DB_HOST;
62+
63+
// Use mysqldump via exec if available (fastest)
64+
$dump_file = wp_upload_dir()['basedir'] . '/dback_dump_' . time() . '.sql.gz';
65+
66+
// Command: mysqldump ... | gzip > file
67+
// Note: We rely on system mysqldump.
68+
$cmd = sprintf(
69+
'mysqldump -h %s -u %s -p%s %s | gzip > %s',
70+
escapeshellarg($db_host),
71+
escapeshellarg($db_user),
72+
escapeshellarg($db_pass),
73+
escapeshellarg($db_name),
74+
escapeshellarg($dump_file)
75+
);
76+
77+
// Execute
78+
exec($cmd, $output, $return_var);
79+
80+
if ($return_var !== 0) {
81+
return new WP_Error('export_failed', 'mysqldump command failed', array('status' => 500));
82+
}
83+
84+
if (!file_exists($dump_file)) {
85+
return new WP_Error('export_failed', 'Dump file not found', array('status' => 500));
86+
}
87+
88+
// Stream file download
89+
$file_size = filesize($dump_file);
90+
91+
// Clean buffer
92+
if (ob_get_level()) ob_end_clean();
93+
94+
header('Content-Description: File Transfer');
95+
header('Content-Type: application/gzip');
96+
header('Content-Disposition: attachment; filename=' . basename($dump_file));
97+
header('Content-Length: ' . $file_size);
98+
header('Expires: 0');
99+
header('Cache-Control: must-revalidate');
100+
header('Pragma: public');
101+
102+
readfile($dump_file);
103+
104+
// Delete after send
105+
unlink($dump_file);
106+
exit;
107+
}
108+
109+
public function handle_import($request) {
110+
// Logic to receive file and pipe to mysql
111+
// PHP REST API usually handles small bodies. For large dumps, we normally stream.
112+
// But WP REST API buffers body?
113+
// Ideally, we should read from php://input.
114+
115+
set_time_limit(0);
116+
117+
global $wpdb;
118+
$db_name = DB_NAME;
119+
$db_user = DB_USER;
120+
$db_pass = DB_PASSWORD;
121+
$db_host = DB_HOST;
122+
123+
// We can't easily pipe php://input to mysql directly in some setups if body is parsed.
124+
// But let's try saving to temp file first.
125+
$upload_dir = wp_upload_dir()['basedir'];
126+
$temp_file = $upload_dir . '/dback_import_' . time() . '.sql.gz';
127+
128+
$input = fopen('php://input', 'rb');
129+
$file = fopen($temp_file, 'wb');
130+
stream_copy_to_stream($input, $file);
131+
fclose($input);
132+
fclose($file);
133+
134+
// Now run mysql command
135+
// gunzip -c file | mysql ...
136+
$cmd = sprintf(
137+
'gunzip -c %s | mysql -h %s -u %s -p%s %s',
138+
escapeshellarg($temp_file),
139+
escapeshellarg($db_host),
140+
escapeshellarg($db_user),
141+
escapeshellarg($db_pass),
142+
escapeshellarg($db_name)
143+
);
144+
145+
exec($cmd, $output, $return_var);
146+
147+
unlink($temp_file);
148+
149+
if ($return_var !== 0) {
150+
return new WP_Error('import_failed', 'mysql command failed', array('status' => 500));
151+
}
152+
153+
return rest_ensure_response(array('success' => true, 'message' => 'Database imported successfully'));
154+
}
155+
}
156+
157+
new DBack_Sync_API();`
158+
13159
// GeneratePlugin creates a WP plugin zip with a unique key
14160
func GeneratePlugin(templatePath, destDir string) (string, string, error) {
15161
// Generate Key
@@ -18,14 +164,11 @@ func GeneratePlugin(templatePath, destDir string) (string, string, error) {
18164
return "", "", err
19165
}
20166

21-
// Read Template
22-
content, err := ioutil.ReadFile(templatePath)
23-
if err != nil {
24-
return "", "", fmt.Errorf("failed to read template: %v", err)
25-
}
167+
// Use embedded template
168+
content := pluginTemplate
26169

27170
// Replace Key
28-
pluginCode := strings.ReplaceAll(string(content), "{{API_KEY}}", key)
171+
pluginCode := strings.ReplaceAll(content, "{{API_KEY}}", key)
29172

30173
// Create Zip
31174
zipPath := fmt.Sprintf("%s/dback-sync-plugin.zip", destDir)

models/models.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ type Profile struct {
4444
AuthKeyPath string `json:"auth_key_path"`
4545

4646
// WordPress Fields
47-
WPUrl string `json:"wp_url"` // e.g. https://example.com
48-
WPKey string `json:"wp_key"` // The API key shared with plugin
47+
WPUrl string `json:"wp_url"` // e.g. https://example.com
48+
WPKey string `json:"wp_key"` // The API key shared with plugin
49+
PluginPath string `json:"plugin_path"` // Path to save generated plugin
4950

5051
DBHost string `json:"db_host"`
5152
DBPort string `json:"db_port"`

ui/export.go

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,22 +67,27 @@ func (u *UI) createExportTab(w fyne.Window) fyne.CanvasObject {
6767
u.expWPKeyEntry = widget.NewEntry()
6868
u.expWPKeyEntry.SetPlaceHolder("API Key")
6969

70-
generatePluginBtn := widget.NewButton("Generate Plugin", func() {
71-
// Ask for save location
70+
u.expWPPluginPathEntry = widget.NewEntry()
71+
u.expWPPluginPathEntry.SetText(u.getExecutableDir())
72+
pluginPathBtn := widget.NewButton("Browse", func() {
7273
dialog.ShowFolderOpen(func(uri fyne.ListableURI, err error) {
73-
if err != nil || uri == nil {
74-
return
75-
}
76-
77-
key, path, err := wordpress.GeneratePlugin("plugin_template/dback-sync.php", uri.Path())
78-
if err != nil {
79-
dialog.ShowError(err, w)
80-
return
74+
if err == nil && uri != nil {
75+
u.expWPPluginPathEntry.SetText(uri.Path())
8176
}
82-
u.expWPKeyEntry.SetText(key)
83-
dialog.ShowInformation("Plugin Generated", fmt.Sprintf("Plugin saved to %s\nAPI Key has been set.", path), w)
8477
}, w)
8578
})
79+
pluginPathContainer := container.NewBorder(nil, nil, nil, pluginPathBtn, u.expWPPluginPathEntry)
80+
81+
generatePluginBtn := widget.NewButton("Generate Plugin", func() {
82+
destDir := u.expWPPluginPathEntry.Text
83+
key, path, err := wordpress.GeneratePlugin("plugin_template/dback-sync.php", destDir)
84+
if err != nil {
85+
dialog.ShowError(err, w)
86+
return
87+
}
88+
u.expWPKeyEntry.SetText(key)
89+
dialog.ShowInformation("Plugin Generated", fmt.Sprintf("Plugin saved to %s\nAPI Key has been set.", path), w)
90+
})
8691

8792
// Containers
8893
sshContainer := container.NewVBox(
@@ -100,6 +105,7 @@ func (u *UI) createExportTab(w fyne.Window) fyne.CanvasObject {
100105
widget.NewForm(
101106
widget.NewFormItem("WordPress URL", u.expWPUrlEntry),
102107
widget.NewFormItem("API Key", u.expWPKeyEntry),
108+
widget.NewFormItem("Plugin Save Path", pluginPathContainer),
103109
),
104110
generatePluginBtn,
105111
)
@@ -298,7 +304,7 @@ func (u *UI) createExportTab(w fyne.Window) fyne.CanvasObject {
298304
dbGroup := widget.NewCard("Source Database", "", dbContainer)
299305

300306
// --- Action ---
301-
u.expDestPathLabel = widget.NewLabel("No folder selected")
307+
u.expDestPathLabel = widget.NewLabel(u.getExecutableDir())
302308

303309
selectFolderBtn := widget.NewButton("Select Destination Folder", func() {
304310
fd := dialog.NewFolderOpen(func(uri fyne.ListableURI, err error) {

ui/ui.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"io/ioutil"
77
"os"
8+
"path/filepath"
89
"time"
910

1011
"dback/models"
@@ -28,6 +29,7 @@ type UI struct {
2829
expConnectionTypeSelect *widget.Select
2930
expWPUrlEntry *widget.Entry
3031
expWPKeyEntry *widget.Entry
32+
expWPPluginPathEntry *widget.Entry
3133

3234
expHostEntry *widget.Entry
3335
expPortEntry *widget.Entry
@@ -121,6 +123,7 @@ func (u *UI) Run() {
121123
u.profiles[i].ConnectionType = models.ConnectionType(u.expConnectionTypeSelect.Selected)
122124
u.profiles[i].WPUrl = u.expWPUrlEntry.Text
123125
u.profiles[i].WPKey = u.expWPKeyEntry.Text
126+
u.profiles[i].PluginPath = u.expWPPluginPathEntry.Text
124127

125128
u.profiles[i].Host = u.expHostEntry.Text
126129
u.profiles[i].Port = u.expPortEntry.Text
@@ -185,6 +188,7 @@ func (u *UI) Run() {
185188
u.expConnectionTypeSelect.SetSelected(string(p.ConnectionType))
186189
u.expWPUrlEntry.SetText(p.WPUrl)
187190
u.expWPKeyEntry.SetText(p.WPKey)
191+
u.expWPPluginPathEntry.SetText(p.PluginPath)
188192

189193
u.expHostEntry.SetText(p.Host)
190194
u.expPortEntry.SetText(p.Port)
@@ -223,6 +227,7 @@ func (u *UI) Run() {
223227
ConnectionType: models.ConnectionType(u.expConnectionTypeSelect.Selected),
224228
WPUrl: u.expWPUrlEntry.Text,
225229
WPKey: u.expWPKeyEntry.Text,
230+
PluginPath: u.expWPPluginPathEntry.Text,
226231

227232
Host: u.expHostEntry.Text,
228233
Port: u.expPortEntry.Text,
@@ -301,3 +306,11 @@ func (u *UI) showErrorAndLog(title string, err error, action string) {
301306
u.log(action, fmt.Sprintf("%s: %v", title, err), "", "Failed", err.Error())
302307
dialog.ShowError(err, u.window)
303308
}
309+
310+
func (u *UI) getExecutableDir() string {
311+
exe, err := os.Executable()
312+
if err != nil {
313+
return "."
314+
}
315+
return filepath.Dir(exe)
316+
}

0 commit comments

Comments
 (0)