# Bundled Capacitor app — deploy & CORS notes

How to make the bundled app SPA (`BUILD_TARGET=capacitor`, see the "Dual build target" recipe in
`recipes.md`) actually work against production. The app runs on origin `https://localhost` and calls
`https://app.logship.de` cross-origin with token auth (`Authorization: Bearer` + `X-Logship-*`
headers, marker `X-Logship-App: 1`).

## 1. Deploy the Nuxt app — that's the whole "server deploy"

The new server code is part of the normal Nitro build, so a standard deploy of the Nuxt app to
`:3001` ships it. No special step.
- `server/middleware/00.app-auth.ts` — emits CORS for the app origin, answers the `OPTIONS`
  preflight, and maps the app's Bearer token + `X-Logship-*` headers into the cookies the existing
  routes read. No-op for web traffic.
- `server/api/app-auth/{login,token-refresh}.post.ts` — the app's auth endpoints.

## 2. nginx — NO CHANGE NEEDED ✅

Verified against the current production directive:
- `location /` → `proxy_pass http://192.168.12.72:3001` catches every `/api/app-auth/*`,
  `/api/mobile/*`, `/api/materials/*`, … call. It forwards `OPTIONS` and all request headers
  (our headers are hyphenated — `X-Logship-App`, `X-Logship-Organization-Id` — so nginx does NOT
  drop them; no `underscores_in_headers` needed), does not short-circuit `OPTIONS`, and adds no
  CORS of its own. So the in-app middleware fully handles CORS + preflight. **Leave it as is.**
- `location /api/v1` → iDempiere `:8443` is unaffected — none of our Nuxt routes use `/api/v1`.
- `client_max_body_size 100M` (global) covers image/voice uploads; `large_client_header_buffers
  4 32k` covers the base64 `X-Logship-User` header. Both already fine.

Do **not** add `add_header Access-Control-*` or an `if ($request_method = OPTIONS) { return 204; }`
to nginx — that would double the CORS headers (browser rejects) or answer the preflight WITHOUT the
CORS headers. Let Nitro handle it.

## 3. Strapi — the ONE change you need: allow the app origin

Image uploads (product galleries, RMA/receipt attachments in return/receive/product/movement) post
to `…/media-api/upload`, which your `location /media-api` block proxies to Strapi (`:1337/api`).
nginx forwards the preflight, but **Strapi's own CORS must allow `https://localhost`**.

On the Strapi server (`192.168.12.72:1337`), edit `config/middlewares.{js,ts}` — keep the rest of
the default array, only adjust the `strapi::cors` entry:

```js
{
  name: 'strapi::cors',
  config: {
    origin: [
      'https://app.logship.de',     // existing web
      'https://localhost',          // Capacitor Android (androidScheme: 'https')
      'capacitor://localhost',      // older Capacitor / iOS scheme
      'http://localhost',           // dev fallback
    ],
    methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
    headers: ['Content-Type', 'Authorization', 'Origin', 'Accept'],
    keepHeaderOnError: true,
  },
},
```

Restart Strapi after the change. (If `config.public.strapi` / `STRAPIPUBLIC` points directly at the
Strapi host rather than `https://app.logship.de`, the same Strapi CORS change still applies; only the
URL the app hits differs.)

## 4. Verify (run from a machine that can reach app.logship.de)

```bash
# Nuxt API preflight — expect: 204 + "Access-Control-Allow-Origin: https://localhost"
curl -i -X OPTIONS https://app.logship.de/api/app-auth/login \
  -H 'Origin: https://localhost' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: authorization,content-type,x-logship-app'

# Strapi upload preflight (after the Strapi CORS change) — expect: 2xx + ACAO: https://localhost
curl -i -X OPTIONS https://app.logship.de/media-api/upload \
  -H 'Origin: https://localhost' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: authorization,content-type'

# A real login (server-deployed) — expect a body with token + refresh_token
curl -i -X POST https://app.logship.de/api/app-auth/login \
  -H 'Origin: https://localhost' -H 'X-Logship-App: 1' -H 'Content-Type: application/json' \
  -d '{"userName":"<u>","password":"<p>"}'
```

If `app-auth/login` returns a `token` but no `refresh_token`, the inner `Set-Cookie` did not surface
through Nitro's internal `$fetch.raw` (see the recipe gotcha) — refresh then falls back to re-login.

## 5. Ship the APK

One command does the whole release (bump → build:app → cap:sync:app → assembleDebug → stage → upload):

```bash
scripts/release-app.sh            # auto-bumps the patch (e.g. 1.0.2 -> 1.0.3)
scripts/release-app.sh 1.1.0      # explicit versionName
scripts/release-app.sh --no-upload   # build + stage only
```

It bumps `android/app/build.gradle` + `android/update-files/version.json` together (versionName must
strictly increase or the in-app updater won't offer it), builds the bundled APK, stages it into
`android/update-files/logship-mobile.apk`, and hands off to `scripts/upload-mobile-update.sh`.
Remember to commit the version bump afterwards.
