Files
tiny-forge/internal/store/models.go
T
alexei.dolgolyov 0632f512e6
Build / build (push) Successful in 10m25s
feat(webhook): per-project and per-site webhook URLs
Replace the single global webhook secret with entity-scoped secrets stored
on each project and static site. Webhook-driven project autocreate is
removed — projects must exist before their URL can trigger deploys.

Also wires static-site webhooks (sync_trigger=push|tag), turning the
previously inert "push" trigger into a functional one: POST the site's
webhook URL from a Git provider and Tinyforge re-syncs on matching refs.

- Adds webhook_secret columns + unique indexes to projects and static_sites
- Per-entity GET/regenerate endpoints under /api/projects/{id}/webhook
  and /api/sites/{id}/webhook (admin-only)
- Removes /api/settings/webhook-url and the global webhook panel
- Reusable WebhookPanel Svelte component on both detail pages, i18n in en/ru
- Tests for matcher (siteRefMatches, ParseImageRef) and handler (project
  match/mismatch/404 and site push/manual/branch-skip)
2026-04-23 15:18:19 +03:00

286 lines
11 KiB
Go

package store
// Project represents a deployable application.
type Project struct {
ID string `json:"id"`
Name string `json:"name"`
Registry string `json:"registry"`
Image string `json:"image"`
Port int `json:"port"`
Healthcheck string `json:"healthcheck"`
Env string `json:"env"` // JSON-encoded map
Volumes string `json:"volumes"` // JSON-encoded map
NpmAccessListID int `json:"npm_access_list_id"` // per-project override, 0 = use global
WebhookSecret string `json:"-"` // per-project webhook secret; never serialized directly
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// Stage represents a deployment stage within a project (e.g. dev, rel, prod).
type Stage struct {
ID string `json:"id"`
ProjectID string `json:"project_id"`
Name string `json:"name"`
TagPattern string `json:"tag_pattern"`
AutoDeploy bool `json:"auto_deploy"`
MaxInstances int `json:"max_instances"`
Confirm bool `json:"confirm"`
EnableProxy bool `json:"enable_proxy"`
PromoteFrom string `json:"promote_from"`
Subdomain string `json:"subdomain"`
NotificationURL string `json:"notification_url"`
CpuLimit float64 `json:"cpu_limit"` // CPU cores (e.g., 0.5, 1, 2), 0 = unlimited
MemoryLimit int `json:"memory_limit"` // megabytes, 0 = unlimited
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// Registry represents a container image registry.
type Registry struct {
ID string `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
Type string `json:"type"`
Token string `json:"token"`
Owner string `json:"owner"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// Settings holds global application configuration (single-row pattern).
type Settings struct {
Domain string `json:"domain"`
ServerIP string `json:"server_ip"` // Docker host IP (for NPM remote forwarding)
PublicIP string `json:"public_ip"` // Public-facing IP for DNS A records (e.g., NPM/proxy host)
Network string `json:"network"`
SubdomainPattern string `json:"subdomain_pattern"`
NotificationURL string `json:"notification_url"`
NpmURL string `json:"npm_url"`
NpmEmail string `json:"npm_email"`
NpmPassword string `json:"npm_password"`
PollingInterval string `json:"polling_interval"`
BaseVolumePath string `json:"base_volume_path"`
SSLCertificateID int `json:"ssl_certificate_id"`
StaleThresholdDays int `json:"stale_threshold_days"`
AllowedVolumePaths string `json:"allowed_volume_paths"` // JSON array of allowed absolute paths
WildcardDNS bool `json:"wildcard_dns"`
DNSProvider string `json:"dns_provider"`
CloudflareAPIToken string `json:"cloudflare_api_token"`
CloudflareZoneID string `json:"cloudflare_zone_id"`
NpmRemote bool `json:"npm_remote"`
NpmAccessListID int `json:"npm_access_list_id"`
ProxyProvider string `json:"proxy_provider"`
TraefikEntrypoint string `json:"traefik_entrypoint"`
TraefikCertResolver string `json:"traefik_cert_resolver"`
TraefikNetwork string `json:"traefik_network"`
TraefikAPIURL string `json:"traefik_api_url"`
ImagePruneThresholdMB int `json:"image_prune_threshold_mb"`
BackupEnabled bool `json:"backup_enabled"`
BackupIntervalHours int `json:"backup_interval_hours"`
BackupRetentionCount int `json:"backup_retention_count"`
UpdatedAt string `json:"updated_at"`
}
// Backup represents a backup metadata record.
type Backup struct {
ID string `json:"id"`
Filename string `json:"filename"`
SizeBytes int64 `json:"size_bytes"`
BackupType string `json:"backup_type"` // "manual" or "auto"
CreatedAt string `json:"created_at"`
}
// DNSRecord tracks a DNS record managed by the application.
type DNSRecord struct {
ID string `json:"id"`
FQDN string `json:"fqdn"`
ProviderRecordID string `json:"provider_record_id"`
ConsumerType string `json:"consumer_type"` // "instance" or "standalone"
ConsumerID string `json:"consumer_id"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// Instance represents a running (or stopped) container for a project stage.
type Instance struct {
ID string `json:"id"`
StageID string `json:"stage_id"`
ProjectID string `json:"project_id"`
ContainerID string `json:"container_id"`
ImageTag string `json:"image_tag"`
Subdomain string `json:"subdomain"`
NpmProxyID int `json:"npm_proxy_id"`
ProxyRouteID string `json:"proxy_route_id"`
Status string `json:"status"` // running, stopped, failed, removing
Port int `json:"port"`
LastAliveAt string `json:"last_alive_at"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// Deploy represents a deployment attempt.
type Deploy struct {
ID string `json:"id"`
ProjectID string `json:"project_id"`
StageID string `json:"stage_id"`
InstanceID string `json:"instance_id"`
ImageTag string `json:"image_tag"`
Status string `json:"status"` // pending, pulling, starting, configuring_proxy, health_checking, success, failed, rolled_back
StartedAt string `json:"started_at"`
FinishedAt string `json:"finished_at"`
Error string `json:"error"`
}
// DeployLog is a single log entry for a deploy.
type DeployLog struct {
ID int64 `json:"id"`
DeployID string `json:"deploy_id"`
Message string `json:"message"`
Level string `json:"level"` // info, warn, error
CreatedAt string `json:"created_at"`
}
// StageEnv represents a per-stage environment variable override.
type StageEnv struct {
ID string `json:"id"`
StageID string `json:"stage_id"`
Key string `json:"key"`
Value string `json:"value"`
Encrypted bool `json:"encrypted"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// VolumeScope defines the sharing scope for a volume mount.
// Valid scopes: instance, stage, project, project_named, named, ephemeral.
type VolumeScope string
const (
VolumeScopeInstance VolumeScope = "instance"
VolumeScopeStage VolumeScope = "stage"
VolumeScopeProject VolumeScope = "project"
VolumeScopeProjectNamed VolumeScope = "project_named"
VolumeScopeNamed VolumeScope = "named"
VolumeScopeEphemeral VolumeScope = "ephemeral"
VolumeScopeAbsolute VolumeScope = "absolute"
)
// ValidVolumeScopes contains all valid scope values for validation.
var ValidVolumeScopes = []VolumeScope{
VolumeScopeInstance, VolumeScopeStage, VolumeScopeProject,
VolumeScopeProjectNamed, VolumeScopeNamed, VolumeScopeEphemeral,
VolumeScopeAbsolute,
}
// IsValidVolumeScope returns true if the given string is a valid scope.
func IsValidVolumeScope(s string) bool {
for _, v := range ValidVolumeScopes {
if string(v) == s {
return true
}
}
return false
}
// Volume represents a volume mount configuration for a project.
type Volume struct {
ID string `json:"id"`
ProjectID string `json:"project_id"`
Source string `json:"source"`
Target string `json:"target"`
Mode string `json:"mode,omitempty"` // legacy: shared/isolated — kept for DB compat
Scope string `json:"scope"` // instance, stage, project, project_named, named, ephemeral
Name string `json:"name"` // required for project_named and named scopes
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// StaticSite represents a static site deployed from a Git repository folder.
type StaticSite struct {
ID string `json:"id"`
Name string `json:"name"`
Provider string `json:"provider"` // "gitea", "github", "gitlab"; empty = autodetect
GiteaURL string `json:"gitea_url"` // base URL, e.g. https://git.example.com
RepoOwner string `json:"repo_owner"`
RepoName string `json:"repo_name"`
Branch string `json:"branch"`
FolderPath string `json:"folder_path"` // path within repo, e.g. "Pages"
AccessToken string `json:"access_token"` // encrypted; optional for public repos
Domain string `json:"domain"` // full domain for proxy
Mode string `json:"mode"` // "static" or "deno"
RenderMarkdown bool `json:"render_markdown"`
SyncTrigger string `json:"sync_trigger"` // "push", "tag", "manual"
TagPattern string `json:"tag_pattern"` // glob pattern for tag-based sync
ContainerID string `json:"container_id"`
ProxyRouteID string `json:"proxy_route_id"`
Status string `json:"status"` // idle, syncing, deployed, failed
LastSyncAt string `json:"last_sync_at"`
LastCommitSHA string `json:"last_commit_sha"`
Error string `json:"error"`
StorageEnabled bool `json:"storage_enabled"`
StorageLimitMB int `json:"storage_limit_mb"` // 0 = unlimited
WebhookSecret string `json:"-"` // per-site webhook secret; never serialized directly
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// StaticSiteSecret represents an encrypted environment variable for a static site's Deno backend.
type StaticSiteSecret struct {
ID string `json:"id"`
SiteID string `json:"site_id"`
Key string `json:"key"`
Value string `json:"value"`
Encrypted bool `json:"encrypted"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// Stack represents a docker-compose stack managed as a single deployable unit.
type Stack struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
ComposeProjectName string `json:"compose_project_name"` // `-p` arg for docker compose
Status string `json:"status"` // stopped, deploying, running, failed
Error string `json:"error"`
CurrentRevisionID string `json:"current_revision_id"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// StackRevision is an append-only record of a YAML version for a stack.
// Rollback = insert a new revision whose YAML is copied from an older one.
type StackRevision struct {
ID string `json:"id"`
StackID string `json:"stack_id"`
Revision int `json:"revision"` // monotonic per stack
YAML string `json:"yaml"`
Author string `json:"author"`
DeployID string `json:"deploy_id"`
Status string `json:"status"` // pending, success, failed
CreatedAt string `json:"created_at"`
}
// StackDeploy records a deployment attempt of a specific revision.
type StackDeploy struct {
ID string `json:"id"`
StackID string `json:"stack_id"`
RevisionID string `json:"revision_id"`
Status string `json:"status"` // pending, deploying, success, failed, rolled_back
Log string `json:"log"`
Error string `json:"error"`
StartedAt string `json:"started_at"`
FinishedAt string `json:"finished_at"`
}
// EventLog represents a persistent event log entry.
type EventLog struct {
ID int64 `json:"id"`
Source string `json:"source"`
Severity string `json:"severity"` // info, warn, error
Message string `json:"message"`
Metadata string `json:"metadata"` // JSON-encoded structured data
CreatedAt string `json:"created_at"`
}