@@ -2,25 +2,29 @@ use std::string::ToString;
22
33use bencher_json:: {
44 project:: { JsonProjectPatch , JsonProjectPatchNull , JsonUpdateProject , Visibility } ,
5- DateTime , JsonNewProject , JsonProject , ProjectUuid , ResourceId , ResourceName , Slug , Url ,
5+ DateTime , JsonNewProject , JsonProject , NameId , NameIdKind , ProjectUuid , ResourceId ,
6+ ResourceName , Slug , Url ,
67} ;
78use bencher_rbac:: { project:: Permission , Organization , Project } ;
89use diesel:: { ExpressionMethods , QueryDsl , RunQueryDsl } ;
910use dropshot:: HttpError ;
1011
1112use crate :: {
13+ conn_lock,
1214 context:: { DbConnection , Rbac } ,
1315 error:: {
14- assert_parentage, forbidden_error, resource_not_found_error, unauthorized_error,
15- BencherResource ,
16+ assert_parentage, bad_request_error, conflict_error, forbidden_error,
17+ resource_conflict_err, resource_not_found_err, resource_not_found_error,
18+ unauthorized_error, BencherResource ,
1619 } ,
1720 macros:: {
18- fn_get:: { fn_get, fn_get_uuid} ,
21+ fn_get:: { fn_from_uuid , fn_get, fn_get_uuid} ,
1922 resource_id:: { fn_eq_resource_id, fn_from_resource_id} ,
2023 slug:: ok_slug,
2124 } ,
2225 model:: { organization:: QueryOrganization , user:: auth:: AuthUser } ,
2326 schema:: { self , project as project_table} ,
27+ ApiContext ,
2428} ;
2529
2630use super :: organization:: OrganizationId ;
@@ -57,10 +61,111 @@ pub struct QueryProject {
5761
5862impl QueryProject {
5963 fn_eq_resource_id ! ( project) ;
60- fn_from_resource_id ! ( project, Project , true ) ;
64+ fn_from_resource_id ! ( project, Project ) ;
6165
6266 fn_get ! ( project, ProjectId ) ;
6367 fn_get_uuid ! ( project, ProjectId , ProjectUuid ) ;
68+ fn_from_uuid ! (
69+ organization_id,
70+ OrganizationId ,
71+ project,
72+ ProjectUuid ,
73+ Project
74+ ) ;
75+
76+ pub async fn get_or_create (
77+ context : & ApiContext ,
78+ organization : & ResourceId ,
79+ project : & NameId ,
80+ auth_user : & AuthUser ,
81+ ) -> Result < ProjectId , HttpError > {
82+ let query_organization =
83+ QueryOrganization :: from_resource_id ( conn_lock ! ( context) , organization) ?;
84+ let query_project =
85+ Self :: get_or_create_inner ( context, & query_organization, project, auth_user) . await ?;
86+ Ok ( query_project. id )
87+ }
88+
89+ async fn get_or_create_inner (
90+ context : & ApiContext ,
91+ query_organization : & QueryOrganization ,
92+ project : & NameId ,
93+ auth_user : & AuthUser ,
94+ ) -> Result < Self , HttpError > {
95+ let Ok ( kind) = NameIdKind :: < ResourceName > :: try_from ( project) else {
96+ return Err ( bad_request_error ( format ! (
97+ "Project ({project}) must be a valid UUID, slug, or name"
98+ ) ) ) ;
99+ } ;
100+ let query_project = match kind {
101+ NameIdKind :: Uuid ( uuid) => {
102+ QueryProject :: from_uuid ( conn_lock ! ( context) , query_organization. id , uuid. into ( ) ) ?
103+ } ,
104+ NameIdKind :: Slug ( slug) => {
105+ if let Ok ( query_project) = schema:: project:: table
106+ . filter ( schema:: project:: organization_id. eq ( query_organization. id ) )
107+ . filter ( schema:: project:: slug. eq ( & slug) )
108+ . first :: < Self > ( conn_lock ! ( context) )
109+ {
110+ query_project
111+ } else {
112+ let new_project = JsonNewProject {
113+ name : slug. clone ( ) . into ( ) ,
114+ slug : Some ( slug. clone ( ) ) ,
115+ url : None ,
116+ visibility : None ,
117+ } ;
118+ Self :: create ( context, query_organization, new_project, auth_user) . await ?
119+ }
120+ } ,
121+ NameIdKind :: Name ( name) => {
122+ if let Ok ( query_project) = schema:: project:: table
123+ . filter ( schema:: project:: organization_id. eq ( query_organization. id ) )
124+ . filter ( schema:: project:: name. eq ( & name) )
125+ . first :: < Self > ( conn_lock ! ( context) )
126+ {
127+ query_project
128+ } else {
129+ let new_project = JsonNewProject {
130+ name,
131+ slug : None ,
132+ url : None ,
133+ visibility : None ,
134+ } ;
135+ Self :: create ( context, query_organization, new_project, auth_user) . await ?
136+ }
137+ } ,
138+ } ;
139+
140+ Ok ( query_project)
141+ }
142+
143+ async fn create (
144+ context : & ApiContext ,
145+ query_organization : & QueryOrganization ,
146+ new_project : JsonNewProject ,
147+ auth_user : & AuthUser ,
148+ ) -> Result < Self , HttpError > {
149+ let insert_project =
150+ InsertProject :: from_json ( conn_lock ! ( context) , query_organization, new_project) ?;
151+
152+ // Check to see if user has permission to create a project within the organization
153+ context
154+ . rbac
155+ . is_allowed_organization (
156+ auth_user,
157+ bencher_rbac:: organization:: Permission :: Create ,
158+ & insert_project,
159+ )
160+ . map_err ( forbidden_error) ?;
161+
162+ let conn = conn_lock ! ( context) ;
163+ diesel:: insert_into ( project_table:: table)
164+ . values ( & insert_project)
165+ . execute ( conn)
166+ . map_err ( resource_conflict_err ! ( Project , & insert_project) ) ?;
167+ Self :: from_uuid ( conn, query_organization. id , insert_project. uuid )
168+ }
64169
65170 pub fn is_public ( & self ) -> bool {
66171 self . visibility . is_public ( )
0 commit comments