mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
* 🐛 Fix incorrect status validation on subscription internal api * 🐛 Make the shortcuts overwritting optional
250 lines
8.1 KiB
Clojure
250 lines
8.1 KiB
Clojure
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
;;
|
|
;; Copyright (c) KALEIDOS INC
|
|
|
|
(ns app.srepl.cli
|
|
"PREPL API for external usage (CLI or ADMIN)"
|
|
(:require
|
|
[app.auth :as auth]
|
|
[app.common.exceptions :as ex]
|
|
[app.common.schema :as sm]
|
|
[app.common.uuid :as uuid]
|
|
[app.db :as db]
|
|
[app.rpc.commands.auth :as cmd.auth]
|
|
[app.rpc.commands.profile :as cmd.profile]
|
|
[app.setup :as-alias setup]
|
|
[app.tokens :as tokens]
|
|
[app.util.time :as dt]
|
|
[cuerdas.core :as str]))
|
|
|
|
(defn coercer
|
|
[schema & {:as opts}]
|
|
(let [decode-fn (sm/decoder schema sm/json-transformer)
|
|
check-fn (sm/check-fn schema opts)]
|
|
(fn [data]
|
|
(-> data decode-fn check-fn))))
|
|
|
|
(defn- get-current-system
|
|
[]
|
|
(or (deref (requiring-resolve 'app.main/system))
|
|
(deref (requiring-resolve 'user/system))))
|
|
|
|
(defmulti ^:private exec-command ::cmd)
|
|
|
|
(defmethod exec-command :default
|
|
[{:keys [::cmd]}]
|
|
(ex/raise :type :internal
|
|
:code :not-implemented
|
|
:hint (str/ffmt "command '%' not implemented" cmd)))
|
|
|
|
(defn exec
|
|
"Entry point with external tools integrations that uses PREPL
|
|
interface for interacting with running penpot backend."
|
|
[data]
|
|
(-> {::cmd (get data :cmd)}
|
|
(merge (:params data))
|
|
(exec-command)))
|
|
|
|
(defmethod exec-command "create-profile"
|
|
[{:keys [fullname email password is-active]
|
|
:or {is-active true}}]
|
|
(some-> (get-current-system)
|
|
(db/tx-run!
|
|
(fn [{:keys [::db/conn] :as system}]
|
|
(let [password (cmd.profile/derive-password system password)
|
|
params {:id (uuid/next)
|
|
:email email
|
|
:fullname fullname
|
|
:is-active is-active
|
|
:password password
|
|
:props {}}]
|
|
(->> (cmd.auth/create-profile! conn params)
|
|
(cmd.auth/create-profile-rels! conn)))))))
|
|
|
|
(defmethod exec-command "update-profile"
|
|
[{:keys [fullname email password is-active]}]
|
|
(some-> (get-current-system)
|
|
(db/tx-run!
|
|
(fn [{:keys [::db/conn] :as system}]
|
|
(let [params (cond-> {}
|
|
(some? fullname)
|
|
(assoc :fullname fullname)
|
|
|
|
(some? password)
|
|
(assoc :password (auth/derive-password password))
|
|
|
|
(some? is-active)
|
|
(assoc :is-active is-active))]
|
|
(when (seq params)
|
|
(let [res (db/update! conn :profile
|
|
params
|
|
{:email email
|
|
:deleted-at nil})]
|
|
(pos? (db/get-update-count res)))))))))
|
|
|
|
(defmethod exec-command "echo"
|
|
[params]
|
|
params)
|
|
|
|
|
|
(defmethod exec-command "delete-profile"
|
|
[{:keys [email soft]}]
|
|
(when-not email
|
|
(ex/raise :type :assertion
|
|
:code :invalid-arguments
|
|
:hint "email should be provided"))
|
|
|
|
(some-> (get-current-system)
|
|
(db/tx-run!
|
|
(fn [{:keys [::db/conn] :as system}]
|
|
(let [res (if soft
|
|
(db/update! conn :profile
|
|
{:deleted-at (dt/now)}
|
|
{:email email :deleted-at nil})
|
|
(db/delete! conn :profile
|
|
{:email email}))]
|
|
(pos? (db/get-update-count res)))))))
|
|
|
|
(defmethod exec-command "search-profile"
|
|
[{:keys [email]}]
|
|
(when-not email
|
|
(ex/raise :type :assertion
|
|
:code :invalid-arguments
|
|
:hint "email should be provided"))
|
|
|
|
(some-> (get-current-system)
|
|
(db/tx-run!
|
|
(fn [{:keys [::db/conn] :as system}]
|
|
(let [sql (str "select email, fullname, created_at, deleted_at from profile "
|
|
" where email similar to ? order by created_at desc limit 100")]
|
|
(db/exec! conn [sql email]))))))
|
|
|
|
(defmethod exec-command "derive-password"
|
|
[{:keys [password]}]
|
|
(auth/derive-password password))
|
|
|
|
(defmethod exec-command "authenticate"
|
|
[{:keys [token]}]
|
|
(when-let [system (get-current-system)]
|
|
(let [props (get system ::setup/props)]
|
|
(tokens/verify props {:token token :iss "authentication"}))))
|
|
|
|
(def ^:private schema:get-customer
|
|
[:map [:id ::sm/uuid]])
|
|
|
|
(def coerce-get-customer-params
|
|
(coercer schema:get-customer
|
|
:type :validation
|
|
:hint "invalid data provided for `get-customer` rpc call"))
|
|
|
|
(def sql:get-customer-slots
|
|
"WITH teams AS (
|
|
SELECT tpr.team_id AS id,
|
|
tpr.profile_id AS profile_id
|
|
FROM team_profile_rel AS tpr
|
|
WHERE tpr.is_owner IS true
|
|
AND tpr.profile_id = ?
|
|
), teams_with_slots AS (
|
|
SELECT tpr.team_id AS id,
|
|
count(*) AS total
|
|
FROM team_profile_rel AS tpr
|
|
WHERE tpr.team_id IN (SELECT id FROM teams)
|
|
AND tpr.can_edit IS true
|
|
GROUP BY 1
|
|
ORDER BY 2
|
|
)
|
|
SELECT max(total) AS total FROM teams_with_slots;")
|
|
|
|
(defn- get-customer-slots
|
|
[system profile-id]
|
|
(let [result (db/exec-one! system [sql:get-customer-slots profile-id])]
|
|
(:total result)))
|
|
|
|
(defmethod exec-command "get-customer"
|
|
[params]
|
|
(when-let [system (get-current-system)]
|
|
(let [{:keys [id] :as params} (coerce-get-customer-params params)
|
|
{:keys [props] :as profile} (cmd.profile/get-profile system id)]
|
|
{:id (get profile :id)
|
|
:name (get profile :fullname)
|
|
:email (get profile :email)
|
|
:num-editors (get-customer-slots system id)
|
|
:subscription (get props :subscription)})))
|
|
|
|
(def ^:private schema:customer-subscription
|
|
[:map {:title "CustomerSubscription"}
|
|
[:id ::sm/text]
|
|
[:customer-id ::sm/text]
|
|
[:type [:enum
|
|
"unlimited"
|
|
"professional"
|
|
"enterprise"]]
|
|
[:status [:enum
|
|
"active"
|
|
"canceled"
|
|
"incomplete"
|
|
"incomplete_expired"
|
|
"past_due"
|
|
"paused"
|
|
"trialing"
|
|
"unpaid"]]
|
|
|
|
[:billing-period [:enum
|
|
"month"
|
|
"day"
|
|
"week"
|
|
"year"]]
|
|
[:quantity :int]
|
|
[:description [:maybe ::sm/text]]
|
|
[:created-at ::sm/timestamp]
|
|
[:start-date [:maybe ::sm/timestamp]]
|
|
[:ended-at [:maybe ::sm/timestamp]]
|
|
[:trial-end [:maybe ::sm/timestamp]]
|
|
[:trial-start [:maybe ::sm/timestamp]]
|
|
[:cancel-at [:maybe ::sm/timestamp]]
|
|
[:canceled-at [:maybe ::sm/timestamp]]
|
|
[:current-period-end [:maybe ::sm/timestamp]]
|
|
[:current-period-start [:maybe ::sm/timestamp]]
|
|
[:cancel-at-period-end :boolean]
|
|
|
|
[:cancellation-details
|
|
[:map {:title "CancellationDetails"}
|
|
[:comment [:maybe ::sm/text]]
|
|
[:reason [:maybe ::sm/text]]
|
|
[:feedback [:maybe
|
|
[:enum
|
|
"customer_service"
|
|
"low_quality"
|
|
"missing_feature"
|
|
"other"
|
|
"switched_service"
|
|
"too_complex"
|
|
"too_expensive"
|
|
"unused"]]]]]])
|
|
|
|
(def ^:private schema:update-customer-subscription
|
|
[:map
|
|
[:id ::sm/uuid]
|
|
[:subscription [:maybe schema:customer-subscription]]])
|
|
|
|
(def coerce-update-customer-subscription-params
|
|
(coercer schema:update-customer-subscription
|
|
:type :validation
|
|
:hint "invalid data provided for `update-customer-subscription` rpc call"))
|
|
|
|
(defmethod exec-command "update-customer-subscription"
|
|
[params]
|
|
(when-let [system (get-current-system)]
|
|
(let [{:keys [id subscription]} (coerce-update-customer-subscription-params params)
|
|
;; FIXME: locking
|
|
{:keys [props] :as profile} (cmd.profile/get-profile system id)
|
|
props (assoc props :subscription subscription)]
|
|
|
|
(db/update! system :profile
|
|
{:props (db/tjson props)}
|
|
{:id id}
|
|
{::db/return-keys false})
|
|
true)))
|