Skip to content

Forslag til overordnede endringer

Tiril V. Stabell edited this page Jun 10, 2024 · 1 revision

Sikkerhet

Det er per i dag 12.12.22 ikke noen sikkerhetsmekanismer for å sjekke at du sender SMS/mail til innbyggere i din kommune. Veldig enklet å endre hvilken kommune man skal sende til.

Webapp (Frontend)

Storyboard

Storybook bør benyttes når det lages komponenter som brukes svært ofte, og som er rimelig generelle.

Noen tips å tenke på:

  • Enkle komponenter som kan reduseres ned til en HTML-tag, bør kun returnere denne HTML-tagen. F.eks. <button>.
  • Det bør helst ikke returnere flere komponenter, da styling kan rote seg til. I tillegg kan det være forvirrende å importere en komponent, men når du legger den til så får du plutselig flere. Se eksempel under:
    return (
      <>
        <div/>
        <div/>
      </>
    );
  • Styling i Storyboard bør helst benytte en styling-konvensjon. Sånn som det er nå er det flere, f.eks. scss og style components. En idé kan være å gå over til kun en av de.

Eksempel på innhold i en komponent:

  • Button-eksempel (Mappe)
    • Button.tsx (Komponentet)
    • Button.types.tsx (Types)
    • Button.module.scss (Styling)
    • Button.stories.tsx (Stories)
    • Button.spec/test.tsx (Testing)
    • index.ts (Export av filene i mappen)

Styling

Det har blitt gjort et forsøk på å generalisere styling. Dette har ført til enorme css/scss-filer med flere hundre linjer, og som blir overskrevet i utallige filer.

Noen tips videre:

  • Trekk ut alle disse enorme stylingene, lag mindre, konkrete for hver komponent
  • Bruk module-styling. Da trenger man ikke å tenke på klassenavn, da de blir hashet uansett for å kunn gjelde der stylingfilen blir importert.
  • Generell styling som fonter, farger, størrelse osv. kan være nærmere roten av kildekoden da samtlige stylinger bør benytte seg av dette.

Struktur

Det er tenkt at på sikt skal komponentutvikling gå over til å benytte seg av en Atomic-design.

Videre kan det være et forslag å få Routes-mappen og Pages-mappen til å være identisk. Slik at en "route" representerer en siden.

Session Storage og Context

Det er gjort et forsøk på kontrollere "state" på en annen måte enn det React tilbyr. Session Storage er et API i browseren som tilbyr lagring så lenge en fane er åpen. Når fanen lukkes, så slettes dataen. Dette har blitt brukt for å før data rundt i appen, og er en unødvendig komplikasjon.

Det er videre en context som ligger helt ytterst (globalt), som er tilgjengelig i hele appen. Resultatet er at hele appen rerendres når noe endrer seg i contexten. Den delere da data som er helt unødvendig rundt i applikasjonen.

Forslag videre:

  • Fjern Session Storage-løsningen fra hele systemet. Det er helt unødvendig å være avhenging av denne lagringen i denne løsningen.
  • Lag mindre contexten og bruk de der de er nødvendig.
  • Spesielt for en bulletin, så kan denne identifiseres i URLen, og deretter lastes inn i contexten.

Diverse

DBAccess er en gigaklasse som er ansvarlig for å kommunisere med Firestore. Det skal ikke være nødvendig å ha en hel klasse for å kommunisere med Firestore, og den alt for komplisert.

Diverse notater:

  • Importeringsløsning, link.
  • Firebase module, tree shaking, link.
  • Fjern metodene for å velge ikoner. Mange switch-case-løsninger. Det er enklere å sende inn ikonet som skal brukes i komponentet.
  • Flyten for å lage ny må bli bedre. Mange bugs, og tilstandslagring for når man er ferdig med forhåndsvisning mangler.
    • Utbedring av flyten kan bruke React Router i stedet for en selvlaget router.

Build Tool

Create React App er en kjapp måte å sette opp en webapplikasjon, men du mister mye kontroll i prosessen. De bruker Webpack til å bundle og hoste en utviklingsserver. Den er svært konfigurerbar og treig, men lukket bak Create React App.

Et anbefalt alternativ er Vite. Den er veldig enkel å konfigurere, og samtidig veldig kjapp til å starte opp en utviklingsserver.

Module Architecture (backend)

Det er startet et arbeid med å modularisere applikasjonen. Problemet er at vi fortsatt har noen store moduler som bør splittes opp. Hovedsaklig message og recipients.

Message

Problemer:

  • Har flere ansvarsområder (KRR og Mailjet spørringer)
  • Bare koblet til epost-utsending

Løsning:

  • Dele opp modulen i én KRR-modul og én Mailjet-modul
  • KRR modulen har bare ansvar for å hente epost og-eller telefonnummer
  • Mailjet modulen har bare ansvar for å sende epost-addresser til mailjet

Recipients

Problemer:

  • Flere ansvarsområder: FREG-spørringer, manuelle mottakerlister og matrikkelspørringer (matrikkelen er på vei ut i egen modul).

Filter

Tanken er å kunne kombinere filtere hvor resultatet av ulike "filterfunksjoner" vil settes sammen med andre som snitt eller union. På denne måten er det mulig å filtrere på flere parametere og enklere skalere løsningen til flere filtere.

For at dette skal fungere optimalt burde det skilles mellom input-filter og ekstra-filter. Der input-filter kjører først for å hente inn data fra de ulike inputtene, slås sammen med andre-input filtre og til slutt eventuelt filtrerer vekk på ekstra-filter.

  • input-filter:
    • Folkeregister-filter
    • Matrikkelen
    • Manuelle lister
  • Ekstra filter
    • Alder
    • Kjønn

I enden av filtere vil KRR-funksjonen avgjøre om meldingen skal sendes til hvilken kanal: SMS eller Epost.

Eksempel på hvordan det gjøres i dag:

På e-postflyten+folke i dag kan du f.eks levere inn filter alle som er født mellom datoX -> datoY, la oss f.eks ta mellom 12.2.2000, 23.9.2007. Folkeregisteret i sitt API har kun mulighet for å velge fødselsår, så i recipients vil den først spørre folkeregisteret om alle født mellom 2000 og 2008 før den manuelt går igjennom alle personer og fjerner folk født mellom 1.1.2000-11.2.2000 og 24.9.2007-30.12.2007.

Utfordringer:

  • Hvordan gjøre dette intuitivt for brukeren å gjøre enkle uttrekk, men samtidig intuitivt å gjøre mer avanserte uttrekk.

Database schema

Firestore rules

Disse er ganske mangelfulle og kan godt utbedres noe. Se også #Sikkerhet

ID > navn

Stiler identifiseres etter navn, i stede for id. Dette skjer i andre sammenhenger også, men i stiler har dette faktisk laget et problem.

Struktur

Strukturen sånn som det er nå er ikke optimal. Data om bruker, tilhørighet og rolle blir lagret dobbelt. Både i collectionen Users og i den enkelte organisasjonen. Dette skal ikke være nødvendig, da det krever å oppdatere rolle på to steder for å de skal være korrekte.

En løsning på dette er å lagre all relevant info for brukerrettigheter i Users. Hvor de hører til og hvilken rolle de har (dette forutsetter at en bruker ikke vil tilhøre flere organisasjoner). Da kan man fjerne den infoseksjonen i organisasjonen som sier noe om hvilken bruker som har hvilken rolle.

For å aktivere triggere fra Firestore benyttes det ofte for når et dokumenter lages, slettes og oppdateres et sted. For å aktivere spesifikke funksjoner har løsningen til nå vært å lagre dokumenter i ulike collections med tilhørende triggere. Det har gjort det rimelig krevende å hente ut, og strukturere data. Queries i Firestore skal fint kunne hente ut den nødvendige dataen som er av interesse, men er ikke mulig når dataen ligger ulike steder.

Dette kan endres ved å ha alle bulletins enten i en collection helt ytterst, eller for hver organisasjon. Deretter kan en funksjon aktiveres ved ulike tilfeller, nemlig update, create og delete. Ut i fra typen de er, så gjør de ulike ting, f.eks. event og search, eller active, finished, draft.

Bulletinene's innhold er laget veldig dynamisk. Feltene i flyten blir generert fra data i Firestore. Dette er veldig uheldig. Den er da koblet opp mot hver eneste organisasjon sin "mal" for hvordan flyten skal være. Så langt, har flyten vært helt identisk for alle organisasjoner. Dette er derfor en abstraksjon som gjør det langt mer komplekst enn nødvendig. Foreslår derfor å standardisere hvordan en utsendelse skal se ut, og hvilke felt som må/kan fylles ut. Så vet man nøyaktig hva Mailjet ønsker, og hva som skal ligge i frontend.

Dette gjør også validering av datamodeller mer presise, sikre og forutsigbare.

Mail system

Mailjet systemet er ikke optimalt. Styling i eposter er et mareritt pga. ulike klienter med ulike restriksjoner. Stylingløsningen for eposter genererer nye templates for hver stil som lages. Stilvariablene som genererer en ny template er:

  • Bunntekst
  • Primær- og sekundærfarger
  • Font

Bunntekst kunne nok blitt standardisert i utsendelsen, og fargevalg og fonttype kunne nok blitt sendt inn som variabler til stilen. På denne måten forhindret behovet for å generere utallige stiler i Mailjet som man må koble seg opp mot og synkronisere ved endringer eller sletting.

Videre kan man tenke seg at behovet for utallige underkontoer kan forsvinne, og dermed samlet template på en masterkonto. Det kan dessverre hende at statistikken for utsendelsen forsvinner da, om det ikke lagres på et annet vis.

Det er kanskje greit å avgjøre dette tidlig før det eventuelt blir et hav av kunder.

Enda ett argument mot Mailjet er at slik systemet er satt opp genererer vi en sub-konto i Mailjet for nye hver ny kunde. Dette var billig frem til 10 sub-accounts, nå har vi en relativ dyr plan der vi har mulighet for 1000 sub-accounts.

IaC Structure

Et stort steg i riktig retning hadde vært å lage et nytt repo for IaC, Terraform.

Det er mange løsninger for hvordan dette skal løses, men her er noen anbefalte praksis:

  • Lagre state til skyen i en bucket (Dette er gjort nå)
    • Låse endringer når en annen endring er påbegynt, for å forhindre error under "concurrency".
  • Miljøvariabel for hvert... miljø. Dev, staging, prod.
  • KMS for secrets, link
  • Bruk moduler for alt det er verdt, link.
    • Forsøk å speil kildekoden for tjenesten.
    • Input der det trengs, og output for det som må deles.
    • Guide
      • Hver modul inkluderer README, main.tf, variables.tf og outputs.tf.
      • Main.tf har konfigurasjoner for modulen.
      • Variables.tf, definisjoner og "default"-verdier.
      • Outputs.tf, brukes for å dele informasjon om deler av modulen med andre moduler

Diverse

SMS rapport

Som det er i dag, så er det ingen enkel løsning for å hente ut data for hvor mange SMS hver kommune har forsøkt å sende. Dataen er lagret i Firestore:

smsEvent > messageId

NB! messageId er en unik Id som Pub/Sub genererer.

I et messageId-dokument vil det være en batch med batchId som er registrert hos Sinch. Ved hjelp av API-kall kan man hente ut rapporter SMS utsendelser på denne Id'en. For enkelthetens skyld er det mulig å aggregere dataen som ligger i Firestore. Der er det registrert bulletindId, batchSize, orgId og dato. Med disse dataene skal det være mulig å komme med en ganske presis beregning for SMS-kostnader per utsendelse, per kommune, per måned.

Maler overføring fra dev til prod

Script:

NB! Scriptet inkluderer ikke for overføring av bilder for malene.

import firebase_admin
from firebase_admin import firestore
from firebase_admin import credentials

# dev
dev_cred = credentials.Certificate('/') # Missing path to certificate for dev
dev_app = firebase_admin.initialize_app(dev_cred)
dev_db = firestore.client(app=dev_app)

# prod
prod_cred = credentials.Certificate('/') # Missing path to certificate for prod
prod_app = firebase_admin.initialize_app(prod_cred, name='prod')
prod_db = firestore.client(app=prod_app)

templates_by_kf = dev_db.collection(u'templates').where(u'orgId', u'==', u'kf').stream()

batch = prod_db.batch()

for doc in templates_by_kf:
    try:
        docDict = doc.to_dict()
        if (docDict['contentArray']):
            array = docDict['contentArray']
            for content in array:
                if (content['image']):
                    content['image'] = content['image'].replace('innbyggerkontakt-dev', 'innbyggerkontakt')
                if (content['content']):
                    content['content'] = content['content'].replace('innbyggerkontakt-dev', 'innbyggerkontakt')
        
        batch.set(prod_db.collection(u'templates').document(doc.id), docDict)
    except e as Exception:
        print(e)
        batch.set(prod_db.collection(u'templates').document(doc.id), doc)
        

batch.commit()