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
12 changes: 10 additions & 2 deletions platform-api/internal/database/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,16 @@ func NewConnection(cfg *config.Database, slogger *slog.Logger) (*DB, error) {
return nil, fmt.Errorf("failed to create database directory: %w", err)
}

// Open SQLite connection to the api_platform.db file
db, err = sql.Open(DriverSQLite, cfg.Path)
// Open SQLite connection to the api_platform.db file.
// _loc=UTC forces the driver to interpret and return timestamps in UTC,
// matching the time.Now().UTC() values written throughout the codebase.
dsn := cfg.Path
if strings.Contains(dsn, "?") {
dsn += "&_loc=UTC"
} else {
dsn += "?_loc=UTC"
}
db, err = sql.Open(DriverSQLite, dsn)
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
}
Expand Down
14 changes: 7 additions & 7 deletions platform-api/internal/database/schema.postgres.sql
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,9 @@ CREATE TABLE IF NOT EXISTS artifact_gateway_mappings (
gateway_uuid VARCHAR(40) NOT NULL,
metadata BYTEA,
created_by VARCHAR(200),
created_at TIMESTAMPTZ DEFAULT NOW(),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_by VARCHAR(200),
updated_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (organization_uuid, artifact_uuid, gateway_uuid),
FOREIGN KEY (artifact_uuid) REFERENCES artifacts(uuid) ON DELETE CASCADE,
FOREIGN KEY (organization_uuid) REFERENCES organizations(uuid) ON DELETE CASCADE,
Expand Down Expand Up @@ -540,9 +540,9 @@ CREATE TABLE IF NOT EXISTS secrets (
type VARCHAR(20) NOT NULL DEFAULT 'GENERIC',
provider VARCHAR(20) NOT NULL DEFAULT 'IN_BUILT',
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMP NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(255),
updated_at TIMESTAMP NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_by VARCHAR(255),
UNIQUE (organization_uuid, handle),
FOREIGN KEY (organization_uuid) REFERENCES organizations(uuid) ON DELETE CASCADE
Expand All @@ -568,7 +568,7 @@ CREATE TABLE IF NOT EXISTS artifact_secret_refs (
artifact_uuid VARCHAR(40) NOT NULL,
secret_handle VARCHAR(40) NOT NULL,
gateway_id VARCHAR(40) NOT NULL DEFAULT '',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (organization_uuid, artifact_uuid, secret_handle, gateway_id),
FOREIGN KEY (organization_uuid) REFERENCES organizations(uuid) ON DELETE CASCADE,
FOREIGN KEY (artifact_uuid) REFERENCES artifacts(uuid) ON DELETE CASCADE
Expand All @@ -587,15 +587,15 @@ CREATE INDEX IF NOT EXISTS idx_asr_org_gateway
CREATE TABLE IF NOT EXISTS user_idp_references (
uuid VARCHAR(40) PRIMARY KEY,
idp_id VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
UNIQUE(idp_id)
);

-- User-to-organization membership, populated on org onboarding.
CREATE TABLE IF NOT EXISTS user_organization_mappings (
user_uuid VARCHAR(40) NOT NULL,
org_uuid VARCHAR(40) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_uuid, org_uuid),
FOREIGN KEY (user_uuid) REFERENCES user_idp_references(uuid) ON DELETE CASCADE,
FOREIGN KEY (org_uuid) REFERENCES organizations(uuid) ON DELETE CASCADE
Expand Down
4 changes: 2 additions & 2 deletions platform-api/internal/database/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -535,9 +535,9 @@ CREATE TABLE IF NOT EXISTS secrets (
type VARCHAR(20) NOT NULL DEFAULT 'GENERIC',
provider VARCHAR(20) NOT NULL DEFAULT 'IN_BUILT',
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
created_at DATETIME NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(255),
updated_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_by VARCHAR(255),
UNIQUE (organization_uuid, handle),
FOREIGN KEY (organization_uuid) REFERENCES organizations(uuid) ON DELETE CASCADE
Expand Down
4 changes: 2 additions & 2 deletions platform-api/internal/database/schema.sqlite.sql
Original file line number Diff line number Diff line change
Expand Up @@ -540,9 +540,9 @@ CREATE TABLE IF NOT EXISTS secrets (
type VARCHAR(20) NOT NULL DEFAULT 'GENERIC',
provider VARCHAR(20) NOT NULL DEFAULT 'IN_BUILT',
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
created_at DATETIME NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(255),
updated_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_by VARCHAR(255),
UNIQUE (organization_uuid, handle),
FOREIGN KEY (organization_uuid) REFERENCES organizations(uuid) ON DELETE CASCADE
Expand Down
7 changes: 6 additions & 1 deletion platform-api/internal/handler/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,12 @@ func (h *APIHandler) AddGatewaysToAPI(w http.ResponseWriter, r *http.Request) er
gatewayIds[i] = gw.GatewayId
}

gatewaysResponse, err := h.apiService.AddGatewaysToAPIByHandle(apiId, gatewayIds, orgId)
createdBy, err := resolveActorErr(r, h.identity, "associate gateways with API")
if err != nil {
return err
}

gatewaysResponse, err := h.apiService.AddGatewaysToAPIByHandle(apiId, gatewayIds, orgId, createdBy)
if err != nil {
return serviceError(err, fmt.Sprintf("failed to associate gateways with API %s in org %s", apiId, orgId))
}
Expand Down
7 changes: 6 additions & 1 deletion platform-api/internal/handler/gateway_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,12 @@ func (h *GatewayInternalAPIHandler) GetGatewaySecrets(w http.ResponseWriter, r *
"Invalid 'updatedAfter' parameter. Expected RFC3339 format."))
return
}
updatedAfter = &t
// Normalize to UTC so the comparison against UTC-stored updated_at
// values is a correct chronological comparison regardless of the
// offset the client sent (some drivers compare timestamp bind
// parameters as text, where mixed offsets sort incorrectly).
utc := t.UTC()
updatedAfter = &utc
}

includeValues := r.URL.Query().Get("includeValues") == "true"
Expand Down
24 changes: 18 additions & 6 deletions platform-api/internal/handler/llm_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,24 @@ import (
// using the shared deployment model.
type LLMProviderDeploymentHandler struct {
deploymentService *service.LLMProviderDeploymentService
identity *service.IdentityService
slogger *slog.Logger
}

// LLMProxyDeploymentHandler handles LLM proxy deployment endpoints
// using the shared deployment model.
type LLMProxyDeploymentHandler struct {
deploymentService *service.LLMProxyDeploymentService
identity *service.IdentityService
slogger *slog.Logger
}

func NewLLMProviderDeploymentHandler(deploymentService *service.LLMProviderDeploymentService, slogger *slog.Logger) *LLMProviderDeploymentHandler {
return &LLMProviderDeploymentHandler{deploymentService: deploymentService, slogger: slogger}
func NewLLMProviderDeploymentHandler(deploymentService *service.LLMProviderDeploymentService, identity *service.IdentityService, slogger *slog.Logger) *LLMProviderDeploymentHandler {
return &LLMProviderDeploymentHandler{deploymentService: deploymentService, identity: identity, slogger: slogger}
}

func NewLLMProxyDeploymentHandler(deploymentService *service.LLMProxyDeploymentService, slogger *slog.Logger) *LLMProxyDeploymentHandler {
return &LLMProxyDeploymentHandler{deploymentService: deploymentService, slogger: slogger}
func NewLLMProxyDeploymentHandler(deploymentService *service.LLMProxyDeploymentService, identity *service.IdentityService, slogger *slog.Logger) *LLMProxyDeploymentHandler {
return &LLMProxyDeploymentHandler{deploymentService: deploymentService, identity: identity, slogger: slogger}
}

// DeployLLMProvider handles POST /api/v0.9/llm-providers/{llmProviderId}/deployments
Expand Down Expand Up @@ -84,7 +86,12 @@ func (h *LLMProviderDeploymentHandler) DeployLLMProvider(w http.ResponseWriter,
return apperror.LLMProviderDeploymentValidationFailed.New("gatewayId is required")
}

deployment, err := h.deploymentService.DeployLLMProvider(providerId, &req, orgId)
createdBy, err := resolveActorErr(r, h.identity, "deploy LLM provider")
if err != nil {
return err
}

deployment, err := h.deploymentService.DeployLLMProvider(providerId, &req, orgId, createdBy)
if err != nil {
return serviceError(err, fmt.Sprintf("failed to deploy LLM provider %s", providerId))
}
Expand Down Expand Up @@ -272,7 +279,12 @@ func (h *LLMProxyDeploymentHandler) DeployLLMProxy(w http.ResponseWriter, r *htt
return apperror.LLMProxyDeploymentValidationFailed.New("gatewayId is required")
}

deployment, err := h.deploymentService.DeployLLMProxy(proxyId, &req, orgId)
createdBy, err := resolveActorErr(r, h.identity, "deploy LLM proxy")
if err != nil {
return err
}

deployment, err := h.deploymentService.DeployLLMProxy(proxyId, &req, orgId, createdBy)
if err != nil {
return serviceError(err, fmt.Sprintf("failed to deploy LLM proxy %s", proxyId))
}
Expand Down
20 changes: 18 additions & 2 deletions platform-api/internal/handler/organization.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ import (
type OrganizationHandler struct {
orgService *service.OrganizationService
identity *service.IdentityService
authzMode string
slogger *slog.Logger
}

func NewOrganizationHandler(orgService *service.OrganizationService, identity *service.IdentityService, slogger *slog.Logger) *OrganizationHandler {
func NewOrganizationHandler(orgService *service.OrganizationService, identity *service.IdentityService, authzMode string, slogger *slog.Logger) *OrganizationHandler {
return &OrganizationHandler{
orgService: orgService,
identity: identity,
authzMode: authzMode,
slogger: slogger,
}
}
Expand Down Expand Up @@ -164,7 +166,21 @@ func (h *OrganizationHandler) GetOrganizationByID(w http.ResponseWriter, r *http
func (h *OrganizationHandler) ListOrganizations(w http.ResponseWriter, r *http.Request) error {
limit, offset := parsePagination(r)

orgs, total, err := h.orgService.ListOrganizations(limit, offset)
// resolveActorErr also creates the caller's user_idp_references row on first
// use, which is what makes the membership heal below FK-safe.
performedBy, err := resolveActorErr(r, h.identity, "list organizations")
if err != nil {
return err
}

var orgs []api.Organization
var total int
if middleware.HasEffectiveScope(r, h.authzMode, "ap:organization:manage") {
orgs, total, err = h.orgService.ListOrganizations(limit, offset)
} else {
resolvedOrgUUID, _ := middleware.GetOrganizationFromRequest(r)
orgs, total, err = h.orgService.ListOrganizationsForUser(performedBy, resolvedOrgUUID, limit, offset)
}
if err != nil {
return apperror.Internal.Wrap(err).
WithLogMessage("failed to list organizations")
Expand Down
Loading
Loading