🌈

Websocket

In modern web applications, WebSocket has become a popular technology for real-time communication. It enables data exchange between clients and servers over a single TCP connection. It is part of HTML5 and is widely supported by modern web browsers, including Chrome, Firefox, Safari, and Edge.

WebSocket connection failure is a common issue in web development.

https://apidog.com/blog/websocket-connection-failed

Ville bare resolve en feil som kommer i browser Console ved navigering som treffer nginx. Viser seg at nginx-configs må tilpasses med følgende direktiver for å slippe til Websocket connection:

    location / {
        ...
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;
    }

Fetching av tenant data

ChatGPT hadde ikke godt svar her, og post på Payload-discord er ikke besvart:

I’m making a PoC with Payload as multi-tenant CMS for some Next frontends. My query for tenant-specific data returns an empty list, and so I’d just like to check that my thinking and setup is correct.

I’m on localhost with my Payload backend (clone of multi-tenant example) and two Next frontends running on each their own port:

  • – The collection I’m querying is set up with a tenant field. I can logon as tenant admin and see other tenants’ data filtered out.
  • – My tenants are set up with a slug field, and their domains field is set up with a test domain that is mocked in Windows hosts file. I’m querying the data like so: http://tenant-mock.localhost/cms/api/courses?where[tenant][slug][equals]=tenant-slug
  • – I have Payload config set up with routes { api: "/cms/api" } to let the Next apps use that route. Then I have nginx configured for each host domain to redirect / to the Next frontend and /admin and /cms to my Payload backend.

Hjalp ikke å sette access: { read: () => true } på collection heller; det sto tenants der før som ville ført til tom liste i seg selv.

Hjalp ikke å fikse Websocket-feilen, forventet ikke egentlig at den spilte inn.

Hjalp ikke å sette cors: [ "*" ] i Payload config.

Sitter med denne på andre dagen og føler at det er en dum feil.

EDIT: La til depth=2 i querystringen og da får jeg data. Men får alt, ikke bare tilhørende tenanten (som også står i querystringen). Er rart for jeg trodde nettopp depth gjorde det mulig å nå tenant-feltet.

EDIT 2: Løsningen var å autorisere requesten til å representere en tenant-admin, da blir andre tenants filtert bort automagisk.

Det er to måter å gjøre det på: Autorisering som user, og autorisering med API-nøkkel tilknyttet en user. Det var mulig å sette config auth: { useAPIKey: true } på User-collection, i adminpanelet ble det da mulig å hake av for om API-nøkkel skal brukes for hver enkelt bruker. Da kan man opprette API-nøkkel på alle tenant-admins, og la hver frontend querye backenden som om de var tenant-admins, med API-nøkkelen.

Å gjøre access: read tilgjengelig overalt førte bare til at alle tenants’ data ble returnert. Uten autorisering klarte ikke Payload å skille mellom tenant-data, antagelig fordi den bruker tilgangskontrollen til å skille mellom dataene.

Filteret med queryparams fikk jeg aldri til å fungere, verken med REST eller graphQL.

depth hadde ikke noe å si til slutt; straks queryen funket kunne jeg fjerne dette parameteret.

👻

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.