11package  api
22
33import  (
4+ 	"context" 
45	"errors" 
56	"log/slog" 
67	"net/http" 
7- 	"context " 
8+ 	"strings " 
89
910	"github.com/diggerhq/digger/opentaco/internal/domain" 
1011	"github.com/diggerhq/digger/opentaco/internal/rbac" 
@@ -29,17 +30,19 @@ func NewOrgHandler(orgRepo domain.OrganizationRepository, userRepo domain.UserRe
2930
3031// CreateOrgRequest is the request body for creating an organization 
3132type  CreateOrgRequest  struct  {
32- 	Name         string  `json:"name" validate:"required"`          // Unique identifier (e.g., "acme") 
33- 	DisplayName  string  `json:"display_name" validate:"required"`  // Friendly name (e.g., "Acme Corp") 
33+ 	Name           string  `json:"name" validate:"required"`          // Unique identifier (e.g., "acme") 
34+ 	DisplayName    string  `json:"display_name" validate:"required"`  // Friendly name (e.g., "Acme Corp") 
35+ 	ExternalOrgID  string  `json:"external_org_id"`                   // External org identifier (optional) 
3436}
3537
3638// CreateOrgResponse is the response for creating an organization 
3739type  CreateOrgResponse  struct  {
38- 	ID           string  `json:"id"`            // UUID 
39- 	Name         string  `json:"name"`          // Unique identifier 
40- 	DisplayName  string  `json:"display_name"`  // Friendly name 
41- 	CreatedBy    string  `json:"created_by"` 
42- 	CreatedAt    string  `json:"created_at"` 
40+ 	ID             string  `json:"id"`              // UUID 
41+ 	Name           string  `json:"name"`            // Unique identifier 
42+ 	DisplayName    string  `json:"display_name"`     // Friendly name 
43+ 	ExternalOrgID  string  `json:"external_org_id"`  // External org identifier 
44+ 	CreatedBy      string  `json:"created_by"` 
45+ 	CreatedAt      string  `json:"created_at"` 
4346}
4447
4548// CreateOrganization handles POST /internal/orgs 
@@ -102,7 +105,7 @@ func (h *OrgHandler) CreateOrganization(c echo.Context) error {
102105
103106	// Create organization in transaction 
104107	err  :=  h .orgRepo .WithTransaction (ctx , func (ctx  context.Context , txRepo  domain.OrganizationRepository ) error  {
105- 		createdOrg , err  :=  txRepo .Create (ctx , req .Name , req .DisplayName , userIDStr )
108+ 		createdOrg , err  :=  txRepo .Create (ctx , req .Name , req .Name ,  req . DisplayName ,  req . ExternalOrgID , userIDStr )
106109		if  err  !=  nil  {
107110			return  err 
108111		}
@@ -122,9 +125,17 @@ func (h *OrgHandler) CreateOrganization(c echo.Context) error {
122125				"error" : err .Error (),
123126			})
124127		}
128+ 		
129+ 		// Check for external org ID conflict 
130+ 		if  strings .Contains (err .Error (), "external org ID already exists" ) {
131+ 			return  c .JSON (http .StatusConflict , map [string ]string {
132+ 				"error" : err .Error (),
133+ 			})
134+ 		}
125135
126136		slog .Error ("Failed to create organization" ,
127137			"name" , req .Name ,
138+ 			"externalOrgID" , req .ExternalOrgID ,
128139			"error" , err ,
129140		)
130141		return  c .JSON (http .StatusInternalServerError , map [string ]string {
@@ -162,11 +173,148 @@ func (h *OrgHandler) CreateOrganization(c echo.Context) error {
162173
163174	// Success - org created (and RBAC initialized if available) 
164175	return  c .JSON (http .StatusCreated , CreateOrgResponse {
165- 		ID :          org .ID ,
166- 		Name :        org .Name ,
167- 		DisplayName : org .DisplayName ,
168- 		CreatedBy :   org .CreatedBy ,
169- 		CreatedAt :   org .CreatedAt .Format ("2006-01-02T15:04:05Z07:00" ),
176+ 		ID :            org .ID ,
177+ 		Name :          org .Name ,
178+ 		DisplayName :   org .DisplayName ,
179+ 		ExternalOrgID : org .ExternalOrgID ,
180+ 		CreatedBy :     org .CreatedBy ,
181+ 		CreatedAt :     org .CreatedAt .Format ("2006-01-02T15:04:05Z07:00" ),
182+ 	})
183+ }
184+ 
185+ // SyncExternalOrgRequest is the request body for syncing an external organization 
186+ type  SyncExternalOrgRequest  struct  {
187+ 	Name           string  `json:"name" validate:"required"`            // Internal name (e.g., "acme") 
188+ 	DisplayName    string  `json:"display_name" validate:"required"`  // Friendly name (e.g., "Acme Corp") 
189+ 	ExternalOrgID  string  `json:"external_org_id" validate:"required"`  // External org identifier 
190+ }
191+ 
192+ // SyncExternalOrgResponse is the response for syncing an external organization 
193+ type  SyncExternalOrgResponse  struct  {
194+ 	Status        string  `json:"status"`         // "created" or "existing" 
195+ 	Organization  * domain.Organization  `json:"organization"` 
196+ }
197+ 
198+ // SyncExternalOrg handles POST /internal/orgs/sync 
199+ // Creates a new organization with external mapping or returns existing one 
200+ func  (h  * OrgHandler ) SyncExternalOrg (c  echo.Context ) error  {
201+ 	ctx  :=  c .Request ().Context ()
202+ 
203+ 	// Get user context from webhook middleware 
204+ 	userID  :=  c .Get ("user_id" )
205+ 	email  :=  c .Get ("email" )
206+ 
207+ 	if  userID  ==  nil  ||  email  ==  nil  {
208+ 		slog .Error ("Missing user context in sync org request" )
209+ 		return  c .JSON (http .StatusBadRequest , map [string ]string {
210+ 			"error" : "user context required" ,
211+ 		})
212+ 	}
213+ 
214+ 	userIDStr , ok  :=  userID .(string )
215+ 	if  ! ok  ||  userIDStr  ==  ""  {
216+ 		slog .Error ("Invalid user_id type in context" )
217+ 		return  c .JSON (http .StatusInternalServerError , map [string ]string {
218+ 			"error" : "invalid user context - webhook middleware misconfigured" ,
219+ 		})
220+ 	}
221+ 
222+ 	// Parse request 
223+ 	var  req  SyncExternalOrgRequest 
224+ 	if  err  :=  c .Bind (& req ); err  !=  nil  {
225+ 		slog .Error ("Failed to bind sync org request" , "error" , err )
226+ 		return  c .JSON (http .StatusBadRequest , map [string ]string {
227+ 			"error" : "invalid request body" ,
228+ 		})
229+ 	}
230+ 
231+ 	if  req .Name  ==  ""  ||  req .DisplayName  ==  ""  ||  req .ExternalOrgID  ==  ""  {
232+ 		return  c .JSON (http .StatusBadRequest , map [string ]string {
233+ 			"error" : "name, display_name, and external_org_id are required" ,
234+ 		})
235+ 	}
236+ 
237+ 	slog .Info ("Syncing external organization" ,
238+ 		"name" , req .Name ,
239+ 		"displayName" , req .DisplayName ,
240+ 		"externalOrgID" , req .ExternalOrgID ,
241+ 		"createdBy" , userIDStr ,
242+ 	)
243+ 
244+ 	// Check if external org ID already exists 
245+ 	existingOrg , err  :=  h .orgRepo .GetByExternalID (ctx , req .ExternalOrgID )
246+ 	if  err  ==  nil  {
247+ 		// External org ID exists, return existing org 
248+ 		slog .Info ("External organization already exists" ,
249+ 			"externalOrgID" , req .ExternalOrgID ,
250+ 			"orgID" , existingOrg .ID ,
251+ 		)
252+ 		return  c .JSON (http .StatusOK , SyncExternalOrgResponse {
253+ 			Status :       "existing" ,
254+ 			Organization : existingOrg ,
255+ 		})
256+ 	}
257+ 
258+ 	if  err  !=  domain .ErrOrgNotFound  {
259+ 		slog .Error ("Failed to check existing external org ID" ,
260+ 			"externalOrgID" , req .ExternalOrgID ,
261+ 			"error" , err ,
262+ 		)
263+ 		return  c .JSON (http .StatusInternalServerError , map [string ]string {
264+ 			"error" : "failed to check existing external organization" ,
265+ 		})
266+ 	}
267+ 
268+ 	// Create new organization with external mapping 
269+ 	var  org  * domain.Organization 
270+ 	err  =  h .orgRepo .WithTransaction (ctx , func (ctx  context.Context , txRepo  domain.OrganizationRepository ) error  {
271+ 		createdOrg , err  :=  txRepo .Create (ctx , req .Name , req .Name , req .DisplayName , req .ExternalOrgID , userIDStr )
272+ 		if  err  !=  nil  {
273+ 			return  err 
274+ 		}
275+ 		org  =  createdOrg 
276+ 		return  nil 
277+ 	})
278+ 
279+ 	if  err  !=  nil  {
280+ 		if  errors .Is (err , domain .ErrOrgExists ) {
281+ 			return  c .JSON (http .StatusConflict , map [string ]string {
282+ 				"error" : "organization name already exists" ,
283+ 			})
284+ 		}
285+ 		if  errors .Is (err , domain .ErrInvalidOrgID ) {
286+ 			return  c .JSON (http .StatusBadRequest , map [string ]string {
287+ 				"error" : err .Error (),
288+ 			})
289+ 		}
290+ 		
291+ 		// Check for external org ID conflict 
292+ 		if  strings .Contains (err .Error (), "external org ID already exists" ) {
293+ 			return  c .JSON (http .StatusConflict , map [string ]string {
294+ 				"error" : err .Error (),
295+ 			})
296+ 		}
297+ 
298+ 		slog .Error ("Failed to create organization during sync" ,
299+ 			"name" , req .Name ,
300+ 			"externalOrgID" , req .ExternalOrgID ,
301+ 			"error" , err ,
302+ 		)
303+ 		return  c .JSON (http .StatusInternalServerError , map [string ]string {
304+ 			"error" : "failed to create organization" ,
305+ 			"detail" : err .Error (),
306+ 		})
307+ 	}
308+ 
309+ 	slog .Info ("External organization synced successfully" ,
310+ 		"name" , req .Name ,
311+ 		"externalOrgID" , req .ExternalOrgID ,
312+ 		"orgID" , org .ID ,
313+ 	)
314+ 
315+ 	return  c .JSON (http .StatusCreated , SyncExternalOrgResponse {
316+ 		Status :       "created" ,
317+ 		Organization : org ,
170318	})
171319}
172320
0 commit comments