| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- package service
- import (
- "context"
- "net/http"
- "strings"
- "cmr-backend/internal/apperr"
- "cmr-backend/internal/store/postgres"
- )
- type AdminPipelineService struct {
- store *postgres.Store
- configService *ConfigService
- }
- type AdminReleaseView struct {
- ID string `json:"id"`
- ReleaseNo int `json:"releaseNo"`
- ConfigLabel string `json:"configLabel"`
- ManifestURL string `json:"manifestUrl"`
- ManifestChecksumSha256 *string `json:"manifestChecksumSha256,omitempty"`
- RouteCode *string `json:"routeCode,omitempty"`
- BuildID *string `json:"buildId,omitempty"`
- Status string `json:"status"`
- PublishedAt string `json:"publishedAt"`
- Runtime *RuntimeSummaryView `json:"runtime,omitempty"`
- Presentation *PresentationSummaryView `json:"presentation,omitempty"`
- ContentBundle *ContentBundleSummaryView `json:"contentBundle,omitempty"`
- }
- type AdminEventPipelineView struct {
- EventID string `json:"eventId"`
- CurrentRelease *AdminReleaseView `json:"currentRelease,omitempty"`
- Sources []EventConfigSourceView `json:"sources"`
- Builds []EventConfigBuildView `json:"builds"`
- Releases []AdminReleaseView `json:"releases"`
- }
- type AdminRollbackReleaseInput struct {
- ReleaseID string `json:"releaseId"`
- }
- type AdminBindReleaseRuntimeInput struct {
- RuntimeBindingID string `json:"runtimeBindingId"`
- }
- type AdminPublishBuildInput struct {
- RuntimeBindingID string `json:"runtimeBindingId,omitempty"`
- PresentationID string `json:"presentationId,omitempty"`
- ContentBundleID string `json:"contentBundleId,omitempty"`
- }
- func NewAdminPipelineService(store *postgres.Store, configService *ConfigService) *AdminPipelineService {
- return &AdminPipelineService{
- store: store,
- configService: configService,
- }
- }
- func (s *AdminPipelineService) GetEventPipeline(ctx context.Context, eventPublicID string, limit int) (*AdminEventPipelineView, error) {
- event, err := s.store.GetEventByPublicID(ctx, strings.TrimSpace(eventPublicID))
- if err != nil {
- return nil, err
- }
- if event == nil {
- return nil, apperr.New(http.StatusNotFound, "event_not_found", "event not found")
- }
- sources, err := s.configService.ListEventConfigSources(ctx, event.PublicID, limit)
- if err != nil {
- return nil, err
- }
- buildRecords, err := s.store.ListEventConfigBuildsByEventID(ctx, event.ID, limit)
- if err != nil {
- return nil, err
- }
- releaseRecords, err := s.store.ListEventReleasesByEventID(ctx, event.ID, limit)
- if err != nil {
- return nil, err
- }
- builds := make([]EventConfigBuildView, 0, len(buildRecords))
- for i := range buildRecords {
- item, err := buildEventConfigBuildView(&buildRecords[i])
- if err != nil {
- return nil, err
- }
- builds = append(builds, *item)
- }
- releases := make([]AdminReleaseView, 0, len(releaseRecords))
- for _, item := range releaseRecords {
- view := buildAdminReleaseView(item)
- if enrichedPresentation, err := loadPresentationSummaryByPublicID(ctx, s.store, item.PresentationID); err != nil {
- return nil, err
- } else if enrichedPresentation != nil {
- view.Presentation = enrichedPresentation
- }
- if enrichedBundle, err := loadContentBundleSummaryByPublicID(ctx, s.store, item.ContentBundleID); err != nil {
- return nil, err
- } else if enrichedBundle != nil {
- view.ContentBundle = enrichedBundle
- }
- releases = append(releases, view)
- }
- result := &AdminEventPipelineView{
- EventID: event.PublicID,
- Sources: sources,
- Builds: builds,
- Releases: releases,
- }
- if event.CurrentReleasePubID != nil {
- result.CurrentRelease = &AdminReleaseView{
- ID: *event.CurrentReleasePubID,
- ConfigLabel: derefStringOrEmpty(event.ConfigLabel),
- ManifestURL: derefStringOrEmpty(event.ManifestURL),
- ManifestChecksumSha256: event.ManifestChecksum,
- RouteCode: event.RouteCode,
- Status: "published",
- Runtime: buildRuntimeSummaryFromEvent(event),
- Presentation: buildPresentationSummaryFromEvent(event),
- ContentBundle: buildContentBundleSummaryFromEvent(event),
- }
- if enrichedPresentation, err := loadPresentationSummaryByPublicID(ctx, s.store, event.PresentationID); err != nil {
- return nil, err
- } else if enrichedPresentation != nil {
- result.CurrentRelease.Presentation = enrichedPresentation
- }
- if enrichedBundle, err := loadContentBundleSummaryByPublicID(ctx, s.store, event.ContentBundleID); err != nil {
- return nil, err
- } else if enrichedBundle != nil {
- result.CurrentRelease.ContentBundle = enrichedBundle
- }
- }
- return result, nil
- }
- func (s *AdminPipelineService) BuildSource(ctx context.Context, sourceID string) (*EventConfigBuildView, error) {
- return s.configService.BuildPreview(ctx, BuildPreviewInput{SourceID: sourceID})
- }
- func (s *AdminPipelineService) GetBuild(ctx context.Context, buildID string) (*EventConfigBuildView, error) {
- return s.configService.GetEventConfigBuild(ctx, buildID)
- }
- func (s *AdminPipelineService) PublishBuild(ctx context.Context, buildID string, input AdminPublishBuildInput) (*PublishedReleaseView, error) {
- return s.configService.PublishBuild(ctx, PublishBuildInput{
- BuildID: buildID,
- RuntimeBindingID: input.RuntimeBindingID,
- PresentationID: input.PresentationID,
- ContentBundleID: input.ContentBundleID,
- })
- }
- func (s *AdminPipelineService) GetRelease(ctx context.Context, releasePublicID string) (*AdminReleaseView, error) {
- release, err := s.store.GetEventReleaseByPublicID(ctx, strings.TrimSpace(releasePublicID))
- if err != nil {
- return nil, err
- }
- if release == nil {
- return nil, apperr.New(http.StatusNotFound, "release_not_found", "release not found")
- }
- view := buildAdminReleaseView(*release)
- if enrichedPresentation, err := loadPresentationSummaryByPublicID(ctx, s.store, release.PresentationID); err != nil {
- return nil, err
- } else if enrichedPresentation != nil {
- view.Presentation = enrichedPresentation
- }
- if enrichedBundle, err := loadContentBundleSummaryByPublicID(ctx, s.store, release.ContentBundleID); err != nil {
- return nil, err
- } else if enrichedBundle != nil {
- view.ContentBundle = enrichedBundle
- }
- return &view, nil
- }
- func (s *AdminPipelineService) BindReleaseRuntime(ctx context.Context, releasePublicID string, input AdminBindReleaseRuntimeInput) (*AdminReleaseView, error) {
- release, err := s.store.GetEventReleaseByPublicID(ctx, strings.TrimSpace(releasePublicID))
- if err != nil {
- return nil, err
- }
- if release == nil {
- return nil, apperr.New(http.StatusNotFound, "release_not_found", "release not found")
- }
- input.RuntimeBindingID = strings.TrimSpace(input.RuntimeBindingID)
- if input.RuntimeBindingID == "" {
- return nil, apperr.New(http.StatusBadRequest, "invalid_params", "runtimeBindingId is required")
- }
- runtimeBinding, err := s.store.GetMapRuntimeBindingByPublicID(ctx, input.RuntimeBindingID)
- if err != nil {
- return nil, err
- }
- if runtimeBinding == nil {
- return nil, apperr.New(http.StatusNotFound, "runtime_binding_not_found", "runtime binding not found")
- }
- if runtimeBinding.EventID != release.EventID {
- return nil, apperr.New(http.StatusConflict, "runtime_binding_not_belong_to_event", "runtime binding does not belong to release event")
- }
- tx, err := s.store.Begin(ctx)
- if err != nil {
- return nil, err
- }
- defer tx.Rollback(ctx)
- if err := s.store.SetEventReleaseRuntimeBinding(ctx, tx, release.ID, &runtimeBinding.ID); err != nil {
- return nil, err
- }
- if err := tx.Commit(ctx); err != nil {
- return nil, err
- }
- updated, err := s.store.GetEventReleaseByPublicID(ctx, release.PublicID)
- if err != nil {
- return nil, err
- }
- if updated == nil {
- return nil, apperr.New(http.StatusNotFound, "release_not_found", "release not found")
- }
- view := buildAdminReleaseView(*updated)
- return &view, nil
- }
- func (s *AdminPipelineService) RollbackRelease(ctx context.Context, eventPublicID string, input AdminRollbackReleaseInput) (*AdminReleaseView, error) {
- event, err := s.store.GetEventByPublicID(ctx, strings.TrimSpace(eventPublicID))
- if err != nil {
- return nil, err
- }
- if event == nil {
- return nil, apperr.New(http.StatusNotFound, "event_not_found", "event not found")
- }
- input.ReleaseID = strings.TrimSpace(input.ReleaseID)
- if input.ReleaseID == "" {
- return nil, apperr.New(http.StatusBadRequest, "invalid_params", "releaseId is required")
- }
- release, err := s.store.GetEventReleaseByPublicID(ctx, input.ReleaseID)
- if err != nil {
- return nil, err
- }
- if release == nil {
- return nil, apperr.New(http.StatusNotFound, "release_not_found", "release not found")
- }
- if release.EventID != event.ID {
- return nil, apperr.New(http.StatusConflict, "release_not_belong_to_event", "release does not belong to event")
- }
- if release.Status != "published" {
- return nil, apperr.New(http.StatusConflict, "release_not_publishable", "release is not published")
- }
- tx, err := s.store.Begin(ctx)
- if err != nil {
- return nil, err
- }
- defer tx.Rollback(ctx)
- if err := s.store.SetCurrentEventRelease(ctx, tx, event.ID, release.ID); err != nil {
- return nil, err
- }
- if err := tx.Commit(ctx); err != nil {
- return nil, err
- }
- view := buildAdminReleaseView(*release)
- return &view, nil
- }
- func buildAdminReleaseView(item postgres.EventRelease) AdminReleaseView {
- return AdminReleaseView{
- ID: item.PublicID,
- ReleaseNo: item.ReleaseNo,
- ConfigLabel: item.ConfigLabel,
- ManifestURL: item.ManifestURL,
- ManifestChecksumSha256: item.ManifestChecksum,
- RouteCode: item.RouteCode,
- BuildID: item.BuildID,
- Status: item.Status,
- PublishedAt: item.PublishedAt.Format(timeRFC3339),
- Runtime: buildRuntimeSummaryFromRelease(&item),
- Presentation: buildPresentationSummaryFromRelease(&item),
- ContentBundle: buildContentBundleSummaryFromRelease(&item),
- }
- }
- func derefStringOrEmpty(value *string) string {
- if value == nil {
- return ""
- }
- return *value
- }
|