(ns saved-for-reddit.reddit-api
  (:require [reagent.core :as r]
            [cljs-http.client :as http]
            [cljs.core.async :refer [<!]]
            [cemerick.url :as url]
            [cljs-time.coerce :as timec]
            [cljs-time.format :as timef]
            [dommy.core :as dommy]
            [goog.string :refer [unescapeEntities]]
            [saved-for-reddit.views :as views])
  (:require-macros [cljs.core.async.macros :refer [go]]))

;; a global time-formatter to handle how time strings are displayed
(def time-formatter (timef/formatter "yyyy-MM-dd HH:mm"))

(defn set-app-state-field [field value]
  (swap! saved-for-reddit.core/app-state update-in [field] #(str value))
  value)

#_(defn make-remote-get-call [endpoint]
    (go (let [response (<! (http/get endpoint {:with-credentials? false}))]
          ;;enjoy your data
          (js/console.log (:body response)))))

#_(defn make-remote-post-call [endpoint]
    (go (let [response (<! (http/post endpoint {:with-credentials? false}))]
          ;;enjoy your data
          (js/console.log (:body response)))))

(defn refresh-reddit-auth-token [client-id redirect-uri]
  ;; request reddit api token from code provided by reddit
  (go (let [response (<! (http/post "https://www.reddit.com/api/v1/access_token"
                                    {:with-credentials? false
                                     :basic-auth {:username client-id :password ""}
                                     :form-params {:grant_type "refresh_token"
                                                   :redirect_uri redirect-uri
                                                   :refresh_token (:token @saved-for-reddit.core/app-state)}}))
            status (:status response)
            body   (:body response)
            error  (:error body)
            access-token (:access_token body)]
        (if (clojure.string/blank? error)
          (do
            (set-app-state-field :token access-token)
            (js/setTimeout #(refresh-reddit-auth-token client-id redirect-uri) 3300000))
          (views/handle-error error)))))

(defn reddit-unsave [thing-id]
  (println (str "using token " (:token @saved-for-reddit.core/app-state)))
  (go
    (let [response (<! (http/post "https://oauth.reddit.com/api/unsave"
                                  {:with-credentials? false
                                   :oauth-token (:token @saved-for-reddit.core/app-state)
                                   :form-params {:id thing-id}}))
          status (:status response)
          body   (:body response)
          error  (:error body)]
      (if (clojure.string/blank? error)
        (do
          (println body))
        (views/handle-error error)))))

(defn repack-post [p]
  (let [id (:id p)
        name (:name p)
        link? (nil? (:title p))
        key (str (:name p) (:subreddit_id p) (:link_id p))
        title (unescapeEntities (if link? (:link_title p) (:title p)))
        url (if link? (str (:link_url p) key) (:url p))
        body (if (not (nil? (:body_html p))) (unescapeEntities (:body_html p)))
        subreddit (:subreddit p)
        author (:author p)
        permalink (if link? (:link_url p) (str "https://www.reddit.com" (:permalink p)))
        created-on-epoch-local (+ (:created_utc p) (* 3600 (.getTimezoneOffset (js/Date.))))
        created-on-str (timef/unparse time-formatter (timec/from-long (* 1000 created-on-epoch-local)))]
    {:id id :name name :link link? :key key :title title :url url :body body :subreddit subreddit :author author :permalink permalink :created-on created-on-str}))

(defn get-saved-posts [token username saved-posts & after]
  (let [saved-post-get-chan (if (nil? after)
                              (http/get (str "https://oauth.reddit.com/user/" username "/saved")
                                        {:with-credentials? false
                                         :oauth-token token})
                              (http/get (str "https://oauth.reddit.com/user/" username "/saved")
                                        {:with-credentials? false
                                         :oauth-token token
                                         :query-params {"after" (first after)}}))]
    (js/console.log "Retreiving saved posts..." after)
    (go (let [response (<! saved-post-get-chan )
              posts (-> response :body :data :children)
              error (-> response :body :error)
              after-str (-> response :body :data :after)
              after-ret (if (clojure.string/blank? after-str) nil after-str)]
          (println "received response")
          (if (clojure.string/blank? error)
            (do
              (set-app-state-field :after after-ret)
              (if (nil? after-ret)
                (set! (.-disabled (dommy/sel1 :#btn-get-posts)) true))
              (doseq [p posts]
                (swap! saved-posts #(conj % %2) (repack-post (:data p))))
              after-ret)
            (views/handle-error (str error " " (:error-text response) "\nYour API token might've expired")))))))

(defn update-view-after-retreive-complete []
  (set! ( .-visibility (.-style (dommy/sel1 :#btn-stop-get-posts))) "hidden")
  (set! ( .-visibility (.-style (dommy/sel1 :#loading-gif))) "hidden")
  (set! (.-disabled (dommy/sel1 :#btn-search-posts)) false)
  (set! (.-disabled (dommy/sel1 :#txt-search-posts)) false)
  (set! (.-placeholder (dommy/sel1 :#txt-search-posts)) "Search..."))

(defn get-all-saved-posts [token username saved-posts & after]
  (let [saved-post-get-chan (if (nil? after)
                              (http/get (str "https://oauth.reddit.com/user/" username "/saved")
                                        {:with-credentials? false
                                         :oauth-token token})
                              (http/get (str "https://oauth.reddit.com/user/" username "/saved")
                                        {:with-credentials? false
                                         :oauth-token token
                                         :query-params {"after" (first after)}}))]
    (js/console.log "Retreiving saved posts... from" after)
    (go
      (if @saved-for-reddit.core/get-posts?
        (let [response (<! saved-post-get-chan )
              posts (-> response :body :data :children)
              error (-> response :body :error)
              after-str (-> response :body :data :after)
              after-ret (if (clojure.string/blank? after-str) nil after-str)]
          (println "received response..." after-ret)
          (if (clojure.string/blank? error)
            (do
              #_(set-app-state-field :after after-ret)
              (doseq [p posts]
                (swap! saved-posts #(conj % %2) (repack-post (:data p))))
              (if (not (nil? after-ret))
                (get-all-saved-posts token username saved-posts after-ret)
                (update-view-after-retreive-complete)))
            (views/handle-error (str error " " (:error-text response) "\nYour API token might've expired"))))
        (update-view-after-retreive-complete)))))

(ns saved-for-reddit.reddit-api
  (:require [reagent.core :as r]
            [cljs-http.client :as http]
            [cljs.core.async :refer [<!]]
            [cemerick.url :as url]
            [cljs-time.coerce :as timec]
            [cljs-time.format :as timef]
            [dommy.core :as dommy]
            [goog.string :refer [unescapeEntities]]
            [saved-for-reddit.views :as views])
  (:require-macros [cljs.core.async.macros :refer [go]]))

;; a global time-formatter to handle how time strings are displayed
(def time-formatter (timef/formatter "yyyy-MM-dd HH:mm"))

(defn set-app-state-field [field value]
  (swap! saved-for-reddit.core/app-state update-in [field] #(str value))
  value)

#_(defn make-remote-get-call [endpoint]
    (go (let [response (<! (http/get endpoint {:with-credentials? false}))]
          ;;enjoy your data
          (js/console.log (:body response)))))

#_(defn make-remote-post-call [endpoint]
    (go (let [response (<! (http/post endpoint {:with-credentials? false}))]
          ;;enjoy your data
          (js/console.log (:body response)))))

(defn refresh-reddit-auth-token [client-id redirect-uri]
  ;; request reddit api token from code provided by reddit
  (go (let [response (<! (http/post "https://www.reddit.com/api/v1/access_token"
                                    {:with-credentials? false
                                     :basic-auth {:username client-id :password ""}
                                     :form-params {:grant_type "refresh_token"
                                                   :redirect_uri redirect-uri
                                                   :refresh_token (:token @saved-for-reddit.core/app-state)}}))
            status (:status response)
            body   (:body response)
            error  (:error body)
            access-token (:access_token body)]
        (if (clojure.string/blank? error)
          (do
            (set-app-state-field :token access-token)
            (js/setTimeout #(refresh-reddit-auth-token client-id redirect-uri) 3300000))
          (views/handle-error error)))))

(defn reddit-unsave [thing-id]
  (println (str "using token " (:token @saved-for-reddit.core/app-state)))
  (go
    (let [response (<! (http/post "https://oauth.reddit.com/api/unsave"
                                  {:with-credentials? false
                                   :oauth-token (:token @saved-for-reddit.core/app-state)
                                   :form-params {:id thing-id}}))
          status (:status response)
          body   (:body response)
          error  (:error body)]
      (if (clojure.string/blank? error)
        (do
          (println body))
        (views/handle-error error)))))

(defn repack-post [p]
  (let [id (:id p)
        name (:name p)
        link? (nil? (:title p))
        key (str (:name p) (:subreddit_id p) (:link_id p))
        title (unescapeEntities (if link? (:link_title p) (:title p)))
        url (if link? (str (:link_url p) key) (:url p))
        body (if (not (nil? (:body_html p))) (unescapeEntities (:body_html p)))
        subreddit (:subreddit p)
        author (:author p)
        permalink (if link? (:link_url p) (str "https://www.reddit.com" (:permalink p)))
        created-on-epoch-local (+ (:created_utc p) (* 3600 (.getTimezoneOffset (js/Date.))))
        created-on-str (timef/unparse time-formatter (timec/from-long (* 1000 created-on-epoch-local)))]
    ;; update the subreddit atom
    (swap! saved-for-reddit.core/subreddits update-in [(keyword subreddit)] #(inc ((keyword subreddit) @saved-for-reddit.core/subreddits)))
    {:id id :name name :link link? :key key :title title :url url :body body :subreddit subreddit :author author :permalink permalink :created-on created-on-str}))

(defn get-saved-posts [token username saved-posts & after]
  (let [saved-post-get-chan (if (nil? after)
                              (http/get (str "https://oauth.reddit.com/user/" username "/saved")
                                        {:with-credentials? false
                                         :oauth-token token})
                              (http/get (str "https://oauth.reddit.com/user/" username "/saved")
                                        {:with-credentials? false
                                         :oauth-token token
                                         :query-params {"after" (first after)}}))]
    (js/console.log "Retreiving saved posts..." after)
    (go (let [response (<! saved-post-get-chan )
              posts (-> response :body :data :children)
              error (-> response :body :error)
              after-str (-> response :body :data :after)
              after-ret (if (clojure.string/blank? after-str) nil after-str)]
          (println "received response")
          (if (clojure.string/blank? error)
            (do
              (set-app-state-field :after after-ret)
              (if (nil? after-ret)
                (set! (.-disabled (dommy/sel1 :#btn-get-posts)) true))
              (doseq [p posts]
                (swap! saved-posts #(conj % %2) (repack-post (:data p))))
              after-ret)
            (views/handle-error (str error " " (:error-text response) "\nYour API token might've expired")))))))

(defn update-view-after-retreive-complete []
  (set! ( .-visibility (.-style (dommy/sel1 :#btn-stop-get-posts))) "hidden")
  (set! ( .-visibility (.-style (dommy/sel1 :#loading-gif))) "hidden")
  (set! (.-disabled (dommy/sel1 :#btn-search-posts)) false)
  (set! (.-disabled (dommy/sel1 :#txt-search-posts)) false)
  (set! (.-placeholder (dommy/sel1 :#txt-search-posts)) "Search..."))

(defn get-all-saved-posts [token username saved-posts & after]
  (let [saved-post-get-chan (if (nil? after)
                              (http/get (str "https://oauth.reddit.com/user/" username "/saved")
                                        {:with-credentials? false
                                         :oauth-token token})
                              (http/get (str "https://oauth.reddit.com/user/" username "/saved")
                                        {:with-credentials? false
                                         :oauth-token token
                                         :query-params {"after" (first after)}}))]
    (js/console.log "Retreiving saved posts... from" after)
    (go
      (if @saved-for-reddit.core/get-posts?
        (let [response (<! saved-post-get-chan )
              posts (-> response :body :data :children)
              error (-> response :body :error)
              after-str (-> response :body :data :after)
              after-ret (if (clojure.string/blank? after-str) nil after-str)]
          (println "received response..." after-ret)
          (if (clojure.string/blank? error)
            (do
              #_(set-app-state-field :after after-ret)
              (doseq [p posts]
                (swap! saved-posts #(conj % %2) (repack-post (:data p))))
              (if (not (nil? after-ret))
                (get-all-saved-posts token username saved-posts after-ret)
                (update-view-after-retreive-complete)))
            (views/handle-error (str error " " (:error-text response) "\nYour API token might've expired"))))
        (update-view-after-retreive-complete)))))

update subreddit atom while receiving saved posts


(ns pepa.processor.page-renderer
  (:require [com.stuartsierra.component :as component]
            [pepa.db :as db]
            [pepa.pdf :as pdf]
            [pepa.processor :as processor :refer [IProcessor]]
            [pepa.log :as log])
  (:import java.security.MessageDigest
           java.math.BigInteger))

(def +hashing-algorithm+ (MessageDigest/getInstance "SHA-256"))

(defn ^:private hash-data
  "Hashes the byte-array BS (returns a string)."
  [bs]
  (let [md (.clone +hashing-algorithm+)]
    (.update md bs)
    (format "%032x" (BigInteger. 1 (.digest md)))))

(defn ^:private render-page [renderer dpis page]
  (try
    (pdf/with-reader [pdf (:data page)]
      ;; Render images in all configured DPI settings
      (mapv (fn [dpi]
              (log/debug renderer "Rendering" (:id page) "with" dpi "dpi")
              (let [image (pdf/render-page pdf (:number page) :png dpi)]
                {:page (:id page)
                 :dpi dpi
                 :image image
                 :hash (hash-data image)}))
            dpis))
    (catch Exception e
      (log/error renderer "Rendering failed:" (str e)))))

(defrecord PageRenderer [config db processor]
  IProcessor
  (next-item [component]
    "SELECT p.id, p.number, f.data
     FROM pages AS p
     JOIN files AS f ON p.file = f.id
     WHERE p.render_status = 'pending'
     ORDER BY p.number, p.id
     LIMIT 1")

  (process-item [component page]
    (log/info component "Rendering page" (:id page))
    (let [db (:db component)
          config (:config component)
          dpis (set (-> config :rendering :png :dpi))
          images (render-page component dpis page)]
      (db/with-transaction [db db]
        (let [status (if images
                       (do (db/insert-coll! db :page_images images)
                           :processing-status/processed)
                       :processing-status/failed)]
          (db/notify! db :pages/updated)
          (db/update! db
                      :pages
                      {:render_status status}
                      ["id = ?" (:id page)])))))

  component/Lifecycle
  (start [component]
    (log/info component "Starting page renderer")
    (assoc component
           :processor (processor/start component :pages/new)))

  (stop [component]
    (log/info component "Stopping page renderer")
    (when-let [processor (:processor component)]
      (processor/stop processor))
    (assoc component
           :processor nil)))

(defn make-component []
  (map->PageRenderer {}))

(ns pepa.processor.page-renderer
  (:require [com.stuartsierra.component :as component]
            [pepa.db :as db]
            [pepa.pdf :as pdf]
            [pepa.processor :as processor :refer [IProcessor]]
            [pepa.log :as log])
  (:import java.security.MessageDigest
           java.math.BigInteger))

(def +hashing-algorithm+ (MessageDigest/getInstance "SHA-256"))

(defn ^:private hash-data
  "Hashes the byte-array BS (returns a string)."
  [bs]
  (let [md (.clone +hashing-algorithm+)]
    (.update md bs)
    (format "%032x" (BigInteger. 1 (.digest md)))))

(defn ^:private render-page [renderer dpis page]
  (try
    (pdf/with-reader [pdf (:data page)]
      ;; Render images in all configured DPI settings
      (mapv (fn [dpi]
              (log/debug renderer "Rendering" (:id page) "with" dpi "dpi")
              (let [image (pdf/render-page pdf (:number page) :png dpi)]
                {:page (:id page)
                 :dpi dpi
                 :image image
                 :hash (hash-data image)}))
            dpis))
    (catch Exception e
      (log/error renderer "Rendering failed:" (str e)))))

(defrecord PageRenderer [config db processor]
  IProcessor
  (next-item [component]
    "SELECT p.id, p.number, f.data
     FROM pages AS p
     JOIN files AS f ON p.file = f.id
     WHERE p.render_status = 'pending'
     ORDER BY p.number, p.id
     LIMIT 1")

  (process-item [component page]
    (log/info component "Rendering page" (:id page))
    (let [db (:db component)
          config (:config component)
          dpis (set (-> config :rendering :png :dpi))
          images (render-page component dpis page)]
      (db/with-transaction [db db]
        (let [status (if images
                       (do (db/insert-coll! db :page_images images)
                           :processing-status/processed)
                       :processing-status/failed)]
          (db/notify! db :pages/updated)
          (db/update! db
                      :pages
                      {:render_status status}
                      ["id = ?" (:id page)])))))

  component/Lifecycle
  (start [component]
    (log/info component "Starting page renderer")
    (assoc component
           :processor (processor/start component :pages/new)))

  (stop [component]
    (log/info component "Stopping page renderer")
    (when-let [processor (:processor component)]
      (processor/stop processor))
    (assoc component
           :processor nil)))

(defn ^:private rerender-all! [component]
  (db/with-transaction [db (:db component)]
    (db/delete! db :page_images [])
    (db/update! db :pages {:render_status :processing-status/pending} [])
    (db/notify! db :pages/updated)
    (db/notify! db :pages/new)))

(defn make-component []
  (map->PageRenderer {}))

add pepa.processor.page-renderer/rerender-all.


(require '[cljs.closure :as cljsc])

(println "Building ...")

(let [start (System/nanoTime)]
  (cljsc/build "src"
    {:output-to "release/{{sanitized}}.js"
     :output-dir "release"
     :optimizations :advanced
     :verbose true})
  (println "... done. Elapsed" (/ (- (System/nanoTime) start) 1e9) "seconds"))

(require '[cljs.closure :as cljsc])

(println "Building ...")

(let [start (System/nanoTime)]
  (cljsc/build "src"
    {:output-to "release/{{sanitized}}.min.js"
     :output-dir "release"
     :optimizations :advanced
     :verbose true})
  (println "... done. Elapsed" (/ (- (System/nanoTime) start) 1e9) "seconds"))

Change release.clj to emit file name that matches index_release.html


(ns akvo.lumen.lib.auth
  (:require
   [akvo.commons.jwt :as jwt]
   [akvo.lumen.component.flow :as c.flow]
   [akvo.lumen.component.tenant-manager :as tenant-manager]
   [akvo.lumen.db.collection :as db.collection]
   [akvo.lumen.db.dashboard :as db.dashboard]
   [akvo.lumen.db.dataset :as db.dataset]
   [akvo.lumen.db.raster :as db.raster]
   [akvo.lumen.db.visualisation :as db.visualisation]
   [akvo.lumen.monitoring :as monitoring]
   [akvo.lumen.protocols :as p]
   [akvo.lumen.specs :as lumen.s]
   [akvo.lumen.specs.collection :as collection.s]
   [akvo.lumen.specs.dashboard :as dashboard.s]
   [akvo.lumen.specs.dataset :as dataset.s]
   [akvo.lumen.specs.visualisation :as visualisation.s]
   [clojure.set :as set]
   [clojure.spec.alpha :as s]
   [clojure.string :as str]
   [clojure.tools.logging :as log]
   [clojure.walk :as w]
   [iapetos.core :as prometheus]
   [iapetos.registry :as registry]
   [integrant.core :as ig]))

(declare ids)

(defn- optimistic-allow?* [this ids]
  {:pre [(= #{:dataset-ids :visualisation-ids :dashboard-ids :collection-ids}
            (set (keys ids)))]}
  (set/subset? (set/union (:dataset-ids ids) (:visualisation-ids ids)
                          (:dashboard-ids ids) (:collection-ids ids))
               (set/union (:auth-datasets-set this) (:auth-visualisations-set this)
                          (:auth-dashboards-set this) (:auth-collections-set this)
                          (:rasters-set this))))

(defn- allow?* [this ids]
  {:pre [(= #{:dataset-ids :visualisation-ids :dashboard-ids :collection-ids}
            (set (keys ids)))]}
  (and (set/subset? (:visualisation-ids ids) (:auth-visualisations-set this))
       (set/subset? (:dataset-ids ids) (:auth-datasets-set this))
       (set/subset? (:dashboard-ids ids) (:auth-dashboards-set this))
       (set/subset? (:collection-ids ids) (:auth-collections-set this))))

(defn- auth* [this ids]
  {:pre [(= #{:dataset-ids :visualisation-ids :dashboard-ids :collection-ids}
            (set (keys ids)))]}
  {:auth-datasets       (set/intersection (:auth-datasets-set this) (set (:dataset-ids ids)))
   :auth-visualisations (set/intersection (:auth-visualisations-set this) (set (:visualisation-ids ids)))
   :auth-dashboards     (set/intersection (:auth-dashboards-set this) (set (:dashboard-ids ids)))
   :auth-collections    (set/intersection (:auth-collections-set this) (set (:collections-ids ids)))})

(defrecord AuthServiceImpl [auth-datasets-set auth-visualisations-set auth-dashboards-set auth-collections-set rasters-set]
  p/AuthService2080
  (optimistic-allow? [this ids]
    (optimistic-allow?* this ids))
  p/AuthService
  (allow? [this ids]
    (allow?* this ids))
  (auth [this ids]
    (auth* this ids))
  (auth [this type* uuid]
    (let [f* (fn [s u] (when (contains? s u) u))]
      (condp = type*
        :dataset       (f* auth-datasets-set uuid)
        :visualisation (f* auth-visualisations-set uuid)
        :dashboard     (f* auth-dashboards-set uuid)
        :collection    (f* auth-collections-set uuid)))))

(defn new-auth-service [{:keys [auth-datasets auth-visualisations auth-dashboards auth-collections rasters] :as auth-uuid-tree}]
  (AuthServiceImpl. (set auth-datasets) (set auth-visualisations) (set auth-dashboards) (set auth-collections) (set rasters)))

(defn match-by-jwt-family-name?
  "Feature flag condition based on `First Name` jwt-claims
  To match just add `$auth` to your `First Name` in your related auth system user profile"
  [request]
  (str/includes? (get (:jwt-claims request) "given_name" "") "$auth$"))

(defn match-by-template-and-method?
  "`data` is a map following this structure {\"/api/library\" {:methods #{:get}}}
  `request` is a `reitit` request that contains reitit data such as :template"
  [data request]
  (-> (get data (:template (:reitit.core/match request)))
      :methods
      (contains? (:request-method request))))

(def auth-calls {"/api/library" {:methods #{:get}}
                 "/api/datasets/:id" {:methods #{:get :put :delete}}
                 "/api/datasets/:id/meta" {:methods #{:get}}
                 "/api/datasets/:id/update" {:methods #{:post}}
                 "/api/datasets/:id/sort/:column-name/text" {:methods #{:get}}
                 "/api/datasets/:id/sort/:column-name/number" {:methods #{:get}}
                 "/api/datasets" {:methods #{:get}}
                 "/api/visualisations" {:methods #{:get :post}}
                 "/api/visualisations/:id" {:methods #{:get :put :delete}}
                 "/api/visualisations/maps" {:methods #{:post}}
                 "/api/dashboards" {:methods #{:get :post}}
                 "/api/dashboards/:id" {:methods #{:get :put :delete}}
                 "/api/collections" {:methods #{:get}}})

(defn- auth-datasets [all-datasets permissions]
  (->> all-datasets
       (filter
        (fn [ds]
          (let [source (:source ds)]
            (if (= "AKVO_FLOW" (get source "kind"))
              (contains? permissions (c.flow/>api-model source))
              true))))
       (mapv :id)))

(defn- auth-visualisations [tenant-conn auth-datasets-set]
  (let [[maps nomaps] (split-with #(= "map" (:visualisationType %))
                                  (w/keywordize-keys (db.visualisation/all-visualisations tenant-conn {} {} {:identifiers identity})))
        maps*         (->> maps
                           (reduce (fn [c m]
                                     (let [datasets (reduce (fn [c l]
                                                              (if-let [ds (let [ds (:datasetId l)]
                                                                            (and ds (not= "null" ds)))]
                                                                (conj c ds)
                                                                c)) #{} (:layers m))]
                                       (conj c {:id       (:id m)
                                                :datasets datasets}))) [])
                           (filter (fn [{:keys [id datasets]}]
                                     (set/superset? auth-datasets-set datasets)))
                           (mapv :id))
        nomaps*       (mapv :id (->> nomaps
                                     (filter #(or (contains? auth-datasets-set (:datasetId %))
                                                  (nil? (:datasetId %))))))]
    (log/debug :vis-maps* maps*)
    (log/debug :vis-others* nomaps*)
    (apply conj nomaps* maps*)))

(defn- auth-dashboards [tenant-conn auth-visualisations-set]
  (->> (db.dashboard/all-dashboards-with-visualisations tenant-conn {} {} {:identifiers identity})
               (group-by :id)
               (reduce (fn [c [i col*]]
                   (conj c (-> (first col*)
                               (assoc  :visualisations (mapv :visualisationId col*))
                               (dissoc :visualisationId)))) '())
               (filter (fn [d]
                         (set/subset? (:visualisation-ids (ids ::dashboard.s/dashboard d))
                                      auth-visualisations-set)))
               (mapv :id)))

(defn- auth-collections [tenant-conn auth-datasets auth-visualisations auth-dashboards]
  (mapv :id (db.collection/auth-collection-ids tenant-conn
                                 {:dataset-ids auth-datasets
                                  :visualisation-ids auth-visualisations
                                  :dashboard-ids auth-dashboards})))

(defn- flow-check-permissions [flow-api {:keys [issuer-type] :as request} collector tenant data]
  (prometheus/with-duration (registry/get collector :app/flow-check-permissions {"tenant" tenant})
    (c.flow/check-permissions (if (= :keycloak issuer-type)
                                (:url flow-api)
                                (:auth0-url flow-api)) (jwt/jwt-token request) data)))

(defn- load-auth-data [dss rasters tenant-conn flow-api request collector tenant]
  (prometheus/with-duration (registry/get collector :app/load-auth-data {"tenant" tenant})
    (let [permissions         (let [flow-data (->> (map :source dss)
                                                   (filter #(= "AKVO_FLOW" (get % "kind")))
                                                   (map c.flow/>api-model)
                                                   (into #{})
                                                   vec)]
                                (if (seq flow-data)
                                  (->> flow-data
                                       (flow-check-permissions flow-api request collector tenant)
                                       :body
                                       set)
                                  #{}))
          auth-datasets (->> (auth-datasets dss permissions)
                             (prometheus/with-duration
                               (registry/get collector :app/auth-datasets {"tenant" tenant})))

          auth-visualisations (->> (auth-visualisations tenant-conn (set auth-datasets))
                                   (prometheus/with-duration
                                     (registry/get collector :app/auth-visualisations {"tenant" tenant})))

          auth-dashboards (->> (auth-dashboards tenant-conn (set auth-visualisations))
                               (prometheus/with-duration
                                 (registry/get collector :app/auth-datasets {"tenant" tenant})))

          auth-collections (->> (auth-collections tenant-conn auth-datasets auth-visualisations auth-dashboards)
                                (prometheus/with-duration
                                  (registry/get collector :app/auth-datasets {"tenant" tenant})))]
      {:rasters             rasters
       :auth-datasets       auth-datasets
       :auth-visualisations auth-visualisations
       :auth-dashboards     auth-dashboards
       :auth-collections    auth-collections})))

(defn wrap-auth-datasets
  "Add to the request an auth-service protocol impl using flow-api check_permissions"
  [tenant-manager flow-api collector]
  (fn [handler]
    (fn [{:keys [jwt-claims tenant] :as request}]
      (if-not (match-by-template-and-method? auth-calls request)
        (handler request)
        (let [tenant-conn    (p/connection tenant-manager tenant)
              dss            (db.dataset/all-datasets tenant-conn)
              rasters        (mapv :id (db.raster/all-rasters tenant-conn))
              auth-uuid-tree (if (match-by-jwt-family-name? request)
                               (load-auth-data dss rasters tenant-conn flow-api request collector tenant)
                               (do
                                 (future
                                   (load-auth-data dss rasters tenant-conn flow-api request collector tenant))
                                 {:rasters             rasters
                                  :auth-datasets       (mapv :id dss)
                                  :auth-visualisations (mapv :id (db.visualisation/all-visualisations-ids tenant-conn))
                                  :auth-dashboards     (mapv :id (db.dashboard/all-dashboards-ids tenant-conn))
                                  :auth-collections    (mapv :id (db.collection/all-collections-ids tenant-conn))}))]
          (handler (assoc request
                          :auth-service (new-auth-service auth-uuid-tree))))))))

(defmethod ig/init-key :akvo.lumen.lib.auth/wrap-auth-datasets  [_ {:keys [tenant-manager flow-api monitoring] :as opts}]
  (wrap-auth-datasets tenant-manager flow-api (:collector monitoring)))

(s/def ::flow-api ::c.flow/config)

(s/def ::monitoring (s/keys :req-un [::monitoring/collector]))

(defmethod ig/pre-init-spec :akvo.lumen.lib.auth/wrap-auth-datasets [_]
  (s/keys :req-un [::tenant-manager/tenant-manager
                   ::flow-api
                   ::monitoring]))

(defn ids
  "returns `{:dataset-ids #{id...} :dashboard-ids #{id...} :visualisation-ids #{id...} :collection-ids #{id...}}` found in `data` arg. Logic based on clojure.spec/def `spec`
   Based on dynamic thread binding."
  [spec data]
  (let [ids    (atom {:collection-ids    #{}
                      :dashboard-ids     #{}
                      :dataset-ids       #{}
                      :visualisation-ids #{}})
        add-id (fn [k id]
                 (when id
                   (swap! ids update k conj id)))]
    (binding [collection.s/*id?*    (partial add-id :collection-ids)
              dashboard.s/*id?*     (partial add-id :dashboard-ids)
              dataset.s/*id?*       (partial add-id :dataset-ids)
              visualisation.s/*id?* (partial add-id :visualisation-ids)]
      (s/explain-str spec data)
      (deref ids))))

(ns akvo.lumen.lib.auth
  (:require
   [akvo.commons.jwt :as jwt]
   [akvo.lumen.component.flow :as c.flow]
   [akvo.lumen.component.tenant-manager :as tenant-manager]
   [akvo.lumen.db.collection :as db.collection]
   [akvo.lumen.db.dashboard :as db.dashboard]
   [akvo.lumen.db.dataset :as db.dataset]
   [akvo.lumen.db.raster :as db.raster]
   [akvo.lumen.db.visualisation :as db.visualisation]
   [akvo.lumen.monitoring :as monitoring]
   [akvo.lumen.protocols :as p]
   [akvo.lumen.specs :as lumen.s]
   [akvo.lumen.specs.collection :as collection.s]
   [akvo.lumen.specs.dashboard :as dashboard.s]
   [akvo.lumen.specs.dataset :as dataset.s]
   [akvo.lumen.specs.visualisation :as visualisation.s]
   [clojure.set :as set]
   [clojure.spec.alpha :as s]
   [clojure.string :as str]
   [clojure.tools.logging :as log]
   [clojure.walk :as w]
   [iapetos.core :as prometheus]
   [iapetos.registry :as registry]
   [integrant.core :as ig]))

(declare ids)

(defn- optimistic-allow?* [this ids]
  {:pre [(= #{:dataset-ids :visualisation-ids :dashboard-ids :collection-ids}
            (set (keys ids)))]}
  (set/subset? (set/union (:dataset-ids ids) (:visualisation-ids ids)
                          (:dashboard-ids ids) (:collection-ids ids))
               (set/union (:auth-datasets-set this) (:auth-visualisations-set this)
                          (:auth-dashboards-set this) (:auth-collections-set this)
                          (:rasters-set this))))

(defn- allow?* [this ids]
  {:pre [(= #{:dataset-ids :visualisation-ids :dashboard-ids :collection-ids}
            (set (keys ids)))]}
  (and (set/subset? (:visualisation-ids ids) (:auth-visualisations-set this))
       (set/subset? (:dataset-ids ids) (:auth-datasets-set this))
       (set/subset? (:dashboard-ids ids) (:auth-dashboards-set this))
       (set/subset? (:collection-ids ids) (:auth-collections-set this))))

(defn- auth* [this ids]
  {:pre [(= #{:dataset-ids :visualisation-ids :dashboard-ids :collection-ids}
            (set (keys ids)))]}
  {:auth-datasets       (set/intersection (:auth-datasets-set this) (set (:dataset-ids ids)))
   :auth-visualisations (set/intersection (:auth-visualisations-set this) (set (:visualisation-ids ids)))
   :auth-dashboards     (set/intersection (:auth-dashboards-set this) (set (:dashboard-ids ids)))
   :auth-collections    (set/intersection (:auth-collections-set this) (set (:collections-ids ids)))})

(defrecord AuthServiceImpl [auth-datasets-set auth-visualisations-set auth-dashboards-set auth-collections-set rasters-set]
  p/AuthService2080
  (optimistic-allow? [this ids]
    (optimistic-allow?* this ids))
  p/AuthService
  (allow? [this ids]
    (allow?* this ids))
  (auth [this ids]
    (auth* this ids))
  (auth [this type* uuid]
    (let [f* (fn [s u] (when (contains? s u) u))]
      (condp = type*
        :dataset       (f* auth-datasets-set uuid)
        :visualisation (f* auth-visualisations-set uuid)
        :dashboard     (f* auth-dashboards-set uuid)
        :collection    (f* auth-collections-set uuid)))))

(defn new-auth-service [{:keys [auth-datasets auth-visualisations auth-dashboards auth-collections rasters] :as auth-uuid-tree}]
  (AuthServiceImpl. (set auth-datasets) (set auth-visualisations) (set auth-dashboards) (set auth-collections) (set rasters)))

(defn match-by-jwt-family-name?
  "Feature flag condition based on `First Name` jwt-claims
  To match just add `$auth` to your `First Name` in your related auth system user profile"
  [request]
  (str/includes? (get (:jwt-claims request) "given_name" "") "$auth$"))

(defn match-by-template-and-method?
  "`data` is a map following this structure {\"/api/library\" {:methods #{:get}}}
  `request` is a `reitit` request that contains reitit data such as :template"
  [data request]
  (-> (get data (:template (:reitit.core/match request)))
      :methods
      (contains? (:request-method request))))

(def auth-calls {"/api/library" {:methods #{:get}}
                 "/api/datasets/:id" {:methods #{:get :put :delete}}
                 "/api/datasets/:id/meta" {:methods #{:get}}
                 "/api/datasets/:id/update" {:methods #{:post}}
                 "/api/datasets/:id/sort/:column-name/text" {:methods #{:get}}
                 "/api/datasets/:id/sort/:column-name/number" {:methods #{:get}}
                 "/api/datasets" {:methods #{:get}}
                 "/api/visualisations" {:methods #{:get :post}}
                 "/api/visualisations/:id" {:methods #{:get :put :delete}}
                 "/api/visualisations/maps" {:methods #{:post}}
                 "/api/dashboards" {:methods #{:get :post}}
                 "/api/dashboards/:id" {:methods #{:get :put :delete}}
                 "/api/collections" {:methods #{:get}}})

(defn- auth-datasets [all-datasets permissions]
  (->> all-datasets
       (filter
        (fn [ds]
          (let [source (:source ds)]
            (if (= "AKVO_FLOW" (get source "kind"))
              (contains? permissions (c.flow/>api-model source))
              true))))
       (mapv :id)))

(defn- auth-visualisations [tenant-conn auth-datasets-set]
  (let [all-visualisations (w/keywordize-keys (db.visualisation/all-visualisations
                                               tenant-conn {} {} {:identifiers identity}))
        maps (filter #(= "map" (:visualisationType %)) all-visualisations)
        nomaps (filter #(not= "map" (:visualisationType %)) all-visualisations)
        maps*         (->> maps
                           (reduce (fn [c m]
                                     (let [datasets (reduce (fn [c l]
                                                              (if-let [ds (let [ds (:datasetId l)]
                                                                            (and ds (not= "null" ds)))]
                                                                (conj c ds)
                                                                c)) #{} (:layers m))]
                                       (conj c {:id       (:id m)
                                                :datasets datasets}))) [])
                           (filter (fn [{:keys [id datasets]}]
                                     (set/superset? auth-datasets-set datasets)))
                           (mapv :id))
        nomaps*       (mapv :id (->> nomaps
                                     (filter #(or (contains? auth-datasets-set (:datasetId %))
                                                  (nil? (:datasetId %))))))]
    (log/debug :vis-maps* maps*)
    (log/debug :vis-others* nomaps*)
    (apply conj nomaps* maps*)))

(defn- auth-dashboards [tenant-conn auth-visualisations-set]
  (->> (db.dashboard/all-dashboards-with-visualisations tenant-conn {} {} {:identifiers identity})
               (group-by :id)
               (reduce (fn [c [i col*]]
                   (conj c (-> (first col*)
                               (assoc  :visualisations (mapv :visualisationId col*))
                               (dissoc :visualisationId)))) '())
               (filter (fn [d]
                         (set/subset? (:visualisation-ids (ids ::dashboard.s/dashboard d))
                                      auth-visualisations-set)))
               (mapv :id)))

(defn- auth-collections [tenant-conn auth-datasets auth-visualisations auth-dashboards]
  (mapv :id (db.collection/auth-collection-ids tenant-conn
                                 {:dataset-ids auth-datasets
                                  :visualisation-ids auth-visualisations
                                  :dashboard-ids auth-dashboards})))

(defn- flow-check-permissions [flow-api {:keys [issuer-type] :as request} collector tenant data]
  (prometheus/with-duration (registry/get collector :app/flow-check-permissions {"tenant" tenant})
    (c.flow/check-permissions (if (= :keycloak issuer-type)
                                (:url flow-api)
                                (:auth0-url flow-api)) (jwt/jwt-token request) data)))

(defn- load-auth-data [dss rasters tenant-conn flow-api request collector tenant]
  (prometheus/with-duration (registry/get collector :app/load-auth-data {"tenant" tenant})
    (let [permissions         (let [flow-data (->> (map :source dss)
                                                   (filter #(= "AKVO_FLOW" (get % "kind")))
                                                   (map c.flow/>api-model)
                                                   (into #{})
                                                   vec)]
                                (if (seq flow-data)
                                  (->> flow-data
                                       (flow-check-permissions flow-api request collector tenant)
                                       :body
                                       set)
                                  #{}))
          auth-datasets (->> (auth-datasets dss permissions)
                             (prometheus/with-duration
                               (registry/get collector :app/auth-datasets {"tenant" tenant})))

          auth-visualisations (->> (auth-visualisations tenant-conn (set auth-datasets))
                                   (prometheus/with-duration
                                     (registry/get collector :app/auth-visualisations {"tenant" tenant})))

          auth-dashboards (->> (auth-dashboards tenant-conn (set auth-visualisations))
                               (prometheus/with-duration
                                 (registry/get collector :app/auth-datasets {"tenant" tenant})))

          auth-collections (->> (auth-collections tenant-conn auth-datasets auth-visualisations auth-dashboards)
                                (prometheus/with-duration
                                  (registry/get collector :app/auth-datasets {"tenant" tenant})))]
      {:rasters             rasters
       :auth-datasets       auth-datasets
       :auth-visualisations auth-visualisations
       :auth-dashboards     auth-dashboards
       :auth-collections    auth-collections})))

(defn wrap-auth-datasets
  "Add to the request an auth-service protocol impl using flow-api check_permissions"
  [tenant-manager flow-api collector]
  (fn [handler]
    (fn [{:keys [jwt-claims tenant] :as request}]
      (if-not (match-by-template-and-method? auth-calls request)
        (handler request)
        (let [tenant-conn    (p/connection tenant-manager tenant)
              dss            (db.dataset/all-datasets tenant-conn)
              rasters        (mapv :id (db.raster/all-rasters tenant-conn))
              auth-uuid-tree (if (match-by-jwt-family-name? request)
                               (load-auth-data dss rasters tenant-conn flow-api request collector tenant)
                               (do
                                 (future
                                   (load-auth-data dss rasters tenant-conn flow-api request collector tenant))
                                 {:rasters             rasters
                                  :auth-datasets       (mapv :id dss)
                                  :auth-visualisations (mapv :id (db.visualisation/all-visualisations-ids tenant-conn))
                                  :auth-dashboards     (mapv :id (db.dashboard/all-dashboards-ids tenant-conn))
                                  :auth-collections    (mapv :id (db.collection/all-collections-ids tenant-conn))}))]
          (handler (assoc request
                          :auth-service (new-auth-service auth-uuid-tree))))))))

(defmethod ig/init-key :akvo.lumen.lib.auth/wrap-auth-datasets  [_ {:keys [tenant-manager flow-api monitoring] :as opts}]
  (wrap-auth-datasets tenant-manager flow-api (:collector monitoring)))

(s/def ::flow-api ::c.flow/config)

(s/def ::monitoring (s/keys :req-un [::monitoring/collector]))

(defmethod ig/pre-init-spec :akvo.lumen.lib.auth/wrap-auth-datasets [_]
  (s/keys :req-un [::tenant-manager/tenant-manager
                   ::flow-api
                   ::monitoring]))

(defn ids
  "returns `{:dataset-ids #{id...} :dashboard-ids #{id...} :visualisation-ids #{id...} :collection-ids #{id...}}` found in `data` arg. Logic based on clojure.spec/def `spec`
   Based on dynamic thread binding."
  [spec data]
  (let [ids    (atom {:collection-ids    #{}
                      :dashboard-ids     #{}
                      :dataset-ids       #{}
                      :visualisation-ids #{}})
        add-id (fn [k id]
                 (when id
                   (swap! ids update k conj id)))]
    (binding [collection.s/*id?*    (partial add-id :collection-ids)
              dashboard.s/*id?*     (partial add-id :dashboard-ids)
              dataset.s/*id?*       (partial add-id :dataset-ids)
              visualisation.s/*id?* (partial add-id :visualisation-ids)]
      (s/explain-str spec data)
      (deref ids))))

[#2376] Fix bug in spliting maps from nomaps viz

Since split-with will split at first chance and the collection
is in a sense a random collection maps could end up in nomap.


(ns metabase.db.metadata-queries
  "Predefined QP queries for getting metadata about an external database."
  (:require [metabase.driver :as driver]
            [metabase.driver.query-processor.expand :as ql]
            [metabase.util :as u]))

(defn- qp-query [db-id query]
  (-> (driver/process-query
       {:type     :query
        :database db-id
        :query    query})
      :data
      :rows))

(defn- field-query [field query]
  (qp-query ((u/deref-> field :table :db) :id)
            (ql/query (merge query)
                      (ql/source-table ((u/deref-> field :table) :id)))))

(defn table-row-count
  "Fetch the row count of TABLE via the query processor."
  [table]
  {:pre  [(map? table)]
   :post [(integer? %)]}
  (-> (qp-query (:db_id table) (ql/query (ql/source-table (:id table))
                                         (ql/aggregation (ql/count))))
      first first int))

(defn field-distinct-values
  "Return the distinct values of FIELD.
   This is used to create a `FieldValues` object for `:category` Fields."
  ([field]
    (field-distinct-values field @(resolve 'metabase.driver.sync/low-cardinality-threshold)))
  ([{field-id :id :as field} max-results]
   {:pre [(integer? max-results)]}
   (mapv first (field-query field (-> {}
                                      (ql/breakout (ql/field-id field-id))
                                      (ql/limit max-results))))))

(defn field-distinct-count
  "Return the distinct count of FIELD."
  [{field-id :id :as field} & [limit]]
  (-> (field-query field (-> {}
                             (ql/aggregation (ql/distinct (ql/field-id field-id)))
                             (ql/limit limit)))
      first first int))

(defn field-count
  "Return the count of FIELD."
  [{field-id :id :as field}]
  (-> (field-query field (ql/aggregation {} (ql/count (ql/field-id field-id))))
      first first int))

(ns metabase.db.metadata-queries
  "Predefined QP queries for getting metadata about an external database."
  (:require [metabase.driver :as driver]
            [metabase.driver.query-processor.expand :as ql]
            [metabase.util :as u]))

(defn- qp-query [db-id query]
  (-> (driver/process-query
       {:type     :query
        :database db-id
        :query    query})
      :data
      :rows))

(defn- field-query [field query]
  (qp-query ((u/deref-> field :table :db) :id)
            (ql/query (merge query)
                      (ql/source-table ((u/deref-> field :table) :id)))))

(defn table-row-count
  "Fetch the row count of TABLE via the query processor."
  [table]
  {:pre  [(map? table)]
   :post [(integer? %)]}
  (-> (qp-query (:db_id table) (ql/query (ql/source-table (:id table))
                                         (ql/aggregation (ql/count))))
      first first long))

(defn field-distinct-values
  "Return the distinct values of FIELD.
   This is used to create a `FieldValues` object for `:category` Fields."
  ([field]
    (field-distinct-values field @(resolve 'metabase.driver.sync/low-cardinality-threshold)))
  ([{field-id :id :as field} max-results]
   {:pre [(integer? max-results)]}
   (mapv first (field-query field (-> {}
                                      (ql/breakout (ql/field-id field-id))
                                      (ql/limit max-results))))))

(defn field-distinct-count
  "Return the distinct count of FIELD."
  [{field-id :id :as field} & [limit]]
  (-> (field-query field (-> {}
                             (ql/aggregation (ql/distinct (ql/field-id field-id)))
                             (ql/limit limit)))
      first first int))

(defn field-count
  "Return the count of FIELD."
  [{field-id :id :as field}]
  (-> (field-query field (ql/aggregation {} (ql/count (ql/field-id field-id))))
      first first int))

use `long` instead of `int` since row counts on tables can be big.


(ns status-im.translations.it)

(def translations
  {
   ;common
   :members-title                         "Membri"
   :not-implemented                       "!non implementato"
   :chat-name                             "Nome chat"
   :notifications-title                   "Notifiche e suoni"
   :offline                               "Offline"

   ;drawer
   :invite-friends                        "Invita amici"
   :faq                                   "FAQ"
   :switch-users                          "Cambia utente"

   ;chat
   :is-typing                             "sta scrivendo"
   :and-you                               "e tu"
   :search-chat                           "Cerca chat"
   :members                               {:one   "1 membro"
                                           :other "{{count}} membri"
                                           :zero  "nessun membro"}
   :members-active                        {:one   "1 membro, 1 attivo"
                                           :other "{{count}} membri, {{count}} attivi"
                                           :zero  "nessun membro"}
   :active-online                         "Online"
   :active-unknown                        "Sconosciuto"
   :available                             "Disponibile"
   :no-messages                           "Nessun messaggio"
   :suggestions-requests                  "Richieste"
   :suggestions-commands                  "Comandi"

   ;sync
   :sync-in-progress                      "Sincronizzazione in corso..."
   :sync-synced                           "Sincronizzato"

   ;messages
   :status-sending                        "Invio"
   :status-pending                        "In attesa di"
   :status-sent                           "Inviato"
   :status-seen-by-everyone               "Visto da tutti"
   :status-seen                           "Visto"
   :status-delivered                      "Consegnato"
   :status-failed                         "Fallito"

   ;datetime
   :datetime-second                       {:one   "secondo"
                                           :other "secondi"}
   :datetime-minute                       {:one   "minuto"
                                           :other "minuti"}
   :datetime-hour                         {:one   "ora"
                                           :other "ore"}
   :datetime-day                          {:one   "giorno"
                                           :other "giorni"}
   :datetime-multiple                     "s"
   :datetime-ago                          "fa"
   :datetime-yesterday                    "ieri"
   :datetime-today                        "oggi"

   ;profile
   :profile                               "Profilo"
   :report-user                           "SEGNALA UTENTE"
   :message                               "Messaggio"
   :username                              "Username"
   :not-specified                         "Non specificato"
   :public-key                            "Chiave pubblica"
   :phone-number                          "Numero di telefono"
   :email                                 "Email"
   :profile-no-status                     "Nessuno stato"
   :add-to-contacts                       "Aggiunto ai contatti"
   :error-incorrect-name                  "Seleziona un altro nome"
   :error-incorrect-email                 "e-mail non valida"

   ;;make_photo
   :image-source-title                    "Immagine del profilo"
   :image-source-make-photo               "Acquisisci"
   :image-source-gallery                  "Scegli dalla galleria"
   :image-source-cancel                   "Elimina"

   ;sign-up
   :contacts-syncronized                  "I tuoi contatti sono stati sincronizzati"
   :confirmation-code                     (str "Grazie! Ti abbiamo inviato un messaggio con un codice di conferma "
                                               "codice. Fornisci questo codice per confermare il tuo numero di telefono")
   :incorrect-code                        (str "Spiacenti, il codice era errato, reinseriscilo")
   :generate-passphrase                   (str "Genereremo una passphrase da utilizzare per riattivare il tuo "
                                               "accesso o accedere da un altro dispositivo")
   :phew-here-is-your-passphrase          "*Uff* è stato difficile, ecco la tua passphrase, *scrivila e tienila al sicuro!* Ti servirà per poter accedere al tuo account."
   :here-is-your-passphrase               "Questa è la tua passphrase, *scrivila e tienila al sicuro!* Ti servirà per poter accedere al tuo account."
   :written-down                          "Assicurati di averla scritta e conservata"
   :phone-number-required                 "Tocca qui per inserire il tuo numero di telefono e cercheremo i tuoi amici"
   :intro-status                          "Fai una chat con me per impostare il tuo account e cambiare le tue impostazioni!"
   :intro-message1                        "Benvenuto su Status\nTocca questo messaggio per impostare la tua password e poter cominciare!"
   :account-generation-message            "Dammi un secondo, devo fare dei calcoli complicati per generare il tuo account!"

   ;chats
   :chats                                 "Chat"
   :new-chat                              "Nuova chat"
   :new-group-chat                        "Nuova chat di gruppo"

   ;discover
   :discover                             "Scopri"
   :none                                  "Nessuno"
   :search-tags                           "Digita qui le tue etichette di ricerca"
   :popular-tags                          "Tag popolari"
   :recent                                "Recenti"
   :no-statuses-discovered                "Nessuno stato trovato"

   ;settings
   :settings                              "Impostazioni"

   ;contacts
   :contacts                              "Contatti"
   :new-contact                           "Nuovo contatto"
   :show-all                              "MOSTRA TUTTI"
   :contacts-group-dapps                  "ÐApps"
   :contacts-group-people                 "Persone"
   :contacts-group-new-chat               "Comincia una nuova chat"
   :no-contacts                           "Ancora nessun contatto"
   :show-qr                               "Mostra QR"

   ;group-settings
   :remove                                "Rimuovi"
   :save                                  "Salva"
   :change-color                          "Cambia colore"
   :clear-history                         "Cancella cronologia"
   :delete-and-leave                      "Cancella e lascia"
   :chat-settings                         "Impostazioni chat"
   :edit                                  "Modifica"
   :add-members                           "Aggiungi membri"
   :blue                                  "Blu"
   :purple                                "Viola"
   :green                                 "Verde"
   :red                                   "Rosso"

   ;commands
   :money-command-description             "Invia denaro"
   :location-command-description          "Invia posizione"
   :phone-command-description             "Invia numero di telefono"
   :phone-request-text                    "Richiesta numero di telefono"
   :confirmation-code-command-description "Invia codice di conferma"
   :confirmation-code-request-text        "Codice di conferma richiesto"
   :send-command-description              "Invia posizione"
   :request-command-description           "Invia richiesta"
   :keypair-password-command-description  ""
   :help-command-description              "Aiuto"
   :request                               "Richiesta"
   :chat-send-eth                         "{{amount}} ETH"
   :chat-send-eth-to                      "{{amount}} ETH per {{chat-name}}"
   :chat-send-eth-from                    "{{amount}} ETH da {{chat-name}}"
   :command-text-location                 "Posizione: {{address}}"
   :command-text-browse                   "Naviga la pagina web: {{webpage}}"
   :command-text-send                     "Transazione: {{amount}} ETH"
   :command-text-help                     "Aiuto"

   ;new-group
   :group-chat-name                       "Nome chat"
   :empty-group-chat-name                 "Inserisci un nome"
   :illegal-group-chat-name               "Scegli un altro nome"

   ;participants
   :add-participants                      "Aggiungi partecipanti"
   :remove-participants                   "Rimuovi partecipanti"

   ;protocol
   :received-invitation                   "invito alla chat ricevuto"
   :removed-from-chat                     "sei stato rimosso dalla chat di gruppo"
   :left                                  "lasciato"
   :invited                               "invitato"
   :removed                               "rimosso"
   :You                                   "Tu"

   ;new-contact
   :add-new-contact                       "Aggiungi un nuovo contatto"
   :import-qr                             "Importa"
   :scan-qr                               "Scansiona codice QR"
   :name                                  "Nome"
   :whisper-identity                      "Sussurra identità"
   :address-explication                   "Forse qui bisognerebbe inserire un testo che spieghi cos'è un indirizzo e dove cercarlo"
   :enter-valid-address                   "Inserisci un indirizzo valido o scansiona un codice QR"
   :contact-already-added                 "Il contatto è stato già aggiunto"
   :can-not-add-yourself                  "Non puoi aggiungere te stesso"
   :unknown-address                       "Indirizzo sconosciuto"

   ;login
   :connect                               "Connetti"
   :address                               "Indirizzo"
   :password                              "Password"
   :login                                 "Accedi"
   :wrong-password                        "Password errata"

   ;recover
   :recover-from-passphrase               "Recupera dalla passphrase"
   :recover-explain                       "Inserisci la passphrase per poter accedere alla tua password"
   :passphrase                            "Passphrase"
   :recover                               "Recupera"
   :enter-valid-passphrase                "Inserisci una passphrase"
   :enter-valid-password                  "Inserisci una password"

   ;accounts
   :recover-access                        "Recupera accessi"
   :add-account                           "Aggiungi account"

   ;wallet-qr-code
   :done                                  "Fatto"
   :main-wallet                           "Portafogli principale"

   ;validation
   :invalid-phone                         "Numero di telefono non valido"
   :amount                                "Ammontare"
   :not-enough-eth                        (str "ETH sul bilancio insufficiente "
                                               "({{balance}} ETH)")
   ;transactions
   :confirm-transactions                  {:one   "Conferma transazione"
                                           :other "Conferma {{count}} transazioni"
                                           :zero  "Nessuna transazione"}
   :status                                "Stato"
   :pending-confirmation                  "Conferma in attesa"
   :recipient                             "Ricevente"
   :one-more-item                         "Un altro oggetto"
   :fee                                   "Commissione"
   :value                                 "Valore"

   ;:webview
   :web-view-error                        "ops, errore"})

(ns status-im.translations.it)

(def translations
  {
   ;common
   :members-title                         "Membri"
   :not-implemented                       "!non implementato"
   :chat-name                             "Nome della chat"
   :notifications-title                   "Notifiche e suoni"
   :offline                               "Offline"

   ;drawer
   :invite-friends                        "Invita amici"
   :faq                                   "FAQ"
   :switch-users                          "Cambia utente"

   ;chat
   :is-typing                             "sta scrivendo"
   :and-you                               "e tu"
   :search-chat                           "Cerca chat"
   :members                               {:one   "1 membro"
                                           :other "{{count}} membri"
                                           :zero  "nessun membro"}
   :members-active                        {:one   "1 membro, 1 attivo"
                                           :other "{{count}} membri, {{count}} attivi"
                                           :zero  "nessun membro"}
   :active-online                         "Online"
   :active-unknown                        "Sconosciuto"
   :available                             "Disponibile"
   :no-messages                           "Nessun messaggio"
   :suggestions-requests                  "Richieste"
   :suggestions-commands                  "Comandi"

   ;sync
   :sync-in-progress                      "Sincronizzazione in corso..."
   :sync-synced                           "Sincronizzato"

   ;messages
   :status-sending                        "Invio"
   :status-pending                        "In attesa di"
   :status-sent                           "Inviato"
   :status-seen-by-everyone               "Visto da tutti"
   :status-seen                           "Visto"
   :status-delivered                      "Consegnato"
   :status-failed                         "Fallito"

   ;datetime
   :datetime-second                       {:one   "secondo"
                                           :other "secondi"}
   :datetime-minute                       {:one   "minuto"
                                           :other "minuti"}
   :datetime-hour                         {:one   "ora"
                                           :other "ore"}
   :datetime-day                          {:one   "giorno"
                                           :other "giorni"}
   :datetime-multiple                     "s"
   :datetime-ago                          "fa"
   :datetime-yesterday                    "ieri"
   :datetime-today                        "oggi"

   ;profile
   :profile                               "Profilo"
   :report-user                           "SEGNALA UTENTE"
   :message                               "Messaggio"
   :username                              "Username"
   :not-specified                         "Non specificato"
   :public-key                            "Chiave pubblica"
   :phone-number                          "Numero di telefono"
   :email                                 "Email"
   :profile-no-status                     "Nessuno stato"
   :add-to-contacts                       "Aggiunto ai contatti"
   :error-incorrect-name                  "Seleziona un altro nome"
   :error-incorrect-email                 "e-mail non valida"

   ;;make_photo
   :image-source-title                    "Immagine del profilo"
   :image-source-make-photo               "Acquisisci"
   :image-source-gallery                  "Scegli dalla galleria"
   :image-source-cancel                   "Elimina"

   ;sign-up
   :contacts-syncronized                  "I tuoi contatti sono stati sincronizzati"
   :confirmation-code                     (str "Grazie! Ti abbiamo inviato un messaggio con un codice di conferma "
                                               "codice. Fornisci questo codice per confermare il tuo numero di telefono")
   :incorrect-code                        (str "Spiacenti, il codice era errato, reinseriscilo")
   :generate-passphrase                   (str "Genereremo una passphrase da utilizzare per riattivare il tuo "
                                               "accesso o accedere da un altro dispositivo")
   :phew-here-is-your-passphrase          "*Uff* è stato difficile, ecco la tua passphrase, *scrivila e tienila al sicuro!* Ti servirà per poter accedere al tuo account."
   :here-is-your-passphrase               "Questa è la tua passphrase, *scrivila e tienila al sicuro!* Ti servirà per poter accedere al tuo account."
   :written-down                          "Assicurati di averla scritta e conservata"
   :phone-number-required                 "Tocca qui per inserire il tuo numero di telefono e cercheremo i tuoi amici"
   :intro-status                          "Fai una chat con me per impostare il tuo account e cambiare le tue impostazioni!"
   :intro-message1                        "Benvenuto su Status\nTocca questo messaggio per impostare la tua password e poter cominciare!"
   :account-generation-message            "Dammi un secondo, devo fare dei calcoli complicati per generare il tuo account!"

   ;chats
   :chats                                 "Chat"
   :new-chat                              "Nuova chat"
   :new-group-chat                        "Nuova chat di gruppo"

   ;discover
   :discover                             "Scopri"
   :none                                  "Nessuno"
   :search-tags                           "Digita qui le tue etichette di ricerca"
   :popular-tags                          "Tag popolari"
   :recent                                "Recenti"
   :no-statuses-discovered                "Nessuno stato trovato"

   ;settings
   :settings                              "Impostazioni"

   ;contacts
   :contacts                              "Contatti"
   :new-contact                           "Nuovo contatto"
   :show-all                              "MOSTRA TUTTI"
   :contacts-group-dapps                  "ÐApps"
   :contacts-group-people                 "Persone"
   :contacts-group-new-chat               "Comincia una nuova chat"
   :no-contacts                           "Ancora nessun contatto"
   :show-qr                               "Mostra QR"

   ;group-settings
   :remove                                "Rimuovi"
   :save                                  "Salva"
   :change-color                          "Cambia colore"
   :clear-history                         "Cancella cronologia"
   :delete-and-leave                      "Elimina e lascia"
   :chat-settings                         "Impostazioni chat"
   :edit                                  "Modifica"
   :add-members                           "Aggiungi membri"
   :blue                                  "Blu"
   :purple                                "Viola"
   :green                                 "Verde"
   :red                                   "Rosso"

   ;commands
   :money-command-description             "Invia denaro"
   :location-command-description          "Invia posizione"
   :phone-command-description             "Invia numero di telefono"
   :phone-request-text                    "Richiesta numero di telefono"
   :confirmation-code-command-description "Invia codice di conferma"
   :confirmation-code-request-text        "Codice di conferma richiesto"
   :send-command-description              "Invia posizione"
   :request-command-description           "Invia richiesta"
   :keypair-password-command-description  ""
   :help-command-description              "Aiuto"
   :request                               "Richiesta"
   :chat-send-eth                         "{{amount}} ETH"
   :chat-send-eth-to                      "{{amount}} ETH per {{chat-name}}"
   :chat-send-eth-from                    "{{amount}} ETH da {{chat-name}}"
   :command-text-location                 "Posizione: {{address}}"
   :command-text-browse                   "Naviga la pagina web: {{webpage}}"
   :command-text-send                     "Transazione: {{amount}} ETH"
   :command-text-help                     "Aiuto"

   ;new-group
   :group-chat-name                       "Nome chat"
   :empty-group-chat-name                 "Inserisci un nome"
   :illegal-group-chat-name               "Scegli un altro nome"

   ;participants
   :add-participants                      "Aggiungi partecipanti"
   :remove-participants                   "Rimuovi partecipanti"

   ;protocol
   :received-invitation                   "invito alla chat ricevuto"
   :removed-from-chat                     "sei stato rimosso dalla chat di gruppo"
   :left                                  "lasciato"
   :invited                               "invitato"
   :removed                               "rimosso"
   :You                                   "Tu"

   ;new-contact
   :add-new-contact                       "Aggiungi un nuovo contatto"
   :import-qr                             "Importa"
   :scan-qr                               "Scansiona codice QR"
   :name                                  "Nome"
   :whisper-identity                      "Sussurra identità"
   :address-explication                   "Forse qui bisognerebbe inserire un testo che spieghi cos'è un indirizzo e dove cercarlo"
   :enter-valid-address                   "Inserisci un indirizzo valido o scansiona un codice QR"
   :contact-already-added                 "Il contatto è stato già aggiunto"
   :can-not-add-yourself                  "Non puoi aggiungere te stesso"
   :unknown-address                       "Indirizzo sconosciuto"

   ;login
   :connect                               "Connettiti"
   :address                               "Indirizzo"
   :password                              "Password"
   :login                                 "Accedi"
   :wrong-password                        "Password errata"

   ;recover
   :recover-from-passphrase               "Recupera l'account usando la passphrase"
   :recover-explain                       "Inserisci la passphrase per poter accedere alla tua password"
   :passphrase                            "Passphrase"
   :recover                               "Recupera"
   :enter-valid-passphrase                "Inserisci una passphrase"
   :enter-valid-password                  "Inserisci una password"

   ;accounts
   :recover-access                        "Recupera accessi"
   :add-account                           "Aggiungi account"

   ;wallet-qr-code
   :done                                  "Fatto"
   :main-wallet                           "Portafogli principale"

   ;validation
   :invalid-phone                         "Numero di telefono non valido"
   :amount                                "Ammontare"
   :not-enough-eth                        (str "Non hai abbastanza ETH "
                                               "({{balance}} ETH)")
   ;transactions
   :confirm-transactions                  {:one   "Conferma transazione"
                                           :other "Conferma {{count}} transazioni"
                                           :zero  "Nessuna transazione"}
   :status                                "Stato"
   :pending-confirmation                  "Conferma in attesa"
   :recipient                             "Ricevente"
   :one-more-item                         "Un altro oggetto"
   :fee                                   "Commissione"
   :value                                 "Valore"

   ;:webview
   :web-view-error                        "ops, errore"})

Update it.cljs

Update it.cljs


(ns mvxcvi.vault.blob.store.file
  (:require
    [clojure.string :as string]
    [clojure.java.io :as io]
    [mvxcvi.vault.blob :as blob]
    [mvxcvi.vault.blob.store :refer :all]))

;; HELPER FUNCTIONS

(defn- blobref->file
  [root blobref]
  (let [blobref (blob/blob-ref blobref)
        {:keys [algorithm digest]} blobref]
    (io/file root
             (name algorithm)
             (subs digest 0 3)
             (subs digest 3 6)
             (subs digest 6))))

(defn- file->blobref
  [root file]
  (let [root (str root)
        file (str file)]
    (when-not (.startsWith file root)
      (throw (IllegalArgumentException.
               (str "File " file " is not a child of root directory " root))))
    (let [[algorithm & digest] (-> file
                                   (subs (inc (count root)))
                                   (string/split #"/"))]
      (blob/blob-ref algorithm (string/join digest)))))

;; FILE STORE

(defrecord FileBlobStore
  [root algorithm]

  BlobStore

  (enumerate [this]
    (->>
      (for [algorithm-dir (.listFiles root)]
        (for [prefix-dir (.listFiles algorithm-dir)]
          (for [midfix-dir (.listFiles prefix-dir)]
            (seq (.listFiles midfix-dir)))))
      flatten
      (map (partial file->blobref root))))

  (content-stream [this blobref]
    (let [file (blobref->file root blobref)]
      (if (.exists file)
        (io/input-stream file)
        nil)))

  (store-content! [this content]
    (let [blobref (blob/hash-content algorithm content)
          file (blobref->file root blobref)]
      (io/make-parents file)
      (io/copy content file)
      blobref))

  (blob-info [this blobref]
    (let [file (blobref->file root blobref)]
      {:location (.toURI file)
       :size (.length file)})))

(defn file-store
  "Creates a new local file-based blobstore."
  ([root] (file-store root :sha256))
  ([root algorithm] (FileBlobStore. (io/file root) algorithm)))

(ns mvxcvi.vault.blob.store.file
  (:require
    [clojure.string :as string]
    [clojure.java.io :as io]
    [mvxcvi.vault.blob :as blob]
    [mvxcvi.vault.blob.store :refer :all]))

;; HELPER FUNCTIONS

(defn- blobref->file
  [root blobref]
  (let [blobref (blob/blob-ref blobref)
        {:keys [algorithm digest]} blobref]
    (io/file root
             (name algorithm)
             (subs digest 0 3)
             (subs digest 3 6)
             (subs digest 6))))

(defn- file->blobref
  [root file]
  (let [root (str root)
        file (str file)]
    (when-not (.startsWith file root)
      (throw (IllegalArgumentException.
               (str "File " file " is not a child of root directory " root))))
    (let [[algorithm & digest] (-> file
                                   (subs (inc (count root)))
                                   (string/split #"/"))]
      (blob/blob-ref algorithm (string/join digest)))))

;; FILE STORE

(defrecord FileBlobStore
  [root algorithm]

  BlobStore

  (enumerate [this]
    (->>
      (for [algorithm-dir (.listFiles root)]
        (for [prefix-dir (.listFiles algorithm-dir)]
          (for [midfix-dir (.listFiles prefix-dir)]
            (seq (.listFiles midfix-dir)))))
      flatten
      (map (partial file->blobref root))))

  (content-stream [this blobref]
    (let [file (blobref->file root blobref)]
      (when (.exists file)
        (io/input-stream file))))

  (store-content! [this content]
    (let [blobref (blob/hash-content algorithm content)
          file (blobref->file root blobref)]
      (io/make-parents file)
      (io/copy content file)
      blobref))

  (blob-info [this blobref]
    (let [file (blobref->file root blobref)]
      (when (.exists file)
        {:location (.toURI file)
         :size (.length file)}))))

(defn file-store
  "Creates a new local file-based blobstore."
  ([root] (file-store root :sha256))
  ([root algorithm] (FileBlobStore. (io/file root) algorithm)))

Fix some conditions in file store.

Fix some conditions in file store.


;; Copyright © 2014 JUXT LTD.

(ns modular.bidi
  (:require
   [schema.core :as s]
   [modular.ring :refer (RingHandler RingBinding)]
   [com.stuartsierra.component :as component]
   [bidi.bidi :as bidi :refer (match-route path-for)]
   [clojure.tools.logging :refer :all]
   [plumbing.core :refer (?>)]
))

;; I've thought hard about a less enterprisy name for this protocol, but
;; components that satisfy it fit most definitions of web
;; services. There's an interface (URIs), coupled to an implementation
;; (via handlers)
(defprotocol WebService
  (ring-handler-map [_])
  (routes [_])
  (uri-context [_]))

#_(defn make-handler
  "Create a Ring handler from the route definition data
  structure. Matches a handler from the uri in the request, and invokes
  it with the request as a parameter."
  [route handlers]
  (fn [{:keys [uri] :as request}]
    (let [{:keys [handler params]} (apply match-route route uri (apply concat (seq request)))]
      (when handler ; in this case, handler is a keyword
        (if-let [handler (get handlers handler)]
          (handler (-> request (update-in [:route-params] merge params)))
          (throw
           (ex-info
            (format "Route for (%s) resolved to a key (%s) which was not represented in the handler-map" uri handler)
            {:route route :handler-key handler :handler-keys (keys handlers) :uri uri}))
          )
        ))))

(defrecord WebServiceFromArguments [ring-handler-map routes uri-context]
  WebService
  (ring-handler-map [this] ring-handler-map)
  (routes [this] routes)
  (uri-context [this] uri-context))

(def new-web-service-schema
  {:ring-handler-map {s/Keyword s/Any}
   :routes [(s/one s/Any "pattern") (s/one s/Any "matched")]
   :uri-context s/Str})

(defn new-web-service
  "Create a component with a given set of bidi routes. An optional web
  context can be given as a keyword argument using the :context key. The
  routes can be a bidi route structure, or a function that returns
  one. When using a function, the component is passed as a single
  argument."
  [& {:as opts}]
  (->> opts
       (merge {:uri-context ""} opts)
       (s/validate new-web-service-schema)
       map->WebServiceFromArguments))

;; The ComponentPreference record modifies a bidi route structure to
;; preference a given component when forming a URI from a
;; keyword. Without ComponentPreference, components using identical
;; keywords in their ring-handler-map maps could inadvertantly get in
;; the way of path-for calls from another component.

(defrecord ComponentPreference [matched component]
  bidi/Matched
  (resolve-handler [this m]
    (bidi/resolve-handler matched m))
  (unresolve-handler [this m]
    (if (keyword? (:handler m))
      (or
       ;; In case there's another component using the same key in a handler-map,
       ;; preference a path to an 'internal' handler first.
       (bidi/unresolve-handler matched (assoc m :handler [component (:handler m)]))
       (bidi/unresolve-handler matched m))
      (bidi/unresolve-handler matched m))))

(defn wrap-component-preference
  "Augment the routes entry bound to a request with data indicating the
  component owner of the handler. This information is used to modify the
  behaviour of the path-for function on those routes, such that the
  component owner is preferenced in the case where multiple handlers
  use identical keywords."
  [h component]
  (if (fn? h)
    (fn [req]
      (h (update-in req [::routes] (fn [r] ["" (->ComponentPreference [r] component)]))))
    h))

;; We use ComponentAddressable to allow the formation of URIs using
;; korks in addition to direct reference to handlers, which may be
;; difficult to obtain in modular applications.

;; If a keyword is used, e.g. (bidi/path-for routes :foo) then we try
;; the ring-handler-map of the calling component first, and if there is
;; no handler entry for that keyword, all other components'
;; ring-handler-maps are tried (in an undefined order). This is what is
;; meant by a component 'preference'.

;; If a keyword sequence is used, e.g. (bidi/path-for routes [:foo
;; :bar]) then :foo is interpreted as a dependency of the router
;; (usually a router is shared between multiple components) and :bar
;; is a handler in the ring-handler-map of :foo.

(defn wrap-capture-component-on-error
  "Wrap handler in a try/catch that will capture the component and
  handler of the error."
  [h & {:keys [component handler]}]
  (when h
    (fn [req]
      (try
        (h req)
        (catch Exception cause
          (throw (ex-info "Failure during request handling"
                          {:component component :handler handler}
                          cause)))))))

(defrecord ComponentAddressable [matched ckey handlers]
  bidi/Matched
  (resolve-handler [this m]
    (when-let [{:keys [handler] :as res} (bidi/resolve-handler matched m)]
      (if (keyword? handler)
        (assoc res :handler (-> (get-in handlers [ckey handler])
                                (wrap-component-preference ckey)
                                (wrap-capture-component-on-error :component ckey :handler handler)))
        res)))

  (unresolve-handler [this m]
    (cond (coll? (:handler m))
          (when (= ckey (first (:handler m)))
            (bidi/unresolve-handler matched (update-in m [:handler] second)))
          :otherwise (bidi/unresolve-handler matched m))))

(defmacro infof-result [level msg form]
  `(let [res# ~form]
     (logf ~level ~msg (pr-str res#))
     res#))

(defrecord Router [compile-routes?]
  component/Lifecycle
  (start [this]
    (let [handlers
          ;; Handlers is a two-level map from dependency key to the
          ;; dependency's handler map.
          (infof-result
           :info "Bidi router determines handlers as %s"
           (apply merge
                  (for [[k v] this
                        :when (satisfies? WebService v)]
                    {k (ring-handler-map v)})))]

      (assoc this
        :handlers handlers
        :routes ["" (vec (for [[ckey v] this
                               :when (satisfies? WebService v)]
                           [(or (uri-context v) "")
                            ;; We wrap in some bidi middleware which
                            ;; allows us to form URIs via a
                            ;; keyword-path: [component-key handler-key]
                            (->ComponentAddressable [(routes v)] ckey handlers)]))])))
  (stop [this] this)

  RingBinding
  (ring-binding [this req] {::routes (:routes this) ::handlers (:handlers this)})

  RingHandler
  (ring-handler [this]
    (-> (:routes this)
        (?> compile-routes? bidi/compile-route)
        bidi/make-handler)))

(def new-router-schema
  {:compile-routes? s/Bool})

(defn new-router
  "Constructor for a ring handler that collates all bidi routes
  provided by its dependencies."
  [& {:as opts}]
  (->> opts
       (merge {:compile-routes? true})
       (s/validate new-router-schema)
       map->Router))

;; Copyright © 2014 JUXT LTD.

(ns modular.bidi
  (:require
   [schema.core :as s]
   [modular.ring :refer (RingHandler RingBinding)]
   [com.stuartsierra.component :as component]
   [bidi.bidi :as bidi :refer (match-route path-for)]
   [clojure.tools.logging :refer :all]
   [plumbing.core :refer (?>)]))

;; I've thought hard about a less enterprisy name for this protocol, but
;; components that satisfy it fit most definitions of web
;; services. There's an interface (URIs), coupled to an implementation
;; (via handlers)
(defprotocol WebService
  (ring-handler-map [_])
  (routes [_])
  (uri-context [_]))

(defrecord WebServiceFromArguments [ring-handler-map routes uri-context]
  WebService
  (ring-handler-map [this] ring-handler-map)
  (routes [this] routes)
  (uri-context [this] uri-context))

(def new-web-service-schema
  {:ring-handler-map {s/Keyword s/Any}
   :routes [(s/one s/Any "pattern") (s/one s/Any "matched")]
   :uri-context s/Str})

(defn new-web-service
  "Create a component with a given set of bidi routes. An optional web
  context can be given as a keyword argument using the :context key. The
  routes can be a bidi route structure, or a function that returns
  one. When using a function, the component is passed as a single
  argument."
  [& {:as opts}]
  (->> opts
       (merge {:uri-context ""} opts)
       (s/validate new-web-service-schema)
       map->WebServiceFromArguments))

;; The ComponentPreference record modifies a bidi route structure to
;; preference a given component when forming a URI from a
;; keyword. Without ComponentPreference, components using identical
;; keywords in their ring-handler-map maps could inadvertantly get in
;; the way of path-for calls from another component.

(defrecord ComponentPreference [matched component]
  bidi/Matched
  (resolve-handler [this m]
    (bidi/resolve-handler matched m))
  (unresolve-handler [this m]
    (if (keyword? (:handler m))
      (or
       ;; In case there's another component using the same key in a handler-map,
       ;; preference a path to an 'internal' handler first.
       (bidi/unresolve-handler matched (assoc m :handler [component (:handler m)]))
       (bidi/unresolve-handler matched m))
      (bidi/unresolve-handler matched m))))

(defn wrap-component-preference
  "Augment the routes entry bound to a request with data indicating the
  component owner of the handler. This information is used to modify the
  behaviour of the path-for function on those routes, such that the
  component owner is preferenced in the case where multiple handlers
  use identical keywords."
  [h component]
  (if (fn? h)
    (fn [req]
      (h (update-in req [::routes] (fn [r] ["" (->ComponentPreference [r] component)]))))
    h))

;; We use ComponentAddressable to allow the formation of URIs using
;; korks in addition to direct reference to handlers, which may be
;; difficult to obtain in modular applications.

;; If a keyword is used, e.g. (bidi/path-for routes :foo) then we try
;; the ring-handler-map of the calling component first, and if there is
;; no handler entry for that keyword, all other components'
;; ring-handler-maps are tried (in an undefined order). This is what is
;; meant by a component 'preference'.

;; If a keyword sequence is used, e.g. (bidi/path-for routes [:foo
;; :bar]) then :foo is interpreted as a dependency of the router
;; (usually a router is shared between multiple components) and :bar
;; is a handler in the ring-handler-map of :foo.

(defn wrap-capture-component-on-error
  "Wrap handler in a try/catch that will capture the component and
  handler of the error."
  [h & {:keys [component handler]}]
  (when h
    (fn [req]
      (try
        (h req)
        (catch Exception cause
          (throw (ex-info "Failure during request handling"
                          {:component component :handler handler}
                          cause)))))))

(defrecord ComponentAddressable [matched ckey handlers]
  bidi/Matched
  (resolve-handler [this m]
    (when-let [{:keys [handler] :as res} (bidi/resolve-handler matched m)]
      (if (keyword? handler)
        (assoc res :handler (-> (get-in handlers [ckey handler])
                                (wrap-component-preference ckey)
                                (wrap-capture-component-on-error :component ckey :handler handler)))
        res)))

  (unresolve-handler [this m]
    (cond (coll? (:handler m))
          (when (= ckey (first (:handler m)))
            (bidi/unresolve-handler matched (update-in m [:handler] second)))
          :otherwise (bidi/unresolve-handler matched m))))

(defmacro infof-result [level msg form]
  `(let [res# ~form]
     (logf ~level ~msg (pr-str res#))
     res#))

(defrecord Router [compile-routes?]
  component/Lifecycle
  (start [this]
    (let [handlers
          ;; Handlers is a two-level map from dependency key to the
          ;; dependency's handler map.
          (infof-result
           :info "Bidi router determines handlers as %s"
           (apply merge
                  (for [[k v] this
                        :when (satisfies? WebService v)]
                    {k (ring-handler-map v)})))]

      (assoc this
        :handlers handlers
        :routes ["" (vec (for [[ckey v] this
                               :when (satisfies? WebService v)]
                           [(or (uri-context v) "")
                            ;; We wrap in some bidi middleware which
                            ;; allows us to form URIs via a
                            ;; keyword-path: [component-key handler-key]
                            (->ComponentAddressable [(routes v)] ckey handlers)]))])))
  (stop [this] this)

  RingBinding
  (ring-binding [this req] {::routes (:routes this) ::handlers (:handlers this)})

  RingHandler
  (ring-handler [this]
    (-> (:routes this)
        (?> compile-routes? bidi/compile-route)
        bidi/make-handler)))

(def new-router-schema
  {:compile-routes? s/Bool})

(defn new-router
  "Constructor for a ring handler that collates all bidi routes
  provided by its dependencies."
  [& {:as opts}]
  (->> opts
       (merge {:compile-routes? true})
       (s/validate new-router-schema)
       map->Router))

Remove redundant code

Remove redundant code


(ns hello-re-natal.ios.core
  (:require [reagent.core :as r :refer [atom]]
            [re-frame.core :refer [subscribe dispatch dispatch-sync]]
            [hello-re-natal.events]
            [hello-re-natal.subs]
            [cljs.reader :as reader]))

(def read-string reader/read-string)
(def ReactNative (js/require "react-native"))

(def app-registry (.-AppRegistry ReactNative))
(def text (r/adapt-react-class (.-Text ReactNative)))
(def text-input (r/adapt-react-class (.-TextInput ReactNative)))
(def view (r/adapt-react-class (.-View ReactNative)))
(def image (r/adapt-react-class (.-Image ReactNative)))
(def touchable-highlight (r/adapt-react-class (.-TouchableHighlight ReactNative)))
(def pan-responder (r/adapt-react-class (.-PanResponder ReactNative)))
(def animated (r/adapt-react-class (.-Animated ReactNative)))
(def dimensions (r/adapt-react-class (.-Dimensions ReactNative)))

(def logo-img (js/require "./images/cljs.png"))

(defn alert [title]
  (.alert (.-Alert ReactNative) title))

(def pounds-per-kilogram 0.45359237)
(defn to-pounds
  [weight]
  (* weight pounds-per-kilogram))

(defn app-root []
  (let [greeting (subscribe [:get-greeting])
        state (atom "Hello")]
    (fn []
      [view {:style {:flex-direction "column"
                     :margin 40}}
       [text {:style {:font-size 30
                      :font-weight "100"
                      :margin-bottom 20
                      :text-align "center"}} @greeting]
       [image {:source logo-img
               :style {:align-self "center"
                       :width 80
                       :height 80
                       :margin-bottom 30}}]
       [text-input {:style {:height 40}
                    :on-change-text #(do
                                       (reset! state %)
                                       (r/flush))
                    :value @state}]
       [touchable-highlight {:style {:background-color "#999"
                                     :padding 10
                                     :border-radius 5}
                             :on-press #(alert (str (to-pounds 135)))}
        [text {:style {:color "white" :text-align "center" :font-weight "bold"}} "press me"]]
       [view {:style {:background-color "#000"
                      `:height 80}}]
       [view {:style {:background-color "red"
                      `:height 80}}]
       [text {:style {:color "black"
                      :text-align "center"
                      :font-weight "bold"}}
                      (if (number? (read-string @state))
                          (str (to-pounds @state))
                          (str "Not a number" @state (rand 10))
                          )]])))

(defn init []
  (dispatch-sync [:initialize-db])
  (.registerComponent app-registry "HelloReNatal" #(r/reactify-component app-root)))

(ns hello-re-natal.ios.core
  (:require [reagent.core :as r :refer [atom]]
            [re-frame.core :refer [subscribe dispatch dispatch-sync]]
            [hello-re-natal.events]
            [hello-re-natal.subs]
            [cljs.reader :as reader]))

(def read-string reader/read-string)
(def ReactNative (js/require "react-native"))

(def app-registry (.-AppRegistry ReactNative))
(def text (r/adapt-react-class (.-Text ReactNative)))
(def text-input (r/adapt-react-class (.-TextInput ReactNative)))
(def view (r/adapt-react-class (.-View ReactNative)))
(def image (r/adapt-react-class (.-Image ReactNative)))
(def touchable-highlight (r/adapt-react-class (.-TouchableHighlight ReactNative)))
(def pan-responder (r/adapt-react-class (.-PanResponder ReactNative)))
(def animated (r/adapt-react-class (.-Animated ReactNative)))
(def dimensions (r/adapt-react-class (.-Dimensions ReactNative)))

(def logo-img (js/require "./images/cljs.png"))

(defn alert [title]
  (.alert (.-Alert ReactNative) title))

(def pounds-per-kilogram 0.45359237)
(defn to-pounds
  [weight]
  (* weight pounds-per-kilogram))

(defn app-root []
  (let [greeting (subscribe [:get-greeting])
        state (atom "")]
    (fn []
      [view {:style {:flex-direction "column"
                     :margin 40}}
       [text {:style {:font-size 30
                      :font-weight "100"
                      :margin-bottom 20
                      :text-align "center"}} @greeting]
       [image {:source logo-img
               :style {:align-self "center"
                       :width 80
                       :height 80
                       :margin-bottom 30}}]
       [text-input {:style {:height 40}
                    :keyboard-type "numeric"
                    :on-change-text #(do
                                       (reset! state %)
                                       (r/flush))
                    :placeholder "Weight (in pounds)"
                    :return-key-type "done"
                    :value @state}]
       [touchable-highlight {:style {:background-color "#999"
                                     :padding 10
                                     :border-radius 5}
                             :on-press #(alert (str (to-pounds 135)))}
        [text {:style {:color "white" :text-align "center" :font-weight "bold"}} "press me"]]
       [view {:style {:background-color "#000"
                      `:height 80}}]
       [view {:style {:background-color "red"
                      `:height 80}}]
       [text {:style {:color "black"
                      :text-align "center"
                      :font-weight "bold"}}
                      (if (number? (read-string @state))
                          (str (to-pounds @state))
                          (str "Not a number" @state (rand 10))
                          )]])))

(defn init []
  (dispatch-sync [:initialize-db])
  (.registerComponent app-registry "HelloReNatal" #(r/reactify-component app-root)))

Add numeric keyboard and placeholder. Return key does not work yet.