Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions backend/controllers/internal_users.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ func (d DiggerController) CreateUserInternal(c *gin.Context) {
return
}

existingUser, err := models.DB.GetUserByEmail(userEmail)
if existingUser != nil && err == nil {
slog.Error("User email already exists", "email", userEmail)
c.JSON(http.StatusConflict, gin.H{"error": "User email already exists"})
return
}

// for now using email for username since we want to deprecate that field
username := userEmail
user, err := models.DB.CreateUser(userEmail, extUserSource, extUserId, org.ID, username)
Expand Down
11 changes: 11 additions & 0 deletions backend/models/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -1394,6 +1394,17 @@ func (db *Database) GetOrganisation(tenantId any) (*Organisation, error) {
return org, nil
}

func (d *Database) GetUserByEmail(email string) (*User, error) {
user := User{}
err := d.GormDB.Where("email = ?", email).First(&user).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
}
return &user, err
}

func (db *Database) CreateUser(email string, externalSource string, externalId string, orgId uint, username string) (*User, error) {
user := &User{
Email: email,
Expand Down
17 changes: 14 additions & 3 deletions taco/internal/tfe/well_known.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package tfe

import (
"os"

"github.com/diggerhq/digger/opentaco/internal/domain/tfe"
"github.com/labstack/echo/v4"
"os"
)

const (
Expand Down Expand Up @@ -68,9 +69,19 @@ func (h *TfeHandler) AuthTokenExchange(c echo.Context) error {
// Helper function to get base URL
func getBaseURL(c echo.Context) string {
scheme := c.Scheme()
if fwd := c.Request().Header.Get("X-Forwarded-Proto"); fwd != "" {
scheme = fwd
allowForwardedFor := os.Getenv("OPENTACO_ALLOW_X_FORWARDED_FOR")
if allowForwardedFor == "true" {
if fwd := c.Request().Header.Get("X-Forwarded-Proto"); fwd != "" {
scheme = fwd
}
}

host := c.Request().Host
if allowForwardedFor == "true" {
if fwdHost := c.Request().Header.Get("X-Forwarded-Host"); fwdHost != "" {
host = fwdHost
}
}

return scheme + "://" + host
}
4 changes: 3 additions & 1 deletion taco/internal/unit/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,10 @@ func (h *Handler) UploadUnit(c echo.Context) error {
}
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to upload unit"})
}
// TODO: This graph update function does not currently work correctly,
// commenting out for now until this functionality is fixed
// Best-effort dependency graph update
go deps.UpdateGraphOnWrite(c.Request().Context(), h.store, id, data)
//go deps.UpdateGraphOnWrite(c.Request().Context(), h.store, id, data)
analytics.SendEssential("taco_unit_push_completed")
return c.JSON(http.StatusOK, map[string]string{"message": "Unit uploaded successfully"})
}
Expand Down
1 change: 1 addition & 0 deletions ui/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.tanstack/
.netlify/
dist/
node_modules/
Expand Down
1 change: 0 additions & 1 deletion ui/src/api/orchestrator_orgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export async function getOrgSettings(

if (!response.ok) {
const text = await response.text()
console.log(text)
throw new Error('Failed to get organization settings')
}

Expand Down
5 changes: 5 additions & 0 deletions ui/src/api/orchestrator_users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export async function syncUserToBackend(userId: string, userEmail: string, orgId
})
})

if (response.status === 409) {
console.log("User already exists in orchestrator")
return response.json();
}

if (!response.ok) {
throw new Error(`Failed to sync user: ${response.statusText}`);
}
Expand Down
15 changes: 8 additions & 7 deletions ui/src/api/statesman_orgs.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@


export async function syncOrgToStatesman(orgId: string, orgName: string, userId: string, adminEmail: string) {
export async function syncOrgToStatesman(orgId: string, orgName: string, displayName: string, userId: string, adminEmail: string) {
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/orgs`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
'X-Org-ID': orgId,
'X-Org-ID': "",
'X-User-ID': userId,
'X-Email': adminEmail,
},
body: JSON.stringify({
"org_id": orgId,
"external_org_id": orgId,
"name": orgName,
"display_name": displayName,
"created_by": adminEmail,
})
})

console.log(orgId)
console.log(orgName)
console.log(userId)
console.log(adminEmail)
if (response.status === 409) {
console.log("User already exists in statesman")
return response.json();
}

if (!response.ok) {
throw new Error(`Failed to sync organization to statesman: ${response.statusText}`);
Expand Down
61 changes: 58 additions & 3 deletions ui/src/api/statesman_serverFunctions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createServerFn } from "@tanstack/react-start"
import { createUnit, getUnit, listUnits } from "./statesman_units"
import { createUnit, getUnit, listUnits, getUnitVersions, unlockUnit, lockUnit, getUnitStatus, deleteUnit, downloadLatestState, forcePushState, restoreUnitStateVersion } from "./statesman_units"

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

export const createUnitFn = createServerFn({method: 'POST'})
export const getUnitVersionsFn = createServerFn({method: 'GET'})
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
.handler(async ({ data }) => {
const unitVersions : any = await getUnitVersions(data.organisationId, data.userId, data.email, data.unitId)
return unitVersions
})

export const lockUnitFn = createServerFn({method: 'POST'})
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
.handler(async ({ data }) => {
const unit : any = await createUnit(data.organisationId, data.userId, data.email, data.unitId)
const unit : any = await lockUnit(data.organisationId, data.userId, data.email, data.unitId)
return unit
})

export const unlockUnitFn = createServerFn({method: 'POST'})
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
.handler(async ({ data }) => {
const unit : any = await unlockUnit(data.organisationId, data.userId, data.email, data.unitId)
return unit
})

export const downloadLatestStateFn = createServerFn({method: 'GET'})
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
.handler(async ({ data }) => {
const state : any = await downloadLatestState(data.organisationId, data.userId, data.email, data.unitId)
return state
})

export const forcePushStateFn = createServerFn({method: 'POST'})
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string, state: string}) => data)
.handler(async ({ data }) => {
const state : any = await forcePushState(data.organisationId, data.userId, data.email, data.unitId, data.state)
return state
})

export const restoreUnitStateVersionFn = createServerFn({method: 'POST'})
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string, timestamp: string, lockId: string}) => data)
.handler(async ({ data }) => {
const state : any = await restoreUnitStateVersion(data.organisationId, data.userId, data.email, data.unitId, data.timestamp, data.lockId)
return state
})

export const getUnitStatusFn = createServerFn({method: 'GET'})
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
.handler(async ({ data }) => {
const unitStatus : any = await getUnitStatus(data.organisationId, data.userId, data.email, data.unitId)
return unitStatus
})

export const createUnitFn = createServerFn({method: 'POST'})
.inputValidator((data : {userId: string, organisationId: string, email: string, name: string}) => data)
.handler(async ({ data }) => {
const unit : any = await createUnit(data.organisationId, data.userId, data.email, data.name)
return unit
})

export const deleteUnitFn = createServerFn({method: 'POST'})
.inputValidator((data : {userId: string, organisationId: string, email: string, unitId: string}) => data)
.handler(async ({ data }) => {
await deleteUnit(data.organisationId, data.userId, data.email, data.unitId)
})
148 changes: 145 additions & 3 deletions ui/src/api/statesman_units.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export async function listUnits(orgId: string, userId: string, email: string) {
if (!response.ok) {
throw new Error(`Failed to list units: ${response.statusText}`);
}

return response.json();
}

Expand All @@ -28,9 +27,135 @@ export async function getUnit(orgId: string, userId: string, email: string, unit
'X-Email': email,
},
});
if (!response.ok) {
throw new Error(`Failed to get unit: ${response.statusText}`);
}
return response.json();
}

export async function getUnitVersions(orgId: string, userId: string, email: string, unitId: string) {
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units/${unitId}/versions`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
'X-Org-ID': orgId,
'X-User-ID': userId,
'X-Email': email,
},
});
if (!response.ok) {
throw new Error(`Failed to get unit: ${response.statusText}`);
}
return response.json();
}


export async function lockUnit(orgId: string, userId: string, email: string, unitId: string) {
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units/${unitId}/lock`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
'X-Org-ID': orgId,
'X-User-ID': userId,
'X-Email': email,
},
});
if (!response.ok) {
throw new Error(`Failed to lock unit: ${response.statusText}`);
}
return response.json();
}

export async function unlockUnit(orgId: string, userId: string, email: string, unitId: string) {
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units/${unitId}/unlock`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
'X-Org-ID': orgId,
'X-User-ID': userId,
'X-Email': email,
},
});
if (!response.ok) {
throw new Error(`Failed to unlock unit: ${response.statusText}`);
}
return response.json();
}

export async function createUnit(orgId: string, userId: string, email: string, unitId: string) {
export async function forcePushState(orgId: string, userId: string, email: string, unitId: string, state: string) {
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units/${unitId}/upload`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
'X-Org-ID': orgId,
'X-User-ID': userId,
'X-Email': email,
},
body: state,
});
if (!response.ok) {
throw new Error(`Failed to force push state: ${response.statusText}`);
}
return response.json();
}

export async function downloadLatestState(orgId: string, userId: string, email: string, unitId: string) {
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units/${unitId}/download`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
'X-Org-ID': orgId,
'X-User-ID': userId,
'X-Email': email,
},
});
return response.json()
}

export async function restoreUnitStateVersion(orgId: string, userId: string, email: string, unitId: string, timestamp: string, lockId: string) {
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units/${unitId}/restore`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
'X-Org-ID': orgId,
'X-User-ID': userId,
'X-Email': email,
},
body: JSON.stringify({
timestamp: timestamp,
lock_id: lockId,
}),
});
if (!response.ok) {
throw new Error(`Failed to restore unit state version: ${response.statusText}`);
}
return response.json();
}

export async function getUnitStatus(orgId: string, userId: string, email: string, unitId: string) {
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units/${unitId}/status`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
'X-Org-ID': orgId,
'X-User-ID': userId,
'X-Email': email,
},
});
if (!response.ok) {
throw new Error(`Failed to get unit status: ${response.statusText}`);
}
return response.json();
}

export async function createUnit(orgId: string, userId: string, email: string, name: string) {
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units`, {
method: 'POST',
headers: {
Expand All @@ -41,7 +166,7 @@ export async function createUnit(orgId: string, userId: string, email: string, u
'X-Email': email,
},
body: JSON.stringify({
id: unitId,
name: name,
}),
});
console.log(response)
Expand All @@ -50,4 +175,21 @@ export async function createUnit(orgId: string, userId: string, email: string, u
}

return response.json();
}

export async function deleteUnit(orgId: string, userId: string, email: string, unitId: string) {
const response = await fetch(`${process.env.STATESMAN_BACKEND_URL}/internal/api/units/${unitId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.STATESMAN_BACKEND_WEBHOOK_SECRET}`,
'X-Org-ID': orgId,
'X-User-ID': userId,
'X-Email': email,
},
});
if (!response.ok) {
throw new Error(`Failed to delete unit: ${response.statusText}`);
}

}
Loading
Loading