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'; import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = ({ request, url }) => { export const load: LayoutServerLoad = ({ request, url }) => {
const adminPath = (loadConfig().adminPath ?? 'admin').replace(/^\/|\/$/g, '') || 'admin';
if (url.pathname.endsWith('/login') || url.pathname.endsWith('/login/')) { if (url.pathname.endsWith('/login') || url.pathname.endsWith('/login/')) {
return { authenticated: false }; return { authenticated: false, adminPath };
} }
if (url.pathname.includes('/api/')) { if (url.pathname.includes('/api/')) {
return {}; return { adminPath };
} }
if (!authRequest(request)) { if (!authRequest(request)) {
const adminPath = (loadConfig().adminPath ?? 'admin').replace(/^\/|\/$/g, '');
throw redirect(302, `/${adminPath}/login`); throw redirect(302, `/${adminPath}/login`);
} }
return { authenticated: true }; return { authenticated: true, adminPath };
}; };

View File

@@ -1,5 +1,8 @@
<script lang="ts"> <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> </script>
<div class="admin-layout"> <div class="admin-layout">

View File

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

View File

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