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
28 changes: 28 additions & 0 deletions services/control-plane/internal/scheduling/entity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Package scheduling provides persistence types for tenant-level cron schedules.
package scheduling

import (
"time"

"github.com/google/uuid"
)

// TenantScheduleEntity is the GORM entity for the tenant_schedule table.
// This table lives in per-tenant schemas and bridges manifest scheduled: triggers
// to the CronScheduler infrastructure.
type TenantScheduleEntity struct {
ID uuid.UUID `gorm:"column:id;type:uuid;primaryKey;default:gen_random_uuid()"`
ScheduleName string `gorm:"column:schedule_name;type:varchar(128);not null;uniqueIndex:uq_tenant_schedule_name"`
SagaName string `gorm:"column:saga_name;type:varchar(128);not null"`
CronExpr string `gorm:"column:cron_expr;type:varchar(64);not null"`
Enabled bool `gorm:"column:enabled;not null;default:true"`
ManifestVersionID *uuid.UUID `gorm:"column:manifest_version_id;type:uuid"`
Metadata *string `gorm:"column:metadata;type:jsonb"`
CreatedAt time.Time `gorm:"column:created_at;not null;autoCreateTime"`
UpdatedAt time.Time `gorm:"column:updated_at;not null;autoUpdateTime"`
}

// TableName returns the table name for GORM.
func (TenantScheduleEntity) TableName() string {
return "tenant_schedule"
}
41 changes: 41 additions & 0 deletions services/control-plane/internal/scheduling/entity_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package scheduling

import (
"testing"
"time"

"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)

func TestTenantScheduleEntity_TableName(t *testing.T) {
e := TenantScheduleEntity{}
assert.Equal(t, "tenant_schedule", e.TableName())
}

func TestTenantScheduleEntity_Fields(t *testing.T) {
id := uuid.New()
versionID := uuid.New()
meta := `{"key":"value"}`
now := time.Now()

e := TenantScheduleEntity{
ID: id,
ScheduleName: "daily-billing",
SagaName: "billing.run_daily",
CronExpr: "0 0 * * *",
Enabled: true,
ManifestVersionID: &versionID,
Metadata: &meta,
CreatedAt: now,
UpdatedAt: now,
}

assert.Equal(t, id, e.ID)
assert.Equal(t, "daily-billing", e.ScheduleName)
assert.Equal(t, "billing.run_daily", e.SagaName)
assert.Equal(t, "0 0 * * *", e.CronExpr)
assert.True(t, e.Enabled)
assert.Equal(t, &versionID, e.ManifestVersionID)
assert.Equal(t, &meta, e.Metadata)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-- Tenant Schedule table for manifest-driven cron scheduling.
-- Written by manifest application, read by ScheduleProvider.
-- Multi-tenancy: Schema-per-tenant architecture means no tenant_id column needed.
-- manifest_version_id is a soft cross-schema reference for audit/debugging - no FK constraint.

CREATE TABLE tenant_schedule (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
schedule_name VARCHAR(128) NOT NULL,
saga_name VARCHAR(128) NOT NULL,
cron_expr VARCHAR(64) NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
manifest_version_id UUID,
metadata JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_tenant_schedule_name UNIQUE (schedule_name)
);

-- Index for ScheduleProvider to load all enabled schedules efficiently.
CREATE INDEX idx_tenant_schedule_enabled ON tenant_schedule (enabled);
Comment thread
bjcoombs marked this conversation as resolved.

-- Index for manifest applier to look up schedules by saga.
CREATE INDEX idx_tenant_schedule_saga_name ON tenant_schedule (saga_name);
3 changes: 2 additions & 1 deletion services/control-plane/migrations/atlas.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
h1:lSjEVLKpE7I6DITUQ1EZ7wbhQMZuah202slFVirtai0=
h1:KbyhuEXJILRR48NzKoX/qDGV7Vy5t98dLYHIMzulk7w=
20260209000001_staff_identity.sql h1:OR9Cq4l2MfrW7It8jLDt0Kzg/z1th9KJdSlZEVpOhqA=
20260209000002_create_manifest_versions.sql h1:+HzLzVoK1e/nPNN9wb56hb1yxxC9K2I53CpwAks6nCE=
20260209000004_manifest_apply_jobs.sql h1:nq4MOflgbXAfdltITMKqKWHg9RuUXVs0mrIa2CDkIig=
Expand All @@ -13,3 +13,4 @@ h1:lSjEVLKpE7I6DITUQ1EZ7wbhQMZuah202slFVirtai0=
20260401000001_add_version_varchar_column.sql h1:s75he7ik8NXkbjj6GM5sf/qFz0ylve7ktHQBdn5MVc4=
20260401000002_migrate_version_to_varchar.sql h1:A+WRTgqwZfgeU8VfQqmYUeXkzb27ArcAplSIMqmO0xs=
20260401000003_rename_version_column.sql h1:1JEVT+cGxBeMuVImnP3/B5RN0YkUoIvN2147VgBydwk=
20260406000001_create_tenant_schedule.sql h1:w88vPDnJfitk+GRwtFHh1XV9iQf688+NvHVENMDu2gg=
Loading