Files
gamertan 92a66db3df docs: publish Preview 19 dogfood evidence
Export the reviewed allowlisted snapshot from private source commit 05928cebd01b586cf9e9d4b8c8537a7605a6068c. This records the exact candidate, bounded capacity result, stateful migration scratch requirement, authenticated batch identity proof, and immediate live acceptance evidence.

AI-Assisted: OpenAI Codex
Signed-off-by: Cole Speelman <crspeelman@gmail.com>
2026-08-18 21:47:08 -04:00

599 lines
25 KiB
Go

// SPDX-License-Identifier: AGPL-3.0-only
package storage
import (
"context"
"crypto/rand"
"database/sql"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"sort"
"strings"
"time"
"unicode/utf8"
"gamertan.com/observatory/internal/model"
"gamertan.com/observatory/internal/query"
)
const (
SavedQueryVersion = 1
DashboardVersion = 1
MaxDashboardPanels = 16
)
var ErrDashboardRevisionConflict = errors.New("dashboard revision conflict")
type ResourceScope struct {
ProjectID string `json:"project_id,omitempty"`
EnvironmentID string `json:"environment_id,omitempty"`
ServiceID string `json:"service_id,omitempty"`
}
type SavedQuery struct {
Version int `json:"version"`
Revision int `json:"revision"`
OrganizationID string `json:"organization_id"`
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Query string `json:"query"`
AST query.AST `json:"ast"`
Scope ResourceScope `json:"scope"`
CreatedBy string `json:"created_by"`
UpdatedBy string `json:"updated_by"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type SavedQueryInput struct {
ID string
ExpectedRevision int
OrganizationID string
Name string
Description string
Query string
Scope ResourceScope
ActorUserID string
MaxRows int
}
type DashboardPanel struct {
ID string `json:"id"`
Position int `json:"position"`
Title string `json:"title"`
Visualization string `json:"visualization"`
SavedQueryID string `json:"saved_query_id"`
}
type Dashboard struct {
Version int `json:"version"`
Revision int `json:"revision"`
OrganizationID string `json:"organization_id"`
ID string `json:"id"`
Slug string `json:"slug"`
Name string `json:"name"`
Description string `json:"description"`
Panels []DashboardPanel `json:"panels"`
CreatedBy string `json:"created_by"`
UpdatedBy string `json:"updated_by"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type DashboardInput struct {
ID string
ExpectedRevision int
OrganizationID string
Slug string
Name string
Description string
Panels []DashboardPanel
ActorUserID string
}
type DashboardExport struct {
Version int `json:"version"`
Dashboard DashboardDefinition `json:"dashboard"`
SavedQueries []SavedQueryDefinition `json:"saved_queries"`
}
type DashboardDefinition struct {
Slug string `json:"slug"`
Name string `json:"name"`
Description string `json:"description"`
Panels []DashboardPanel `json:"panels"`
}
type SavedQueryDefinition struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Query string `json:"query"`
Scope ResourceScope `json:"scope"`
}
type DashboardImportInput struct {
OrganizationID string
ActorUserID string
MaxRows int
Bundle DashboardExport
}
func (s *Store) SaveQuery(ctx context.Context, input SavedQueryInput, now time.Time) (SavedQuery, error) {
ast, err := validateSavedQueryInput(input, now)
if err != nil {
return SavedQuery{}, err
}
astJSON, err := json.Marshal(ast)
if err != nil {
return SavedQuery{}, errors.New("encode saved query AST")
}
timestamp := now.UTC().Format(time.RFC3339Nano)
if input.ID == "" {
input.ID, err = storageID("query")
if err != nil {
return SavedQuery{}, err
}
_, err = s.control.ExecContext(ctx, `INSERT INTO saved_queries(organization_id,id,version,revision,name,description,query_text,ast_json,project_id,environment_id,service_id,created_by,updated_by,created_at,updated_at) VALUES(?,?,1,1,?,?,?,?,?,?,?,?,?,?,?)`, input.OrganizationID, input.ID, input.Name, input.Description, input.Query, string(astJSON), input.Scope.ProjectID, input.Scope.EnvironmentID, input.Scope.ServiceID, input.ActorUserID, input.ActorUserID, timestamp, timestamp)
if err != nil {
return SavedQuery{}, errors.New("create saved query")
}
} else {
if input.ExpectedRevision < 1 || model.ValidateSourceID(input.ID) != nil {
return SavedQuery{}, errors.New("saved query revision input is invalid")
}
result, updateErr := s.control.ExecContext(ctx, `UPDATE saved_queries SET revision=revision+1,name=?,description=?,query_text=?,ast_json=?,project_id=?,environment_id=?,service_id=?,updated_by=?,updated_at=? WHERE organization_id=? AND id=? AND revision=?`, input.Name, input.Description, input.Query, string(astJSON), input.Scope.ProjectID, input.Scope.EnvironmentID, input.Scope.ServiceID, input.ActorUserID, timestamp, input.OrganizationID, input.ID, input.ExpectedRevision)
if updateErr != nil {
return SavedQuery{}, errors.New("update saved query")
}
if changed, _ := result.RowsAffected(); changed != 1 {
return SavedQuery{}, errors.New("saved query revision conflict")
}
}
return s.SavedQuery(ctx, input.OrganizationID, input.ID)
}
func (s *Store) SavedQuery(ctx context.Context, organizationID, id string) (SavedQuery, error) {
if model.ValidateSourceID(organizationID) != nil || model.ValidateSourceID(id) != nil {
return SavedQuery{}, errors.New("saved query identity is invalid")
}
row := s.control.QueryRowContext(ctx, `SELECT version,revision,name,description,query_text,ast_json,project_id,environment_id,service_id,created_by,updated_by,created_at,updated_at FROM saved_queries WHERE organization_id=? AND id=?`, organizationID, id)
return scanSavedQuery(row, organizationID, id)
}
func (s *Store) SavedQueries(ctx context.Context, organizationID string) ([]SavedQuery, error) {
if model.ValidateSourceID(organizationID) != nil {
return nil, errors.New("invalid organization identifier")
}
rows, err := s.control.QueryContext(ctx, `SELECT id,version,revision,name,description,query_text,ast_json,project_id,environment_id,service_id,created_by,updated_by,created_at,updated_at FROM saved_queries WHERE organization_id=? ORDER BY name,id`, organizationID)
if err != nil {
return nil, errors.New("list saved queries")
}
defer rows.Close()
var result []SavedQuery
for rows.Next() {
var id string
var value SavedQuery
var astJSON, createdAt, updatedAt string
value.OrganizationID = organizationID
if err = rows.Scan(&id, &value.Version, &value.Revision, &value.Name, &value.Description, &value.Query, &astJSON, &value.Scope.ProjectID, &value.Scope.EnvironmentID, &value.Scope.ServiceID, &value.CreatedBy, &value.UpdatedBy, &createdAt, &updatedAt); err != nil {
return nil, errors.New("read saved query")
}
value.ID = id
if err = decodeSavedQuery(&value, astJSON, createdAt, updatedAt); err != nil {
return nil, err
}
result = append(result, value)
}
if err = rows.Err(); err != nil {
return nil, errors.New("list saved queries")
}
return result, nil
}
type rowScanner interface{ Scan(...any) error }
func scanSavedQuery(row rowScanner, organizationID, id string) (SavedQuery, error) {
value := SavedQuery{OrganizationID: organizationID, ID: id}
var astJSON, createdAt, updatedAt string
if err := row.Scan(&value.Version, &value.Revision, &value.Name, &value.Description, &value.Query, &astJSON, &value.Scope.ProjectID, &value.Scope.EnvironmentID, &value.Scope.ServiceID, &value.CreatedBy, &value.UpdatedBy, &createdAt, &updatedAt); errors.Is(err, sql.ErrNoRows) {
return SavedQuery{}, errors.New("saved query not found")
} else if err != nil {
return SavedQuery{}, errors.New("read saved query")
}
if err := decodeSavedQuery(&value, astJSON, createdAt, updatedAt); err != nil {
return SavedQuery{}, err
}
return value, nil
}
func decodeSavedQuery(value *SavedQuery, astJSON, createdAt, updatedAt string) error {
if value.Version != SavedQueryVersion || value.Revision < 1 || model.ValidateSourceID(value.OrganizationID) != nil || model.ValidateSourceID(value.ID) != nil || model.ValidateSourceID(value.CreatedBy) != nil || model.ValidateSourceID(value.UpdatedBy) != nil || !validResourceScope(value.Scope) || !boundedText(value.Name, 128, false) || !boundedText(value.Description, 1024, true) {
return errors.New("stored saved query is invalid")
}
parsed, err := query.Parse(value.Query, 100_000)
if err != nil || json.Unmarshal([]byte(astJSON), &value.AST) != nil || hydrateSavedAST(&value.AST) != nil || query.Validate(value.AST, 100_000) != nil {
return errors.New("stored saved query AST is invalid")
}
parsedJSON, _ := json.Marshal(parsed)
storedJSON, _ := json.Marshal(value.AST)
if string(parsedJSON) != string(storedJSON) {
return errors.New("stored saved query AST does not match query text")
}
value.CreatedAt, err = time.Parse(time.RFC3339Nano, createdAt)
if err != nil {
return errors.New("stored saved query created time is invalid")
}
value.UpdatedAt, err = time.Parse(time.RFC3339Nano, updatedAt)
if err != nil || value.UpdatedAt.Before(value.CreatedAt) {
return errors.New("stored saved query updated time is invalid")
}
return nil
}
func hydrateSavedAST(ast *query.AST) error {
var err error
if ast.WindowText != "" {
ast.Window, err = time.ParseDuration(ast.WindowText)
}
if err == nil && ast.BucketText != "" {
ast.Bucket, err = time.ParseDuration(ast.BucketText)
}
if err != nil {
return errors.New("stored saved query duration is invalid")
}
return nil
}
func (s *Store) SaveDashboard(ctx context.Context, input DashboardInput, now time.Time) (Dashboard, error) {
if err := validateDashboardInput(input, now); err != nil {
return Dashboard{}, err
}
if input.ExpectedRevision == 0 && input.ID != "" {
return Dashboard{}, errors.New("new dashboard identity is server-generated")
}
if input.ExpectedRevision > 0 && model.ValidateSourceID(input.ID) != nil {
return Dashboard{}, errors.New("dashboard revision input is invalid")
}
var err error
if input.ID == "" {
input.ID, err = storageID("dashboard")
if err != nil {
return Dashboard{}, err
}
}
panels := append([]DashboardPanel(nil), input.Panels...)
for index := range panels {
if panels[index].ID == "" {
panels[index].ID, err = storageID("panel")
if err != nil {
return Dashboard{}, err
}
}
}
tx, err := s.control.BeginTx(ctx, nil)
if err != nil {
return Dashboard{}, errors.New("begin dashboard update")
}
defer tx.Rollback()
timestamp := now.UTC().Format(time.RFC3339Nano)
if input.ExpectedRevision == 0 {
_, err = tx.ExecContext(ctx, `INSERT INTO dashboards(organization_id,id,version,revision,slug,name,description,created_by,updated_by,created_at,updated_at) VALUES(?,?,1,1,?,?,?,?,?,?,?)`, input.OrganizationID, input.ID, input.Slug, input.Name, input.Description, input.ActorUserID, input.ActorUserID, timestamp, timestamp)
if err != nil {
return Dashboard{}, errors.New("create dashboard")
}
} else {
if model.ValidateSourceID(input.ID) != nil || input.ExpectedRevision < 1 {
return Dashboard{}, errors.New("dashboard revision input is invalid")
}
result, updateErr := tx.ExecContext(ctx, `UPDATE dashboards SET revision=revision+1,slug=?,name=?,description=?,updated_by=?,updated_at=? WHERE organization_id=? AND id=? AND revision=?`, input.Slug, input.Name, input.Description, input.ActorUserID, timestamp, input.OrganizationID, input.ID, input.ExpectedRevision)
if updateErr != nil {
return Dashboard{}, errors.New("update dashboard")
}
if changed, _ := result.RowsAffected(); changed != 1 {
return Dashboard{}, ErrDashboardRevisionConflict
}
if _, err = tx.ExecContext(ctx, `DELETE FROM dashboard_panels WHERE organization_id=? AND dashboard_id=?`, input.OrganizationID, input.ID); err != nil {
return Dashboard{}, errors.New("replace dashboard panels")
}
}
for _, panel := range panels {
if _, err = tx.ExecContext(ctx, `INSERT INTO dashboard_panels(organization_id,dashboard_id,id,position,title,visualization,saved_query_id) VALUES(?,?,?,?,?,?,?)`, input.OrganizationID, input.ID, panel.ID, panel.Position, panel.Title, panel.Visualization, panel.SavedQueryID); err != nil {
return Dashboard{}, errors.New("store dashboard panel")
}
}
if err = tx.Commit(); err != nil {
return Dashboard{}, errors.New("commit dashboard update")
}
return s.Dashboard(ctx, input.OrganizationID, input.Slug)
}
func (s *Store) Dashboard(ctx context.Context, organizationID, slug string) (Dashboard, error) {
if model.ValidateSourceID(organizationID) != nil || !validSlug(slug) {
return Dashboard{}, errors.New("dashboard identity is invalid")
}
value := Dashboard{OrganizationID: organizationID}
var createdAt, updatedAt string
err := s.control.QueryRowContext(ctx, `SELECT id,version,revision,name,description,created_by,updated_by,created_at,updated_at FROM dashboards WHERE organization_id=? AND slug=?`, organizationID, slug).Scan(&value.ID, &value.Version, &value.Revision, &value.Name, &value.Description, &value.CreatedBy, &value.UpdatedBy, &createdAt, &updatedAt)
if errors.Is(err, sql.ErrNoRows) {
return Dashboard{}, errors.New("dashboard not found")
}
if err != nil {
return Dashboard{}, errors.New("read dashboard")
}
value.Slug = slug
value.CreatedAt, err = time.Parse(time.RFC3339Nano, createdAt)
if err != nil {
return Dashboard{}, errors.New("stored dashboard created time is invalid")
}
value.UpdatedAt, err = time.Parse(time.RFC3339Nano, updatedAt)
if err != nil || value.UpdatedAt.Before(value.CreatedAt) {
return Dashboard{}, errors.New("stored dashboard updated time is invalid")
}
rows, err := s.control.QueryContext(ctx, `SELECT id,position,title,visualization,saved_query_id FROM dashboard_panels WHERE organization_id=? AND dashboard_id=? ORDER BY position,id`, organizationID, value.ID)
if err != nil {
return Dashboard{}, errors.New("read dashboard panels")
}
defer rows.Close()
for rows.Next() {
var panel DashboardPanel
if err = rows.Scan(&panel.ID, &panel.Position, &panel.Title, &panel.Visualization, &panel.SavedQueryID); err != nil || validatePanel(panel) != nil {
return Dashboard{}, errors.New("stored dashboard panel is invalid")
}
value.Panels = append(value.Panels, panel)
}
if err = rows.Err(); err != nil || validateDashboard(value) != nil {
return Dashboard{}, errors.New("stored dashboard is invalid")
}
return value, nil
}
func (s *Store) Dashboards(ctx context.Context, organizationID string) ([]Dashboard, error) {
if model.ValidateSourceID(organizationID) != nil {
return nil, errors.New("invalid organization identifier")
}
rows, err := s.control.QueryContext(ctx, `SELECT slug FROM dashboards WHERE organization_id=? ORDER BY name,slug`, organizationID)
if err != nil {
return nil, errors.New("list dashboards")
}
var slugs []string
for rows.Next() {
var slug string
if err = rows.Scan(&slug); err != nil {
_ = rows.Close()
return nil, errors.New("list dashboards")
}
slugs = append(slugs, slug)
}
if err = rows.Close(); err != nil || rows.Err() != nil {
return nil, errors.New("list dashboards")
}
result := make([]Dashboard, 0, len(slugs))
for _, slug := range slugs {
value, loadErr := s.Dashboard(ctx, organizationID, slug)
if loadErr != nil {
return nil, loadErr
}
result = append(result, value)
}
return result, nil
}
func (s *Store) ExportDashboard(ctx context.Context, organizationID, slug string) (DashboardExport, error) {
dashboard, err := s.Dashboard(ctx, organizationID, slug)
if err != nil {
return DashboardExport{}, err
}
queries := make([]SavedQueryDefinition, 0, len(dashboard.Panels))
seen := map[string]bool{}
for _, panel := range dashboard.Panels {
if seen[panel.SavedQueryID] {
continue
}
value, loadErr := s.SavedQuery(ctx, organizationID, panel.SavedQueryID)
if loadErr != nil {
return DashboardExport{}, loadErr
}
seen[value.ID] = true
queries = append(queries, SavedQueryDefinition{ID: value.ID, Name: value.Name, Description: value.Description, Query: value.Query, Scope: value.Scope})
}
sort.Slice(queries, func(i, j int) bool { return queries[i].ID < queries[j].ID })
definition := DashboardDefinition{Slug: dashboard.Slug, Name: dashboard.Name, Description: dashboard.Description, Panels: dashboard.Panels}
return DashboardExport{Version: DashboardVersion, Dashboard: definition, SavedQueries: queries}, nil
}
// ImportDashboard validates one source-control-safe export and creates its
// queries, dashboard, and panels atomically with new server-owned identities.
// Tenant and actor metadata are supplied independently of the bundle.
func (s *Store) ImportDashboard(ctx context.Context, input DashboardImportInput, now time.Time) (Dashboard, error) {
if input.Bundle.Version != DashboardVersion || model.ValidateSourceID(input.OrganizationID) != nil || model.ValidateSourceID(input.ActorUserID) != nil || input.MaxRows < 1 || input.MaxRows > 100_000 || now.IsZero() || len(input.Bundle.SavedQueries) > MaxDashboardPanels {
return Dashboard{}, errors.New("dashboard import is invalid")
}
queryIDs := make(map[string]string, len(input.Bundle.SavedQueries))
type importedQuery struct {
definition SavedQueryDefinition
id string
astJSON string
ast query.AST
}
queries := make([]importedQuery, 0, len(input.Bundle.SavedQueries))
for _, definition := range input.Bundle.SavedQueries {
if model.ValidateSourceID(definition.ID) != nil || queryIDs[definition.ID] != "" {
return Dashboard{}, errors.New("dashboard import query identity is invalid or duplicated")
}
ast, err := validateSavedQueryInput(SavedQueryInput{OrganizationID: input.OrganizationID, ActorUserID: input.ActorUserID, MaxRows: input.MaxRows, Name: definition.Name, Description: definition.Description, Query: definition.Query, Scope: definition.Scope}, now)
if err != nil {
return Dashboard{}, err
}
encoded, err := json.Marshal(ast)
if err != nil {
return Dashboard{}, errors.New("encode imported saved query AST")
}
id, err := storageID("query")
if err != nil {
return Dashboard{}, err
}
queryIDs[definition.ID] = id
queries = append(queries, importedQuery{definition: definition, id: id, astJSON: string(encoded), ast: ast})
}
queryASTs := make(map[string]query.AST, len(queries))
for _, imported := range queries {
queryASTs[imported.definition.ID] = imported.ast
}
panels := make([]DashboardPanel, len(input.Bundle.Dashboard.Panels))
referenced := make(map[string]bool, len(queries))
for index, panel := range input.Bundle.Dashboard.Panels {
mapped := queryIDs[panel.SavedQueryID]
if mapped == "" {
return Dashboard{}, errors.New("dashboard import panel references an unknown saved query")
}
ast := queryASTs[panel.SavedQueryID]
if panel.Visualization == "stat" && ast.Summary == nil || panel.Visualization == "timeseries" && (ast.Summary == nil || ast.Bucket <= 0) {
return Dashboard{}, errors.New("dashboard import presentation does not match its saved query")
}
panelID, err := storageID("panel")
if err != nil {
return Dashboard{}, err
}
panel.ID, panel.SavedQueryID = panelID, mapped
panels[index] = panel
referenced[panel.SavedQueryID] = true
}
if len(referenced) != len(queries) {
return Dashboard{}, errors.New("dashboard import contains an unreferenced saved query")
}
dashboardID, err := storageID("dashboard")
if err != nil {
return Dashboard{}, err
}
dashboardInput := DashboardInput{ID: dashboardID, ExpectedRevision: 1, OrganizationID: input.OrganizationID, ActorUserID: input.ActorUserID, Slug: input.Bundle.Dashboard.Slug, Name: input.Bundle.Dashboard.Name, Description: input.Bundle.Dashboard.Description, Panels: panels}
if err = validateDashboardInput(dashboardInput, now); err != nil {
return Dashboard{}, err
}
tx, err := s.control.BeginTx(ctx, nil)
if err != nil {
return Dashboard{}, errors.New("begin dashboard import")
}
defer tx.Rollback()
timestamp := now.UTC().Format(time.RFC3339Nano)
for _, imported := range queries {
definition := imported.definition
_, err = tx.ExecContext(ctx, `INSERT INTO saved_queries(organization_id,id,version,revision,name,description,query_text,ast_json,project_id,environment_id,service_id,created_by,updated_by,created_at,updated_at) VALUES(?,?,1,1,?,?,?,?,?,?,?,?,?,?,?)`, input.OrganizationID, imported.id, definition.Name, definition.Description, strings.TrimSpace(definition.Query), imported.astJSON, definition.Scope.ProjectID, definition.Scope.EnvironmentID, definition.Scope.ServiceID, input.ActorUserID, input.ActorUserID, timestamp, timestamp)
if err != nil {
return Dashboard{}, errors.New("import saved query")
}
}
_, err = tx.ExecContext(ctx, `INSERT INTO dashboards(organization_id,id,version,revision,slug,name,description,created_by,updated_by,created_at,updated_at) VALUES(?,?,1,1,?,?,?,?,?,?,?)`, input.OrganizationID, dashboardID, input.Bundle.Dashboard.Slug, input.Bundle.Dashboard.Name, input.Bundle.Dashboard.Description, input.ActorUserID, input.ActorUserID, timestamp, timestamp)
if err != nil {
return Dashboard{}, errors.New("import dashboard")
}
for _, panel := range panels {
if _, err = tx.ExecContext(ctx, `INSERT INTO dashboard_panels(organization_id,dashboard_id,id,position,title,visualization,saved_query_id) VALUES(?,?,?,?,?,?,?)`, input.OrganizationID, dashboardID, panel.ID, panel.Position, panel.Title, panel.Visualization, panel.SavedQueryID); err != nil {
return Dashboard{}, errors.New("import dashboard panel")
}
}
if err = tx.Commit(); err != nil {
return Dashboard{}, errors.New("commit dashboard import")
}
return s.Dashboard(ctx, input.OrganizationID, input.Bundle.Dashboard.Slug)
}
func validateSavedQueryInput(input SavedQueryInput, now time.Time) (query.AST, error) {
if model.ValidateSourceID(input.OrganizationID) != nil || model.ValidateSourceID(input.ActorUserID) != nil || !boundedText(input.Name, 128, false) || !boundedText(input.Description, 1024, true) || !validResourceScope(input.Scope) || input.MaxRows < 1 || input.MaxRows > 100_000 || now.IsZero() {
return query.AST{}, errors.New("saved query input is invalid")
}
ast, err := query.Parse(strings.TrimSpace(input.Query), input.MaxRows)
if err != nil {
return query.AST{}, fmt.Errorf("saved query: %w", err)
}
return ast, nil
}
func validateDashboardInput(input DashboardInput, now time.Time) error {
if model.ValidateSourceID(input.OrganizationID) != nil || model.ValidateSourceID(input.ActorUserID) != nil || !validSlug(input.Slug) || !boundedText(input.Name, 128, false) || !boundedText(input.Description, 1024, true) || len(input.Panels) > MaxDashboardPanels || now.IsZero() {
return errors.New("dashboard input is invalid")
}
positions := map[int]bool{}
ids := map[string]bool{}
for _, panel := range input.Panels {
if err := validatePanelInput(panel); err != nil || positions[panel.Position] || panel.ID != "" && ids[panel.ID] {
return errors.New("dashboard panel input is invalid")
}
positions[panel.Position] = true
if panel.ID != "" {
ids[panel.ID] = true
}
}
return nil
}
func validateDashboard(value Dashboard) error {
if value.Version != DashboardVersion || value.Revision < 1 || model.ValidateSourceID(value.OrganizationID) != nil || model.ValidateSourceID(value.ID) != nil || model.ValidateSourceID(value.CreatedBy) != nil || model.ValidateSourceID(value.UpdatedBy) != nil || !validSlug(value.Slug) || !boundedText(value.Name, 128, false) || !boundedText(value.Description, 1024, true) || len(value.Panels) > MaxDashboardPanels {
return errors.New("dashboard is invalid")
}
return nil
}
func validatePanel(panel DashboardPanel) error {
if model.ValidateSourceID(panel.ID) != nil {
return errors.New("dashboard panel is invalid")
}
return validatePanelInput(panel)
}
func validatePanelInput(panel DashboardPanel) error {
if panel.ID != "" && model.ValidateSourceID(panel.ID) != nil || panel.Position < 0 || panel.Position >= 64 || !boundedText(panel.Title, 128, false) || model.ValidateSourceID(panel.SavedQueryID) != nil {
return errors.New("dashboard panel is invalid")
}
switch panel.Visualization {
case "table", "stat", "timeseries":
return nil
default:
return errors.New("dashboard panel visualization is invalid")
}
}
func validResourceScope(scope ResourceScope) bool {
values := []string{scope.ProjectID, scope.EnvironmentID, scope.ServiceID}
for _, value := range values {
if value != "" && model.ValidateSourceID(value) != nil {
return false
}
}
if scope.EnvironmentID != "" && scope.ProjectID == "" {
return false
}
if scope.ServiceID != "" && (scope.ProjectID == "" || scope.EnvironmentID == "") {
return false
}
return true
}
func boundedText(value string, maximum int, empty bool) bool {
return utf8.ValidString(value) && !strings.ContainsAny(value, "\x00\r\n") && len(value) <= maximum && (empty || value != "")
}
func validSlug(value string) bool {
if len(value) < 2 || len(value) > 63 || value[0] < 'a' || value[0] > 'z' {
return false
}
for _, character := range value {
if character != '-' && (character < 'a' || character > 'z') && (character < '0' || character > '9') {
return false
}
}
return true
}
func storageID(prefix string) (string, error) {
random := make([]byte, 18)
if _, err := rand.Read(random); err != nil {
return "", errors.New("cryptographic randomness unavailable")
}
return prefix + "_" + base64.RawURLEncoding.EncodeToString(random), nil
}