Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
cf3b2b8
refactor: enhance migration executor with slug-based function naming …
Flatts3000 Oct 2, 2025
0b33f12
feat: add migration filtering by slug or filename with improved input…
Flatts3000 Oct 2, 2025
6e2b10e
feat: overhaul Classifier with robust OpenAI integration and resilien…
Flatts3000 Oct 2, 2025
5ea6957
feat: add REST API endpoint for debug log inspection with line limit …
Flatts3000 Oct 2, 2025
78cc5d4
fix: update debug-log endpoint permissions to restrict access
Flatts3000 Oct 2, 2025
2e0985e
fix: ensure default detail level in Classifier method to prevent inva…
Flatts3000 Oct 2, 2025
46a9e3e
fix: relax debug-log endpoint permissions to only require logged-in u…
Flatts3000 Oct 2, 2025
8e0d82c
feat: add helper functions for consistent error handling and improve …
Flatts3000 Oct 2, 2025
dcde72b
feat: enhance EmbeddingService with detailed error handling and impro…
Flatts3000 Oct 3, 2025
587ab7a
feat: improve error handling for embedding and update schema with new…
Flatts3000 Oct 3, 2025
2a0da29
feat: add embedding input builder and enhance Qdrant upsert response …
Flatts3000 Oct 3, 2025
ba6c1f0
feat: add input_hash column to embeddings table and enhance error han…
Flatts3000 Oct 3, 2025
d9b09a6
feat: add Qdrant diagnostic endpoint and enhance error logging/debugging
Flatts3000 Oct 3, 2025
e85fa89
feat: enhance Qdrant diagnostics and error handling
Flatts3000 Oct 3, 2025
0980920
feat: add IP detection endpoint and enhance Qdrant diagnostics
Flatts3000 Oct 3, 2025
4f93808
feat: add Qdrant initialization endpoint and enhance admin UI
Flatts3000 Oct 3, 2025
114b74e
feat: improve semantic search UI/UX with dark mode, animations, and a…
Flatts3000 Oct 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*
* @global \wpdb $wpdb
*/
function up() {
function up_001_init() {
global $wpdb;

$charset_collate = $wpdb->get_charset_collate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*
* @global \wpdb $wpdb
*/
function up() {
function up_002_facets() {
global $wpdb;

// Drop ps_tag_alias table - no longer needed with facets
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*
* @global \wpdb $wpdb
*/
function up() {
function up_003_embeddings() {
global $wpdb;

$charset_collate = $wpdb->get_charset_collate();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php
/**
* Migration: Add input_hash column to ps_text_embeddings table.
*
* The input_hash column stores a SHA-256 hash of the embedding input
* (topics, feelings, meanings, text) to avoid regenerating embeddings
* when the input hasn't changed.
*
* @package PostSecret\Admin
*/

namespace PostSecret\Admin\Migrations;

/**
* Run the migration.
*
* @global \wpdb $wpdb
*/
function up_004_embeddings_input_hash() {
global $wpdb;

$table_name = $wpdb->prefix . 'ps_text_embeddings';

// Check if column already exists
$column_exists = $wpdb->get_results(
$wpdb->prepare(
"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = %s
AND TABLE_NAME = %s
AND COLUMN_NAME = 'input_hash'",
DB_NAME,
$table_name
)
);

if (empty($column_exists)) {
$wpdb->query(
"ALTER TABLE $table_name
ADD COLUMN input_hash varchar(64) NULL AFTER dimension"
);
}
}
123 changes: 96 additions & 27 deletions wp-content/plugins/postsecret-admin/run-migrations.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,128 @@
/**
* Manual migration runner for PostSecret Admin.
*
* Run this file directly to execute all pending migrations.
* Or visit: wp-admin/admin.php?page=postsecret-run-migrations
* Run this file directly to execute all pending migrations,
* or pass ?only=<slug[,slug2,...]> to run specific ones.
*
* @package PostSecret\Admin
*/

// Load WordPress
// Path: /wp-content/plugins/postsecret-admin/ -> /wp-load.php
require_once __DIR__ . '/../../../wp-load.php';

if ( ! current_user_can( 'manage_options' ) && ! defined( 'WP_CLI' ) ) {
wp_die( 'Unauthorized' );
if (!current_user_can('manage_options') && !defined('WP_CLI')) {
wp_die('Unauthorized');
}

global $wpdb;

echo "<h1>PostSecret Database Migrations</h1>\n";

$migrations_dir = __DIR__ . '/migrations/';
$migrations = glob( $migrations_dir . '*.php' );
sort( $migrations );
$migrations = glob($migrations_dir . '*.php');
sort($migrations);

/**
* Turn a filename (e.g., "002_facets.php") into a safe slug "002_facets".
*/
$slugify = static function (string $filename): string {
$base = pathinfo($filename, PATHINFO_FILENAME);
return preg_replace('/[^a-zA-Z0-9_]+/', '_', strtolower($base));
};

/**
* Optional filter: ?only=003_embeddings or ?only=001_init,003_embeddings.php
* Accepts slugs (without .php) or exact filenames; case-insensitive.
*/
$only_raw = isset($_GET['only']) ? trim((string)$_GET['only']) : '';
if ($only_raw !== '') {
$only_set = [];
foreach (preg_split('/\s*,\s*/', $only_raw, -1, PREG_SPLIT_NO_EMPTY) as $piece) {
$p = strtolower($piece);
// accept either "003_embeddings" or "003_embeddings.php"
$only_set[$p] = true;
if (substr($p, -4) !== '.php') {
$only_set[$p . '.php'] = true;
} else {
$only_set[substr($p, 0, -4)] = true; // slug form
}
// also accept slugified variant of whatever was passed
$only_set[$slugify($p)] = true;
}

foreach ( $migrations as $migration_file ) {
$migration_name = basename( $migration_file );
echo "<p>Running migration: <strong>{$migration_name}</strong>...";
$migrations = array_values(array_filter($migrations, function ($file) use ($only_set, $slugify) {
$base = strtolower(basename($file)); // e.g. 003_embeddings.php
$slug = strtolower($slugify($base)); // e.g. 003_embeddings
return isset($only_set[$base]) || isset($only_set[$slug]);
}));

// Create isolated scope and execute
$result = ( function() use ( $migration_file, $wpdb ) {
if (empty($migrations)) {
echo "<p><em>No migrations matched <code>" . esc_html($only_raw) . "</code>.</em></p>";
echo "<p><a href='" . esc_url(admin_url()) . "'>← Back to Dashboard</a></p>";
exit;
}

echo "<p><strong>Filtered run:</strong> "
. esc_html(implode(', ', array_map('basename', $migrations)))
. "</p>";
}

foreach ($migrations as $migration_file) {
$migration_name = basename($migration_file);
$slug = $slugify($migration_name);

// Preferred function name inside the migration file
$preferred_fn = "\\PostSecret\\Admin\\Migrations\\up_{$slug}";
$legacy_fn = "\\PostSecret\\Admin\\Migrations\\up";

echo "<p>Running migration: <strong>" . esc_html($migration_name) . "</strong>...";

$result = (static function (string $file, string $preferred_fn, string $legacy_fn) {
ob_start();
try {
require $migration_file;
\PostSecret\Admin\Migrations\up();
// Include once and capture any return (for closure-based migrations)
/** @var mixed $maybe_callable */
$maybe_callable = (static function ($f) {
return include $f;
})($file);

// Preferred: function up_<slug>()
if (function_exists($preferred_fn)) {
$preferred_fn();
} // Legacy: function up()
elseif (function_exists($legacy_fn)) {
echo "[notice] Using legacy migration function `{$legacy_fn}`. Consider renaming to `{$preferred_fn}`.\n";
$legacy_fn();
} // Closure-based: migration file returned a callable
elseif (is_callable($maybe_callable)) {
$maybe_callable();
} else {
throw new \RuntimeException(
"No callable migration found. Expected {$preferred_fn}(), {$legacy_fn}(), or a returned closure."
);
}

$output = ob_get_clean();
return [ 'success' => true, 'output' => $output ];
} catch ( \Throwable $e ) {
return ['success' => true, 'output' => $output];
} catch (\Throwable $e) {
$output = ob_get_clean();
return [ 'success' => false, 'error' => $e->getMessage(), 'output' => $output ];
return ['success' => false, 'error' => $e->getMessage(), 'output' => $output];
}
} )();
})($migration_file, $preferred_fn, $legacy_fn);

if ( $result['success'] ) {
if ($result['success']) {
echo " <span style='color:green;'>✓ Success</span></p>\n";
if ( ! empty( $result['output'] ) ) {
echo "<pre>" . esc_html( $result['output'] ) . "</pre>\n";
if (!empty($result['output'])) {
echo "<pre>" . esc_html($result['output']) . "</pre>\n";
}
} else {
echo " <span style='color:red;'>✗ Failed</span></p>\n";
echo "<p style='color:red;'>Error: " . esc_html( $result['error'] ) . "</p>\n";
if ( ! empty( $result['output'] ) ) {
echo "<pre>" . esc_html( $result['output'] ) . "</pre>\n";
echo "<p style='color:red;'>Error: " . esc_html($result['error']) . "</p>\n";
if (!empty($result['output'])) {
echo "<pre>" . esc_html($result['output']) . "</pre>\n";
}
// Bail on first failure so you can fix and re-run.
break;
}
}

echo "<p><strong>Migrations complete!</strong></p>\n";
echo "<p><a href='" . admin_url() . "'>← Back to Dashboard</a></p>\n";
echo "<p><a href='" . esc_url(admin_url()) . "'>← Back to Dashboard</a></p>\n";
Loading