Git

Vil bare logge at Github krever autentisering hvis du skal klone private prosjekter, og det kan løses med SSH-nøkler, men hvis du har flere kontoer krever Github at hver konto har sin egen nøkkel, og den private delen av nøkkelen må registreres lokalt på PCen, så hvis du har flere må de skilles fra hverandre i en egen config-fil. Så når du kloner ned må du spesifisere aliaset fra configen.

Lage nøkkel

Du lager et privat/public RSA-nøkkelpar (to filer) i Administrator: Powershell. De skal ligge i mappa .ssh, som skal ligge rett på useren din i Windows:

> cd C:\Users\ingri\.ssh
> ssh-keygen -t rsa -b 4096 -C "din@epostadresse.com"

Du blir bedt om å gi nøkkelen et navn; den private fila blir da stående uten extension: navn og den public med: navn.pub.

Enable SSH-agenten

Sjekk at SSH-agenten er enablet (hos meg var den ikke). Du bruker den til å registrere den private nøkkelen lokalt på PCen:

> Get-Service ssh-agent | Select-Object -Property StartType

Hvis det kommer opp at den er disablet, kjør:

> Set-Service -Name ssh-agent -StartupType Automatic

Registrere privat nøkkel

Du kan nå legge til den private nøkkelen:

> ssh-add ~\.ssh\navn

Kopiere public nøkkel

Hvis det funket skal du nå kopiere innholdet i den public-e nøkkelen:

> Get-Content ~\.ssh\ingridskard.pub

Kopier hele outputten, den starter med ssh-rsa og slutter med epostadressen du oppga.

Legge til public nøkkel på Github

Gå til Github-kontoen din, Settings, SSH And GPG Keys. Opprett ny SSH-nøkkel, type “Authentication Key”, lim inn outputten og lagre.

Lokal konfig ved flere nøkler

Dette vil nå funke ut av boksen hvis du holder på med bare én Github-konto. Men hvis på flere kontoer kan SSH-nøklene snuble i hverandre lokalt. Da kan du opprette en fil config i .ssh-mappa der du aliaser nøklene, så systemet klare å holde dem fra hverandre:

# Default GitHub account (ingridskard)
Host github.com-ingridskard
  HostName github.com
  User git
  IdentityFile ~/.ssh/ingridskard
  IdentitiesOnly yes

# Another GitHub account
Host github.com-shinysticker
  HostName github.com
  User git
  IdentityFile ~/.ssh/shinysticker
  IdentitiesOnly yes

Bruk av alias ved kloning

Når du kloner repo da må du bruke aliaset:

$ git clone git@github.com-ingridskard:ingridskard/ever-ip.git

Det som skjer nå er at Git sender med public-nøkkelen når du prøver å klone, og hvis systemet klarer å finne referanse til riktig privat-nøkkel i konfigen og den matcher, får du fortsette. Hvis nøklene ikke matcher får du feilmelding:

git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.

Hvis det ikke er satt opp noen nøkkel eller systemet ikke finner den, får du feilmelding om at repoet ikke fins.

Første gang systemet autentiserer med ny nøkkel spør git:

The authenticity of host 'github.com (140.82.121.4)' can't be established.
ED25519 key fingerprint is SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? 

Her skal du bare svare ja.

Strafferunde: Få VS Code til å lese aliasene

Du må sette aliaset på remote-url i repoet ditt for at VS Code skal klare å lese det. Du kan se hva VS Code leser nå med

> git remote -v

Hvis den urlen uten alias (typ git@github.com:ingridskard/repoet-ditt.git) kan du oppdatere den med:

> git remote set-url origin git@github.com-ingridskard:ingridskard/repoet-ditt.git

🔥

Bare litt om det frontend Auth dramaet. Kall det sunken cost fallacy, bruke to dager på å få opp frontend Auth når det ikke står på klientens liste, bare for å se om det er fort gjort å få opp eksempelet – for så å krasje appen slik at en dag ble å finne ut at [csrf] settingen gjør så cookien ikke blir slettet, som antagelig har å gjøre med at user ble undefined i requesten av og til.

Da følte jeg meg litt dum. Men her er det jeg ser nå. Det er en liste med ting som ikke bare Siv skal ha, alle skal ha det: Brukerinnlogging, theme, betaling med betalingsløsning for ting, reservering av plass, sending av epost til gruppe, gavekort, klippekort, blogg, nyhetsbrev, og sider der du kan redigere i teksten, bytte ut og legge til bilder…

Jeg tror det vil koste mer på sikt å ikke bygge funksjonene inn under brukerinnlogging nå. Det gir mer komplette data. Siv trenger det kanskje ikke nå men de aller fleste bedrifter som selger ting på nettet skal ha det, for både datakompletthet og rapporter og egen presentasjon. Litt flere klikk, men innlogging oppleves tryggere for kunder.

Det sitter liksom langt inne men jeg fant i går at Siv trenger brukerinnlogging for å få til klippekort (og gavekort) uansett. Siden hennes blir bedre med brukerinnlogging. Det er viktig funksjonalitet som har med kundenes datasikkerhet å gjøre. Så det er fornuftig å innarbeide det tidlig for min egen kompetanse sin del.

(˘・_・˘)

På tide å reprioritere etter to uker på Gran Canaria.

Intimsonen: Design kom delvis. Jobben må legges ut på nytt og gjøres ferdig av en annen designer. Det tar 1-2 uker. Skal implementeres, kontrakt utformes, fakturasystem være oppe. Utforming kontrakt og oppretting fakturasystem er bare gjørejobb. Siten trenger at epost fungerer, adminbranding.

  1. Fiks tenantlogin
  2. Epost for forms
  3. Admin datafilter virker

OFP: Design på vei. MVP skal ha foredragsliste, påmelding, rapport, epostklient. Krever alt over pluss:

  1. Ferdig epostklient
  2. Media

Overordnet skal vi ha:

  1. Unittester
  2. Adminbranding

Trenger vi egentlig?:

  1. Frontend-for-backend auth for:
  2. Edit i frontend

(▀̿Ĺ̯▀̿ ̿)

Module resolution med node

Det tok tre dager å få til bygg og serve av backend igjen, og det har lært meg følgende facts:

  • Som utvikler setter man path aliases for å unnslippe path hell – relative paths som må endres manuelt overalt hvis du flytter på ting (i tillegg til at lange relative paths er stygge å se på.) Vi vil ha import { foo } from 'bar/foo', ikke import { foo } from '../../../../../bar/foo'.
  • Du legger til path aliases i tsconfig.json for å få intellisense på de pathene.
  • Hvis du setter path aliases i tsconfig.json må du samtidig sette baseUrl, ellers funker det ikke. Du skal også sette moduleResolution til node.
  • Byggescript, f. eks. next build resolver path aliases ut av boksen, men hvis du bruker bundler må path aliasene legges til bundler-configens resolve.alias for at bundleren skal klare å resolve dem.
  • Vi kjører appen i Node.js, som er et runtime environment for Javascript. Det første node gjør ved oppstart er å resolve alle imports og exports. Da er det slik at node ikke har begrep om path aliases. Den må ha relative paths. Så hvis du bygger og bundler med Webpack og så prøver å serve med node, så knekker det med vår venn, MODULE_NOT_FOUND.
  • Det fins ulike måter å løse det på, blant annet kan du kjøre noe som heter tsc-alias rett etter tsc som leser path aliases fra tsconfig.json, går gjennom alle modulene dine og erstatter path aliases med relative paths i Javascript-outputen.
  • Har ikke sett IDE bemerke det, men “trailing commas” er ikke støttet i JSON. Kan føre til at verktøy som tsc-alias feiler ved parsing av tsconfig.json.

ᓚᘏᗢ

Edit: Da blir det klonede instanser av én rigg med blandede tenants. Første milepæl er publisering av kampanje. Før det skal tre sites opp: OF Promo, Siv Nancy, og Firstbird Promo til portfolio.

Lurte på om jeg skal ha bare én rigg som jeg utvikler i ett og bare ett gitrepo. Så blir multitenant-0, multitenant-1 og multitenant-2 instanser av den med ulike bruksområder. Da har alle instanser alle collections, endpoints, plugins osv. men de bruker ulike partisjoner.

Kan jeg slippe unna med å deploye alt fra ett og samme repo? Si de ligger på egne branches som trigger deploys, så utvikler jeg på main og merger inn fortløpende. Da blir det mulig å holde alle up-to-date.

Men hvilken gevinst har det å partisjonere instansene da, kan jeg ikke bare bygge ut riggen og plusse på tenants etter hvert som de dukker opp? Mer fleksibilitet til å utvikle riggen etter behov?

Tanken var at poenget med multitenancy var å la tenants dele funksjonalitet og også unngå å tilpasse adminpanelet for dem. Men lignende tenants vil fortsatt ha ulike behov til adminpanelet, så den jobben må gjøres uansett. Da opprettes nye instanser etter hvert som de gamle begynner å fylle seg opp med data. Og det blir lett å utvide tenants med flere features.

Angående multitenant-0 så var den tenkt uten adminpanel eller noe funksjonalitet, kun enkle “visittkort på nett”. Men da trenger jeg bare en Next app og ingen backend.

Da gjenstår bare å se hvilke features skal på plass først.

Kan man gruppere features etter vanlige usecases? Typ:

Multitenant-0: Visittkort-på-nett

Adminpanel: Nei
Auth: Nei
Features: Kontaktskjema
Pris: 15

Disse er ikke tenants men Next-apper på kun Vercel.

Usecases er bedrifter som trenger et SEO-optimalisert webnærvær der det informeres om tjenestene de tilbyr, hvorfor du bør velge dem og hvordan man kommer i kontakt. For SEO skal de være SSG og optimalisert for SEO for øvrig, kanskje inkludert forvaltning av Google Bedrift-oppføring, hjelp med innsamling av reviews osv. Prototyper fra ekstern designer.

Multitenant-X: Auth uten adminpanel

Adminpanel: Nei
Auth: Ja (inkl. Payload)
Features: Edit-in-place, Auth
Pris: Ca. 20

Spesielt setup for edit-in-place uten adminpanel.

Usecase er websider med innhold som endrer seg eller oppdaterer seg ofte, men strukturen (sider, layout) aldri endrer seg, f. eks. bedrifter som legger til eller endrer tjenestene sine ofte.

Redigerbart innhold bør være variert for å fange maks nisje-usecases: Richtekst-editor til HTML (vanlig tekst med utvidet støtte), bilde og bildegalleri eller -karusell, accordion, kodeblokk, sitatblokk, osv. Det er kult fordi implisitt kommer alltid editerbare strukturer inkl. innhold i sidens design. (Selve editmodus kan være superenkelt.)

Typer redigerbart innhold kan være Payload Blocks som lages støtte for i frontend. Merk at admin må kunne legge til og fjerne blocks i frontend.

Det blir som en WordPress-side for basic behov, men med bedre brukbarhet og design.

Ved login blir admin redirectet til frontend. De er logget inn i Payload, uten adminpanel men kan nå åpne innhold for redigering i frontend. Endringene blir committet til collections i Payload.

Vil unngå en første-iterasjon der admin må logge inn i adminpanelet for å oppdatere innhold der, det er dårlig brukbarhet.

Krever ikke admin-branding siden adminpanelet ikke er i bruk, men edit-in-place krever at Auth er implementert, og det krever mye utvikling av frontend for å støtte oppretting, endring og fjerning av ulike typer blocks og committing av ulike typer endringer til Payload.

Multitenant-1

1 collection, kun standardfeatures.

Du kan logge inn og oppdatere én toppcollection (f. eks. Events). Du får en frontend med et tilpasset view av dataene (f. eks. en liste over foredrag, konsertplan, turneplan).

Login: Ja, med adminpanel
Collections: 1 (inkl. view)
Features: Ingen tilpassede features
Pris: Ca. 25

Multitenant-2

Collections med booking (inkl. betaling) og aktivitetsrapporter.

Du kan logge inn og oppdatere flere collections (f. eks. Courses og Events). Du får visning av dataene i frontend, inkl. funksjonalitet for påmelding og betaling. Du får aktivitetsrapporter i adminpanelet.

Login: Ja, med adminpanel
Features: Påmelding/betaling, aktivitetsrapporter
Pris: Ca. 35

Tilpassede features

  • Booking: “Å kjøpe plass på et arrangement. Når arrangementet er over blir plassen borte. For å kjøpe ny plass må nytt arrangement opprettes
  • Reservation: “Å reservere en ressurs i et tidsrom. Etter tidsrommet blir ressursen tilgjengelig for reservering igjen.
  • Contract: Arbeidsflyt for utarbeidelse av avtaletekst med maltekster og -variabler, signatur med “magic link” el, PDF på epost…
  • Payment: Integrering med API for betaling med Vipps, Paypal, Stripe, krypto mm, registrering av påmelding…
  • Activity Report: Tabellvisning av historikk/aktivitet i forbindelse med booking, reservation mm, inkl. mulighet for eksport til fil eller Google Drive…

Standardfeatures

Ikke-tilpassede features som bør tilbys på alle produkter:

  • Kontaktskjema
  • Nyhetsbrev
  • Blogg
  • Admin: Branding
  • Admin: Pluss-add

Collections

Collections er samlinger av dokumenter. De har en viss shape gitt ved et konfigurert skjema i Payload. Features kan interface trygt med dem via typer.

Toppcollection er collections der andre, ikke-trivielle collections er nøstet inn. F. eks. et dokument av Courses gir typisk ikke mening hvis den mangler en liste med dokumenter av Events (mens en collection Addresses for å angi lokasjon er heller triviell.)

(✿◡‿◡)

Git

Jeg har jo et monorepo med ett backend-prosjekt som server n frontend-prosjekter. Så en vanlig workflow for meg er å kjøre backendprosjektet på localhost samtidig som jeg kjører ett av frontendprosjektene på localhost.

Samtidig er backenden deployet på Payload Cloud med bygg-trigger på innsjekk til en egen branch payload-cms, og frontendene deployet på Vercel med bygg-trigger på deres respektive branches. Men,

  • Det er klønete å jobbe mot to deploy-branches i ett repo fordi alle endringer må sjekkes inn til riktig branch hele tiden, hvis jeg sjekker inn endringer på prosjekt A til prosjekt B sin branch går As deploy glipp av dem og man må merge branchene manuelt.
  • Fordi branchene typisk ikke er i sync med hverandre kan jeg ikke ha branch A sjekket ut lokalt og kjøre branch B sin app i siste versjon. Jeg gidder ikke å merge branchene med hverandre (og med main) kontinuerlig.
  • Jeg kan ikke åpne repoet to ganger i VS Code; git endrer jo selve filsystemet etter utsjekket branch, så hvis jeg bytter branch i den ene instansen av VS Code så reflekteres det bare i den andre instansen.
  • Jeg kan ikke droppe branches og jobbe mot kun main, for da trigger jeg bygg på alle prosjektene hver gang jeg sjekker inn.
  • Jeg kan klone repoet to ganger og sjekke ut en branch i hver, men det fins bedre måter.

Arbeidsflyt med working trees

Man kan sjekke ut egne working trees fra repoet, ett for branch A og ett for branch B: https://andrewlock.net/working-on-two-git-branches-at-once-with-git-worktree/

Working trees er bare kopier av hele repoet, så det er som å ha klonet det to ganger, men i stedet for egne git-mapper har du en git-fil som peker på et rot-repo, så working-trærne jobber mot ett og samme filsystem; egne kloner er egne filsystemer.

Man kan da ta opp hver branch i egen instans av VS Code og kjøre appene i nyeste versjon der. Hvert innsjekk vil trigge bygg.

Kommando for å ta ut nytt working tree project-a fra branch-a:

git worktree add ../project-a branch-a

Trunk-based arbeidsflyt

Et annet alternativ er å jobbe rett på main. Ingen av prosjektene trigger der, så jeg kan sjekke inn kode fortløpende og merge til de respektive branchene når de er klare. Jeg trenger ikke tenke på at endringer i A må sjekkes inn ett sted og B et annet sted.

Ved større endringer kan jeg ta ut en arbeidsbranch, gjøre endringene der, merge til main og merge til trigger-branchen når alt er klart. Hvis flere utviklere skal jobbe mot samme repo kan main (og trigger-branchene) beskyttes med PR.

main er alltid nyest, så prosjektene kan kjøre parallelt på localhost i siste versjon. Pluss branchene blir alltid merget med siste nytt fra alle andre branches – som ikke har noen praktisk betydning, tror jeg, men jeg foretrekker det.

Jeg kan derfor jobbe mot alle prosjektene i samme instans av VS Code. Ved store endringer der jeg foretrekker å ha A og B i egne instanser av VS Code kan jeg lage en ny klon av repoet. Eventuelt holde meg i samme klon med egen arbeidsbranch i eget working tree. Det går ikke å ta ut samme branch to ganger i parallelle working trees, derav behovet for egen arbeidsbranch i så fall.

Kommando for å merge nyeste lokale commit (HEAD) med branch-a (husk å stå i main):

git push origin HEAD:branch-a

🪔

Vipps

På tide å bli kjent med Vipps sine APIer. De er jo “Vipps MobilePay” nå. Mannen til Eva snakket om det på NDC, han jobber i Vipps. De kjøpte opp MobilePay for et år siden, finner jeg på nettsidene deres. Dette handlet til dels om å få integrert med CBDCene som er på vei. Men det er fint, da er Vipps på topp og det er bare å integrere med dem.

Ser de har såkalt “partnerprogram” for utviklere som lager løsninger på vegne av merchants. Da får jeg én API-nøkkel fra Vipps, en “partnernøkkel”, som lar bruke et eget “Management API” der jeg kan integrere Vipps på merchants’ vegne uten å måtte logge inn på bedriftsportalene deres. Regner med at merchants da inngår avtale med Vipps om samarbeidet med meg, så blir jeg tildelt tilgangene jeg trenger.

Men jeg trenger noen API-nøkler for å få testet integrasjonen. Det var mulig å lage konto med BankId og et gammelt enkeltpersonforetak og opprette test-salgssted, der var det nøkler.

Forresten så er visst partnernøkler kun for “Partner Pluss” og så videre, hvis du har 30+ merchants eller over ganske mye i omsetning. Innen den tid er det mulig for merchants å gi meg brukertilgang i portalen sin: https://developer.vippsmobilepay.com/docs/partner/add-portal-user/

Når man er “Platform partner” (som er typen partner som passer for meg) skal merchants onboardes slik: https://developer.vippsmobilepay.com/docs/partner/#how-to-sign-up-new-merchants

Best practices: Vipps anbefaler å sjekke return-responsen fra hvert API-kall. I tillegg anbefales det å benytte seg av API-dashboardet på vippsportalen: https://developer.vippsmobilepay.com/docs/developer-resources/api-dashboard/

Wow, implementere Vipps i backenden etter hvert?: https://developer.vippsmobilepay.com/docs/SDKs/node-sdk/

Eksterne script i Next

Man kan inkludere Scripts fra next/scripts for å legge til eksterne scripts med src. Det legger seg i header, eller man kan sette parametre som bestemmer hvor det legger seg.

Man kan da kalle elementer i scriptet, men Typescript vil klage på de ukjente typene. Så man må legge til egne typespesifikasjoner som Typescript leser i henhold til include-direktivet i tsconfig.json.

Jeg laget en ny mappe “/types” med en “custom-elements.d.ts”:

declare namespace JSX {
    interface IntrinsicElements {
      [elemName: string]: any;
    }
  }

Da tillater Typescript alle custom-elementer, typ <vipps-checkout-button variant="orange" branded="false" /> fra Vipps.

Man kunne vært mer spesifikk og bevart typesikkerhet på alle andre custom-elementer:

declare namespace JSX {
  interface IntrinsicElements {
    'vipps-checkout-button': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
      variant?: string;
      branded?: boolean;
    };
  }
}

🌈

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.