<IfModule mod_ssl.c>
<VirtualHost *:443>
    ServerAdmin admin@texise.com
    ServerName www.texise.com
    ServerAlias texise.com
    DocumentRoot /var/www/texise.com/public_html/dist

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/texise.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/texise.com/privkey.pem

    RewriteEngine On
    RewriteCond %{HTTP_HOST} ^texise\.com$ [NC]
    RewriteRule ^ https://www.texise.com%{REQUEST_URI} [R=301,L,NE]

    # Internal SEO renderer is reachable only through Apache rewrite proxying.
    RewriteRule ^/?__texise(?:/|$) - [R=404,L]

    # Legacy public routes must be real HTTP redirects, not SPA fallbacks.
    RewriteRule ^/?examples/?$ https://www.texise.com/templates [R=301,L,NE]
    RewriteRule ^/?latex-templates/?$ https://www.texise.com/templates [R=301,L,NE]
    RewriteRule ^/?latex-templates/([A-Za-z0-9-]+)/?$ https://www.texise.com/templates/$1 [R=301,L,NE]
    RewriteRule ^/?index\.html$ https://www.texise.com/ [R=301,L,NE]

    ProxyPreserveHost On
    ProxyRequests Off
    RequestHeader set X-Forwarded-Proto "https"
    RequestHeader set X-Forwarded-Port "443"

    # Trusted Node API. The source defaults to 127.0.0.1:3100; installer/runtime
    # validation must confirm the listener before enabling this vhost.
    ProxyPass        /api/ http://127.0.0.1:3100/api/
    ProxyPassReverse /api/ http://127.0.0.1:3100/api/
    ProxyPass        /sitemap.xml http://127.0.0.1:3100/sitemap.xml
    ProxyPassReverse /sitemap.xml http://127.0.0.1:3100/sitemap.xml
    ProxyPass        /robots.txt http://127.0.0.1:3100/robots.txt
    ProxyPassReverse /robots.txt http://127.0.0.1:3100/robots.txt
    ProxyPass        /blog/rss.xml http://127.0.0.1:3100/blog/rss.xml
    ProxyPassReverse /blog/rss.xml http://127.0.0.1:3100/blog/rss.xml

    # TeXise collaboration. /collab-live is the authoritative Yjs document +
    # awareness relay; /collab-signal remains compatibility-only for older clients.
    ProxyPass        /collab-live ws://127.0.0.1:3101/collab-live
    ProxyPassReverse /collab-live ws://127.0.0.1:3101/collab-live
    ProxyPass        /collab-signal ws://127.0.0.1:3101/collab-signal
    ProxyPassReverse /collab-signal ws://127.0.0.1:3101/collab-signal

    # TexLab. Browser websocket credentials are short-lived and
    # issued only by the trusted /api/terminal broker after account login and
    # session-ownership checks; TexLab is not a Pro-only feature.
    RewriteCond %{HTTP:Upgrade} =websocket [NC]
    RewriteCond %{HTTP:Connection} upgrade [NC]
    RewriteRule ^/console-gateway/?(.*)$ ws://127.0.0.1:8787/$1 [P,L]
    <LocationMatch "^/console-gateway">
        Require all granted
    </LocationMatch>

    # Legacy Hocuspocus and Laravel Reverb routes remain retired. Their old
    # units/source trees are preserved as rollback material.

    # Blog/Docs CMS media is published into dist/texise-media from the persistent
    # /var/www/texise.com/texise-media staging tree. Missing media must stay a
    # 404 and must never fall through to the SPA.

    # Private server/runtime namespaces physically live below public_html but are
    # never web content.
    <Directory "/var/www/texise.com/public_html/texise-data">
        Require all denied
    </Directory>
    <Directory "/var/www/texise.com/public_html/texise-services">
        Require all denied
    </Directory>
    <LocationMatch "^/(?:texise-data|texise-services)(?:/|$)">
        Require all denied
    </LocationMatch>

    <Directory "/var/www/texise.com/public_html/dist">
        Options -Indexes
        AllowOverride None
        Require all granted
        DirectoryIndex index.html
        RewriteEngine On
        # The document-root request resolves to the dist directory itself. Route
        # it explicitly through the trusted renderer instead of depending on the
        # host's mod_dir/DirectoryIndex defaults. This keeps the homepage SEO
        # response and deployment health behavior identical to other public SPA
        # routes while index.html remains the renderer's immutable shell input.
        RewriteRule ^$ http://127.0.0.1:3100/__texise/render/ [P,L,NE]
        RewriteCond %{REQUEST_URI} !^/(?:api|collab-live|collab-signal|console-gateway|texise-media|texise-data|texise-services|__texise)/ [NC]
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteRule ^(.*)$ http://127.0.0.1:3100/__texise/render/$1 [P,L,NE]
    </Directory>

    AddType application/wasm .wasm
    AddType image/x-icon .ico
    AddType image/svg+xml .svg
    AddType application/manifest+json .webmanifest
    AddType font/woff2 .woff2
    AddType font/otf .otf
    AddType font/ttf .ttf

    Header always set Cross-Origin-Opener-Policy "same-origin"
    Header always set Cross-Origin-Embedder-Policy "credentialless"
    Header always set X-Content-Type-Options "nosniff"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"
    Header always set Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; form-action 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' https://cdn.jsdelivr.net https://webr.r-wasm.org https://www.statcounter.com https://www.googletagmanager.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https: wss://www.texise.com http://127.0.0.1:* http://localhost:*; worker-src 'self' blob: https://cdn.jsdelivr.net https://webr.r-wasm.org; child-src 'self' blob:; manifest-src 'self'; media-src 'self' blob:"
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"

    <FilesMatch "^(index\.html|site\.webmanifest|manifest\.webmanifest|texise-texlive-sw\.js|release\.json)$">
        Header always set Cache-Control "no-cache, no-store, must-revalidate"
    </FilesMatch>
    <FilesMatch "^(favicon\.ico|favicon\.svg|favicon-96x96\.png|apple-touch-icon\.png)$">
        Header always set Cache-Control "public, max-age=86400"
    </FilesMatch>
    <LocationMatch "^/(assets|compiler|texise-icons|texise-fonts|locales|texlive|mathlive-fonts)/">
        Header always set Cache-Control "public, max-age=31536000, immutable"
    </LocationMatch>
    <LocationMatch "^/texise-media/(?:blog|docs)/[A-Za-z0-9-]+\.(?:png|jpg|webp)$">
        Header always set Cache-Control "public, max-age=31536000, immutable"
    </LocationMatch>
    <Location "/compiler/status.json">
        Header always set Cache-Control "no-cache, no-store, must-revalidate"
    </Location>
    <Location "/texise-templates/templates.json">
        Header always set Cache-Control "no-cache, no-store, must-revalidate"
    </Location>
    <Location "/texise-templates/categories.json">
        Header always set Cache-Control "no-cache, no-store, must-revalidate"
    </Location>
    # Template source remains inside public_html for filesystem-first operations,
    # but Apache must never serve source archives/files directly. Browser imports
    # use the entitlement-aware /api/templates/... delivery boundary instead.
    <LocationMatch "^/texise-templates/(free|pro)/">
        Require all denied
        Header always set Cache-Control "private, no-store"
    </LocationMatch>
    <LocationMatch "^/texise-templates/(?:institutions|redirects)\.json$">
        Require all denied
        Header always set Cache-Control "private, no-store"
    </LocationMatch>
    <LocationMatch "^/compiler/busytex/">
        Header always set Cache-Control "public, max-age=0, must-revalidate"
    </LocationMatch>
    <LocationMatch "^/texlive/(bundle\.json|cache-manifest-(text|binary)\.txt)$">
        Header always set Cache-Control "no-cache, no-store, must-revalidate"
    </LocationMatch>

    ErrorLog ${APACHE_LOG_DIR}/texise-error.log
    CustomLog ${APACHE_LOG_DIR}/texise-access.log combined
</VirtualHost>
</IfModule>
