Prepare production deployment for baic.oliver.solutions/modcomms

- Add VITE_BASE_PATH support to vite.config.ts so assets resolve correctly under /modcomms/ subpath
- Fix home URL in urlState.ts to use BASE_URL instead of hardcoded '/'
- Fix sidebar logo src to use BASE_URL prefix (Vite doesn't rewrite TSX src attributes)
- Fix Azure AD redirect/logout URIs to include BASE_URL subpath in authConfig.ts and App.tsx
- Add migration 009 to remove Mindshare/Zenith and add Rapp agency
- Update .env.deploy.example with production values for baic.oliver.solutions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-03-06 11:54:00 +00:00
parent a2382cf027
commit ff1c809249
7 changed files with 48 additions and 11 deletions

View file

@ -24,17 +24,24 @@ POSTGRES_PASSWORD=change_this_in_production
POSTGRES_DB=modcomms
# Frontend deployment directory (Apache document root)
# Examples: /var/www/modcomms-prod, /var/www/modcomms-dev
FRONTEND_DEPLOY_DIR=/var/www/html/modcomms
# Production: /var/vhosts/baic.oliver.solutions/htdocs/modcomms
# Dev/staging: /var/www/html/modcomms
FRONTEND_DEPLOY_DIR=/var/vhosts/baic.oliver.solutions/htdocs/modcomms
# CORS origins (should match your frontend domain)
# Multiple origins can be comma-separated
CORS_ORIGINS=https://your-domain.com
CORS_ORIGINS=https://baic.oliver.solutions
# NOTE: Frontend environment variables (VITE_*) are NOT configured here.
# Create frontend/.env.local manually with:
# VITE_BACKEND_WS_URL=wss://your-domain.com/ws/analyze
# VITE_BACKEND_URL=https://your-domain.com
# VITE_BASE_PATH=/modcomms/
# VITE_BACKEND_URL=https://baic.oliver.solutions/back
# VITE_BACKEND_WS_URL=wss://baic.oliver.solutions/back/ws/analyze
# VITE_AZURE_CLIENT_ID=your_azure_client_id
# VITE_AZURE_TENANT_ID=your_azure_tenant_id
# VITE_AZURE_REDIRECT_URI=https://your-domain.com
# VITE_AZURE_REDIRECT_URI=https://baic.oliver.solutions/modcomms/
#
# Apache proxy config (production, port 8000):
# ProxyPass /back/ws/analyze ws://localhost:8000/ws/analyze
# ProxyPass /back/ http://localhost:8000/
# ProxyPassReverse /back/ http://localhost:8000/

View file

@ -0,0 +1,29 @@
"""Production agencies: add Rapp, remove Mindshare and Zenith
Revision ID: 009_production_agencies
Revises: 008_add_user_change_log
Create Date: 2026-03-06
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = '009_production_agencies'
down_revision: Union[str, None] = '008_add_user_change_log'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# Remove agencies not used in production
op.execute("DELETE FROM agencies WHERE name IN ('Mindshare', 'Zenith')")
# Add Rapp (idempotent)
op.execute("INSERT INTO agencies (name) VALUES ('Rapp') ON CONFLICT (name) DO NOTHING")
def downgrade() -> None:
op.execute("DELETE FROM agencies WHERE name = 'Rapp'")
op.execute("INSERT INTO agencies (name) VALUES ('Mindshare') ON CONFLICT (name) DO NOTHING")
op.execute("INSERT INTO agencies (name) VALUES ('Zenith') ON CONFLICT (name) DO NOTHING")

View file

@ -848,7 +848,7 @@ const AppContent: React.FC<{ msalInstance: any }> = ({ msalInstance }) => {
const handleLogout = async () => {
try {
await msalInstance.logoutPopup({
postLogoutRedirectUri: window.location.origin,
postLogoutRedirectUri: window.location.origin + import.meta.env.BASE_URL,
});
} catch (error) {
console.error('Logout failed:', error);

View file

@ -38,7 +38,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ activeItem, onNavigate, userNa
<aside className="w-72 flex-shrink-0 bg-oliver-black text-slate-200 flex flex-col font-sans">
{/* Brand Header */}
<div className="py-6 px-8 border-b border-white/10 flex flex-col items-center text-center">
<img src="/BAR-ModComms-logo-v4.png" alt="Mod Comms AI" className="w-full h-auto object-contain" />
<img src={`${import.meta.env.BASE_URL}BAR-ModComms-logo-v4.png`} alt="Mod Comms AI" className="w-full h-auto object-contain" />
</div>
{/* Navigation */}

View file

@ -12,8 +12,8 @@ export const msalConfig: Configuration = {
auth: {
clientId: CLIENT_ID,
authority: `https://login.microsoftonline.com/${import.meta.env.VITE_AZURE_TENANT_ID || 'common'}`,
redirectUri: import.meta.env.VITE_AZURE_REDIRECT_URI || window.location.origin,
postLogoutRedirectUri: window.location.origin,
redirectUri: import.meta.env.VITE_AZURE_REDIRECT_URI || window.location.origin + import.meta.env.BASE_URL,
postLogoutRedirectUri: window.location.origin + import.meta.env.BASE_URL,
},
cache: {
cacheLocation: 'localStorage', // Persists auth state across browser tabs/refresh

View file

@ -24,7 +24,7 @@ export function buildUrl(state: Partial<UrlNavigationState>): string {
if (state.campaignName) params.set('campaign', state.campaignName);
if (state.proofId) params.set('proof', state.proofId);
const query = params.toString();
return query ? `?${query}` : '/';
return query ? `?${query}` : import.meta.env.BASE_URL;
}
export function pushUrlState(state: Partial<UrlNavigationState>): void {

View file

@ -18,6 +18,7 @@ export default defineConfig(({ mode }) => {
'process.env.VITE_BACKEND_WS_URL': JSON.stringify(env.VITE_BACKEND_WS_URL || 'ws://localhost:8000/ws/analyze'),
'process.env.VITE_BACKEND_URL': JSON.stringify(env.VITE_BACKEND_URL || 'http://localhost:8000'),
},
base: env.VITE_BASE_PATH || '/',
resolve: {
alias: {
'@': path.resolve(__dirname, '.'),