mirror of
https://github.com/penpot/penpot.git
synced 2025-01-09 00:10:11 -05:00
Merge pull request #5137 from penpot/niwinz-enhancements-1
✨ Add limits for invitation creation RPC method
This commit is contained in:
commit
ae7e28b71b
7 changed files with 68 additions and 30 deletions
|
@ -6,6 +6,7 @@
|
|||
|
||||
- Fix problem with Ctrl+F shortcut on the dashboard [Taiga #8876](https://tree.taiga.io/project/penpot/issue/8876)
|
||||
- Fix visual problem with the font-size dropdown in assets [Taiga #8872](https://tree.taiga.io/project/penpot/issue/8872)
|
||||
- Add limits for invitation RPC methods (hard limit 25 emails per request)
|
||||
|
||||
## 2.2.0
|
||||
|
||||
|
|
|
@ -908,6 +908,10 @@
|
|||
[:role schema:role]
|
||||
[:emails [::sm/set ::sm/email]]])
|
||||
|
||||
(def ^:private max-invitations-by-request-threshold
|
||||
"The number of invitations can be sent in a single rpc request"
|
||||
25)
|
||||
|
||||
(sv/defmethod ::create-team-invitations
|
||||
"A rpc call that allow to send a single or multiple invitations to
|
||||
join the team."
|
||||
|
@ -920,6 +924,12 @@
|
|||
team (db/get-by-id conn :team team-id)
|
||||
emails (into #{} (map profile/clean-email) emails)]
|
||||
|
||||
(when (> (count emails) max-invitations-by-request-threshold)
|
||||
(ex/raise :type :validation
|
||||
:code :max-invitations-by-request
|
||||
:hint "the maximum of invitation on single request is reached"
|
||||
:threshold max-invitations-by-request-threshold))
|
||||
|
||||
(run! (partial quotes/check-quote! conn)
|
||||
(list {::quotes/id ::quotes/invitations-per-team
|
||||
::quotes/profile-id profile-id
|
||||
|
@ -994,6 +1004,12 @@
|
|||
profile (db/get-by-id conn :profile profile-id)
|
||||
emails (into #{} (map profile/clean-email) emails)]
|
||||
|
||||
(when (> (count emails) max-invitations-by-request-threshold)
|
||||
(ex/raise :type :validation
|
||||
:code :max-invitations-by-request
|
||||
:hint "the maximum of invitation on single request is reached"
|
||||
:threshold max-invitations-by-request-threshold))
|
||||
|
||||
(let [props {:name name :features features}
|
||||
event (-> (audit/event-from-rpc-params params)
|
||||
(assoc ::audit/name "create-team")
|
||||
|
|
|
@ -448,7 +448,7 @@
|
|||
(defn parse-email
|
||||
[s]
|
||||
(if (string? s)
|
||||
(re-matches email-re s)
|
||||
(first (re-seq email-re s))
|
||||
nil))
|
||||
|
||||
(defn email-string?
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.spec :as us]
|
||||
[app.config :as cfg]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.events :as ev]
|
||||
|
@ -30,7 +29,6 @@
|
|||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
@ -129,17 +127,10 @@
|
|||
]
|
||||
(filterv identity)))
|
||||
|
||||
(s/def ::emails (s/and ::us/set-of-valid-emails d/not-empty?))
|
||||
(s/def ::role ::us/keyword)
|
||||
(s/def ::team-id ::us/uuid)
|
||||
|
||||
(s/def ::invite-member-form
|
||||
(s/keys :req-un [::role ::emails ::team-id]))
|
||||
|
||||
(def ^:private schema:invite-member-form
|
||||
[:map {:title "InviteMemberForm"}
|
||||
[:role :keyword]
|
||||
[:emails [::sm/set {:kind ::sm/email :min 1}]]
|
||||
[:emails [::sm/set {:min 1} ::sm/email]]
|
||||
[:team-id ::sm/uuid]])
|
||||
|
||||
(mf/defc invite-members-modal
|
||||
|
@ -181,6 +172,10 @@
|
|||
(st/emit! (ntf/error (tr "errors.profile-is-muted"))
|
||||
(modal/hide))
|
||||
|
||||
(and (= :validation type)
|
||||
(= :max-invitations-by-request code))
|
||||
(swap! error-text (tr "errors.maximum-invitations-by-request-reached" (:threshold error)))
|
||||
|
||||
(or (= :member-is-muted code)
|
||||
(= :email-has-permanent-bounces code)
|
||||
(= :email-has-complaints code))
|
||||
|
@ -226,10 +221,9 @@
|
|||
:name :emails
|
||||
:auto-focus? true
|
||||
:trim true
|
||||
:valid-item-fn us/parse-email
|
||||
:valid-item-fn sm/parse-email
|
||||
:caution-item-fn current-members-emails
|
||||
:label (tr "modals.invite-member.emails")
|
||||
:on-submit on-submit
|
||||
:invite-email invite-email}]]
|
||||
|
||||
[:div {:class (stl/css :action-buttons)}
|
||||
|
|
|
@ -11,11 +11,11 @@
|
|||
[app.common.schema :as sm]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.notifications.context-notification :refer [context-notification]]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[potok.v2.core :as ptk]
|
||||
|
@ -57,7 +57,7 @@
|
|||
(def ^:private schema:invite-form
|
||||
[:map {:title "InviteForm"}
|
||||
[:role :keyword]
|
||||
[:emails {:optional true} [::sm/set {:kind ::sm/email}]]])
|
||||
[:emails {:optional true} [::sm/set ::sm/email]]])
|
||||
|
||||
(defn- get-available-roles
|
||||
[]
|
||||
|
@ -67,17 +67,14 @@
|
|||
(mf/defc team-form-step-2
|
||||
{::mf/props :obj}
|
||||
[{:keys [name on-back go-to-team?]}]
|
||||
(let [initial (mf/use-memo
|
||||
#(do {:role "editor"
|
||||
:name name}))
|
||||
(let [initial (mf/with-memo []
|
||||
{:role "editor" :name name})
|
||||
|
||||
form (fm/use-form :schema schema:invite-form
|
||||
:initial initial)
|
||||
|
||||
params (:clean-data @form)
|
||||
emails (:emails params)
|
||||
|
||||
roles (mf/use-memo get-available-roles)
|
||||
error* (mf/use-state nil)
|
||||
|
||||
on-success
|
||||
(mf/use-fn
|
||||
|
@ -90,8 +87,24 @@
|
|||
|
||||
on-error
|
||||
(mf/use-fn
|
||||
(fn [_]
|
||||
(st/emit! (ntf/error (tr "errors.generic")))))
|
||||
(fn [cause]
|
||||
(let [{:keys [type code] :as error} (ex-data cause)]
|
||||
(cond
|
||||
(and (= :validation type)
|
||||
(= :profile-is-muted code))
|
||||
(swap! error* (tr "errors.profile-is-muted"))
|
||||
|
||||
(and (= :validation type)
|
||||
(= :max-invitations-by-request code))
|
||||
(swap! error* (tr "errors.maximum-invitations-by-request-reached" (:threshold error)))
|
||||
|
||||
(or (= :member-is-muted code)
|
||||
(= :email-has-permanent-bounces code)
|
||||
(= :email-has-complaints code))
|
||||
(swap! error* (tr "errors.email-spam-or-permanent-bounces" (:email error)))
|
||||
|
||||
:else
|
||||
(swap! error* (tr "errors.generic"))))))
|
||||
|
||||
on-invite-later
|
||||
(mf/use-fn
|
||||
|
@ -111,7 +124,7 @@
|
|||
|
||||
on-invite-now
|
||||
(mf/use-fn
|
||||
(fn [{:keys [name] :as params}]
|
||||
(fn [{:keys [name emails] :as params}]
|
||||
(let [mdata {:on-success on-success
|
||||
:on-error on-error}]
|
||||
|
||||
|
@ -143,6 +156,10 @@
|
|||
[:& fm/form {:form form
|
||||
:class (stl/css :modal-form-invitations)
|
||||
:on-submit on-submit}
|
||||
|
||||
(when-let [content (deref error*)]
|
||||
[:& context-notification {:content content :level :error}])
|
||||
|
||||
[:div {:class (stl/css :role-select)}
|
||||
[:p {:class (stl/css :role-title)} (tr "onboarding.choice.team-up.roles")]
|
||||
[:& fm/select {:name :role :options roles}]]
|
||||
|
@ -155,18 +172,22 @@
|
|||
:valid-item-fn sm/parse-email
|
||||
:caution-item-fn #{}
|
||||
:label (tr "modals.invite-member.emails")
|
||||
:on-submit on-submit}]]
|
||||
;; :on-submit on-submit
|
||||
}]]
|
||||
|
||||
[:div {:class (stl/css :action-buttons)}
|
||||
[:button {:class (stl/css :back-button)
|
||||
:on-click on-back}
|
||||
(tr "labels.back")]
|
||||
|
||||
[:> fm/submit-button*
|
||||
{:class (stl/css :accept-button)
|
||||
:label (if (> (count emails) 0)
|
||||
(tr "onboarding.choice.team-up.create-team-and-invite")
|
||||
(tr "onboarding.choice.team-up.create-team-without-invite"))}]]
|
||||
(let [params (:clean-data @form)
|
||||
emails (:emails params)]
|
||||
[:> fm/submit-button*
|
||||
{:class (stl/css :accept-button)
|
||||
:label (if (> (count emails) 0)
|
||||
(tr "onboarding.choice.team-up.create-team-and-invite")
|
||||
(tr "onboarding.choice.team-up.create-team-without-invite"))}])]
|
||||
|
||||
[:div {:class (stl/css :modal-hint)}
|
||||
"(" (tr "onboarding.choice.team-up.create-team-and-send-invites-description") ")"]]]
|
||||
|
||||
|
|
|
@ -923,6 +923,9 @@ msgstr "Are you sure?"
|
|||
msgid "errors.auth-provider-not-allowed"
|
||||
msgstr "Auth provider not allowed for this profile"
|
||||
|
||||
msgid "errors.maximum-invitations-by-request-reached"
|
||||
msgstr "The maximum (%s) number of emails that can be invited in a single request has been reached"
|
||||
|
||||
#: src/app/main/ui/auth/login.cljs:61
|
||||
msgid "errors.auth-provider-not-configured"
|
||||
msgstr "Authentication provider not configured."
|
||||
|
|
|
@ -6094,3 +6094,6 @@ msgstr "Actualizar"
|
|||
#, unused
|
||||
msgid "workspace.viewport.click-to-close-path"
|
||||
msgstr "Pulsar para cerrar la ruta"
|
||||
|
||||
msgid "errors.maximum-invitations-by-request-reached"
|
||||
msgstr "Se ha alcanzado el número máximo (%s) de correos electrónicos que se pueden invitar en una sola solicitud"
|
||||
|
|
Loading…
Reference in a new issue