Review nitrate add team members permission

This commit is contained in:
Pablo Alba 2026-06-03 19:09:49 +02:00 committed by Pablo Alba
parent 03c02d5adf
commit 2a48747cf6
3 changed files with 113 additions and 22 deletions

View File

@ -710,7 +710,8 @@ LEFT JOIN profile AS p
:organizations organizations}))))
nil)
;; API: cleanup-org-team-invitations
;; API: exists-org-team-invitations-for-non-members /
;; delete-org-team-invitations-for-non-members
(def ^:private sql:get-profile-emails-by-ids
"SELECT email
@ -718,35 +719,68 @@ LEFT JOIN profile AS p
WHERE id = ANY(?)
AND deleted_at IS NULL")
(def ^:private sql:delete-orphaned-team-invitations
(def ^:private sql:exists-non-member-org-team-invitations
"SELECT EXISTS (
SELECT 1
FROM team_invitation
WHERE team_id = ANY(?)
AND email_to <> ALL(?)
) AS non_member")
(def ^:private sql:delete-non-member-org-team-invitations
"DELETE FROM team_invitation
WHERE team_id = ANY(?)
AND email_to <> ALL(?)
RETURNING email_to")
(def ^:private schema:cleanup-org-team-invitations-params
(def ^:private schema:org-team-invitations-for-non-members-params
[:map
[:team-ids [:vector ::sm/uuid]]
[:member-ids [:vector ::sm/uuid]]])
(sv/defmethod ::cleanup-org-team-invitations
"Delete team invitations for emails that are not organization members"
(def ^:private schema:exists-org-team-invitations-for-non-members-result
[:map [:exists ::sm/boolean]])
(defn- org-team-invitations-for-non-members-arrays
"Member emails and PG arrays used by exists/delete org team invitation endpoints."
[conn {:keys [team-ids member-ids]}]
(let [member-ids-array (db/create-array conn "uuid" member-ids)
member-emails (->> (db/exec! conn [sql:get-profile-emails-by-ids member-ids-array])
(map :email)
(into #{}))]
{:emails-array (db/create-array conn "text" (vec member-emails))
:teams-array (db/create-array conn "uuid" team-ids)}))
(defn- non-member-org-team-invitations-exist?
[conn params]
(let [{:keys [emails-array teams-array]}
(org-team-invitations-for-non-members-arrays conn params)]
(-> (db/exec-one! conn [sql:exists-non-member-org-team-invitations
teams-array
emails-array])
:non-member)))
(sv/defmethod ::exists-org-team-invitations-for-non-members
"Return if there are any team invitations for emails that are not organization members."
{::doc/added "2.18"
::sm/params schema:cleanup-org-team-invitations-params
::db/transaction true}
[cfg {:keys [team-ids member-ids]}]
::sm/params schema:org-team-invitations-for-non-members-params
::sm/result schema:exists-org-team-invitations-for-non-members-result}
[cfg params]
(db/run! cfg (fn [{:keys [::db/conn]}]
(let [;; Get emails of organization members
member-ids-array (db/create-array conn "uuid" member-ids)
member-emails (->> (db/exec! conn [sql:get-profile-emails-by-ids member-ids-array])
(map :email)
(into #{}))
{:exists (boolean (non-member-org-team-invitations-exist? conn params))})))
emails-array (db/create-array conn "text" (vec member-emails))
teams-array (db/create-array conn "uuid" team-ids)]
;; Delete invitations that are not in the keep list
(db/exec! conn [sql:delete-orphaned-team-invitations teams-array emails-array])
(sv/defmethod ::delete-org-team-invitations-for-non-members
"Delete team invitations for emails that are not organization members."
{::doc/added "2.18"
::sm/params schema:org-team-invitations-for-non-members-params
::db/transaction true}
[cfg params]
(db/run! cfg (fn [{:keys [::db/conn]}]
(let [{:keys [emails-array teams-array]}
(org-team-invitations-for-non-members-arrays conn params)]
(db/exec! conn [sql:delete-non-member-org-team-invitations
teams-array
emails-array])
nil))))
;; ---- API: push-audit-events

View File

@ -684,14 +684,72 @@
(t/is (nil? (:result out)))
(t/is (empty? remaining)))))
(t/deftest cleanup-org-team-invitations-removes-orphaned-invitations
(t/deftest exists-org-team-invitations-for-non-members-reports-invitations-to-delete
(let [member1 (th/create-profile* 1 {:is-active true :email "member1@example.com"})
profile (th/create-profile* 4 {:is-active true})
team-1 (th/create-team* 1 {:profile-id (:id profile)})
team-2 (th/create-team* 2 {:profile-id (:id profile)})
outside-team (th/create-team* 3 {:profile-id (:id profile)})
org-id (uuid/random)
base-params {::th/type :exists-org-team-invitations-for-non-members
::rpc/profile-id (:id profile)
:organization-id org-id
:team-ids [(:id team-1) (:id team-2)]
:member-ids [(:id member1)]}
exist! (fn [] (-> (management-command-with-nitrate! base-params)
:result
:exists))]
(t/is (false? (exist!)))
(th/db-insert! :team-invitation
{:id (uuid/random)
:team-id (:id team-1)
:org-id nil
:email-to "member1@example.com"
:created-by (:id profile)
:role "editor"
:valid-until (ct/in-future "24h")})
(t/is (false? (exist!)))
(th/db-insert! :team-invitation
{:id (uuid/random)
:org-id org-id
:team-id nil
:email-to "pending@example.com"
:created-by (:id profile)
:role "editor"
:valid-until (ct/in-future "24h")})
(t/is (false? (exist!)))
(th/db-insert! :team-invitation
{:id (uuid/random)
:team-id (:id outside-team)
:org-id nil
:email-to "outsider@example.com"
:created-by (:id profile)
:role "editor"
:valid-until (ct/in-future "24h")})
(t/is (false? (exist!)))
(th/db-insert! :team-invitation
{:id (uuid/random)
:team-id (:id team-2)
:org-id nil
:email-to "orphan@example.com"
:created-by (:id profile)
:role "editor"
:valid-until (ct/in-future "24h")})
(t/is (true? (exist!)))))
(t/deftest delete-org-team-invitations-for-non-members-removes-non-member-invitations
(let [member1 (th/create-profile* 1 {:is-active true :email "member1@example.com"})
profile (th/create-profile* 4 {:is-active true})
team-1 (th/create-team* 1 {:profile-id (:id profile)})
team-2 (th/create-team* 2 {:profile-id (:id profile)})
outside-team (th/create-team* 3 {:profile-id (:id profile)})
org-id (uuid/random)
params {::th/type :cleanup-org-team-invitations
params {::th/type :delete-org-team-invitations-for-non-members
::rpc/profile-id (:id profile)
:organization-id org-id
:team-ids [(:id team-1) (:id team-2)]

View File

@ -973,7 +973,7 @@
.modal-select-org-body {
display: flex;
flex-direction: column;
gap: var(--sp-xxl);
gap: var(--sp-s);
}
.modal-select-org-warning {
@ -998,7 +998,6 @@
@include t.use-typography("headline-large");
color: var(--color-foreground-primary);
height: $sz-40;
}
.modal-select-org-text {