🎉 Add the ability to copy team invitation link

This commit is contained in:
Andrey Antukh 2022-12-13 15:29:43 +01:00
parent 7d2e3a0864
commit 842463ed1b
9 changed files with 262 additions and 135 deletions

View file

@ -322,7 +322,7 @@
::http.client/client (ig/ref ::http.client/client)
::db/pool (ig/ref ::db/pool)
::wrk/executor (ig/ref ::wrk/executor)
::props (ig/ref :app.setup/props)
:pool (ig/ref ::db/pool)
:session (ig/ref :app.http.session/manager)
:sprops (ig/ref :app.setup/props)

View file

@ -15,6 +15,7 @@
[app.db :as db]
[app.emails :as eml]
[app.loggers.audit :as audit]
[app.main :as-alias main]
[app.media :as media]
[app.rpc.climit :as climit]
[app.rpc.doc :as-alias doc]
@ -260,7 +261,7 @@
(def sql:team-invitations
"select email_to as email, role, (valid_until < now()) as expired
from team_invitation where team_id = ? order by valid_until desc")
from team_invitation where team_id = ? order by valid_until desc, created_at desc")
(defn get-team-invitations
[conn team-id]
@ -628,25 +629,37 @@
"insert into team_invitation(team_id, email_to, role, valid_until)
values (?, ?, ?, ?)
on conflict(team_id, email_to) do
update set role = ?, valid_until = ?, updated_at = now();")
update set role = ?, updated_at = now();")
(defn- create-invitation-token
[cfg {:keys [expire profile-id team-id member-id member-email role]}]
(tokens/generate (::main/props cfg)
{:iss :team-invitation
:exp expire
:profile-id profile-id
:role role
:team-id team-id
:member-email member-email
:member-id member-id}))
(defn- create-profile-identity-token
[cfg profile]
(tokens/generate (::main/props cfg)
{:iss :profile-identity
:profile-id (:id profile)
:exp (dt/in-future {:days 30})}))
(defn- create-invitation
[{:keys [conn sprops team profile role email] :as cfg}]
(let [member (profile/retrieve-profile-data-by-email conn email)
token-exp (dt/in-future "168h") ;; 7 days
email (str/lower email)
itoken (tokens/generate sprops
{:iss :team-invitation
:exp token-exp
:profile-id (:id profile)
:role role
:team-id (:id team)
:member-email (:email member email)
:member-id (:id member)})
ptoken (tokens/generate sprops
{:iss :profile-identity
:profile-id (:id profile)
:exp (dt/in-future {:days 30})})]
[{:keys [::conn] :as cfg} {:keys [team profile role email] :as params}]
(let [member (profile/retrieve-profile-data-by-email conn email)
expire (dt/in-future "168h") ;; 7 days
itoken (create-invitation-token cfg {:profile-id (:id profile)
:expire expire
:team-id (:id team)
:member-email (or (:email member) email)
:member-id (:id member)
:role role})
ptoken (create-profile-identity-token cfg profile)]
(when (and member (not (eml/allow-send-emails? conn member)))
(ex/raise :type :validation
@ -687,11 +700,10 @@
{:id (:id member)})))
(do
(db/exec-one! conn [sql:upsert-team-invitation
(:id team) (str/lower email) (name role)
token-exp (name role) token-exp])
(:id team) (str/lower email) (name role) expire (name role)])
(eml/send! {::eml/conn conn
::eml/factory eml/invite-to-team
:public-uri (:public-uri cfg)
:public-uri (cf/get :public-uri)
:to email
:invited-by (:fullname profile)
:team (:name team)
@ -727,15 +739,14 @@
:code :profile-is-muted
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces"))
(let [invitations (->> emails
(let [cfg (assoc cfg ::conn conn)
invitations (->> emails
(map (fn [email]
(assoc cfg
:email email
:conn conn
:team team
:profile profile
:role role)))
(map create-invitation))]
{:email (str/lower email)
:team team
:profile profile
:role role}))
(map (partial create-invitation cfg)))]
(with-meta (vec invitations)
{::audit/props {:invitations (count invitations)}})))))
@ -743,26 +754,26 @@
;; --- Mutation: Create Team & Invite Members
(s/def ::emails ::us/set-of-valid-emails)
(s/def ::create-team-and-invitations
(s/def ::create-team-with-invitations
(s/merge ::create-team
(s/keys :req-un [::emails ::role])))
(sv/defmethod ::create-team-and-invitations
(sv/defmethod ::create-team-with-invitations
{::doc/added "1.17"}
[{:keys [pool] :as cfg} {:keys [profile-id emails role] :as params}]
(db/with-atomic [conn pool]
(let [team (create-team conn params)
profile (db/get-by-id conn :profile profile-id)]
profile (db/get-by-id conn :profile profile-id)
cfg (assoc cfg ::conn conn)]
;; Create invitations for all provided emails.
(doseq [email emails]
(create-invitation
(assoc cfg
:conn conn
:team team
:profile profile
:email email
:role role)))
(->> emails
(map (fn [email]
{:team team
:profile profile
:email (str/lower email)
:role role}))
(run! (partial create-invitation cfg)))
(-> team
(vary-meta assoc ::audit/props {:invitations (count emails)})
@ -777,6 +788,28 @@
:profile-id profile-id
:invitations (count emails)}})))))))
;; --- Query: get-team-invitation-token
(s/def ::get-team-invitation-token
(s/keys :req-un [::profile-id ::team-id ::email]))
(sv/defmethod ::get-team-invitation-token
{::doc/added "1.17"}
[{:keys [::db/pool] :as cfg} {:keys [profile-id team-id email] :as params}]
(check-read-permissions! pool profile-id team-id)
(let [invit (-> (db/get pool :team-invitation
{:team-id team-id
:email-to (str/lower email)})
(update :role keyword))
member (profile/retrieve-profile-data-by-email pool (:email invit))
token (create-invitation-token cfg {:team-id (:team-id invit)
:profile-id profile-id
:expire (:expire invit)
:role (:role invit)
:member-id (:id member)
:member-email (or (:email member) (:email-to invit))})]
{:token token}))
;; --- Mutation: Update invitation role
(s/def ::update-team-invitation-role

View file

@ -129,7 +129,7 @@
[{:keys [conn session] :as cfg} {:keys [profile-id token]}
{:keys [member-id team-id member-email] :as claims}]
(us/assert ::team-invitation-claims claims)
(us/verify! ::team-invitation-claims claims)
(let [invitation (db/get* conn :team-invitation
{:team-id team-id :email-to member-email})

View file

@ -153,21 +153,20 @@
:code :profile-is-muted
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces"))
(let [invitations (->> emails
(let [cfg (assoc cfg ::cmd.teams/conn conn)
invitations (->> emails
(map (fn [email]
(assoc cfg
:email email
:conn conn
:team team
:profile profile
:role role)))
(map #'cmd.teams/create-invitation))]
{:email (str/lower email)
:team team
:profile profile
:role role}))
(map (partial #'cmd.teams/create-invitation cfg)))]
(with-meta (vec invitations)
{::audit/props {:invitations (count invitations)}})))))
;; --- Mutation: Create Team & Invite Members
(s/def ::create-team-and-invite-members ::cmd.teams/create-team-and-invitations)
(s/def ::create-team-and-invite-members ::cmd.teams/create-team-with-invitations)
(sv/defmethod ::create-team-and-invite-members
{::doc/added "1.0"
@ -175,17 +174,17 @@
[{:keys [::db/pool] :as cfg} {:keys [profile-id emails role] :as params}]
(db/with-atomic [conn pool]
(let [team (cmd.teams/create-team conn params)
profile (db/get-by-id conn :profile profile-id)]
profile (db/get-by-id conn :profile profile-id)
cfg (assoc cfg ::cmd.teams/conn conn)]
;; Create invitations for all provided emails.
(doseq [email emails]
(#'cmd.teams/create-invitation
(assoc cfg
:conn conn
:team team
:profile profile
:email email
:role role)))
(->> emails
(map (fn [email]
{:team team
:profile profile
:email (str/lower email)
:role role}))
(run! (partial #'cmd.teams/create-invitation cfg)))
(-> team
(vary-meta assoc ::audit/props {:invitations (count emails)})