Fork me on GitHub
#clojure-norway
<
2023-12-12
>
magnars07:12:40

God morgen!

magnars07:12:37

Jeg har skrevet et lite julemysterie på Parenteser-bloggen. Jakten på de forsvunnede 85 sekunder. :male-detective: https://parenteser.mattilsynet.io/85-sekunder/

3
👀 1
cjohansen07:12:09

Sjelden har jeg sett ny kunnskap betale seg så direkte, så raskt 😄

magnars07:12:50

Ja, det var snakk om timer 😄

slipset09:12:54

Vi fikk introdusert en trace-service med with-span! som logger til sentry for ca et år siden. Aldri har performance tuning vært lettere.

👌 2
augustl12:12:12

herlig! Spans ligner veldig på flame graphs for rendring i nettleseren osv

leifericf08:12:41

God morgen, godtfolk!

magnars08:12:42

For å pirre nysgjerrigheten ytterligere så har bloggposten over en snedig clojure macro og dynamisk bundede variable!

leifericf08:12:51

Dagens nøtt! Jeg har denne funksjonen:

(defn get-cached-token [name]
  (if-let [cached-token (slurp-file (str (file/cwd) token-cache-dir
                                         "/"
                                         name ".edn"))]
    cached-token
    (refresh-token-cache (get-scope name))))

(defn get-token [name]
  (when-let [token (get-cached-token name)] token))
Når jeg kaller get-token, laster den cached token fra fil på disk (hvis den finnes), ellers henter den en ny token og skriver til disk. Men! Første gang jeg kaller get-token returnerer den nil, fordi spit (som brukes i refresh-token-cache) returnerer nil. Andre gang jeg kaller get-token returnerer den token fra disk, slik jeg ønsker. Hvordan kan jeg få get-token til å returnere token fra disk, første gang, etter refresh-token-cache har hentet en ny token? :thinking_face:

cjohansen08:12:49

Ikke ha spit nederst i refresh-token-cache 😄

cjohansen08:12:27

(defn refresh-token-cache [scope]
  (let [token "LOL"]
    (spit "/tmp/lol.cache" token)
    token))

leifericf08:12:41

Ja, jeg sitter og lurer på hvordan jeg kan få refresh-token-cache til å returnere path til filen den nettopp har skrevet.

cjohansen08:12:01

Forøvrig, å kalle denne funksjonen get-cached-token er å dra "get" litt langt 😅 Det er ikke det den gjør

😅 1
cjohansen08:12:18

Jeg ville vurder acquire-token! eller noe sånt

👍 1
leifericf08:12:20

Definitivt room for improvement på navnene også, ja! Hehe.

cjohansen08:12:23

En annen smell: Det ser ut som om du har en funksjon som leser fra denne fila, og en som skriver til den - og begge setter manuelt sammen filnavnet.

leifericf08:12:30

Dette er koden som skriver fila:

(defn spit-file [filename data]
  (let [path (get-absolute-path filename)]
    (create-dirs (file/parent path))
    (spit path (pr-str data))))

(defn write-to-files [file-ext data]
  (doseq [[name [token]] data]
    (spit-file (str name file-ext) token)))

(defn refresh-token-cache [scopes]
  (doall
   (->> scopes
        (map add-token)
        (group-by :name)
        (write-to-files ".edn"))))

cjohansen08:12:11

(defn get-token [name]
  (when-let [token (get-cached-token name)] token))
Dette kan forenkles til:
(get-cached-token name)

cjohansen08:12:54

Hvorfor brukes token-cache-dir kun ved lesing og ikke når det skrives?

leifericf08:12:14

spit-file kaller get-absolute-path som bruker token-cache-dir:

(defn get-absolute-path
  ([filename]
   (get-absolute-path (str (file/cwd) token-cache-dir)
                      filename))

  ([path filename]
   (str path "/" filename)))

leifericf08:12:09

Jeg burde kanskje bruke get-absolute-path når fila leses også da.

cjohansen08:12:13

Er token-cache-dir en global var?

leifericf08:12:37

Ja, det er en def helt øverst i fila: (def token-cache-dir "/tmp/tokens")

leifericf08:12:04

(Dette er forresten et Babashka skript.)

cjohansen08:12:06

Du har en del informasjon som egentlig hører sammen spredt litt for alle vinder

😅 1
cjohansen08:12:27

Jeg tror jeg ville laget denne:

(defn get-cache-file [n]
  (str (file/cwd) "/tmp/tokens/" n ".edn"))

👀 1
leifericf08:12:47

Ville du gjentatt "/tmp/tokens/" på to steder (der filene skrives og leses) fremfor å gjøre en global def på ett sted?

cjohansen08:12:06

Nei, jeg ville laget funksjonen jeg nettopp ga deg 😄

cjohansen08:12:14

to sek så kan jeg sette det sammen i et forslag

leifericf08:12:23

Åja, tror jeg skjønner hva du mener!

cjohansen08:12:54

(defn get-cache-file [n]
  (str (file/cwd) "/tmp/tokens/" n ".edn"))

(defn spit-file [filename data]
  (let [path (get-absolute-path filename)]
    (create-dirs (file/parent path))
    (spit path (pr-str data))))

(defn write-to-files [data]
  (doseq [[name [token]] data]
    (spit-file (get-cache-file name) token)))

(defn refresh-token-cache [scopes]
  (->> scopes
       (map add-token)
       (group-by :name)
       write-to-files))

(defn acquire-token [name]
  (let [file-name (get-cache-file name)]
    (when-not (.exists (io/file file-name))
      (refresh-token-cache (get-scope name)))
    (slurp-file file-name)))

👀 1
cjohansen08:12:37

Med mindre du har tenkt til å gjøre noe mer i din get-token trenger du den ikke

leifericf08:12:01

Jeg har (hadde 😅) planer om å sjekke hvorvidt cached token fortsatt er gyldig litt senere.

leifericf08:12:17

Typ en is-token-valid? predicate eller noe sånt.

cjohansen08:12:23

Men hvorfor spre det over to funksjoner?

cjohansen08:12:56

Du har allerede en funksjon som henter fra cache eller henter nytt om den ikke finnes

cjohansen08:12:03

Den kan bare sjekke gyldighet i tillegg

👍 1
cjohansen08:12:19

Forøvrig trenger du ikke både doall og doseq, en av dem holder

💡 1
cjohansen08:12:26

doseq er mest eksplisitt

👍 1
cjohansen08:12:04

Også ville jeg som vi diskuterte tidligere droppa den add-funksjonen og heller gjort:

(defn refresh-token-cache [scopes]
  (->> scopes
       (map #(assoc % :token (get-token %)))
       (group-by :name)
       write-to-files))

leifericf08:12:32

Aha, jeg misforstod at det burde være add- istedenfor get-

cjohansen08:12:36

Hvis get-token faktisk henter over nettverk ville jeg kanskje heller kalt den fetch-token e.l.

leifericf08:12:59

Det er disse som faktisk henter token:

(defn sh-out->json [opts & args]
  (-> (apply process/sh opts args)
      :out
      (json/parse-string true)))

(defn obtain-token [scope-url]
  (sh-out->json "az" "account" "get-access-token"
                "--scope" scope-url))

cjohansen08:12:26

Jeg tror jeg ville latt obtain-token få det den trenger for å bygge URL-en selv, og så kalle den fra map-en over: (map #(assoc % :token (obtain-token %)))

😮 1
leifericf08:12:01

Ja, nice! Det gir mening. Jeg har vel kanskje forsøkt å dele opp for mye i egne funksjoner og ikke brukt core greier nok (smartere bruk av assoc i dette tilfellet).

leifericf09:12:38

Tusen takk for alle tipsene! Da kan jeg rydde opp ganske bra og gjøre dette mer konsist. Dette må være verdens mest interaktive og dynamiske remote code review ever 😛

leifericf12:12:22

Resultatet etter litt opprydning 🙂

(ns myscript (:require [babashka.fs :as file]
                       [babashka.process :as process]
                       [cheshire.core :as json]
                       [clojure.edn :as edn]))

(def scopes
  [{:name "graph"
    :url ""}
   {:name "myservice"
    :url ""}])

(defn sh-out->json [opts & args]
  (-> (apply process/sh opts args)
      :out
      (json/parse-string true)))

(defn fetch-token [url]
  (sh-out->json "az" "account" "get-access-token"
                "--scope" url))

(defn write-file [path data]
  (let [dirs (file/parent path)]
    (when-not (file/exists? dirs)
      (file/create-dirs dirs)))
  (spit (str path) (pr-str data)))

(defn get-filepath [filename]
  (-> (file/cwd)
      (str "/tmp/tokens/" filename ".edn")))

(defn write-to-files [data]
  (doseq [[name [token]] data]
    (-> (get-filepath name)
        (write-file token))))

(defn refresh-token [scopes]
  (->> scopes
       (map #(assoc % :token (fetch-token (:url %))))
       (group-by :name)
       (write-to-files)))

(defn read-file [path]
  (when (file/exists? path)
    (-> (slurp path)
        edn/read-string)))

(defn get-token [name]
  (let [file-name (get-filepath name)]
    (when-not (file/exists? file-name)
      (-> (filter #(= name (:name %)) scopes)
          refresh-token))
    (read-file file-name)))

(comment
  (get-token "graph")

  (->> (map :name scopes)
       (map get-token)))

leifericf14:12:30

Og der fant jeg ut hvordan jeg kan sjekke om en token har gått ut på dato også! Dette funker, men det ser ikke spesielt pent ut, og det er nok mye rom for forbedring her også:

(defn has-token-expired? [token]
  (let [format (DateTimeFormatter/ofPattern "yyyy-MM-dd HH:mm:ss.SSSSSS")
        timestamp (LocalDate/parse (:expiresOn token) format)]
    (.isAfter (LocalDate/now) timestamp)))

(defn load-token [name]
  (let [path (get-filepath name)]
    (when-not (file/exists? path)
      (-> (filter #(= name (:name %)) scopes)
          refresh-token))
    (let [token (read-file path)]
      (when (has-token-expired? (:token token))
        (-> (filter #(= name (:name %)) scopes)
            refresh-token))
      token)))

leifericf14:12:55

Jeg likker ikke at dette er repetert to ganger:

(-> (filter #(= name (:name %)) scopes)
          refresh-token)
Og at jeg må gjøre (read-file path) to ganger.

Fredrik02:12:34

Trur du kan unngå det ved å skrive litt om på koden, for eksempel

(defn get-token [name]
  (let [file-name (get-filepath name)]
    (when-not (file/exists? file-name)
      (-> (filter #(= name (:name %)) scopes)
          refresh-token))))

(defn valid-token? [token]
  (let [format (DateTimeFormatter/ofPattern "yyyy-MM-dd HH:mm:ss.SSSSSS")
        timestamp (LocalDate/parse (:expiresOn token) format)]
    (.isBefore (LocalDate/now) timestamp)))

(defn load-token [name]
  (let [token (read-file (get-filepath name))]
    (if (valid-token? token)
      token
      (-> (filter #(= name (:name %)) scopes)
          refresh-token))))

👀 1
leifericf11:12:07

Takk for tipsene, @U024X3V2YN4! Du satte jeg på rett spor. Jeg endte opp med noe litt annerledes:

(defn token-exists? [name]
  (let [path (get-filepath name)]
    (if (file/exists? path)
      (edn/read-string (slurp path))
      false)))

(defn token-valid? [token]
  (let [format (DateTimeFormatter/ofPattern "yyyy-MM-dd HH:mm:ss.SSSSSS")
        timestamp (LocalDate/parse (:expiresOn token) format)]
    (.isBefore (LocalDate/now) timestamp)))

(defn filter-scope [name]
  (filter #(= name (:name %)) scopes))

(defn refresh-token [scopes]
  (->> scopes
       (map #(assoc % :token (fetch-token (:url %))))
       (group-by :name)
       (write-to-files)))

(defn load-token [name]
  (let [token (token-exists? name)]
    (when-not (and (token-exists? name)
                   (token-valid? (:token token)))
      (refresh-token (filter-scope name)))
    (token-exists? name)))

slipset10:12:33

morn, morn