This commit is contained in:
@ -1,4 +1,29 @@
|
||||
const API_BASE = '../api';
|
||||
/**
|
||||
* Resolve API base for sibling (/website + /api) or subpath (/prefix/ + /prefix/api) layouts.
|
||||
* Override per deploy: <script>window.QDB_API_BASE = '/nat-as-server/api';</script>
|
||||
*/
|
||||
function resolveApiBase() {
|
||||
if (typeof window !== 'undefined' && window.QDB_API_BASE) {
|
||||
return String(window.QDB_API_BASE).replace(/\/$/, '');
|
||||
}
|
||||
const fromRelative = new URL('../api', window.location.href);
|
||||
const relPath = fromRelative.pathname.replace(/\/$/, '');
|
||||
const pageDir = window.location.pathname.replace(/\/[^/]*$/, '/') || '/';
|
||||
const mount = pageDir.split('/').filter(Boolean)[0];
|
||||
// Subpath deploy: /prefix/ is aliased to website/, so ../api wrongly becomes /api
|
||||
if (mount && mount !== 'api' && mount !== 'website' && relPath === '/api') {
|
||||
return `/${mount}/api`;
|
||||
}
|
||||
return relPath;
|
||||
}
|
||||
|
||||
const API_BASE = resolveApiBase();
|
||||
|
||||
/** Build a URL under the API base (path may include query string). */
|
||||
export function apiUrl(endpoint) {
|
||||
const clean = String(endpoint).replace(/^\//, '').replace(/\.php(\?|$)/, '$1');
|
||||
return `${API_BASE}/${clean}`;
|
||||
}
|
||||
|
||||
function getToken() {
|
||||
return localStorage.getItem('qdb_token');
|
||||
@ -107,7 +132,7 @@ async function apiFetch(endpoint, options = {}) {
|
||||
|
||||
// Strip .php suffix for clean URLs
|
||||
const cleanEndpoint = endpoint.replace(/\.php(\?|$)/, '$1');
|
||||
const res = await fetch(`${API_BASE}/${cleanEndpoint}`, { ...options, headers });
|
||||
const res = await fetch(apiUrl(cleanEndpoint), { ...options, headers });
|
||||
|
||||
if (res.status === 401 || res.status === 403) {
|
||||
await handleAuthResponse(res);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { apiGet, apiPost, apiPut, apiDownloadFetch, redirectToLogin } from '../api.js';
|
||||
import { apiGet, apiPost, apiPut, apiDownloadFetch, redirectToLogin, apiUrl } from '../api.js';
|
||||
import { getRole, pageHeaderHTML, showToast } from '../app.js';
|
||||
import { confirmAction } from '../confirm-modal.js';
|
||||
import { navigate } from '../router.js';
|
||||
@ -494,7 +494,7 @@ function wireAdminZipExport() {
|
||||
const prev = btn.textContent;
|
||||
btn.textContent = 'Preparing ZIP…';
|
||||
try {
|
||||
await downloadExportZip('../api/export?exportAll=1', 'responses_export_current.zip');
|
||||
await downloadExportZip(apiUrl('export?exportAll=1'), 'responses_export_current.zip');
|
||||
showToast('ZIP download started (current data)', 'success');
|
||||
} catch (e) {
|
||||
showToast(e.message, 'error');
|
||||
@ -510,7 +510,7 @@ function wireAdminZipExport() {
|
||||
const prev = btn.textContent;
|
||||
btn.textContent = 'Preparing ZIP…';
|
||||
try {
|
||||
await downloadExportZip('../api/export?exportAll=1&allVersions=1', 'responses_export_all_versions.zip');
|
||||
await downloadExportZip(apiUrl('export?exportAll=1&allVersions=1'), 'responses_export_all_versions.zip');
|
||||
showToast('ZIP download started (all versions)', 'success');
|
||||
} catch (e) {
|
||||
showToast(e.message, 'error');
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { apiGet, apiDownloadFetch } from '../api.js';
|
||||
import { apiGet, apiDownloadFetch, apiUrl } from '../api.js';
|
||||
import { canEdit, pageHeaderHTML, showToast } from '../app.js';
|
||||
let questionnairesList = [];
|
||||
let filterSearch = '';
|
||||
@ -153,7 +153,7 @@ function wireExportActions() {
|
||||
const prev = btn.textContent;
|
||||
btn.textContent = 'Preparing…';
|
||||
try {
|
||||
const res = await apiDownloadFetch('../api/export?bundle=1');
|
||||
const res = await apiDownloadFetch(apiUrl('export?bundle=1'));
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
throw new Error(err.error?.message || err.error || 'Export failed');
|
||||
@ -197,7 +197,7 @@ async function onExportCsvClick(ev) {
|
||||
const prev = btn.textContent;
|
||||
btn.textContent = 'Downloading…';
|
||||
try {
|
||||
const url = `../api/export?questionnaireID=${encodeURIComponent(btn.dataset.id)}`;
|
||||
const url = apiUrl(`export?questionnaireID=${encodeURIComponent(btn.dataset.id)}`);
|
||||
await downloadExportCsv(btn, url, `${btn.dataset.name}_v${btn.dataset.version}.csv`);
|
||||
showToast('Download started', 'success');
|
||||
} catch (e) {
|
||||
@ -214,7 +214,7 @@ async function onExportAllVersionsClick(ev) {
|
||||
const prev = btn.textContent;
|
||||
btn.textContent = 'Downloading…';
|
||||
try {
|
||||
const url = `../api/export?questionnaireID=${encodeURIComponent(btn.dataset.id)}&allVersions=1`;
|
||||
const url = apiUrl(`export?questionnaireID=${encodeURIComponent(btn.dataset.id)}&allVersions=1`);
|
||||
const stamp = new Date().toISOString().slice(0, 10);
|
||||
await downloadExportCsv(btn, url, `${btn.dataset.name}_all_versions_${stamp}.csv`);
|
||||
showToast('All versions download started', 'success');
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { navigate } from '../router.js';
|
||||
import { isLoggedIn, showToast } from '../app.js';
|
||||
import { apiGet, invalidateSessionCheck } from '../api.js';
|
||||
import { apiGet, invalidateSessionCheck, apiUrl } from '../api.js';
|
||||
|
||||
const WEBSITE_ROLES = ['admin', 'supervisor'];
|
||||
|
||||
@ -103,7 +103,7 @@ export async function loginPage() {
|
||||
|
||||
setFormBusy('loginForm', true, 'Signing in…');
|
||||
try {
|
||||
const res = await fetch('../api/auth/login', {
|
||||
const res = await fetch(apiUrl('auth/login'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -176,7 +176,7 @@ export async function loginPage() {
|
||||
if (pendingTempToken) {
|
||||
headers['Authorization'] = `Bearer ${pendingTempToken}`;
|
||||
}
|
||||
const res = await fetch('../api/auth/change-password', {
|
||||
const res = await fetch(apiUrl('auth/change-password'), {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ username: pendingUsername, old_password: oldPw, new_password: newPw }),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { apiGet, apiPost, apiPut, apiDelete, apiDownloadFetch } from '../api.js';
|
||||
import { apiGet, apiPost, apiPut, apiDelete, apiDownloadFetch, apiUrl } from '../api.js';
|
||||
import { canEdit, pageHeaderHTML, showToast } from '../app.js';
|
||||
import { confirmAction } from '../confirm-modal.js';
|
||||
import {
|
||||
@ -634,7 +634,7 @@ function bindTranslationBundleActions() {
|
||||
const prev = exportBtn.textContent;
|
||||
exportBtn.textContent = 'Preparing…';
|
||||
try {
|
||||
const res = await apiDownloadFetch('../api/translations?exportBundle=1');
|
||||
const res = await apiDownloadFetch(apiUrl('translations?exportBundle=1'));
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
throw new Error(err.error?.message || err.error || 'Export failed');
|
||||
|
||||
Reference in New Issue
Block a user