Persist local database write permissions
This commit is contained in:
+40
-6
@@ -63,6 +63,12 @@ const mainWorkspaceToggle = document.querySelector("#mainWorkspaceToggle");
|
|||||||
let sidebarUserClosed = false;
|
let sidebarUserClosed = false;
|
||||||
let mappingLoadToken = 0;
|
let mappingLoadToken = 0;
|
||||||
const mappingCacheKey = "catalog-maker:mapping-cache";
|
const mappingCacheKey = "catalog-maker:mapping-cache";
|
||||||
|
const legacyPermissionStorageKey = "catalog-maker:db-permissions";
|
||||||
|
const permissionStorageKeys = {
|
||||||
|
local: "catalog-maker:db-permissions:local-9bplus",
|
||||||
|
live: "catalog-maker:db-permissions:live-9bplus",
|
||||||
|
scrape: "catalog-maker:db-permissions:local-scrape",
|
||||||
|
};
|
||||||
const nativeFetch = window.fetch.bind(window);
|
const nativeFetch = window.fetch.bind(window);
|
||||||
window.fetch = (input, init = {}) => {
|
window.fetch = (input, init = {}) => {
|
||||||
const url = typeof input === "string" ? input : input.url;
|
const url = typeof input === "string" ? input : input.url;
|
||||||
@@ -70,7 +76,7 @@ window.fetch = (input, init = {}) => {
|
|||||||
return nativeFetch(input, init);
|
return nativeFetch(input, init);
|
||||||
const headers = new Headers(init.headers || (typeof input !== "string" ? input.headers : undefined));
|
const headers = new Headers(init.headers || (typeof input !== "string" ? input.headers : undefined));
|
||||||
const selectedMode = localStorage.getItem("catalog-maker:database-mode") || "local";
|
const selectedMode = localStorage.getItem("catalog-maker:database-mode") || "local";
|
||||||
const permissions = JSON.parse(localStorage.getItem("catalog-maker:db-permissions") || "{}");
|
const permissions = readDatabasePermissions(selectedMode);
|
||||||
const localWritesEnabled = selectedMode === "local" && [
|
const localWritesEnabled = selectedMode === "local" && [
|
||||||
permissions.allowInsertToggle,
|
permissions.allowInsertToggle,
|
||||||
permissions.allowUpdateToggle,
|
permissions.allowUpdateToggle,
|
||||||
@@ -1048,9 +1054,7 @@ function loadDatabaseMode() {
|
|||||||
const input = document.querySelector(`input[name="databaseMode"][value="${mode}"]`);
|
const input = document.querySelector(`input[name="databaseMode"][value="${mode}"]`);
|
||||||
if (input)
|
if (input)
|
||||||
input.checked = true;
|
input.checked = true;
|
||||||
const permissions = JSON.parse(localStorage.getItem("catalog-maker:db-permissions") || "{}");
|
applyPermissionInputs(readDatabasePermissions(mode));
|
||||||
for (const input of permissionInputs)
|
|
||||||
input.checked = permissions[input.id] === true;
|
|
||||||
const transactionMode = localStorage.getItem("catalog-maker:transaction-mode") || "transaction";
|
const transactionMode = localStorage.getItem("catalog-maker:transaction-mode") || "transaction";
|
||||||
const transactionInput = document.querySelector(`input[name="transactionMode"][value="${transactionMode}"]`);
|
const transactionInput = document.querySelector(`input[name="transactionMode"][value="${transactionMode}"]`);
|
||||||
if (transactionInput)
|
if (transactionInput)
|
||||||
@@ -1069,6 +1073,7 @@ function loadDatabaseMode() {
|
|||||||
function saveDatabaseMode(event) {
|
function saveDatabaseMode(event) {
|
||||||
const mode = ["local", "live", "scrape"].includes(event.target.value) ? event.target.value : "local";
|
const mode = ["local", "live", "scrape"].includes(event.target.value) ? event.target.value : "local";
|
||||||
localStorage.setItem("catalog-maker:database-mode", mode);
|
localStorage.setItem("catalog-maker:database-mode", mode);
|
||||||
|
applyPermissionInputs(readDatabasePermissions(mode));
|
||||||
syncDatabasePermissions(mode);
|
syncDatabasePermissions(mode);
|
||||||
syncLocalDbFields(mode);
|
syncLocalDbFields(mode);
|
||||||
saveSettings();
|
saveSettings();
|
||||||
@@ -1184,10 +1189,14 @@ function syncDatabasePermissions(mode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function saveSettings() {
|
function saveSettings() {
|
||||||
|
const mode = localStorage.getItem("catalog-maker:database-mode") || "local";
|
||||||
const permissions = {};
|
const permissions = {};
|
||||||
for (const input of permissionInputs)
|
for (const input of permissionInputs)
|
||||||
permissions[input.id] = input.checked;
|
permissions[input.id] = input.checked;
|
||||||
localStorage.setItem("catalog-maker:db-permissions", JSON.stringify(permissions));
|
localStorage.setItem(permissionStorageKeys[mode] || permissionStorageKeys.local, JSON.stringify(permissions));
|
||||||
|
if (mode === "local") {
|
||||||
|
localStorage.setItem(legacyPermissionStorageKey, JSON.stringify(permissions));
|
||||||
|
}
|
||||||
const transactionMode = [...transactionModeInputs].find((input) => input.checked)?.value || "transaction";
|
const transactionMode = [...transactionModeInputs].find((input) => input.checked)?.value || "transaction";
|
||||||
localStorage.setItem("catalog-maker:transaction-mode", transactionMode);
|
localStorage.setItem("catalog-maker:transaction-mode", transactionMode);
|
||||||
refreshDatabaseStatusLabel();
|
refreshDatabaseStatusLabel();
|
||||||
@@ -1195,7 +1204,7 @@ function saveSettings() {
|
|||||||
function refreshDatabaseStatusLabel() {
|
function refreshDatabaseStatusLabel() {
|
||||||
const mode = localStorage.getItem("catalog-maker:database-mode") || "local";
|
const mode = localStorage.getItem("catalog-maker:database-mode") || "local";
|
||||||
const transactionMode = localStorage.getItem("catalog-maker:transaction-mode") || "transaction";
|
const transactionMode = localStorage.getItem("catalog-maker:transaction-mode") || "transaction";
|
||||||
const permissions = JSON.parse(localStorage.getItem("catalog-maker:db-permissions") || "{}");
|
const permissions = readDatabasePermissions(mode);
|
||||||
const writesEnabled = mode === "local" && (permissions.allowInsertToggle || permissions.allowUpdateToggle || permissions.allowDeleteToggle);
|
const writesEnabled = mode === "local" && (permissions.allowInsertToggle || permissions.allowUpdateToggle || permissions.allowDeleteToggle);
|
||||||
const dbLabel = mode === "live" ? "Live 9bplus DB" : mode === "scrape" ? "Local scrape DB" : "Local 9bplus DB";
|
const dbLabel = mode === "live" ? "Live 9bplus DB" : mode === "scrape" ? "Local scrape DB" : "Local 9bplus DB";
|
||||||
const configuredDriver = databaseStatus.dataset.driver;
|
const configuredDriver = databaseStatus.dataset.driver;
|
||||||
@@ -1211,6 +1220,31 @@ function refreshDatabaseStatusLabel() {
|
|||||||
databaseStateDot.className = `database-state-dot ${databaseConnectionState === "unknown" ? "is-pending" : connectionError ? "is-error" : "is-ready"}`;
|
databaseStateDot.className = `database-state-dot ${databaseConnectionState === "unknown" ? "is-pending" : connectionError ? "is-error" : "is-ready"}`;
|
||||||
databaseStateDot.setAttribute("aria-label", connectionError ? "Database disconnected" : databaseConnectionState === "unknown" ? "Database connection pending" : "Database connected");
|
databaseStateDot.setAttribute("aria-label", connectionError ? "Database disconnected" : databaseConnectionState === "unknown" ? "Database connection pending" : "Database connected");
|
||||||
}
|
}
|
||||||
|
function readDatabasePermissions(mode = "local") {
|
||||||
|
const storageKey = permissionStorageKeys[mode] || permissionStorageKeys.local;
|
||||||
|
const stored = readJsonStorage(storageKey);
|
||||||
|
if (stored)
|
||||||
|
return stored;
|
||||||
|
if (mode === "local") {
|
||||||
|
const legacy = readJsonStorage(legacyPermissionStorageKey);
|
||||||
|
if (legacy)
|
||||||
|
return legacy;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
function applyPermissionInputs(permissions) {
|
||||||
|
for (const input of permissionInputs)
|
||||||
|
input.checked = permissions[input.id] === true;
|
||||||
|
}
|
||||||
|
function readJsonStorage(key) {
|
||||||
|
try {
|
||||||
|
const value = localStorage.getItem(key);
|
||||||
|
return value ? JSON.parse(value) : null;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
function renderDatabaseModeNote(mode) {
|
function renderDatabaseModeNote(mode) {
|
||||||
databaseModeNote.textContent = mode === "live"
|
databaseModeNote.textContent = mode === "live"
|
||||||
? "Live 9bplus DB selected for read-only data."
|
? "Live 9bplus DB selected for read-only data."
|
||||||
|
|||||||
+41
-5
@@ -63,6 +63,12 @@ const mainWorkspaceToggle = document.querySelector("#mainWorkspaceToggle");
|
|||||||
let sidebarUserClosed = false;
|
let sidebarUserClosed = false;
|
||||||
let mappingLoadToken = 0;
|
let mappingLoadToken = 0;
|
||||||
const mappingCacheKey = "catalog-maker:mapping-cache";
|
const mappingCacheKey = "catalog-maker:mapping-cache";
|
||||||
|
const legacyPermissionStorageKey = "catalog-maker:db-permissions";
|
||||||
|
const permissionStorageKeys = {
|
||||||
|
local: "catalog-maker:db-permissions:local-9bplus",
|
||||||
|
live: "catalog-maker:db-permissions:live-9bplus",
|
||||||
|
scrape: "catalog-maker:db-permissions:local-scrape",
|
||||||
|
};
|
||||||
|
|
||||||
const nativeFetch = window.fetch.bind(window);
|
const nativeFetch = window.fetch.bind(window);
|
||||||
window.fetch = (input, init = {}) => {
|
window.fetch = (input, init = {}) => {
|
||||||
@@ -71,7 +77,7 @@ window.fetch = (input, init = {}) => {
|
|||||||
|
|
||||||
const headers = new Headers(init.headers || (typeof input !== "string" ? input.headers : undefined));
|
const headers = new Headers(init.headers || (typeof input !== "string" ? input.headers : undefined));
|
||||||
const selectedMode = localStorage.getItem("catalog-maker:database-mode") || "local";
|
const selectedMode = localStorage.getItem("catalog-maker:database-mode") || "local";
|
||||||
const permissions = JSON.parse(localStorage.getItem("catalog-maker:db-permissions") || "{}");
|
const permissions = readDatabasePermissions(selectedMode);
|
||||||
const localWritesEnabled = selectedMode === "local" && [
|
const localWritesEnabled = selectedMode === "local" && [
|
||||||
permissions.allowInsertToggle,
|
permissions.allowInsertToggle,
|
||||||
permissions.allowUpdateToggle,
|
permissions.allowUpdateToggle,
|
||||||
@@ -1140,8 +1146,7 @@ function loadDatabaseMode() {
|
|||||||
const mode = localStorage.getItem("catalog-maker:database-mode") || "local";
|
const mode = localStorage.getItem("catalog-maker:database-mode") || "local";
|
||||||
const input = document.querySelector(`input[name="databaseMode"][value="${mode}"]`);
|
const input = document.querySelector(`input[name="databaseMode"][value="${mode}"]`);
|
||||||
if (input) input.checked = true;
|
if (input) input.checked = true;
|
||||||
const permissions = JSON.parse(localStorage.getItem("catalog-maker:db-permissions") || "{}");
|
applyPermissionInputs(readDatabasePermissions(mode));
|
||||||
for (const input of permissionInputs) input.checked = permissions[input.id] === true;
|
|
||||||
const transactionMode = localStorage.getItem("catalog-maker:transaction-mode") || "transaction";
|
const transactionMode = localStorage.getItem("catalog-maker:transaction-mode") || "transaction";
|
||||||
const transactionInput = document.querySelector(`input[name="transactionMode"][value="${transactionMode}"]`);
|
const transactionInput = document.querySelector(`input[name="transactionMode"][value="${transactionMode}"]`);
|
||||||
if (transactionInput) transactionInput.checked = true;
|
if (transactionInput) transactionInput.checked = true;
|
||||||
@@ -1159,6 +1164,7 @@ function loadDatabaseMode() {
|
|||||||
function saveDatabaseMode(event) {
|
function saveDatabaseMode(event) {
|
||||||
const mode = ["local", "live", "scrape"].includes(event.target.value) ? event.target.value : "local";
|
const mode = ["local", "live", "scrape"].includes(event.target.value) ? event.target.value : "local";
|
||||||
localStorage.setItem("catalog-maker:database-mode", mode);
|
localStorage.setItem("catalog-maker:database-mode", mode);
|
||||||
|
applyPermissionInputs(readDatabasePermissions(mode));
|
||||||
syncDatabasePermissions(mode);
|
syncDatabasePermissions(mode);
|
||||||
syncLocalDbFields(mode);
|
syncLocalDbFields(mode);
|
||||||
saveSettings();
|
saveSettings();
|
||||||
@@ -1272,9 +1278,13 @@ function syncDatabasePermissions(mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function saveSettings() {
|
function saveSettings() {
|
||||||
|
const mode = localStorage.getItem("catalog-maker:database-mode") || "local";
|
||||||
const permissions = {};
|
const permissions = {};
|
||||||
for (const input of permissionInputs) permissions[input.id] = input.checked;
|
for (const input of permissionInputs) permissions[input.id] = input.checked;
|
||||||
localStorage.setItem("catalog-maker:db-permissions", JSON.stringify(permissions));
|
localStorage.setItem(permissionStorageKeys[mode] || permissionStorageKeys.local, JSON.stringify(permissions));
|
||||||
|
if (mode === "local") {
|
||||||
|
localStorage.setItem(legacyPermissionStorageKey, JSON.stringify(permissions));
|
||||||
|
}
|
||||||
const transactionMode = [...transactionModeInputs].find((input) => input.checked)?.value || "transaction";
|
const transactionMode = [...transactionModeInputs].find((input) => input.checked)?.value || "transaction";
|
||||||
localStorage.setItem("catalog-maker:transaction-mode", transactionMode);
|
localStorage.setItem("catalog-maker:transaction-mode", transactionMode);
|
||||||
refreshDatabaseStatusLabel();
|
refreshDatabaseStatusLabel();
|
||||||
@@ -1283,7 +1293,7 @@ function saveSettings() {
|
|||||||
function refreshDatabaseStatusLabel() {
|
function refreshDatabaseStatusLabel() {
|
||||||
const mode = localStorage.getItem("catalog-maker:database-mode") || "local";
|
const mode = localStorage.getItem("catalog-maker:database-mode") || "local";
|
||||||
const transactionMode = localStorage.getItem("catalog-maker:transaction-mode") || "transaction";
|
const transactionMode = localStorage.getItem("catalog-maker:transaction-mode") || "transaction";
|
||||||
const permissions = JSON.parse(localStorage.getItem("catalog-maker:db-permissions") || "{}");
|
const permissions = readDatabasePermissions(mode);
|
||||||
const writesEnabled = mode === "local" && (
|
const writesEnabled = mode === "local" && (
|
||||||
permissions.allowInsertToggle || permissions.allowUpdateToggle || permissions.allowDeleteToggle
|
permissions.allowInsertToggle || permissions.allowUpdateToggle || permissions.allowDeleteToggle
|
||||||
);
|
);
|
||||||
@@ -1302,6 +1312,32 @@ function refreshDatabaseStatusLabel() {
|
|||||||
databaseStateDot.setAttribute("aria-label", connectionError ? "Database disconnected" : databaseConnectionState === "unknown" ? "Database connection pending" : "Database connected");
|
databaseStateDot.setAttribute("aria-label", connectionError ? "Database disconnected" : databaseConnectionState === "unknown" ? "Database connection pending" : "Database connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readDatabasePermissions(mode = "local") {
|
||||||
|
const storageKey = permissionStorageKeys[mode] || permissionStorageKeys.local;
|
||||||
|
const stored = readJsonStorage(storageKey);
|
||||||
|
if (stored) return stored;
|
||||||
|
|
||||||
|
if (mode === "local") {
|
||||||
|
const legacy = readJsonStorage(legacyPermissionStorageKey);
|
||||||
|
if (legacy) return legacy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPermissionInputs(permissions) {
|
||||||
|
for (const input of permissionInputs) input.checked = permissions[input.id] === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readJsonStorage(key) {
|
||||||
|
try {
|
||||||
|
const value = localStorage.getItem(key);
|
||||||
|
return value ? JSON.parse(value) : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderDatabaseModeNote(mode) {
|
function renderDatabaseModeNote(mode) {
|
||||||
databaseModeNote.textContent = mode === "live"
|
databaseModeNote.textContent = mode === "live"
|
||||||
? "Live 9bplus DB selected for read-only data."
|
? "Live 9bplus DB selected for read-only data."
|
||||||
|
|||||||
+1
-1
@@ -259,7 +259,7 @@
|
|||||||
<input type="checkbox" id="allowDeleteToggle" />
|
<input type="checkbox" id="allowDeleteToggle" />
|
||||||
<span>Allow delete</span>
|
<span>Allow delete</span>
|
||||||
</label>
|
</label>
|
||||||
<p class="settings-note">Live DB is always read-only. Write permissions apply only to Local DB.</p>
|
<p class="settings-note">Write permissions are saved only for Local 9bplus DB. Live and scrape DB stay read-only here.</p>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset class="settings-group">
|
<fieldset class="settings-group">
|
||||||
|
|||||||
@@ -40,4 +40,21 @@ test.describe("database connection", () => {
|
|||||||
expect(data.items.length).toBeGreaterThan(0);
|
expect(data.items.length).toBeGreaterThan(0);
|
||||||
expect(data.items.some((item) => item.name === "La Sportiva")).toBeTruthy();
|
expect(data.items.some((item) => item.name === "La Sportiva")).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("persists Local 9bplus write permissions across reloads", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await page.locator("#settingsButton").click();
|
||||||
|
await page.locator('input[name="databaseMode"][value="local"]').check();
|
||||||
|
await page.locator("#allowInsertToggle").check();
|
||||||
|
await page.locator("#allowUpdateToggle").check();
|
||||||
|
await page.locator("#allowDeleteToggle").check();
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
await page.locator("#settingsButton").click();
|
||||||
|
|
||||||
|
await expect(page.locator('input[name="databaseMode"][value="local"]')).toBeChecked();
|
||||||
|
await expect(page.locator("#allowInsertToggle")).toBeChecked();
|
||||||
|
await expect(page.locator("#allowUpdateToggle")).toBeChecked();
|
||||||
|
await expect(page.locator("#allowDeleteToggle")).toBeChecked();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user