👻

Fant ut hvorfor tenant admin ikke ble autorisert for admin-dashboardet ved login. Den må navigere inn fra domenet til tenanten den er admin for. Det er fordi Payload leser av domenet og matcher den mot tenant-domenene til useren. Så useren blir autentisert, men ikke autorisert hvis den logger seg inn fra et domene den ikke administrerer.

Custom /api route for Payload

Man kan definere custom routes i Payloads config:

  routes: {
    api: "/cms/api"
  }

Nginx config måtte endres fra /api til:

location /cms {
    proxy_pass http://localhost:3000; # Your CMS app port
    ...
}

Next-appene får da /api-routen for seg selv.

🙆

Ser i TODOen at det skal lages testtype i CMSen i dag, Courses. Den skal ha data fra begge tenants, tenants skal querye den i frontends og få sine data, og gjest skal kunne legge items av den i handlekurv og kjøpe dem med Vipps, og Vipps skal callbacke så systemet får sendt en epost.

GraphQL

Begynner å designe noen typer og kommer på at det blir aktuelt å utvide dem litt for litt (etter hvert som nye klienter kommer med nye behov). Da er det vel fint at Payload støtter graphQL tror jeg.

⚜️

Strategi

Er det overkill å starte opp med multi-tenant setup? Målet er jo å bli kjent med CMSen først. Hvis ikke det blir store forsinkelser med plunder og heft er det kanskje verdt det. Mer avansert oppsett krever mer forståelse og jeg vil gjerne ha CMSen til fingerspissene. Det får jeg tilbake for når jeg må lage single-tenant setups.

Lurer på om det er meningen å hoste frontendene sammen med backenden – eller kanskje som del av backenden, med komponentene som bare typer i backenden – for å få full uttelling for multi-tenancy? Nå tenker jeg ikke å hoste dem på samme sted som backenden engang.

Jeg tror det er gjenbrukspotensialet i dokumentstrukturene i backenden som gir uttelling ved multi-tenancy. Payload foreslår jo selv multi-tenant setups med separate frontends.

Utvidet Onenoten med ny TODO for oppføringen av testprosjekt og pipeline, så tok jeg med kolonner for Beslutning og Behov. Følte det manglet litt skriftlig forankring for tidsbruken.

Hosts-fila

Hosts-fila (ligger i C:\Windows\System32\drivers\etc på Windows) er en statisk fil der man kan legge inn custom mappinger fra domenenavn til IP-adresser. Ved HTTP requests sjekker operativsystemet hosts-fila før den eventuelt gjør en ekstern DNS-lookup.

Jeg bruker den til å mappe test-domener til 127.0.0.1 for å mocke produksjon, der forskjellige domener blir å route inn utenfra. Testdomenene representerer tenants som jeg kan jobbe med lokalt. Da kan jeg navigere til dem i browseren, og hosts-fila mapper meg til localhost:

# First Bird tenants
127.0.0.1 hjertets-tempel.localhost
127.0.0.1 oscar-floor.localhost

Navigering uten port, eller til kun localhost får refusal to connect i browseren, antagelig fordi det er ambiguøst. Det er to Next-apper jeg har kjørende, en på port 3001 og en på port 3002. De kan jeg nå på hjertets-tempel.localhost:3001 respektivt oscar-floor.localhost:3002.

Det funker også å bruke nginx til å revers-proxye fra hostnavn til riktig port. I tillegg bruker jeg nginx til å revers-proxye hvert domene til backend eller frontend basert på route:

  • Alle apper har felles backend på route testdomene.localhost/admin og /api (port 3000)
  • Hver app har egen frontend på route testdomene.localhost/ (egen port)

⚠️ Det viste seg at ved navigering til /admin gjør backenden kall til endepunkter under /api, der jeg får 404 hvis ikke også disse kallene blir revers-proxyet til 3000-porten. Det er litt dumt, for frontendene har også kall de trenger å gjøre til endepunkter under samme route, /api.

ChatGPT foreslår å endre en eller alle routene for å separere dem skikkelig, i så fall gir det mening å skille ut kun backend-routen så ikke den går i beina på noen av frontendene. Jeg vet ikke hvordan man får til det uten å endre i kildekoden til Payload.

Forresten – det blir ikke et problem hvis jeg hoster backenden på et annet sted enn frontendene. Tanken er å hoste backenden i Payload Cloud. Da får man en del features på kjøpet, typ epost. Det er sikkert mulig å hoste frontendene sammen med den, og sikkert billigere.

😎

Status: Sette opp monorepo for første gruppe med multi-tenants. Det blir Payload CMS med multi-tenant setup i backend som kjører i Payload Cloud, og parallelle Next-apper som deployes fra en server hos kanskje Dreamhost, hvis ikke Vercel. Denne første gruppa skal ha tenants med bare 1-2 admins og enkle collections i backend og enkel Vipps-handlekurv i frontend.

Yarn Workspaces

Man har en package.json på rot som erklærer at det ligger workspaces i /packages. I packages har jeg 1 prosjekt for backend som er en multitenant Payload CMS, og x antall Next-prosjekter, som er mine tenants i gruppa.

  • Kjør yarn install på rot for å installere alle prosjektene og “hoiste” (konsolidere) dependenciene deres (unngå node_modules bloat). Dette er native funksjonalitet i Yarn Workspaces.
  • Kjør yarn workspace <project-name> run <script> for å kjøre et script på et prosjekt.
  • NB! Yarn vil ikke kjøre flere script på en gang. Du må gå på rot og installere Concurrently med yarn add concurrently --dev -W og så legge til et script i package.json på rot som angir scriptene det gjelder:
  "scripts": {
    "dev-concurrently": "concurrently \"yarn workspace backend run dev\" \"yarn workspace hjertets-tempel run dev\" \"yarn workspace oscar-floor run dev\""
  }

Nyttig å sette opp for å kjøre backend samtidig med prosjektet som jobbes med.

--dev flagget adder Concurrently som en dev-dependency.

-W flagget er for å forsikre Yarn om at du virkelig vil installere Concurrently på rot.

Nginx

ChatGPT foreslår nginx for å løse følgende:

  • Routing av innkommende HTTP-requests til riktig prosess: Jeg trenger at en requests til www.tenant-1.com blir servet av kun Next-appen til tenant 1.

Lokalt prøver jeg å mocke noen tenant-domener ved å legge til typ dette i hosts-fila:

127.0.0.1 tenant-1.localhost
127.0.0.1 tenant-2.localhost

Da skal de URLene rute til 127.0.0.1.

Så har jeg nginx-kjørende med følgende config, som skal revers-proxye disse requestene slik at URLene routes til riktig prosess:

# Configuration for the first tenant
server {
    listen 80;
    server_name tenant-1.localhost;

    # Specific path routing to the CMS app
    location /admin {
        proxy_pass http://localhost:3000; # Your CMS app port
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Default routing to the tenant's Next.js app
    location / {
        proxy_pass http://localhost:3001; # Tenant 1's Next.js app port
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
  • Maskering av URL når request kommer fra en tenant-frontend til admin-grensesnittet som serves av backend: Jeg vil at admin-login skal finnes på URLen www.tenant-1.com/login, ikke tenant-1.firstbird.is/login

Dette oppnås med nginx-configen over, der requests til /admin routes til backend-prosessen. I følge chatGPT skal alle subroutes også maskeres da, typ /admin/login og så videre.

Next-apper og port

I produksjon vil Next-appene kjøre på samme server, så de må gis spesifikke porter som nginx kan route trafikken deres til.