Extend clean up expired shared entries

This commit is contained in:
Ryan
2025-05-04 02:28:33 -04:00
committed by GitHub
parent 8d6a1be777
commit bde35d1d31
5 changed files with 131 additions and 50 deletions

View File

@@ -48,6 +48,10 @@
- Adjusted endpoint paths to match controller filenames - Adjusted endpoint paths to match controller filenames
- Fix FolderController readOnly create folder permission - Fix FolderController readOnly create folder permission
### Additional changes
- Extend clean up expired shared entries
--- ---
## Changes 4/30/2025 v1.2.8 ## Changes 4/30/2025 v1.2.8

View File

@@ -3,42 +3,61 @@
require_once __DIR__ . '/../../../config/config.php'; require_once __DIR__ . '/../../../config/config.php';
// Simple authcheck: only admins may read these // Only admins may read these
if (empty($_SESSION['isAdmin']) || $_SESSION['isAdmin'] !== true) { if (empty($_SESSION['isAdmin']) || $_SESSION['isAdmin'] !== true) {
http_response_code(403); http_response_code(403);
echo json_encode(['error'=>'Forbidden']); echo json_encode(['error' => 'Forbidden']);
exit; exit;
} }
// Expect a ?file=share_links.json or share_folder_links.json // Must supply ?file=share_links.json or share_folder_links.json
if (empty($_GET['file'])) { if (empty($_GET['file'])) {
http_response_code(400); http_response_code(400);
echo json_encode(['error'=>'Missing `file` parameter']); echo json_encode(['error' => 'Missing `file` parameter']);
exit; exit;
} }
$file = basename($_GET['file']); $file = basename($_GET['file']);
$allowed = ['share_links.json','share_folder_links.json']; $allowed = ['share_links.json', 'share_folder_links.json'];
if (!in_array($file, $allowed, true)) { if (!in_array($file, $allowed, true)) {
http_response_code(403); http_response_code(403);
echo json_encode(['error'=>'Invalid file requested']); echo json_encode(['error' => 'Invalid file requested']);
exit; exit;
} }
$path = META_DIR . $file; $path = META_DIR . $file;
if (!file_exists($path)) { if (!file_exists($path)) {
http_response_code(404); // Return empty object so JS sees `{}` not an error
echo json_encode((object)[]); // return empty object http_response_code(200);
header('Content-Type: application/json');
echo json_encode((object)[]);
exit; exit;
} }
$data = file_get_contents($path); $jsonData = file_get_contents($path);
$json = json_decode($data, true); $data = json_decode($jsonData, true);
if (json_last_error() !== JSON_ERROR_NONE) { if (json_last_error() !== JSON_ERROR_NONE || !is_array($data)) {
http_response_code(500); http_response_code(500);
echo json_encode(['error'=>'Corrupted JSON']); echo json_encode(['error' => 'Corrupted JSON']);
exit; exit;
} }
// ——— Clean up expired entries ———
$now = time();
$changed = false;
foreach ($data as $token => $entry) {
if (!empty($entry['expires']) && $entry['expires'] < $now) {
unset($data[$token]);
$changed = true;
}
}
if ($changed) {
// overwrite file with cleaned data
file_put_contents($path, json_encode($data, JSON_PRETTY_PRINT));
}
// ——— Send cleaned data back ———
http_response_code(200);
header('Content-Type: application/json'); header('Content-Type: application/json');
echo json_encode($json); echo json_encode($data);
exit;

View File

@@ -184,49 +184,63 @@ function loadShareLinksSection() {
const container = document.getElementById("shareLinksContent"); const container = document.getElementById("shareLinksContent");
container.textContent = t("loading") + "..."; container.textContent = t("loading") + "...";
// Helper to fetch a metadata file or return {} on any error // helper: fetch one metadata file, but never throw —
const fetchMeta = file => // on non-2xx (including 404) or network error, resolve to {}
fetch(`/api/admin/readMetadata.php?file=${file}`, { credentials: "include" }) function fetchMeta(fileName) {
.then(r => r.ok ? r.json() : {}) // non-2xx → treat as empty return fetch(`/api/admin/readMetadata.php?file=${encodeURIComponent(fileName)}`, {
.catch(() => ({})); credentials: "include"
})
.then(resp => {
if (!resp.ok) {
// 404 or any other non-OK → treat as empty
return {};
}
return resp.json();
})
.catch(() => {
// network failure, parse error, etc → also empty
return {};
});
}
Promise.all([ Promise.all([
fetchMeta("share_folder_links.json"), fetchMeta("share_folder_links.json"),
fetchMeta("share_links.json") fetchMeta("share_links.json")
]) ])
.then(([folders, files]) => { .then(([folders, files]) => {
// If nothing at all, show a friendly message // if *both* are empty, show "no shared links"
if (Object.keys(folders).length === 0 && Object.keys(files).length === 0) { const hasAny = Object.keys(folders).length || Object.keys(files).length;
container.textContent = t("no_shared_links_available"); if (!hasAny) {
container.innerHTML = `<p>${t("no_shared_links_available")}</p>`;
return; return;
} }
let html = `<h5>${t("folder_shares")}</h5><ul>`; let html = `<h5>${t("folder_shares")}</h5><ul>`;
Object.entries(folders).forEach(([token, o]) => { Object.entries(folders).forEach(([token, o]) => {
const lock = o.password ? `🔒 ` : ""; const lock = o.password ? "🔒 " : "";
html += ` html += `
<li> <li>
${lock}<strong>${o.folder}</strong> ${lock}<strong>${o.folder}</strong>
<small>(${new Date(o.expires * 1000).toLocaleString()})</small> <small>(${new Date(o.expires * 1000).toLocaleString()})</small>
<button type="button" <button type="button"
data-key="${token}" data-key="${token}"
data-type="folder" data-type="folder"
class="btn btn-sm btn-link delete-share">🗑️</button> class="btn btn-sm btn-link delete-share">🗑️</button>
</li>`; </li>`;
}); });
html += `</ul><h5 style="margin-top:1em;">${t("file_shares")}</h5><ul>`; html += `</ul><h5 style="margin-top:1em;">${t("file_shares")}</h5><ul>`;
Object.entries(files).forEach(([token, o]) => { Object.entries(files).forEach(([token, o]) => {
const lock = o.password ? `🔒 ` : ""; const lock = o.password ? "🔒 " : "";
html += ` html += `
<li> <li>
${lock}<strong>${o.folder}/${o.file}</strong> ${lock}<strong>${o.folder}/${o.file}</strong>
<small>(${new Date(o.expires * 1000).toLocaleString()})</small> <small>(${new Date(o.expires * 1000).toLocaleString()})</small>
<button type="button" <button type="button"
data-key="${token}" data-key="${token}"
data-type="file" data-type="file"
class="btn btn-sm btn-link delete-share">🗑️</button> class="btn btn-sm btn-link delete-share">🗑️</button>
</li>`; </li>`;
}); });
html += `</ul>`; html += `</ul>`;
@@ -243,11 +257,11 @@ function loadShareLinksSection() {
: "/api/file/deleteShareLink.php"; : "/api/file/deleteShareLink.php";
fetch(endpoint, { fetch(endpoint, {
method: "POST", method: "POST",
credentials: "include", credentials: "include",
headers: { "Content-Type": "application/x-www-form-urlencoded" }, headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ token }) body: new URLSearchParams({ token })
}) })
.then(res => { .then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`); if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json(); return res.json();

View File

@@ -1582,6 +1582,31 @@ class FileController
echo json_encode($shareFile, JSON_PRETTY_PRINT); echo json_encode($shareFile, JSON_PRETTY_PRINT);
} }
public function getAllShareLinks(): void
{
header('Content-Type: application/json');
$shareFile = META_DIR . 'share_links.json';
$links = file_exists($shareFile)
? json_decode(file_get_contents($shareFile), true) ?? []
: [];
$now = time();
$cleaned = [];
// remove expired
foreach ($links as $token => $record) {
if (!empty($record['expires']) && $record['expires'] < $now) {
continue;
}
$cleaned[$token] = $record;
}
if (count($cleaned) !== count($links)) {
file_put_contents($shareFile, json_encode($cleaned, JSON_PRETTY_PRINT));
}
echo json_encode($cleaned);
}
/** /**
* POST /api/file/deleteShareLink.php * POST /api/file/deleteShareLink.php
*/ */

View File

@@ -1082,11 +1082,30 @@ class FolderController
/** /**
* GET /api/folder/getShareFolderLinks.php * GET /api/folder/getShareFolderLinks.php
*/ */
public function getShareFolderLinks() public function getAllShareFolderLinks(): void
{ {
header('Content-Type: application/json'); header('Content-Type: application/json');
$links = FolderModel::getAllShareFolderLinks(); $shareFile = META_DIR . 'share_folder_links.json';
echo json_encode($links, JSON_PRETTY_PRINT); $links = file_exists($shareFile)
? json_decode(file_get_contents($shareFile), true) ?? []
: [];
$now = time();
$cleaned = [];
// 1) Remove expired
foreach ($links as $token => $record) {
if (!empty($record['expires']) && $record['expires'] < $now) {
continue;
}
$cleaned[$token] = $record;
}
// 2) Persist back if anything was pruned
if (count($cleaned) !== count($links)) {
file_put_contents($shareFile, json_encode($cleaned, JSON_PRETTY_PRINT));
}
echo json_encode($cleaned);
} }
/** /**