mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
wip
This commit is contained in:
@@ -17,9 +17,11 @@
|
||||
[app.db :as db]
|
||||
[app.http.access-token :refer [get-token]]
|
||||
[app.main :as-alias main]
|
||||
[app.msgbus :as mbus]
|
||||
[app.setup :as-alias setup]
|
||||
[app.tokens :as tokens]
|
||||
[app.worker :as-alias wrk]
|
||||
[clojure.walk :as walk]
|
||||
[integrant.core :as ig]
|
||||
[yetti.response :as-alias yres]))
|
||||
|
||||
@@ -29,6 +31,10 @@
|
||||
(declare ^:private get-organization)
|
||||
(declare ^:private create-organization)
|
||||
(declare ^:private update-organization)
|
||||
(declare ^:private list-organizations)
|
||||
(declare ^:private list-teams)
|
||||
(declare ^:private set-team-org)
|
||||
|
||||
|
||||
(defmethod ig/assert-key ::routes
|
||||
[_ params]
|
||||
@@ -44,9 +50,9 @@
|
||||
(let [token (get-token request)]
|
||||
(if (= token shared-key)
|
||||
(handler request)
|
||||
{::yres/status 403})))
|
||||
{::yres/status 401})))
|
||||
(fn [_ _]
|
||||
{::yres/status 403}))))})
|
||||
{::yres/status 401}))))})
|
||||
|
||||
(def ^:private default-system
|
||||
{:name ::default-system
|
||||
@@ -74,6 +80,21 @@
|
||||
{:handler authenticate
|
||||
:allowed-methods #{:post}}]
|
||||
|
||||
["/list-organizations"
|
||||
{:handler list-organizations
|
||||
:allowed-methods #{:post}
|
||||
:transaction true}]
|
||||
|
||||
["/list-teams"
|
||||
{:handler list-teams
|
||||
:allowed-methods #{:post}
|
||||
:transaction true}]
|
||||
|
||||
["/set-team-org"
|
||||
{:handler set-team-org
|
||||
:allowed-methods #{:post}
|
||||
:transaction true}]
|
||||
|
||||
["/get-organization"
|
||||
{:handler get-organization
|
||||
:allowed-methods #{:post}
|
||||
@@ -108,7 +129,7 @@
|
||||
|
||||
(defn with-current-user
|
||||
"Wraps a handler to inject the current user ID if the provided token is valid.
|
||||
Returns 403 if no valid user is found or token verification fails.
|
||||
Returns 401 if no valid user is found or token verification fails.
|
||||
|
||||
@param handler - function of [cfg request current-user-id]
|
||||
@return handler of [cfg request]"
|
||||
@@ -119,18 +140,31 @@
|
||||
uid (:uid auth)]
|
||||
(if uid
|
||||
(handler cfg request uid)
|
||||
{::yres/status 403}))))
|
||||
{::yres/status 401}))))
|
||||
|
||||
(def ^:private sql:get-role
|
||||
"SELECT role
|
||||
FROM organization_profile_rel
|
||||
WHERE organization_id=?
|
||||
and profile_id=?;")
|
||||
WHERE organization_id = ?
|
||||
and profile_id = ?;")
|
||||
|
||||
(defn- get-role
|
||||
[cfg organization_id profile-id]
|
||||
(let [result (db/exec-one! cfg [sql:get-role organization_id profile-id])]
|
||||
(:role result)))
|
||||
(defn- is-org-owner?
|
||||
[cfg organization-id current-user-id]
|
||||
(let [result (db/exec-one! cfg [sql:get-role organization-id current-user-id])]
|
||||
(= "owner" (:role result))))
|
||||
|
||||
(def ^:private sql:is-team-owner
|
||||
"SELECT COUNT(*) AS n
|
||||
FROM team_profile_rel
|
||||
WHERE team_id = ?
|
||||
AND profile_id = ?
|
||||
AND is_owner = true;")
|
||||
|
||||
(defn- is-team-owner?
|
||||
[cfg team-id current-user-id]
|
||||
(-> (db/exec-one! cfg [sql:is-team-owner team-id current-user-id])
|
||||
:n
|
||||
pos?))
|
||||
|
||||
;; ---- API: AUTHENTICATE
|
||||
|
||||
@@ -143,7 +177,7 @@
|
||||
token (string): The access token to validate.
|
||||
@returns
|
||||
200 OK: Returns decoded token claims if valid.
|
||||
403 Forbidden: If the shared key or token is invalid."
|
||||
401 Unauthorized: If the shared key or token is invalid."
|
||||
[cfg request]
|
||||
(let [token (-> request :params :token)
|
||||
result (tokens/verify cfg {:token token :iss "authentication"})]
|
||||
@@ -170,12 +204,40 @@
|
||||
@returns
|
||||
200 OK: Returns the organization record.
|
||||
400 Bad Request: Invalid input data.
|
||||
403 Forbidden: If the shared key or user token is invalid.
|
||||
401 Unauthorized: If the shared key or user token is invalid.
|
||||
404 Not Found: Organization not found."
|
||||
(with-current-user
|
||||
(fn [cfg request _]
|
||||
(let [organization-id (-> request :params coerce-get-organization-params :id)
|
||||
result (db/get-by-id cfg :organization organization-id)]
|
||||
result (-> (db/get-by-id cfg :organization organization-id)
|
||||
(walk/stringify-keys))]
|
||||
{::yres/status 200
|
||||
::yres/body result}))))
|
||||
|
||||
;; ---- API: LIST-ORGANIZATIONS
|
||||
|
||||
(def ^:private sql:list-organizations
|
||||
"SELECT o.*
|
||||
FROM organization AS o
|
||||
JOIN organization_profile_rel AS opr ON o.id = opr.organization_id
|
||||
WHERE opr.profile_id = ?
|
||||
AND opr.role = 'owner';")
|
||||
|
||||
|
||||
(def list-organizations
|
||||
"List organizations for which current user is owner.
|
||||
|
||||
@api POST /list-organizations
|
||||
@auth SharedKey
|
||||
@returns
|
||||
200 OK: Returns the list of organizations for the user.
|
||||
401 Unauthorized: If the shared key or user token is invalid."
|
||||
(with-current-user
|
||||
(fn [cfg _request current-user-id]
|
||||
(let [result (->> (db/exec! cfg [sql:list-organizations current-user-id])
|
||||
(map #(update % :id str))
|
||||
vec
|
||||
walk/stringify-keys)]
|
||||
{::yres/status 200
|
||||
::yres/body result}))))
|
||||
|
||||
@@ -201,7 +263,8 @@
|
||||
name (string, max 250): Name of the organization.
|
||||
@returns
|
||||
201 Created: Returns the newly created organization.
|
||||
400 Bad Request: Invalid data."
|
||||
400 Bad Request: Invalid data.
|
||||
401 Unauthorized: If the shared key or user token is invalid."
|
||||
(with-current-user
|
||||
(fn [cfg request current-user-id]
|
||||
(let [{:keys [name]}
|
||||
@@ -236,31 +299,112 @@
|
||||
id (uuid): Organization identifier.
|
||||
name (string, max 250): New organization name.
|
||||
@returns
|
||||
201 Updated: Operation successful.
|
||||
204 Updated: Operation successful.
|
||||
400 Bad Request: Invalid input data.
|
||||
401 Unauthorized: The user is not authenticated (missing or invalid credentials).
|
||||
403 Forbidden: Insufficient permissions."
|
||||
401 Unauthorized: If the shared key or user token is invalid.
|
||||
403 Forbidden: The user doesn't have permissions to execute this operation."
|
||||
(with-current-user
|
||||
(fn [cfg request current-user-id]
|
||||
(let [{:keys [id name]}
|
||||
(-> request :params coerce-update-organization-params)
|
||||
org-owner? (is-org-owner? cfg id current-user-id)]
|
||||
|
||||
organization (db/get-by-id cfg :organization id)
|
||||
|
||||
role (when organization (get-role cfg id current-user-id))]
|
||||
|
||||
(if (= (keyword role) :owner)
|
||||
(if org-owner?
|
||||
(do
|
||||
(l/dbg :hint "update organization"
|
||||
:id (str id)
|
||||
:name name)
|
||||
|
||||
(db/update! cfg :organization
|
||||
{:name name}
|
||||
{:id id}
|
||||
{::db/return-keys false})
|
||||
|
||||
{::yres/status 201
|
||||
{::yres/status 204
|
||||
::yres/body nil})
|
||||
{::yres/status 403
|
||||
::yres/body nil})))))
|
||||
|
||||
;; ---- API: LIST-TEAMS
|
||||
|
||||
(def ^:private sql:list-teams
|
||||
"SELECT t.*
|
||||
FROM team AS t
|
||||
JOIN team_profile_rel AS tpr ON t.id = tpr.team_id
|
||||
WHERE tpr.profile_id = ?
|
||||
AND tpr.is_owner = 't'
|
||||
AND t.is_default = 'f';")
|
||||
|
||||
|
||||
(def list-teams
|
||||
"List teams for which current user is owner.
|
||||
|
||||
@api POST /list-teams
|
||||
@auth SharedKey
|
||||
@returns
|
||||
200 OK: Returns the list of teams for the user.
|
||||
401 Unauthorized: If the shared key or user token is invalid."
|
||||
(with-current-user
|
||||
(fn [cfg _request current-user-id]
|
||||
(let [result (->> (db/exec! cfg [sql:list-teams current-user-id])
|
||||
(map #(dissoc % :features :subscription :is-default))
|
||||
(map #(update % :organization-id str))
|
||||
(map #(update % :id str))
|
||||
vec
|
||||
walk/stringify-keys)]
|
||||
{::yres/status 200
|
||||
::yres/body result}))))
|
||||
|
||||
|
||||
;; ---- API: SET-TEAM-ORG
|
||||
|
||||
(def ^:private schema:set-team-org
|
||||
[:map
|
||||
[:team-id ::sm/uuid]
|
||||
[:organization-id {:optional true} [:maybe ::sm/uuid]]])
|
||||
|
||||
(def ^:private coerce-set-team-org-params
|
||||
(coercer schema:set-team-org
|
||||
:type :validation
|
||||
:hint "invalid data provided for `set-team-org` rpc call"))
|
||||
|
||||
(def ^:private sql:set-team-org
|
||||
"UPDATE team
|
||||
SET organization_id = ?
|
||||
WHERE team.id = ?;")
|
||||
|
||||
|
||||
(def set-team-org
|
||||
"Set the organization of a team.
|
||||
|
||||
@api POST /set-team-org
|
||||
@auth SharedKey
|
||||
@params
|
||||
team-id (uuid): Team identifier.
|
||||
organization-id (uuid | null): Organization identifier, or null to remove association.
|
||||
@returns
|
||||
204 Updated: Operation successful.
|
||||
401 Unauthorized: If the shared key or user token is invalid.
|
||||
403 Forbidden: The user doesn't have permissions to execute this operation."
|
||||
(with-current-user
|
||||
(fn [cfg request current-user-id]
|
||||
(let [{:keys [organization-id team-id]}
|
||||
(-> request :params coerce-set-team-org-params)
|
||||
org-owner? (if (nil? organization-id)
|
||||
true
|
||||
(is-org-owner? cfg organization-id current-user-id))
|
||||
|
||||
team-owner? (is-team-owner? cfg team-id current-user-id)
|
||||
organization (when (and team-owner? org-owner?) (db/get-by-id cfg :organization organization-id))
|
||||
msgbus (::mbus/msgbus cfg)]
|
||||
|
||||
|
||||
(if (or
|
||||
(not team-owner?)
|
||||
(not org-owner?))
|
||||
{::yres/status 403}
|
||||
(do
|
||||
(db/exec! cfg [sql:set-team-org organization-id team-id])
|
||||
(mbus/pub! msgbus
|
||||
:topic uuid/zero #_team-id
|
||||
:message {:type :team-org-change
|
||||
:team-id team-id
|
||||
:organization-id organization-id
|
||||
:organization-name (:name organization)})
|
||||
{::yres/status 204}))))))
|
||||
@@ -279,7 +279,8 @@
|
||||
|
||||
::nitrate/routes
|
||||
{::db/pool (ig/ref ::db/pool)
|
||||
::setup/props (ig/ref ::setup/props)}
|
||||
::setup/props (ig/ref ::setup/props)
|
||||
::mbus/msgbus (ig/ref ::mbus/msgbus)}
|
||||
|
||||
:app.http/router
|
||||
{::session/manager (ig/ref ::session/manager)
|
||||
|
||||
@@ -121,9 +121,11 @@
|
||||
tp.is_owner,
|
||||
tp.is_admin,
|
||||
tp.can_edit,
|
||||
(t.id = ?) AS is_default
|
||||
(t.id = ?) AS is_default,
|
||||
o.name AS organization_name
|
||||
FROM team_profile_rel AS tp
|
||||
JOIN team AS t ON (t.id = tp.team_id)
|
||||
LEFT JOIN organization AS o ON o.id = t.organization_id
|
||||
WHERE t.deleted_at IS null
|
||||
AND tp.profile_id = ?
|
||||
ORDER BY tp.created_at ASC")
|
||||
@@ -134,6 +136,7 @@
|
||||
tp.is_admin,
|
||||
tp.can_edit,
|
||||
(t.id = ?) AS is_default,
|
||||
o.name AS organization_name,
|
||||
|
||||
jsonb_build_object(
|
||||
'~:type', COALESCE(p.props->'~:subscription'->>'~:type', 'professional'),
|
||||
@@ -149,6 +152,7 @@
|
||||
ON (tpr.team_id = t.id AND tpr.is_owner IS true)
|
||||
JOIN profile AS p
|
||||
ON (tpr.profile_id = p.id)
|
||||
LEFT JOIN organization AS o ON o.id = t.organization_id
|
||||
WHERE t.deleted_at IS null
|
||||
AND tp.profile_id = ?
|
||||
ORDER BY tp.created_at ASC")
|
||||
|
||||
@@ -649,10 +649,23 @@
|
||||
(rx/of (dcm/change-team-role params)
|
||||
(modal/hide)))))
|
||||
|
||||
|
||||
(defn handle-change-team-org
|
||||
[{:keys [team-id organization-id organization-name] :as message}]
|
||||
(ptk/reify ::handle-change-team-org
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if (contains? (:teams state) team-id)
|
||||
(-> state
|
||||
(assoc-in [:teams team-id :organization-id] organization-id)
|
||||
(assoc-in [:teams team-id :organization-name] organization-name))
|
||||
state))))
|
||||
|
||||
(defn- process-message
|
||||
[{:keys [type] :as msg}]
|
||||
(case type
|
||||
:notification (dcm/handle-notification msg)
|
||||
:team-role-change (handle-change-team-role msg)
|
||||
:team-membership-change (dcm/team-membership-change msg)
|
||||
:team-org-change (handle-change-team-org msg)
|
||||
nil))
|
||||
|
||||
@@ -59,6 +59,8 @@
|
||||
{:type :subscribe-team
|
||||
:team-id team-id}]
|
||||
|
||||
_ (prn "messages for team-id" team-id)
|
||||
|
||||
endmsg {:type :unsubscribe-file
|
||||
:file-id file-id}
|
||||
|
||||
@@ -72,6 +74,7 @@
|
||||
(rx/filter (ptk/type? ::dws/message))
|
||||
(rx/map deref)
|
||||
(rx/filter (fn [{:keys [topic] :as msg}]
|
||||
(prn "topic" topic)
|
||||
(or (= topic uuid/zero)
|
||||
(= topic profile-id)
|
||||
(= topic team-id)
|
||||
@@ -122,8 +125,12 @@
|
||||
(dpl/close-current-plugin {:close-only-edition-plugins? true}))
|
||||
(rx/of (dwly/set-options-mode :design)))))))
|
||||
|
||||
|
||||
|
||||
|
||||
(defn- process-message
|
||||
[{:keys [type] :as msg}]
|
||||
(prn "process-message" msg)
|
||||
(case type
|
||||
:join-file (handle-presence msg)
|
||||
:leave-file (handle-presence msg)
|
||||
|
||||
@@ -603,6 +603,8 @@
|
||||
|
||||
team-id (get team :id)
|
||||
|
||||
_ (prn team)
|
||||
|
||||
projects? (= section :dashboard-recent)
|
||||
fonts? (= section :dashboard-fonts)
|
||||
libs? (= section :dashboard-libraries)
|
||||
@@ -702,6 +704,9 @@
|
||||
[:*
|
||||
[:div {:class (stl/css-case :sidebar-content true)
|
||||
:ref container}
|
||||
[:div {:class (stl/css :sidebar-content-section)}
|
||||
[:div {:class (stl/css :sidebar-section-title)}
|
||||
(str (:organization-name team))]]
|
||||
[:> sidebar-team-switch* {:team team :profile profile}]
|
||||
|
||||
[:> sidebar-search* {:search-term search-term
|
||||
|
||||
Reference in New Issue
Block a user