Skip to content

Commit d76b0f8

Browse files
authored
tfe units ui functionalities (#2343)
* fix webhooks * suport tfe endpoints * support tfe and ui * support more state operations * added all missing functionalities * ignore graph udpate for now * remove accidental file * fix settings route * switcher * dont redirect * removal of netlify
1 parent 3ad3933 commit d76b0f8

33 files changed

+1197
-297
lines changed

backend/controllers/internal_users.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ func (d DiggerController) CreateUserInternal(c *gin.Context) {
9191
return
9292
}
9393

94+
existingUser, err := models.DB.GetUserByEmail(userEmail)
95+
if existingUser != nil && err == nil {
96+
slog.Error("User email already exists", "email", userEmail)
97+
c.JSON(http.StatusConflict, gin.H{"error": "User email already exists"})
98+
return
99+
}
100+
94101
// for now using email for username since we want to deprecate that field
95102
username := userEmail
96103
user, err := models.DB.CreateUser(userEmail, extUserSource, extUserId, org.ID, username)

backend/models/storage.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,6 +1394,17 @@ func (db *Database) GetOrganisation(tenantId any) (*Organisation, error) {
13941394
return org, nil
13951395
}
13961396

1397+
func (d *Database) GetUserByEmail(email string) (*User, error) {
1398+
user := User{}
1399+
err := d.GormDB.Where("email = ?", email).First(&user).Error
1400+
if err != nil {
1401+
if errors.Is(err, gorm.ErrRecordNotFound) {
1402+
return nil, nil
1403+
}
1404+
}
1405+
return &user, err
1406+
}
1407+
13971408
func (db *Database) CreateUser(email string, externalSource string, externalId string, orgId uint, username string) (*User, error) {
13981409
user := &User{
13991410
Email: email,

taco/internal/tfe/well_known.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package tfe
22

33
import (
4+
"os"
5+
46
"github.com/diggerhq/digger/opentaco/internal/domain/tfe"
57
"github.com/labstack/echo/v4"
6-
"os"
78
)
89

910
const (
@@ -68,9 +69,19 @@ func (h *TfeHandler) AuthTokenExchange(c echo.Context) error {
6869
// Helper function to get base URL
6970
func getBaseURL(c echo.Context) string {
7071
scheme := c.Scheme()
71-
if fwd := c.Request().Header.Get("X-Forwarded-Proto"); fwd != "" {
72-
scheme = fwd
72+
allowForwardedFor := os.Getenv("OPENTACO_ALLOW_X_FORWARDED_FOR")
73+
if allowForwardedFor == "true" {
74+
if fwd := c.Request().Header.Get("X-Forwarded-Proto"); fwd != "" {
75+
scheme = fwd
76+
}
7377
}
78+
7479
host := c.Request().Host
80+
if allowForwardedFor == "true" {
81+
if fwdHost := c.Request().Header.Get("X-Forwarded-Host"); fwdHost != "" {
82+
host = fwdHost
83+
}
84+
}
85+
7586
return scheme + "://" + host
7687
}

taco/internal/unit/handler.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,10 @@ func (h *Handler) UploadUnit(c echo.Context) error {
300300
}
301301
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to upload unit"})
302302
}
303+
// TODO: This graph update function does not currently work correctly,
304+
// commenting out for now until this functionality is fixed
303305
// Best-effort dependency graph update
304-
go deps.UpdateGraphOnWrite(c.Request().Context(), h.store, id, data)
306+
//go deps.UpdateGraphOnWrite(c.Request().Context(), h.store, id, data)
305307
analytics.SendEssential("taco_unit_push_completed")
306308
return c.JSON(http.StatusOK, map[string]string{"message": "Unit uploaded successfully"})
307309
}

ui/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
.tanstack/
12
.netlify/
23
dist/
34
node_modules/

ui/src/api/orchestrator_orgs.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ export async function getOrgSettings(
4646

4747
if (!response.ok) {
4848
const text = await response.text()
49-
console.log(text)
5049
throw new Error('Failed to get organization settings')
5150
}
5251

ui/src/api/orchestrator_users.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ export async function syncUserToBackend(userId: string, userEmail: string, orgId
1515
})
1616
})
1717

18+
if (response.status === 409) {
19+
console.log("User already exists in orchestrator")
20+
return response.json();
21+
}
22+
1823
if (!response.ok) {
1924
throw new Error(`Failed to sync user: ${response.statusText}`);
2025
}

ui/src/api/statesman_orgs.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11

22

3-
export async function syncOrgToStatesman(orgId: string, orgName: string, userId: string, adminEmail: string) {
3+
export async function syncOrgToStatesman(orgId: string, orgName: string, displayName: string, userId: string, adminEmail: string) {
44
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/orgs`, {
55
method: 'POST',
66
headers: {
77
'Content-Type': 'application/json',
88
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
9-
'X-Org-ID': orgId,
9+
'X-Org-ID': "",
1010
'X-User-ID': userId,
1111
'X-Email': adminEmail,
1212
},
1313
body: JSON.stringify({
14-
"org_id": orgId,
14+
"external_org_id": orgId,
1515
"name": orgName,
16+
"display_name": displayName,
1617
"created_by": adminEmail,
1718
})
1819
})
1920

20-
console.log(orgId)
21-
console.log(orgName)
22-
console.log(userId)
23-
console.log(adminEmail)
21+
if (response.status === 409) {
22+
console.log("User already exists in statesman")
23+
return response.json();
24+
}
2425

2526
if (!response.ok) {
2627
throw new Error(`Failed to sync organization to statesman: ${response.statusText}`);

ui/src/api/statesman_serverFunctions.ts

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createServerFn } from "@tanstack/react-start"
2-
import { createUnit, getUnit, listUnits } from "./statesman_units"
2+
import { createUnit, getUnit, listUnits, getUnitVersions, unlockUnit, lockUnit, getUnitStatus, deleteUnit, downloadLatestState, forcePushState, restoreUnitStateVersion } from "./statesman_units"
33

44
export const listUnitsFn = createServerFn({method: 'GET'})
55
.inputValidator((data : {userId: string, organisationId: string, email: string}) => data)
@@ -15,9 +15,64 @@ export const getUnitFn = createServerFn({method: 'GET'})
1515
return unit
1616
})
1717

18-
export const createUnitFn = createServerFn({method: 'POST'})
18+
export const getUnitVersionsFn = createServerFn({method: 'GET'})
19+
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
20+
.handler(async ({ data }) => {
21+
const unitVersions : any = await getUnitVersions(data.organisationId, data.userId, data.email, data.unitId)
22+
return unitVersions
23+
})
24+
25+
export const lockUnitFn = createServerFn({method: 'POST'})
1926
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
2027
.handler(async ({ data }) => {
21-
const unit : any = await createUnit(data.organisationId, data.userId, data.email, data.unitId)
28+
const unit : any = await lockUnit(data.organisationId, data.userId, data.email, data.unitId)
2229
return unit
30+
})
31+
32+
export const unlockUnitFn = createServerFn({method: 'POST'})
33+
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
34+
.handler(async ({ data }) => {
35+
const unit : any = await unlockUnit(data.organisationId, data.userId, data.email, data.unitId)
36+
return unit
37+
})
38+
39+
export const downloadLatestStateFn = createServerFn({method: 'GET'})
40+
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
41+
.handler(async ({ data }) => {
42+
const state : any = await downloadLatestState(data.organisationId, data.userId, data.email, data.unitId)
43+
return state
44+
})
45+
46+
export const forcePushStateFn = createServerFn({method: 'POST'})
47+
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string, state: string}) => data)
48+
.handler(async ({ data }) => {
49+
const state : any = await forcePushState(data.organisationId, data.userId, data.email, data.unitId, data.state)
50+
return state
51+
})
52+
53+
export const restoreUnitStateVersionFn = createServerFn({method: 'POST'})
54+
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string, timestamp: string, lockId: string}) => data)
55+
.handler(async ({ data }) => {
56+
const state : any = await restoreUnitStateVersion(data.organisationId, data.userId, data.email, data.unitId, data.timestamp, data.lockId)
57+
return state
58+
})
59+
60+
export const getUnitStatusFn = createServerFn({method: 'GET'})
61+
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
62+
.handler(async ({ data }) => {
63+
const unitStatus : any = await getUnitStatus(data.organisationId, data.userId, data.email, data.unitId)
64+
return unitStatus
65+
})
66+
67+
export const createUnitFn = createServerFn({method: 'POST'})
68+
.inputValidator((data : {userId: string, organisationId: string, email: string, name: string}) => data)
69+
.handler(async ({ data }) => {
70+
const unit : any = await createUnit(data.organisationId, data.userId, data.email, data.name)
71+
return unit
72+
})
73+
74+
export const deleteUnitFn = createServerFn({method: 'POST'})
75+
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
76+
.handler(async ({ data }) => {
77+
await deleteUnit(data.organisationId, data.userId, data.email, data.unitId)
2378
})

ui/src/api/statesman_units.ts

Lines changed: 145 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export async function listUnits(orgId: string, userId: string, email: string) {
1313
if (!response.ok) {
1414
throw new Error(`Failed to list units: ${response.statusText}`);
1515
}
16-
1716
return response.json();
1817
}
1918

@@ -28,9 +27,135 @@ export async function getUnit(orgId: string, userId: string, email: string, unit
2827
'X-Email': email,
2928
},
3029
});
30+
if (!response.ok) {
31+
throw new Error(`Failed to get unit: ${response.statusText}`);
32+
}
33+
return response.json();
34+
}
35+
36+
export async function getUnitVersions(orgId: string, userId: string, email: string, unitId: string) {
37+
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units/${unitId}/versions`, {
38+
method: 'GET',
39+
headers: {
40+
'Content-Type': 'application/json',
41+
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
42+
'X-Org-ID': orgId,
43+
'X-User-ID': userId,
44+
'X-Email': email,
45+
},
46+
});
47+
if (!response.ok) {
48+
throw new Error(`Failed to get unit: ${response.statusText}`);
49+
}
50+
return response.json();
51+
}
52+
53+
54+
export async function lockUnit(orgId: string, userId: string, email: string, unitId: string) {
55+
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units/${unitId}/lock`, {
56+
method: 'POST',
57+
headers: {
58+
'Content-Type': 'application/json',
59+
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
60+
'X-Org-ID': orgId,
61+
'X-User-ID': userId,
62+
'X-Email': email,
63+
},
64+
});
65+
if (!response.ok) {
66+
throw new Error(`Failed to lock unit: ${response.statusText}`);
67+
}
68+
return response.json();
69+
}
70+
71+
export async function unlockUnit(orgId: string, userId: string, email: string, unitId: string) {
72+
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units/${unitId}/unlock`, {
73+
method: 'DELETE',
74+
headers: {
75+
'Content-Type': 'application/json',
76+
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
77+
'X-Org-ID': orgId,
78+
'X-User-ID': userId,
79+
'X-Email': email,
80+
},
81+
});
82+
if (!response.ok) {
83+
throw new Error(`Failed to unlock unit: ${response.statusText}`);
84+
}
85+
return response.json();
3186
}
3287

33-
export async function createUnit(orgId: string, userId: string, email: string, unitId: string) {
88+
export async function forcePushState(orgId: string, userId: string, email: string, unitId: string, state: string) {
89+
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units/${unitId}/upload`, {
90+
method: 'POST',
91+
headers: {
92+
'Content-Type': 'application/json',
93+
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
94+
'X-Org-ID': orgId,
95+
'X-User-ID': userId,
96+
'X-Email': email,
97+
},
98+
body: state,
99+
});
100+
if (!response.ok) {
101+
throw new Error(`Failed to force push state: ${response.statusText}`);
102+
}
103+
return response.json();
104+
}
105+
106+
export async function downloadLatestState(orgId: string, userId: string, email: string, unitId: string) {
107+
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units/${unitId}/download`, {
108+
method: 'GET',
109+
headers: {
110+
'Content-Type': 'application/json',
111+
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
112+
'X-Org-ID': orgId,
113+
'X-User-ID': userId,
114+
'X-Email': email,
115+
},
116+
});
117+
return response.json()
118+
}
119+
120+
export async function restoreUnitStateVersion(orgId: string, userId: string, email: string, unitId: string, timestamp: string, lockId: string) {
121+
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units/${unitId}/restore`, {
122+
method: 'POST',
123+
headers: {
124+
'Content-Type': 'application/json',
125+
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
126+
'X-Org-ID': orgId,
127+
'X-User-ID': userId,
128+
'X-Email': email,
129+
},
130+
body: JSON.stringify({
131+
timestamp: timestamp,
132+
lock_id: lockId,
133+
}),
134+
});
135+
if (!response.ok) {
136+
throw new Error(`Failed to restore unit state version: ${response.statusText}`);
137+
}
138+
return response.json();
139+
}
140+
141+
export async function getUnitStatus(orgId: string, userId: string, email: string, unitId: string) {
142+
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units/${unitId}/status`, {
143+
method: 'GET',
144+
headers: {
145+
'Content-Type': 'application/json',
146+
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
147+
'X-Org-ID': orgId,
148+
'X-User-ID': userId,
149+
'X-Email': email,
150+
},
151+
});
152+
if (!response.ok) {
153+
throw new Error(`Failed to get unit status: ${response.statusText}`);
154+
}
155+
return response.json();
156+
}
157+
158+
export async function createUnit(orgId: string, userId: string, email: string, name: string) {
34159
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units`, {
35160
method: 'POST',
36161
headers: {
@@ -41,7 +166,7 @@ export async function createUnit(orgId: string, userId: string, email: string, u
41166
'X-Email': email,
42167
},
43168
body: JSON.stringify({
44-
id: unitId,
169+
name: name,
45170
}),
46171
});
47172
console.log(response)
@@ -50,4 +175,21 @@ export async function createUnit(orgId: string, userId: string, email: string, u
50175
}
51176

52177
return response.json();
178+
}
179+
180+
export async function deleteUnit(orgId: string, userId: string, email: string, unitId: string) {
181+
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units/${unitId}`, {
182+
method: 'DELETE',
183+
headers: {
184+
'Content-Type': 'application/json',
185+
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
186+
'X-Org-ID': orgId,
187+
'X-User-ID': userId,
188+
'X-Email': email,
189+
},
190+
});
191+
if (!response.ok) {
192+
throw new Error(`Failed to delete unit: ${response.statusText}`);
193+
}
194+
53195
}

0 commit comments

Comments
 (0)