SCS — School Creation Service
Location: syncad/apps/scs
Stack: Go 1.21+
Port: 8080 (dev)
Overview
Automates the full onboarding of a new school: creates an isolated PostgreSQL database, provisions DNS, issues SSL, runs migrations, and registers the school in the central DB.
API
Protected by API key header: X-SCS-Key: <secret>
POST /school
Content-Type: application/json
X-SCS-Key: <admin-key>
{
"name": "Springfield High School",
"slug": "springfield-high",
"district": "Delhi",
"board": "CBSE",
"adminEmail": "admin@springfield.edu",
"phone": "+919876543210"
}
Response:
{
"id": "uuid",
"name": "Springfield High School",
"slug": "springfield-high",
"databaseName": "school_abc123",
"subdomain": "springfield-high.syncad.in",
"status": "provisioning",
"createdAt": "2024-04-01T10:00:00Z"
}
GET /school/:id
DELETE /school/:id # Tear down: drop DB, delete DNS, revoke SSL
Provisioning Steps
When POST /school is called, the SCS service executes:
| Step | Action | Duration |
|---|---|---|
| 1 | Validate slug uniqueness in central DB | ~100ms |
| 2 | Create PostgreSQL database school_{uuid} | ~500ms |
| 3 | Run Drizzle migrations (50+ tables) | ~2s |
| 4 | Create Route53 DNS record {slug}.syncad.in | ~1s |
| 5 | Issue Let's Encrypt SSL certificate | ~3s |
| 6 | Insert school record into central DB | ~100ms |
| 7 | Send welcome email to admin | ~500ms |
Total: ~8 seconds. Client polls GET /school/:id for status updates.
Code Structure
apps/scs/
├── cmd/server/main.go # Entry point
├── internal/
│ ├── provision.go # Orchestrates provisioning steps
│ ├── database.go # PostgreSQL pool management
│ ├── dns.go # Route53 client
│ ├── ssl.go # Let's Encrypt ACM client
│ ├── mail.go # SES welcome email
│ └── handlers/
│ ├── school.go # HTTP handlers
│ └── health.go
├── migrations/
│ └── 001_initial/ # SQL files for school DB schema
└── go.mod
Main Provisioning Logic
// internal/provision.go
func ProvisionSchool(ctx context.Context, req CreateSchoolRequest) (*School, error) {
dbName := fmt.Sprintf("school_%s", uuid.New().String())
// 1. Create isolated DB
if err := s.pgPool.CreateDatabase(ctx, dbName); err != nil {
return nil, fmt.Errorf("create database: %w", err)
}
// 2. Run migrations
schoolPool, err := s.pgPool.GetPool(dbName)
if err != nil {
return nil, fmt.Errorf("get pool: %w", err)
}
if err := s.migrator.Run(schoolPool, s.migrations); err != nil {
return nil, fmt.Errorf("run migrations: %w", err)
}
// 3. Create DNS record
subdomain := fmt.Sprintf("%s.syncad.in", req.Slug)
if err := s.route53.CreateSubdomain(ctx, subdomain); err != nil {
return nil, fmt.Errorf("create dns: %w", err)
}
// 4. Issue SSL
cert, err := s.acm.IssueCertificate(ctx, subdomain)
if err != nil {
return nil, fmt.Errorf("issue cert: %w", err)
}
// 5. Register in central DB
school := &School{
ID: uuid.New().String(),
Name: req.Name,
Slug: req.Slug,
DatabaseName: dbName,
Subdomain: subdomain,
ACMArn: cert,
}
return s.centralDB.CreateSchool(school)
}
Error Handling & Rollback
If any step fails, the service attempts rollback:
func (s *SCS) rollback(ctx context.Context, school *School) {
s.pgPool.DropDatabase(ctx, school.DatabaseName)
s.route53.DeleteRecord(ctx, school.Subdomain)
if school.ACMArn != "" {
s.acm.RevokeCertificate(ctx, school.ACMArn)
}
s.centralDB.DeleteSchool(ctx, school.ID)
}
Environment Variables
| Variable | Description |
|---|---|
DATABASE_URL | Central PostgreSQL connection string |
AWS_ACCESS_KEY_ID | AWS credentials |
AWS_SECRET_ACCESS_KEY | AWS credentials |
AWS_REGION | AWS region |
ROUTE53_HOSTED_ZONE_ID | Route53 hosted zone ID |
SCS_API_KEY | API key for authenticating provisioning requests |
SES_FROM_EMAIL | Sender email for welcome emails |