osaet/backend/internal/admin/builder.go
yarnom f0b50d13ea feat: add admin publishing workflow and yar theme
Add Go/Postgres admin APIs, Angular admin UI, manual build flow, asset uploads, markdown import/export, configurable slug generation, and the Yar reading theme. Exclude local docs and generated development artifacts from version control.
2026-06-01 15:48:04 +08:00

91 lines
1.7 KiB
Go

package admin
import (
"bytes"
"context"
"fmt"
"os/exec"
"sync"
"time"
)
type Builder struct {
store *Store
exporter *Exporter
siteDir string
queue chan string
once sync.Once
}
func NewBuilder(store *Store, exporter *Exporter, siteDir string) *Builder {
return &Builder{
store: store,
exporter: exporter,
siteDir: siteDir,
queue: make(chan string, 32),
}
}
func (b *Builder) Start(ctx context.Context) {
b.once.Do(func() {
go b.loop(ctx)
})
}
func (b *Builder) Enqueue(jobID string) bool {
select {
case b.queue <- jobID:
return true
default:
return false
}
}
func (b *Builder) loop(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case jobID := <-b.queue:
b.runBuildJob(ctx, jobID)
}
}
}
func (b *Builder) runBuildJob(ctx context.Context, jobID string) {
log, err := b.run(ctx, jobID)
if err != nil {
_ = b.store.MarkBuildJobFailed(context.Background(), jobID, log, err.Error())
return
}
_ = b.store.MarkBuildJobSuccess(context.Background(), jobID, log)
}
func (b *Builder) run(ctx context.Context, jobID string) (string, error) {
if err := b.store.MarkBuildJobRunning(ctx, jobID); err != nil {
return "", err
}
posts, err := b.store.PublishedPostsForExport(ctx)
if err != nil {
return "", err
}
if err := b.exporter.ExportPublishedPosts(ctx, posts); err != nil {
return "", err
}
buildCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
cmd := exec.CommandContext(buildCtx, "npm", "run", "build")
cmd.Dir = b.siteDir
var output bytes.Buffer
cmd.Stdout = &output
cmd.Stderr = &output
if err := cmd.Run(); err != nil {
return output.String(), fmt.Errorf("astro build failed: %w", err)
}
return output.String(), nil
}