Initial commit
This commit is contained in:
commit
05c65aad4d
155 changed files with 93617 additions and 0 deletions
89
viewer/admin/api/actions.php
Normal file
89
viewer/admin/api/actions.php
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
require __DIR__ . '/common.php';
|
||||
|
||||
$root = workspace_root();
|
||||
$body = file_get_contents('php://input');
|
||||
$data = json_decode($body, true);
|
||||
if (!$data || !isset($data['action'])) { http_response_code(400); json_response(['error'=>'invalid_request']); }
|
||||
|
||||
$action = $data['action'];
|
||||
if ($action === 'clear_cache') {
|
||||
$target = $root . '/viewer/build';
|
||||
$out = '';
|
||||
if (is_dir($target)) {
|
||||
// remove all files
|
||||
$cmd = 'rm -rf ' . escapeshellarg($target) . '/*';
|
||||
$out = shell_exec($cmd . ' 2>&1');
|
||||
} else { $out = 'no build dir'; }
|
||||
json_response(['ok'=>true,'output'=>$out]);
|
||||
}
|
||||
|
||||
if ($action === 'rebuild_thumbs') {
|
||||
$script = $root . '/scripts/render.sh';
|
||||
if (file_exists($script) && is_executable($script)) {
|
||||
$out = shell_exec(escapeshellcmd($script) . ' 2>&1');
|
||||
json_response(['ok'=>true,'output'=>$out]);
|
||||
}
|
||||
http_response_code(500); json_response(['error'=>'script_missing']);
|
||||
}
|
||||
|
||||
if ($action === 'diagnostics') {
|
||||
$out = [];
|
||||
$out['php'] = shell_exec('php -v 2>&1');
|
||||
$out['uname'] = shell_exec('uname -a 2>&1');
|
||||
$out['df'] = shell_exec('df -h 2>&1');
|
||||
json_response(['ok'=>true,'output'=>$out]);
|
||||
}
|
||||
|
||||
if ($action === 'entity_resave') {
|
||||
$entity_id = $data['entity_id'] ?? null;
|
||||
$entity_type = $data['entity_type'] ?? 'wisski_individual';
|
||||
if (!$entity_id) { http_response_code(400); json_response(['error'=>'missing_entity_id']); }
|
||||
|
||||
$out = "Attempting to re-save entity $entity_type:$entity_id ...\n\n";
|
||||
|
||||
// Try to bootstrap Drupal if available
|
||||
$drupal_root = $root;
|
||||
if (file_exists($drupal_root . '/index.php')) {
|
||||
$out .= "Found index.php, attempting Drupal bootstrap...\n";
|
||||
try {
|
||||
// Push to Drupal context
|
||||
$cwd = getcwd();
|
||||
chdir($drupal_root);
|
||||
|
||||
// Simple Drupal load without full bootstrap (safer)
|
||||
if (function_exists('drush_main')) {
|
||||
$out .= "Drush detected, will attempt re-save.\n";
|
||||
}
|
||||
|
||||
// Try using Drupal's entity loader via autoloader
|
||||
if (file_exists($drupal_root . '/vendor/autoload.php')) {
|
||||
require_once $drupal_root . '/vendor/autoload.php';
|
||||
$out .= "Drupal autoloader loaded.\n";
|
||||
|
||||
// Simple check: can we access Drupal\Core ?
|
||||
if (class_exists('Drupal\Core\Entity\EntityTypeManager')) {
|
||||
$out .= "Drupal namespace available - attempting entity save.\n";
|
||||
// Real bootstrap would require more setup
|
||||
$out .= "INFO: Full entity re-save requires Drupal context.\n";
|
||||
}
|
||||
}
|
||||
chdir($cwd);
|
||||
$out .= "\nTo re-save this entity, use Drupal CLI:\n";
|
||||
$out .= " drush entity:save $entity_type $entity_id\n";
|
||||
$out .= "Or access Drupal admin panel and update the entity.\n";
|
||||
} catch (\Throwable $e) {
|
||||
$out .= "Bootstrap check failed: " . $e->getMessage() . "\n";
|
||||
$out .= "\nTo re-save this entity, use Drupal CLI:\n";
|
||||
$out .= " drush entity:save $entity_type $entity_id\n";
|
||||
}
|
||||
} else {
|
||||
$out .= "Drupal not detected in this directory.\n";
|
||||
$out .= "To re-save an entity, use:\n";
|
||||
$out .= " drush entity:save $entity_type $entity_id\n";
|
||||
$out .= "Or trigger via Drupal admin panel.\n";
|
||||
}
|
||||
json_response(['ok'=>true,'output'=>$out]);
|
||||
}
|
||||
|
||||
http_response_code(400); json_response(['error'=>'unknown_action']);
|
||||
62
viewer/admin/api/backups.php
Normal file
62
viewer/admin/api/backups.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
require __DIR__ . '/common.php';
|
||||
|
||||
$root = workspace_root();
|
||||
|
||||
// GET ?target=settings|env -> list backups (most recent first, limited to 5)
|
||||
// POST restore {target, file}
|
||||
|
||||
function list_backups($target) {
|
||||
global $root;
|
||||
if ($target === 'settings') {
|
||||
$path = $root . '/viewer-settings.json';
|
||||
$base = 'viewer-settings.json';
|
||||
} elseif ($target === 'env') {
|
||||
$path = $root . '/scripts/.env';
|
||||
$base = '.env';
|
||||
$dir = dirname($path);
|
||||
$base = basename($path);
|
||||
} else {
|
||||
return ['error'=>'invalid_target'];
|
||||
}
|
||||
$dir = dirname($path);
|
||||
$pattern = $dir . '/' . $base . '.*';
|
||||
$files = glob($pattern);
|
||||
// filter timestamped copies only (YYYYmmdd-HHMMSS)
|
||||
$backups = array_filter($files, function($f) use ($base){ return preg_match('/' . preg_quote($base, '/') . '\.\d{8}-\d{6}$/', $f); });
|
||||
usort($backups, function($a,$b){ return filemtime($b) - filemtime($a); });
|
||||
$backups = array_slice($backups, 0, 5);
|
||||
$out = [];
|
||||
foreach($backups as $b) $out[] = ['file'=>basename($b),'path'=>$b,'ts'=>date('c', filemtime($b))];
|
||||
return ['backups'=>$out];
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
$target = $_GET['target'] ?? null;
|
||||
if (!$target) { http_response_code(400); json_response(['error'=>'missing_target']); }
|
||||
json_response(list_backups($target));
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$body = file_get_contents('php://input');
|
||||
$data = json_decode($body, true);
|
||||
if (!$data || !isset($data['target']) || !isset($data['file'])) { http_response_code(400); json_response(['error'=>'invalid_request']); }
|
||||
$target = $data['target'];
|
||||
$file = basename($data['file']);
|
||||
if ($target === 'settings') {
|
||||
$path = $root . '/viewer-settings.json';
|
||||
$dir = dirname($path);
|
||||
$src = $dir . '/' . $file;
|
||||
} elseif ($target === 'env') {
|
||||
$path = $root . '/scripts/.env';
|
||||
$dir = dirname($path);
|
||||
$src = $dir . '/' . $file;
|
||||
} else { http_response_code(400); json_response(['error'=>'invalid_target']); }
|
||||
if (!file_exists($src)) { http_response_code(404); json_response(['error'=>'not_found']); }
|
||||
// make a backup of current file before restore
|
||||
if (file_exists($path)) backup_file($path);
|
||||
if (!copy($src, $path)) { http_response_code(500); json_response(['error'=>'restore_failed']); }
|
||||
json_response(['ok'=>true]);
|
||||
}
|
||||
|
||||
http_response_code(405); json_response(['error'=>'method_not_allowed']);
|
||||
55
viewer/admin/api/common.php
Normal file
55
viewer/admin/api/common.php
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['admin'])) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
function json_response($data) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($data);
|
||||
exit;
|
||||
}
|
||||
|
||||
function workspace_root() {
|
||||
// admin/api is at viewer/admin/api -> go up 3
|
||||
return realpath(__DIR__ . '/../../../');
|
||||
}
|
||||
|
||||
function backup_file($path, $keep = 5) {
|
||||
if (!file_exists($path)) return;
|
||||
$dir = dirname($path);
|
||||
$base = basename($path);
|
||||
// simple .bak (latest)
|
||||
copy($path, $dir . '/' . $base . '.bak');
|
||||
// timestamped
|
||||
$ts = date('Ymd-His');
|
||||
$copy = $dir . '/' . $base . '.' . $ts;
|
||||
copy($path, $copy);
|
||||
|
||||
// rotate old backups: match $base.* in same dir (exclude .bak)
|
||||
$pattern = $dir . '/' . $base . '.*';
|
||||
$files = glob($pattern);
|
||||
// filter timestamped copies only (YYYYmmdd-HHMMSS)
|
||||
$backups = array_filter($files, function($f) use ($base){ return preg_match('/' . preg_quote($base, '/') . '\.\d{8}-\d{6}$/', $f); });
|
||||
usort($backups, function($a,$b){ return filemtime($b) - filemtime($a); });
|
||||
if (count($backups) > $keep) {
|
||||
$remove = array_slice($backups, $keep);
|
||||
foreach($remove as $r) @unlink($r);
|
||||
}
|
||||
}
|
||||
|
||||
function save_uploaded_schema($targetPath) {
|
||||
if (!isset($_FILES['file'])) return ['error'=>'no_file'];
|
||||
$f = $_FILES['file'];
|
||||
if ($f['error'] !== UPLOAD_ERR_OK) return ['error'=>'upload_error'];
|
||||
// validate json
|
||||
$content = file_get_contents($f['tmp_name']);
|
||||
if (json_decode($content) === null) return ['error'=>'invalid_json'];
|
||||
// backup existing
|
||||
if (file_exists($targetPath)) backup_file($targetPath, intval(getenv('ADMIN_BACKUP_KEEP') ?: 10));
|
||||
if (!is_dir(dirname($targetPath))) mkdir(dirname($targetPath), 0755, true);
|
||||
if (!move_uploaded_file($f['tmp_name'], $targetPath)) return ['error'=>'move_failed'];
|
||||
return ['ok'=>true];
|
||||
}
|
||||
53
viewer/admin/api/env.php
Normal file
53
viewer/admin/api/env.php
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
require __DIR__ . '/common.php';
|
||||
|
||||
$root = workspace_root();
|
||||
$target = $root . '/scripts/.env';
|
||||
$example = $root . '/scripts/.env.example';
|
||||
|
||||
// Ensure .env exists by copying example
|
||||
if (!file_exists($target)) {
|
||||
if (file_exists($example)) copy($example, $target);
|
||||
else file_put_contents($target, "# .env\n");
|
||||
}
|
||||
|
||||
function parse_env($path) {
|
||||
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$out = [];
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || strpos($line, '#') === 0) continue;
|
||||
if (strpos($line, '=') !== false) {
|
||||
list($k,$v) = explode('=', $line, 2);
|
||||
$out[trim($k)] = trim($v);
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
json_response(parse_env($target));
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$body = file_get_contents('php://input');
|
||||
$decoded = json_decode($body, true);
|
||||
if (!is_array($decoded)) {
|
||||
http_response_code(400);
|
||||
json_response(['error' => 'invalid_json']);
|
||||
}
|
||||
// backup
|
||||
backup_file($target);
|
||||
// write env
|
||||
$lines = [];
|
||||
foreach ($decoded as $k => $v) {
|
||||
$lines[] = $k . '=' . $v;
|
||||
}
|
||||
$tmp = $target . '.tmp';
|
||||
file_put_contents($tmp, implode("\n", $lines) . "\n");
|
||||
rename($tmp, $target);
|
||||
json_response(['ok' => true]);
|
||||
}
|
||||
|
||||
http_response_code(405);
|
||||
json_response(['error' => 'method_not_allowed']);
|
||||
17
viewer/admin/api/env_schema.php
Normal file
17
viewer/admin/api/env_schema.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
require __DIR__ . '/common.php';
|
||||
|
||||
$root = workspace_root();
|
||||
$p = $root . '/scripts/.env.schema.json';
|
||||
if (file_exists($p)) {
|
||||
if (isset($_GET['delete']) && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (@unlink($p)) json_response(['ok'=>true]);
|
||||
http_response_code(500); json_response(['error'=>'delete_failed']);
|
||||
}
|
||||
header('Content-Type: application/json');
|
||||
echo file_get_contents($p);
|
||||
exit;
|
||||
}
|
||||
|
||||
http_response_code(204);
|
||||
exit;
|
||||
36
viewer/admin/api/hdri.php
Normal file
36
viewer/admin/api/hdri.php
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
require __DIR__ . '/common.php';
|
||||
|
||||
$root = workspace_root();
|
||||
$dir = $root . '/viewer/hdri';
|
||||
if (!is_dir($dir)) mkdir($dir, 0755, true);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
$files = array_values(array_filter(scandir($dir), function($f){ return !in_array($f, ['.','..']); }));
|
||||
json_response(['files' => $files]);
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// If multipart upload
|
||||
if (!empty($_FILES['file'])) {
|
||||
$f = $_FILES['file'];
|
||||
if ($f['error'] !== UPLOAD_ERR_OK) { http_response_code(400); json_response(['error'=>'upload_error']); }
|
||||
$name = basename($f['name']);
|
||||
$target = $dir . '/' . $name;
|
||||
if (!move_uploaded_file($f['tmp_name'], $target)) { http_response_code(500); json_response(['error'=>'move_failed']); }
|
||||
json_response(['ok'=>true,'file'=>$name]);
|
||||
}
|
||||
// JSON action (delete)
|
||||
$body = file_get_contents('php://input');
|
||||
$data = json_decode($body, true);
|
||||
if (!$data) { http_response_code(400); json_response(['error'=>'invalid_json']); }
|
||||
if (isset($data['action']) && $data['action'] === 'delete' && isset($data['file'])) {
|
||||
$file = basename($data['file']);
|
||||
$path = $dir . '/' . $file;
|
||||
if (file_exists($path)) { unlink($path); json_response(['ok'=>true]); }
|
||||
http_response_code(404); json_response(['error'=>'not_found']);
|
||||
}
|
||||
http_response_code(400); json_response(['error'=>'unknown_action']);
|
||||
}
|
||||
|
||||
http_response_code(405); json_response(['error'=>'method_not_allowed']);
|
||||
41
viewer/admin/api/settings.php
Normal file
41
viewer/admin/api/settings.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
require __DIR__ . '/common.php';
|
||||
|
||||
$root = workspace_root();
|
||||
$target = $root . '/viewer-settings.json';
|
||||
$example1 = $root . '/viewer-settings-example.json';
|
||||
$example2 = $root . '/viewer/viewer-settings-example.json';
|
||||
|
||||
// Ensure file exists by copying example if needed
|
||||
if (!file_exists($target)) {
|
||||
if (file_exists($example1)) copy($example1, $target);
|
||||
elseif (file_exists($example2)) copy($example2, $target);
|
||||
else file_put_contents($target, json_encode(new stdClass()));
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
$data = file_get_contents($target);
|
||||
header('Content-Type: application/json');
|
||||
echo $data;
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$body = file_get_contents('php://input');
|
||||
// validate JSON
|
||||
$decoded = json_decode($body, true);
|
||||
if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
|
||||
http_response_code(400);
|
||||
json_response(['error' => 'invalid_json']);
|
||||
}
|
||||
// backup
|
||||
backup_file($target);
|
||||
// atomic write
|
||||
$tmp = $target . '.tmp';
|
||||
file_put_contents($tmp, json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
rename($tmp, $target);
|
||||
json_response(['ok' => true]);
|
||||
}
|
||||
|
||||
http_response_code(405);
|
||||
json_response(['error' => 'method_not_allowed']);
|
||||
24
viewer/admin/api/settings_schema.php
Normal file
24
viewer/admin/api/settings_schema.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
require __DIR__ . '/common.php';
|
||||
|
||||
$root = workspace_root();
|
||||
$candidates = [
|
||||
$root . '/viewer-settings.schema.json',
|
||||
$root . '/viewer/viewer-settings.schema.json',
|
||||
];
|
||||
|
||||
foreach ($candidates as $p) {
|
||||
if (file_exists($p)) {
|
||||
if (isset($_GET['delete']) && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (@unlink($p)) json_response(['ok'=>true]);
|
||||
http_response_code(500); json_response(['error'=>'delete_failed']);
|
||||
}
|
||||
header('Content-Type: application/json');
|
||||
echo file_get_contents($p);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// no schema found
|
||||
http_response_code(204);
|
||||
exit;
|
||||
24
viewer/admin/api/upload_schema.php
Normal file
24
viewer/admin/api/upload_schema.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
require __DIR__ . '/common.php';
|
||||
|
||||
$root = workspace_root();
|
||||
|
||||
// expects multipart/form-data with field 'file' and 'target' in POST (values: 'settings' or 'env')
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405); json_response(['error'=>'method_not_allowed']);
|
||||
}
|
||||
|
||||
$target = $_POST['target'] ?? $_GET['target'] ?? null;
|
||||
if (!$target) { http_response_code(400); json_response(['error'=>'missing_target']); }
|
||||
|
||||
if ($target === 'settings') {
|
||||
$dest = $root . '/viewer-settings.schema.json';
|
||||
} elseif ($target === 'env') {
|
||||
$dest = $root . '/scripts/.env.schema.json';
|
||||
} else {
|
||||
http_response_code(400); json_response(['error'=>'invalid_target']);
|
||||
}
|
||||
|
||||
$res = save_uploaded_schema($dest);
|
||||
if (isset($res['ok'])) json_response($res);
|
||||
http_response_code(400); json_response($res);
|
||||
Loading…
Add table
Add a link
Reference in a new issue