mirror of
https://github.com/penpot/penpot.git
synced 2026-09-10 05:58:47 +00:00
✨ Add nitrate endpoint to delete teams keeping your-penpot projects
This commit is contained in:
parent
ca72dcdcbb
commit
2a09f30199
@ -339,6 +339,21 @@
|
|||||||
;; --- COMMAND: Move project
|
;; --- COMMAND: Move project
|
||||||
|
|
||||||
(defn move-project
|
(defn move-project
|
||||||
|
"Moves a project from one team to another.
|
||||||
|
|
||||||
|
Performs comprehensive validation including:
|
||||||
|
- Permission checks on both source and destination teams
|
||||||
|
- Team compatibility verification between source and destination
|
||||||
|
- File features compatibility with destination team
|
||||||
|
|
||||||
|
The operation also:
|
||||||
|
- Updates the project's team assignment
|
||||||
|
- Cleans up any broken library relations after the move
|
||||||
|
|
||||||
|
Throws:
|
||||||
|
- :cant-move-to-same-team if trying to move project to its current team
|
||||||
|
- Permission exceptions if user lacks required permissions
|
||||||
|
- Team compatibility exceptions if teams are incompatible"
|
||||||
[{:keys [::db/conn] :as cfg} {:keys [profile-id team-id project-id] :as params}]
|
[{:keys [::db/conn] :as cfg} {:keys [profile-id team-id project-id] :as params}]
|
||||||
(let [project (db/get-by-id conn :project project-id {:columns [:id :team-id]})
|
(let [project (db/get-by-id conn :project project-id {:columns [:id :team-id]})
|
||||||
pids (->> (db/query conn :project {:team-id (:team-id project)} {:columns [:id]})
|
pids (->> (db/query conn :project {:team-id (:team-id project)} {:columns [:id]})
|
||||||
|
|||||||
@ -8,6 +8,8 @@
|
|||||||
"Internal Nitrate HTTP RPC API. Provides authenticated access to
|
"Internal Nitrate HTTP RPC API. Provides authenticated access to
|
||||||
organization management and token validation endpoints."
|
organization management and token validation endpoints."
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
|
[app.common.exceptions :as ex]
|
||||||
[app.common.features :as cfeat]
|
[app.common.features :as cfeat]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.types.profile :refer [schema:profile, schema:basic-profile]]
|
[app.common.types.profile :refer [schema:profile, schema:basic-profile]]
|
||||||
@ -18,12 +20,14 @@
|
|||||||
[app.msgbus :as mbus]
|
[app.msgbus :as mbus]
|
||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
[app.rpc.commands.files :as files]
|
[app.rpc.commands.files :as files]
|
||||||
|
[app.rpc.commands.management :as management]
|
||||||
[app.rpc.commands.profile :as profile]
|
[app.rpc.commands.profile :as profile]
|
||||||
[app.rpc.commands.teams :as teams]
|
[app.rpc.commands.teams :as teams]
|
||||||
[app.rpc.doc :as doc]
|
[app.rpc.doc :as doc]
|
||||||
[app.rpc.quotes :as quotes]
|
[app.rpc.quotes :as quotes]
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[clojure.set :as set]))
|
[clojure.set :as set]
|
||||||
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
;; ---- API: authenticate
|
;; ---- API: authenticate
|
||||||
|
|
||||||
@ -212,3 +216,124 @@
|
|||||||
{:teams (mapv #(select-keys % [:id :name]) teams)
|
{:teams (mapv #(select-keys % [:id :name]) teams)
|
||||||
:num-files files-count})))))
|
:num-files files-count})))))
|
||||||
|
|
||||||
|
|
||||||
|
;; ---- API: delete-teams-keeping-your-penpot-projects
|
||||||
|
|
||||||
|
(def ^:private sql:get-projects-and-default-teams
|
||||||
|
"Get projects from specified teams along with their team owner's default team information.
|
||||||
|
This query:
|
||||||
|
- Selects projects (id, team_id, name) from teams in the provided list
|
||||||
|
- Gets the profile_id of each team owner
|
||||||
|
- Gets the default_team_id where projects should be moved
|
||||||
|
- Only includes teams where the user is owner
|
||||||
|
- Only includes projects that contain at least one non-deleted file
|
||||||
|
- Excludes deleted projects and teams"
|
||||||
|
"SELECT p.id AS project_id,
|
||||||
|
p.team_id AS source_team_id,
|
||||||
|
p.name AS project_name,
|
||||||
|
tpr.profile_id,
|
||||||
|
pr.default_team_id
|
||||||
|
FROM project AS p
|
||||||
|
JOIN team AS tm ON p.team_id = tm.id
|
||||||
|
JOIN team_profile_rel AS tpr ON tm.id = tpr.team_id
|
||||||
|
JOIN profile AS pr ON tpr.profile_id = pr.id
|
||||||
|
WHERE p.team_id = ANY(?)
|
||||||
|
AND p.deleted_at IS NULL
|
||||||
|
AND tm.deleted_at IS NULL
|
||||||
|
AND tpr.is_owner IS TRUE
|
||||||
|
AND EXISTS (SELECT 1 FROM file f WHERE f.project_id = p.id AND f.deleted_at IS NULL);")
|
||||||
|
|
||||||
|
(def ^:private sql:delete-teams
|
||||||
|
"UPDATE team SET deleted_at = ? WHERE id = ANY(?)")
|
||||||
|
|
||||||
|
(def ^:private schema:delete-teams-keeping-your-penpot-projects
|
||||||
|
[:map
|
||||||
|
[:org-name ::sm/text]
|
||||||
|
[:teams [:vector [:map
|
||||||
|
[:id ::sm/uuid]
|
||||||
|
[:is-your-penpot ::sm/boolean]]]]])
|
||||||
|
|
||||||
|
(def ^:private schema:delete-teams-error
|
||||||
|
[:map
|
||||||
|
[:error ::sm/keyword]
|
||||||
|
[:message ::sm/text]
|
||||||
|
[:cause ::sm/text]
|
||||||
|
[:project-id {:optional true} ::sm/uuid]
|
||||||
|
[:project-name {:optional true} ::sm/text]
|
||||||
|
[:team-id {:optional true} ::sm/uuid]
|
||||||
|
[:phase {:optional true} [:enum :move-projects :delete-teams]]])
|
||||||
|
|
||||||
|
(def ^:private schema:delete-teams-result
|
||||||
|
[:or [:= nil] schema:delete-teams-error])
|
||||||
|
|
||||||
|
(defn- ^:private clean-org-name
|
||||||
|
"Clean and sanitize organization name to remove emojis, special characters,
|
||||||
|
and prevent potential injections. Only allows alphanumeric characters,
|
||||||
|
spaces, hyphens, underscores, and parentheses."
|
||||||
|
[org-name]
|
||||||
|
(when org-name
|
||||||
|
(-> org-name
|
||||||
|
str
|
||||||
|
str/trim
|
||||||
|
(str/replace #"[^\w\s\-_()]+" "")
|
||||||
|
(str/replace #"\s+" " ")
|
||||||
|
str/trim)))
|
||||||
|
|
||||||
|
(sv/defmethod ::delete-teams-keeping-your-penpot-projects
|
||||||
|
"For a list of teams, move the projects of your-penpot teams to the
|
||||||
|
default team of each team owner, then delete all provided teams."
|
||||||
|
{::doc/added "2.15"
|
||||||
|
::sm/params schema:delete-teams-keeping-your-penpot-projects
|
||||||
|
::sm/result schema:delete-teams-result}
|
||||||
|
[cfg {:keys [teams org-name ::rpc/request-at]}]
|
||||||
|
|
||||||
|
(let [your-penpot-team-ids (into [] (comp (filter :is-your-penpot) d/xf:map-id) teams)
|
||||||
|
all-team-ids (into [] d/xf:map-id teams)
|
||||||
|
cleaned-org-name (clean-org-name org-name)
|
||||||
|
org-prefix (if (str/empty? cleaned-org-name)
|
||||||
|
"imported: "
|
||||||
|
(str cleaned-org-name " imported: "))]
|
||||||
|
|
||||||
|
(when (seq all-team-ids)
|
||||||
|
|
||||||
|
(db/tx-run! cfg
|
||||||
|
(fn [{:keys [::db/conn] :as cfg}]
|
||||||
|
|
||||||
|
;; ---- Move projects ----
|
||||||
|
(when (seq your-penpot-team-ids)
|
||||||
|
(let [ids-array (db/create-array conn "uuid" your-penpot-team-ids)
|
||||||
|
projects (db/exec! conn [sql:get-projects-and-default-teams ids-array])]
|
||||||
|
|
||||||
|
(doseq [{:keys [default-team-id profile-id project-id project-name source-team-id]} projects
|
||||||
|
:when default-team-id]
|
||||||
|
|
||||||
|
(try
|
||||||
|
(management/move-project cfg {:profile-id profile-id
|
||||||
|
:team-id default-team-id
|
||||||
|
:project-id project-id})
|
||||||
|
|
||||||
|
(db/update! conn :project
|
||||||
|
{:is-default false
|
||||||
|
:name (str org-prefix project-name)}
|
||||||
|
{:id project-id})
|
||||||
|
|
||||||
|
(catch Throwable cause
|
||||||
|
(ex/raise :type :internal
|
||||||
|
:code :nitrate-project-move-failed
|
||||||
|
:context {:project-id project-id
|
||||||
|
:project-name project-name
|
||||||
|
:team-id source-team-id}
|
||||||
|
:cause cause))))))
|
||||||
|
|
||||||
|
;; ---- Delete teams ----
|
||||||
|
(try
|
||||||
|
(let [team-ids-array (db/create-array conn "uuid" all-team-ids)]
|
||||||
|
(db/exec-one! conn [sql:delete-teams request-at team-ids-array]))
|
||||||
|
(catch Throwable cause
|
||||||
|
(ex/raise :type :internal
|
||||||
|
:code :nitrate-team-deletion-failed
|
||||||
|
:context {:team-ids all-team-ids}
|
||||||
|
:cause cause))))))
|
||||||
|
|
||||||
|
nil))
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user