Wire SPA + SSO redirect URI to /social-reports/ prefix; in-place cutover script
Phase A scaffolded the SPA at the bare origin (`/`); production lives behind
Apache at `/social-reports/`. Without these fixes, V2's built assets 404 and
Azure SSO rejects the redirect URI mismatch.
- Vite `base: /social-reports/` (overridable via VITE_BASE for dev).
- BrowserRouter basename = import.meta.env.BASE_URL.
- apiFetch + msal-browser script src + token-exchange URL all prefix BASE.
- MSAL redirectUri now matches V1's Azure-registered URI:
`${origin}/social-reports/login.html`.
- New `<Route path="/login.html">` alias renders the same Login component
so React Router matches the redirect URI when MSAL returns.
Deploy ergonomics (the user wants V1 gone from the server):
- v2/deploy/cutover-in-place.sh: run from /opt/social-reporting; stops V1,
pulls main (v2/ appears, V1 dirs deleted), migrates secrets from V1's
.env into v2/.env, swaps Apache, starts V2. Single command, no clone of
a sibling dir needed.
- setup-v2.sh: PURGE_V1=true flag now cleans /opt/social-reporting and
the V1 docker volume after V2 is healthy.
- rollback-to-v1.sh: re-clones the v1-archive branch when V1 is no longer
on disk (REPO_URL required).
62/62 unit tests still pass; vite build emits assets under /social-reports/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
17a635099a
commit
5770b2579d
9 changed files with 226 additions and 27 deletions
|
|
@ -14,13 +14,13 @@ For the full V2 spec see [DEVELOPER_BRIEF_V2.md](./DEVELOPER_BRIEF_V2.md).
|
|||
|
||||
## V1 archive
|
||||
|
||||
V1 source has been removed from `main`. It is preserved on the `v1-archive`
|
||||
branch and the running deployment at `/opt/social-reporting` on the server.
|
||||
|
||||
To roll back from V2 to V1:
|
||||
V1 source is preserved on the `v1-archive` branch (frozen at the last V1
|
||||
commit) and is no longer kept on the deployed server. To roll back from
|
||||
V2 to V1, the rollback script will re-clone `v1-archive` if needed:
|
||||
|
||||
```bash
|
||||
# On the server
|
||||
export REPO_URL="https://x-token-auth:YOUR_TOKEN@bitbucket.org/zlalani/social-reporting-tool.git"
|
||||
bash /opt/social-reporting-v2/v2/deploy/rollback-to-v1.sh
|
||||
```
|
||||
|
||||
|
|
|
|||
155
v2/deploy/cutover-in-place.sh
Executable file
155
v2/deploy/cutover-in-place.sh
Executable file
|
|
@ -0,0 +1,155 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# ═══════════════════════════════════════════════════════
|
||||
# Social Reporting V2 — In-place cutover
|
||||
# Run from the existing V1 deployment directory:
|
||||
# cd /opt/social-reporting && bash v2/deploy/cutover-in-place.sh
|
||||
# (It will git pull first, so the v2/ tree appears.)
|
||||
#
|
||||
# What it does:
|
||||
# 1. Stops V1 docker stack (so V1's compose file is freed BEFORE git pull deletes it).
|
||||
# 2. git pull origin main — drops V1 dirs, adds v2/.
|
||||
# 3. Migrates secrets from /opt/social-reporting/.env into v2/.env (preserves your
|
||||
# APIFY_TOKEN, ANTHROPIC_API_KEY, AZURE_*, etc.; generates a new SESSION_SECRET).
|
||||
# 4. Swaps the Apache conf to V2's, reloads.
|
||||
# 5. Builds + starts V2 docker stack.
|
||||
# ═══════════════════════════════════════════════════════
|
||||
|
||||
DIR="$(pwd)"
|
||||
APACHE_CONF="/etc/apache2/conf-available/social-reports.conf"
|
||||
|
||||
GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
||||
log() { echo -e "${GREEN}[+]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
||||
err() { echo -e "${RED}[x]${NC} $1"; exit 1; }
|
||||
|
||||
[[ -d "$DIR/.git" ]] || err "Run this from the deployment dir (e.g. /opt/social-reporting). $DIR has no .git."
|
||||
command -v docker >/dev/null || err "Docker not installed"
|
||||
command -v apache2ctl >/dev/null || err "Apache not installed"
|
||||
|
||||
# ─── Sanity-confirm ───
|
||||
warn "Cutover plan: stop V1 stack → git pull (V1 dirs deleted, v2/ appears) → swap Apache → start V2."
|
||||
warn "Working dir: $DIR"
|
||||
read -r -p "Proceed? [y/N] " ans
|
||||
[[ "$ans" != "y" && "$ans" != "Y" ]] && err "Aborted"
|
||||
|
||||
# ─── 1. Stop V1 BEFORE git pull (the pull deletes V1's docker-compose.yml) ───
|
||||
if [[ -f "$DIR/docker-compose.yml" ]]; then
|
||||
log "Stopping V1 stack..."
|
||||
docker compose -p social-listening down 2>/dev/null || warn "V1 was not running"
|
||||
fi
|
||||
|
||||
# Snapshot V1's .env so we can migrate values after the pull.
|
||||
V1_ENV_TMP=""
|
||||
if [[ -f "$DIR/.env" ]]; then
|
||||
V1_ENV_TMP="$(mktemp)"
|
||||
cp "$DIR/.env" "$V1_ENV_TMP"
|
||||
log "Snapshotted V1 .env to $V1_ENV_TMP"
|
||||
fi
|
||||
|
||||
# ─── 2. git pull main (this removes V1 source, adds v2/) ───
|
||||
log "Pulling main..."
|
||||
git pull origin main || err "git pull failed"
|
||||
[[ -d "$DIR/v2" ]] || err "After pull, v2/ directory still missing — main may not be the V2 branch"
|
||||
|
||||
cd "$DIR/v2"
|
||||
|
||||
# ─── 3. Migrate secrets to v2/.env ───
|
||||
get_old() { [[ -n "$V1_ENV_TMP" && -f "$V1_ENV_TMP" ]] && grep "^$1=" "$V1_ENV_TMP" | head -1 | cut -d= -f2- || true; }
|
||||
set_new() {
|
||||
local key="$1" val="$2"
|
||||
[[ -z "$val" ]] && return 0
|
||||
if grep -q "^${key}=" .env 2>/dev/null; then
|
||||
sed -i.bak "s|^${key}=.*|${key}=${val}|" .env && rm -f .env.bak
|
||||
else
|
||||
echo "${key}=${val}" >> .env
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ ! -f "$DIR/v2/.env" ]]; then
|
||||
log "Creating v2/.env from .env.example..."
|
||||
cp .env.example .env
|
||||
fi
|
||||
|
||||
# Always-fresh SESSION_SECRET (V1's was tied to V1's HMAC; cutting over invalidates anyway)
|
||||
set_new SESSION_SECRET "$(openssl rand -hex 32)"
|
||||
|
||||
# Migrate secrets from V1 .env if present
|
||||
for key in APIFY_TOKEN ANTHROPIC_API_KEY AZURE_TENANT_ID AZURE_CLIENT_ID DASH_USER DASH_PASS APIFY_LIVE_APPROVED ALLOWED_ORIGIN; do
|
||||
val="$(get_old $key)"
|
||||
[[ -n "$val" ]] && set_new "$key" "$val"
|
||||
done
|
||||
|
||||
# Generate a DB password if none present
|
||||
if ! grep -q '^DB_V2_PASSWORD=.\+' .env; then
|
||||
DB_PW="$(openssl rand -hex 16)"
|
||||
set_new DB_V2_PASSWORD "$DB_PW"
|
||||
set_new DATABASE_URL "postgresql://srv2_user:${DB_PW}@db-v2:5432/social_reporting_v2"
|
||||
fi
|
||||
|
||||
# Production knobs
|
||||
set_new NODE_ENV production
|
||||
set_new ALLOW_PASSWORD_FALLBACK false
|
||||
|
||||
# Force one VITE_AZURE_* surfacing — vite needs them at build time
|
||||
TENANT="$(grep '^AZURE_TENANT_ID=' .env | head -1 | cut -d= -f2-)"
|
||||
CLIENT="$(grep '^AZURE_CLIENT_ID=' .env | head -1 | cut -d= -f2-)"
|
||||
set_new VITE_AZURE_TENANT_ID "$TENANT"
|
||||
set_new VITE_AZURE_CLIENT_ID "$CLIENT"
|
||||
|
||||
if ! grep -q '^BOOTSTRAP_SUPER_ADMIN_EMAIL=.\+' .env; then
|
||||
warn "BOOTSTRAP_SUPER_ADMIN_EMAIL is not set in v2/.env."
|
||||
read -r -p "Email of first super-admin (must match the SSO sign-in): " admin_email
|
||||
set_new BOOTSTRAP_SUPER_ADMIN_EMAIL "$admin_email"
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
[[ -n "$V1_ENV_TMP" && -f "$V1_ENV_TMP" ]] && rm -f "$V1_ENV_TMP"
|
||||
|
||||
log "v2/.env populated. Review with: less $DIR/v2/.env"
|
||||
|
||||
# ─── 4. Apache: swap conf to V2's ───
|
||||
log "Swapping Apache config to V2..."
|
||||
[[ -f "$APACHE_CONF" ]] && sudo cp "$APACHE_CONF" "${APACHE_CONF}.v1.bak.$(date +%s)"
|
||||
sudo cp "$DIR/v2/deploy/apache-social-reports-v2.conf" "$APACHE_CONF"
|
||||
for mod in proxy proxy_http headers rewrite; do
|
||||
apache2ctl -M 2>/dev/null | grep -q "${mod}_module" || sudo a2enmod "$mod"
|
||||
done
|
||||
sudo a2enconf social-reports >/dev/null 2>&1 || true
|
||||
sudo apache2ctl configtest || err "Apache config test failed"
|
||||
|
||||
# ─── 5. Build + start V2 ───
|
||||
log "Building & starting V2 stack..."
|
||||
docker compose -f docker-compose.v2.yml -f docker-compose.v2.prod.yml --env-file .env up -d --build
|
||||
|
||||
log "Waiting for V2 backend (port 3457)..."
|
||||
for i in {1..40}; do
|
||||
curl -sf http://127.0.0.1:3457/api/health >/dev/null 2>&1 && { log "V2 healthy"; break; }
|
||||
[ "$i" -eq 40 ] && err "V2 not responding — docker compose -p social-reporting-v2 logs app-v2"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
log "Reloading Apache..."
|
||||
sudo systemctl reload apache2
|
||||
|
||||
# ─── Optional: clean up V1 docker volume ───
|
||||
if docker volume ls --format '{{.Name}}' | grep -q '^social-listening_pgdata$'; then
|
||||
warn "V1 docker volume 'social-listening_pgdata' is orphaned (V1 docker-compose.yml is gone after the pull)."
|
||||
read -r -p "Remove V1 db volume too? [y/N] " yn
|
||||
if [[ "$yn" == "y" || "$yn" == "Y" ]]; then
|
||||
docker volume rm social-listening_pgdata && log "V1 db volume removed."
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "════════════════════════════════════════════════════"
|
||||
echo -e " ${GREEN}V2 in-place cutover done.${NC}"
|
||||
echo " URL: https://optical-dev.oliver.solutions/social-reports/"
|
||||
echo " Backend: 127.0.0.1:3457"
|
||||
echo " Dir: $DIR (v2/ subdirectory)"
|
||||
echo " Logs: docker compose -p social-reporting-v2 logs -f app-v2"
|
||||
echo ""
|
||||
echo " First super-admin sign-in:"
|
||||
grep '^BOOTSTRAP_SUPER_ADMIN_EMAIL=' v2/.env || echo " (set BOOTSTRAP_SUPER_ADMIN_EMAIL in v2/.env)"
|
||||
echo "════════════════════════════════════════════════════"
|
||||
|
|
@ -2,8 +2,12 @@
|
|||
set -euo pipefail
|
||||
|
||||
# Roll back from V2 → V1 at the /social-reports URL.
|
||||
# V1 source must still be present at /opt/social-reporting (we don't delete it during cutover).
|
||||
#
|
||||
# V1 may or may not still be on disk:
|
||||
# - If /opt/social-reporting/.git exists, we use it.
|
||||
# - Otherwise, we re-clone the v1-archive branch (REPO_URL must be set).
|
||||
|
||||
REPO_URL="${REPO_URL:-}"
|
||||
BACKEND_DIR_V1="/opt/social-reporting"
|
||||
BACKEND_DIR_V2="/opt/social-reporting-v2"
|
||||
APACHE_CONF="/etc/apache2/conf-available/social-reports.conf"
|
||||
|
|
@ -13,7 +17,13 @@ log() { echo -e "${GREEN}[+]${NC} $1"; }
|
|||
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
||||
err() { echo -e "${RED}[x]${NC} $1"; exit 1; }
|
||||
|
||||
[[ -d "$BACKEND_DIR_V1/.git" ]] || err "V1 source not found at $BACKEND_DIR_V1 — cannot roll back"
|
||||
if [[ ! -d "$BACKEND_DIR_V1/.git" ]]; then
|
||||
[[ -z "$REPO_URL" ]] && err "V1 source not on disk and REPO_URL not set. Export REPO_URL and re-run."
|
||||
warn "V1 source not on disk; cloning v1-archive branch..."
|
||||
sudo mkdir -p "$BACKEND_DIR_V1"
|
||||
sudo chown "$(whoami):$(whoami)" "$BACKEND_DIR_V1"
|
||||
git clone -b v1-archive "$REPO_URL" "$BACKEND_DIR_V1"
|
||||
fi
|
||||
|
||||
warn "About to roll back /social-reports from V2 → V1."
|
||||
read -r -p "Proceed? [y/N] " ans
|
||||
|
|
@ -29,12 +39,13 @@ sudo apache2ctl configtest || err "Apache config test failed"
|
|||
|
||||
log "Starting V1 stack..."
|
||||
cd "$BACKEND_DIR_V1"
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||
[[ -f .env ]] || { warn "V1 .env missing — copy from a backup or recreate before running again"; err "Aborting before docker up"; }
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
|
||||
|
||||
log "Waiting for V1..."
|
||||
for i in {1..20}; do
|
||||
for i in {1..30}; do
|
||||
curl -sf http://127.0.0.1:3456/status >/dev/null 2>&1 && { log "V1 healthy"; break; }
|
||||
[ "$i" -eq 20 ] && err "V1 not responding — docker compose logs social-listening"
|
||||
[ "$i" -eq 30 ] && err "V1 not responding — docker compose logs social-listening"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,15 @@ set -euo pipefail
|
|||
# ═══════════════════════════════════════════════════════
|
||||
# Social Reporting V2 — Server Setup (one-time)
|
||||
# Target: optical-dev.oliver.solutions
|
||||
# Cuts over from V1 at the same URL. V1 source kept on disk for rollback.
|
||||
# Replaces V1 at the same URL. V1 is removed from the server; rollback re-clones
|
||||
# the v1-archive branch (see rollback-to-v1.sh).
|
||||
# ═══════════════════════════════════════════════════════
|
||||
|
||||
REPO_URL="${REPO_URL:-}"
|
||||
BACKEND_DIR_V2="/opt/social-reporting-v2"
|
||||
BACKEND_DIR_V1="/opt/social-reporting"
|
||||
APACHE_CONF="/etc/apache2/conf-available/social-reports.conf"
|
||||
PURGE_V1="${PURGE_V1:-}" # set to 'true' to delete /opt/social-reporting after V2 is healthy
|
||||
|
||||
GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
||||
log() { echo -e "${GREEN}[+]${NC} $1"; }
|
||||
|
|
@ -42,17 +44,15 @@ if [[ ! -f "$BACKEND_DIR_V2/v2/.env" ]]; then
|
|||
warn "Edit $BACKEND_DIR_V2/v2/.env: APIFY_TOKEN, ANTHROPIC_API_KEY, AZURE_*, BOOTSTRAP_SUPER_ADMIN_EMAIL"
|
||||
fi
|
||||
|
||||
# ─── Cutover from V1 (graceful) ───
|
||||
warn "About to cut over the /social-reports URL from V1 → V2."
|
||||
warn "V1 source remains at $BACKEND_DIR_V1 (untouched). Rollback: deploy/rollback-to-v1.sh"
|
||||
# ─── Cutover ───
|
||||
warn "About to take over the /social-reports URL with V2."
|
||||
read -r -p "Proceed? [y/N] " ans
|
||||
[[ "$ans" != "y" && "$ans" != "Y" ]] && err "Aborted"
|
||||
|
||||
# Stop V1 stack if it's running (no-op if V1 was never deployed here).
|
||||
if [[ -d "$BACKEND_DIR_V1" ]]; then
|
||||
log "Stopping V1 stack..."
|
||||
cd "$BACKEND_DIR_V1"
|
||||
docker compose -p social-listening down || warn "V1 was not running"
|
||||
cd "$BACKEND_DIR_V2"
|
||||
log "Stopping V1 stack (if running)..."
|
||||
(cd "$BACKEND_DIR_V1" && docker compose -p social-listening down 2>/dev/null) || warn "V1 was not running"
|
||||
fi
|
||||
|
||||
# ─── Apache: swap conf to V2 ───
|
||||
|
|
@ -80,12 +80,25 @@ done
|
|||
log "Reloading Apache..."
|
||||
sudo systemctl reload apache2
|
||||
|
||||
# ─── Optional V1 purge ───
|
||||
if [[ "$PURGE_V1" == "true" && -d "$BACKEND_DIR_V1" ]]; then
|
||||
warn "PURGE_V1=true — removing $BACKEND_DIR_V1 and the V1 docker volume"
|
||||
docker volume rm social-listening_pgdata 2>/dev/null || warn "(V1 db volume already gone)"
|
||||
sudo rm -rf "$BACKEND_DIR_V1"
|
||||
log "V1 source and db volume removed. Rollback now re-clones the v1-archive branch."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "════════════════════════════════════════════════════"
|
||||
echo -e " ${GREEN}V2 deployed!${NC}"
|
||||
echo " URL: https://optical-dev.oliver.solutions/social-reports/"
|
||||
echo " Backend: http://127.0.0.1:3457 (Docker)"
|
||||
echo " V2 dir: $BACKEND_DIR_V2"
|
||||
echo " V1 dir: $BACKEND_DIR_V1 (kept for rollback)"
|
||||
if [[ "$PURGE_V1" == "true" ]]; then
|
||||
echo " V1: purged"
|
||||
echo " Rollback: git checkout v1-archive on a new clone, then run V1's setup.sh"
|
||||
else
|
||||
echo " V1 dir: $BACKEND_DIR_V1 (still on disk; remove with PURGE_V1=true on next run)"
|
||||
echo " Rollback: bash $BACKEND_DIR_V2/v2/deploy/rollback-to-v1.sh"
|
||||
fi
|
||||
echo "════════════════════════════════════════════════════"
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ export default function App() {
|
|||
return (
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
{/* alias matches the V1 Azure-registered redirect URI (.../social-reports/login.html) */}
|
||||
<Route path="/login.html" element={<Login />} />
|
||||
<Route
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,13 @@ export class ApiError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
// API base mirrors the Vite `base` (e.g. `/social-reports/`) so requests resolve
|
||||
// to the Apache-proxied backend rather than the bare origin.
|
||||
const BASE = (import.meta.env.BASE_URL ?? '/').replace(/\/$/, '');
|
||||
|
||||
export async function fetcher<T = unknown>(path: string, init?: RequestInit): Promise<T> {
|
||||
const url = path.startsWith('/api') ? path : `/api${path.startsWith('/') ? path : `/${path}`}`;
|
||||
const apiPath = path.startsWith('/api') ? path : `/api${path.startsWith('/') ? path : `/${path}`}`;
|
||||
const url = `${BASE}${apiPath}`;
|
||||
const res = await fetch(url, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ async function ensureMsalLoaded(): Promise<void> {
|
|||
if (window.msal) return;
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const s = document.createElement('script');
|
||||
s.src = '/msal-browser.min.js';
|
||||
const base = (import.meta.env.BASE_URL ?? '/').replace(/\/?$/, '/');
|
||||
s.src = `${base}msal-browser.min.js`;
|
||||
s.async = true;
|
||||
s.onload = () => resolve();
|
||||
s.onerror = () => reject(new Error('Failed to load msal-browser.min.js'));
|
||||
|
|
@ -27,11 +28,16 @@ export async function getMsal() {
|
|||
if (!tenantId || !clientId) {
|
||||
throw new Error('Missing VITE_AZURE_TENANT_ID or VITE_AZURE_CLIENT_ID');
|
||||
}
|
||||
// Match the Azure-registered redirect URI from V1:
|
||||
// https://optical-dev.oliver.solutions/social-reports/login.html
|
||||
// BASE_URL is '/social-reports/' in prod, '/' in dev.
|
||||
const base = (import.meta.env.BASE_URL ?? '/').replace(/\/?$/, '/');
|
||||
const redirectUri = `${window.location.origin}${base}login.html`;
|
||||
pca = new window.msal.PublicClientApplication({
|
||||
auth: {
|
||||
clientId,
|
||||
authority: `https://login.microsoftonline.com/${tenantId}`,
|
||||
redirectUri: window.location.origin + '/login',
|
||||
redirectUri,
|
||||
},
|
||||
cache: { cacheLocation: 'sessionStorage' },
|
||||
});
|
||||
|
|
@ -48,7 +54,8 @@ export async function handleRedirectAndExchange(): Promise<{ ok: boolean } | nul
|
|||
const app = await getMsal();
|
||||
const result = await app.handleRedirectPromise();
|
||||
if (!result?.idToken) return null;
|
||||
const res = await fetch('/api/sso/token-exchange', {
|
||||
const base = (import.meta.env.BASE_URL ?? '/').replace(/\/$/, '');
|
||||
const res = await fetch(`${base}/api/sso/token-exchange`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import './styles.css';
|
|||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<BrowserRouter basename={import.meta.env.BASE_URL}>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</QueryClientProvider>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,21 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
// V2 ships behind Apache at /social-reports/ — same external URL V1 used. The base
|
||||
// here makes built asset URLs resolve under that prefix; pair this with React
|
||||
// Router's basename + apiFetch's prefix.
|
||||
//
|
||||
// Override via VITE_BASE in .env to e.g. '/' for local dev or a different path.
|
||||
const base = process.env.VITE_BASE ?? '/social-reports/';
|
||||
|
||||
export default defineConfig({
|
||||
base,
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:3457',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/api': { target: 'http://localhost:3457', changeOrigin: true },
|
||||
'/social-reports': { target: 'http://localhost:3457', changeOrigin: true },
|
||||
},
|
||||
},
|
||||
build: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue