Fetch team organization in a single batch

This commit is contained in:
Marina López 2026-08-07 13:55:57 +02:00
parent 9875db2f82
commit 87eb72977b
3 changed files with 105 additions and 15 deletions

View File

@ -259,6 +259,14 @@
(generate-nitrate-uri "api/teams/" team-id)
cto/schema:team-with-organization params))
(defn- get-teams-organizations-api
[cfg {:keys [team-ids] :as params}]
(let [params (assoc params :request-params {:team-ids team-ids})]
(request-to-nitrate cfg :post
(generate-nitrate-uri "api/teams/organizations")
[:vector cto/schema:team-with-organization]
params)))
(defn- get-organization-membership-api
[cfg {:keys [profile-id organization-id] :as params}]
(request-to-nitrate cfg :get
@ -489,6 +497,7 @@
[_ cfg]
(when (contains? cf/flags :admin-console)
{:get-team-organization (partial get-team-organization-api cfg)
:get-teams-organizations (partial get-teams-organizations-api cfg)
:set-team-organization (partial set-team-organization-api cfg)
:get-organization-membership (partial get-organization-membership-api cfg)
:get-organization-membership-by-team (partial get-organization-membership-by-team-api cfg)
@ -596,22 +605,25 @@
:cause cause)
profile)))))
(defn- apply-organization-info-to-team
[team team-with-organization]
(let [organization (:organization team-with-organization)]
(if (some? organization)
(-> (cto/apply-organization team (assoc organization :custom-photo
(when-let [logo-id (:logo-id organization)]
(generate-public-uri "assets/by-id/" logo-id))))
(assoc :is-default (or (:is-default team) (true? (:is-your-penpot team-with-organization)))))
team)))
(defn add-organization-info-to-team
"Enriches a team map with organization information from Nitrate.
Adds organization-id, organization-name, organization-slug, organization-owner-id, and your-penpot fields.
Returns the original team unchanged if the request fails or organization data is nil.
Propagates `:nitrate-unavailable` so the request is rejected when Nitrate is unreachable."
[cfg team params]
(try
(let [params (assoc (or params {}) :team-id (:id team))
team-with-organization (call cfg :get-team-organization params)
organization (:organization team-with-organization)]
(if (some? organization)
(-> (cto/apply-organization team (assoc organization :custom-photo
(when-let [logo-id (:logo-id organization)]
(generate-public-uri "assets/by-id/" logo-id))))
(assoc :is-default (or (:is-default team) (true? (:is-your-penpot team-with-organization)))))
team))
(let [params (assoc (or params {}) :team-id (:id team))
team-with-organization (call cfg :get-team-organization params)]
(apply-organization-info-to-team team team-with-organization))
(catch Throwable cause
(if (= :nitrate-unavailable (-> cause ex-data :type))
(throw cause)
@ -621,6 +633,23 @@
:cause cause)
team)))))
(defn add-organization-info-to-teams
"Enriches teams with organization information using one batched Nitrate request.
Teams absent from the Nitrate response are returned unchanged.
Rejects the request when Nitrate does not return a valid batch response."
[cfg teams params]
(let [request-params (assoc (or params {}) :team-ids (mapv :id teams))
teams-with-organization (call cfg :get-teams-organizations request-params)]
(when (nil? teams-with-organization)
(ex/raise :type :nitrate-unavailable
:hint "nitrate did not return a valid teams organization response"))
(let [organizations-by-team (into {} (map (juxt :id identity)) teams-with-organization)]
(mapv (fn [{:keys [id] :as team}]
(if-let [team-with-organization (get organizations-by-team id)]
(apply-organization-info-to-team team team-with-organization)
team))
teams))))
(defn set-team-organization
"Associates a team with an organization in Nitrate.
Requires organization-id and is-default in params.

View File

@ -196,11 +196,11 @@
::sm/params schema:get-teams}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
(dm/with-open [conn (db/open pool)]
(cond->> (get-teams conn profile-id)
(contains? cf/flags :admin-console)
(map #(nitrate/add-organization-info-to-team cfg % params))
(contains? cf/flags :admin-console)
(remove #(get-in % [:organization :expired-license])))))
(let [teams (get-teams conn profile-id)]
(if (contains? cf/flags :admin-console)
(->> (nitrate/add-organization-info-to-teams cfg teams params)
(remove #(get-in % [:organization :expired-license])))
teams))))
(def ^:private sql:get-owned-teams
"SELECT t.id, t.name,

View File

@ -719,6 +719,67 @@
(t/is (not= (:default-team-id profile1) (:id item1))))))
(t/deftest get-teams-fetches-organizations-in-one-batch
(let [profile (th/create-profile* 1 {:is-active true})
organization-team (th/create-team* 1 {:profile-id (:id profile)})
plain-team (th/create-team* 2 {:profile-id (:id profile)})
expired-team (th/create-team* 3 {:profile-id (:id profile)})
organization-id (uuid/random)
calls (atom [])
organization {:id organization-id
:name "Acme"
:slug "acme"
:owner-id (:id profile)
:avatar-bg-url "https://example.com/avatar.svg"}
nitrate-call (fn [_cfg method params]
(swap! calls conj [method params])
[{:id (:id organization-team)
:is-your-penpot false
:organization organization}
{:id (:id expired-team)
:is-your-penpot false
:organization (assoc organization :expired-license true)}])
params {::th/type :get-teams
::rpc/profile-id (:id profile)}]
(with-redefs [cf/flags (conj cf/flags :admin-console)
nitrate/call nitrate-call]
(let [out (th/command! params)
teams (:result out)]
(t/is (th/success? out))
(t/is (= 1 (count @calls)))
(t/is (= :get-teams-organizations (ffirst @calls)))
(t/is (= #{(:default-team-id profile)
(:id organization-team)
(:id plain-team)
(:id expired-team)}
(-> @calls first second :team-ids set)))
(t/is (= #{(:default-team-id profile)
(:id organization-team)
(:id plain-team)}
(into #{} (map :id) teams)))
(t/is (= organization
(->> teams
(filter #(= (:id organization-team) (:id %)))
first
:organization)))))))
(t/deftest get-teams-rejects-invalid-organization-batch-response
(let [profile (th/create-profile* 1 {:is-active true})
calls (atom [])
params {::th/type :get-teams
::rpc/profile-id (:id profile)}]
(with-redefs [cf/flags (conj cf/flags :admin-console)
nitrate/call (fn [_cfg method call-params]
(swap! calls conj [method call-params])
nil)]
(let [out (th/command! params)]
(t/is (not (th/success? out)))
(t/is (= :nitrate-unavailable (th/ex-type (:error out))))
(t/is (= 1 (count @calls)))
(t/is (= :get-teams-organizations (ffirst @calls)))))))
(t/deftest team-deletion-1
(let [profile1 (th/create-profile* 1 {:is-active true})
team (th/create-team* 1 {:profile-id (:id profile1)})