fix: all admin client-side fetches and redirects use configured adminPath

This commit is contained in:
Falkan
2026-03-17 20:49:38 -04:00
parent e655204064
commit 3598a2dd25
4 changed files with 36 additions and 25 deletions

View File

@@ -4,18 +4,19 @@ import { loadConfig } from '$lib/server/data';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = ({ request, url }) => {
const adminPath = (loadConfig().adminPath ?? 'admin').replace(/^\/|\/$/g, '') || 'admin';
if (url.pathname.endsWith('/login') || url.pathname.endsWith('/login/')) {
return { authenticated: false };
return { authenticated: false, adminPath };
}
if (url.pathname.includes('/api/')) {
return {};
return { adminPath };
}
if (!authRequest(request)) {
const adminPath = (loadConfig().adminPath ?? 'admin').replace(/^\/|\/$/g, '');
throw redirect(302, `/${adminPath}/login`);
}
return { authenticated: true };
return { authenticated: true, adminPath };
};

View File

@@ -1,5 +1,8 @@
<script lang="ts">
let { children } = $props();
import { setContext } from 'svelte';
let { children, data } = $props();
// Make adminPath available to all child components via context
setContext('adminPath', data.adminPath ?? 'admin');
</script>
<div class="admin-layout">

View File

@@ -1,6 +1,10 @@
<script lang="ts">
import { getContext } from 'svelte';
import type { Unit, Group, UnitsData } from '$lib/types';
const adminPath = getContext<string>('adminPath') ?? 'admin';
const api = `/${adminPath}/api`;
// ── State ────────────────────────────────────────────────────────────────────
let unitsData = $state<UnitsData>({ units: [], groups: [] });
let selectedItem = $state<{ type: 'unit' | 'group'; id: string } | null>(null);
@@ -111,7 +115,7 @@
dragGroupId = null;
dragOverGroupId = null;
// Persist
fetch('/admin/api/order', {
fetch('${api}/order', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ groups: groups.map((g) => g.id) })
@@ -165,7 +169,7 @@
// Build ordered IDs for this group
const groupKey = groupId ?? 'null';
const groupUnitIds = units.filter((u) => (u.group ?? null) === groupId).map((u) => u.id);
fetch('/admin/api/order', {
fetch('${api}/order', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ units: { [groupKey]: groupUnitIds } })
@@ -181,8 +185,8 @@
// ── Load data ────────────────────────────────────────────────────────────────
async function loadData() {
const [res, gres] = await Promise.all([
fetch('/admin/api/units'),
fetch('/admin/api/groups')
fetch('${api}/units'),
fetch('${api}/groups')
]);
if (!res.ok) return;
const units: Unit[] = await res.json();
@@ -287,12 +291,12 @@
}
try {
const res = unitFormNew
? await fetch('/admin/api/units', {
? await fetch('${api}/units', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(unitForm)
})
: await fetch(`/admin/api/units/${selectedItem?.id}`, {
: await fetch(`${api}/units/${selectedItem?.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(unitForm)
@@ -318,12 +322,12 @@
}
try {
const res = groupFormNew
? await fetch('/admin/api/groups', {
? await fetch('${api}/groups', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(groupForm)
})
: await fetch(`/admin/api/groups/${selectedItem?.id}`, {
: await fetch(`${api}/groups/${selectedItem?.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(groupForm)
@@ -410,7 +414,7 @@
const targetGroup = bulkMoveTargetGroupId === '__orphan__' ? null : bulkMoveTargetGroupId;
const results = await Promise.all(
[...checkedUnitIds].map((unitId) =>
fetch(`/admin/api/units/${unitId}`, {
fetch(`${api}/units/${unitId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ group: targetGroup })
@@ -432,7 +436,7 @@
formError = null;
const ids = [...checkedUnitIds];
const results = await Promise.all(
ids.map((unitId) => fetch(`/admin/api/units/${unitId}`, { method: 'DELETE' }))
ids.map((unitId) => fetch(`${api}/units/${unitId}`, { method: 'DELETE' }))
);
const failed = results.filter((r) => !r.ok);
if (failed.length) formError = `${failed.length} unit(s) failed to delete`;
@@ -455,12 +459,12 @@
}
try {
const res = unitFormNew
? await fetch('/admin/api/units', {
? await fetch('${api}/units', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(unitForm)
})
: await fetch(`/admin/api/units/${selectedItem?.id}`, {
: await fetch(`${api}/units/${selectedItem?.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(unitForm)
@@ -484,7 +488,7 @@
if (!selectedItem || selectedItem.type !== 'unit') return;
if (!confirm(`Delete unit "${unitForm.label}"?`)) return;
try {
const res = await fetch(`/admin/api/units/${selectedItem.id}`, { method: 'DELETE' });
const res = await fetch(`${api}/units/${selectedItem.id}`, { method: 'DELETE' });
if (!res.ok) { formError = await extractError(res, 'Delete failed'); return; }
selectedItem = null;
unitForm = {};
@@ -505,12 +509,12 @@
}
try {
const res = groupFormNew
? await fetch('/admin/api/groups', {
? await fetch('${api}/groups', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(groupForm)
})
: await fetch(`/admin/api/groups/${selectedItem?.id}`, {
: await fetch(`${api}/groups/${selectedItem?.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(groupForm)
@@ -547,7 +551,7 @@
? { action: 'orphan' }
: { action: 'reassign', targetGroupId: deleteGroupTargetId, toBaseAction: deleteGroupToBaseAction };
try {
const res = await fetch(`/admin/api/groups/${deleteGroupTarget.id}`, {
const res = await fetch(`${api}/groups/${deleteGroupTarget.id}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
@@ -572,8 +576,8 @@
});
async function logout() {
await fetch('/admin/api/auth/logout', { method: 'POST' });
window.location.href = '/admin/login';
await fetch(`${api}/auth/logout`, { method: 'POST' });
window.location.href = `/${adminPath}/login`;
}
</script>

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { getContext } from 'svelte';
const adminPath = getContext<string>('adminPath') ?? 'admin';
let password = $state('');
let error = $state<string | null>(null);
@@ -11,14 +14,14 @@
loading = true;
try {
const res = await fetch('/admin/api/auth/login', {
const res = await fetch(`/${adminPath}/api/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password })
});
if (res.ok) {
await goto('/admin');
await goto(`/${adminPath}`);
} else {
const data = await res.json().catch(() => ({}));
error = data.error ?? 'Incorrect password';