package repository import ( "context" "fmt" "strconv" "time" "xprem/internal/database" "xprem/internal/database/postgres/pgdb" "xprem/internal/types" ) type PostgresBundlePatchRepository struct { engine *database.Engine } func NewPostgresBundlePatchRepository(engine *database.Engine) *PostgresBundlePatchRepository { return &PostgresBundlePatchRepository{engine: engine} } // ParseUpdateID reads the numeric id an update is keyed by; name says which // update in the error. func ParseUpdateID(name, value string) (int64, error) { id, err := strconv.ParseInt(value, 10, 64) if err != nil { return 0, fmt.Errorf("failed to parse %s: %w", name, err) } return id, nil } // MarkPending records a patch job about to be scheduled, resetting the pair // if it was already handled. func (s *PostgresBundlePatchRepository) MarkPending(ctx context.Context, appId, branch, targetUpdateId, sourceUpdateId string) error { targetID, err := ParseUpdateID("target update id", targetUpdateId) if err != nil { return err } sourceID, err := ParseUpdateID("source update id", sourceUpdateId) if err != nil { return err } rows, err := s.engine.Queries.UpsertBundlePatchPending(ctx, pgdb.UpsertBundlePatchPendingParams{ TargetUpdateID: targetID, SourceUpdateID: sourceID, AppID: ToPgUUID(appId), BranchName: branch, }) if err != nil { return fmt.Errorf("failed to record the pending bundle patch in database: %w", err) } if rows == 0 { return fmt.Errorf("branch %q of app %s not found", branch, appId) } return nil } func (s *PostgresBundlePatchRepository) MarkRunning(ctx context.Context, appId, branch, targetUpdateId, sourceUpdateId string) error { targetID, err := ParseUpdateID("target update id", targetUpdateId) if err != nil { return err } sourceID, err := ParseUpdateID("source update id", sourceUpdateId) if err != nil { return err } rows, err := s.engine.Queries.SetBundlePatchRunning(ctx, pgdb.SetBundlePatchRunningParams{ AppID: ToPgUUID(appId), BranchName: branch, TargetUpdateID: targetID, SourceUpdateID: sourceID, }) if err != nil { return fmt.Errorf("failed to mark the bundle patch running in database: %w", err) } if rows == 0 { return fmt.Errorf("no bundle patch record for %s -> %s on branch %s", sourceUpdateId, targetUpdateId, branch) } return nil } // Finish records how the job ended. reason is empty for a stored patch. func (s *PostgresBundlePatchRepository) Finish(ctx context.Context, appId, branch, targetUpdateId, sourceUpdateId string, status types.BundlePatchStatus, reason string, patchSize, fullDownloadSize *int64) error { targetID, err := ParseUpdateID("target update id", targetUpdateId) if err != nil { return err } sourceID, err := ParseUpdateID("source update id", sourceUpdateId) if err != nil { return err } var reasonPtr *string if reason != "" { reasonPtr = &reason } rows, err := s.engine.Queries.FinishBundlePatch(ctx, pgdb.FinishBundlePatchParams{ Status: string(status), Reason: reasonPtr, PatchSize: patchSize, FullDownloadSize: fullDownloadSize, AppID: ToPgUUID(appId), BranchName: branch, TargetUpdateID: targetID, SourceUpdateID: sourceID, }) if err != nil { return fmt.Errorf("failed to finish the bundle patch in database: %w", err) } if rows == 0 { return fmt.Errorf("no bundle patch record for %s -> %s on branch %s", sourceUpdateId, targetUpdateId, branch) } return nil } // ListByTarget returns the patches toward one update, newest source first. func (s *PostgresBundlePatchRepository) ListByTarget(ctx context.Context, appId, branch, targetUpdateId string) ([]types.BundlePatch, error) { targetID, err := ParseUpdateID("target update id", targetUpdateId) if err != nil { return nil, err } rows, err := s.engine.Queries.GetBundlePatchesByTarget(ctx, pgdb.GetBundlePatchesByTargetParams{ AppID: ToPgUUID(appId), BranchName: branch, TargetUpdateID: targetID, }) if err != nil { return nil, fmt.Errorf("failed to list bundle patches from database: %w", err) } patches := make([]types.BundlePatch, 0, len(rows)) for _, row := range rows { patch := types.BundlePatch{ TargetUpdateId: strconv.FormatInt(row.TargetUpdateID, 10), SourceUpdateId: strconv.FormatInt(row.SourceUpdateID, 10), SourceCommitHash: row.SourceCommitHash, Status: types.BundlePatchStatus(row.Status), PatchSize: row.PatchSize, FullDownloadSize: row.FullDownloadSize, Attempts: int(row.Attempts), UpdatedAt: row.UpdatedAt.Time.UTC().Format(time.RFC3339), SourceCreatedAt: row.SourceCreatedAt.Time.UTC().Format(time.RFC3339), } if row.TargetUpdateUuid.Valid { patch.TargetUpdateUUID = row.TargetUpdateUuid.String() } if row.SourceUpdateUuid.Valid { patch.SourceUpdateUUID = row.SourceUpdateUuid.String() } if row.SourceMessage != nil { patch.SourceMessage = *row.SourceMessage } if row.Reason != nil { patch.Reason = *row.Reason } patches = append(patches, patch) } return patches, nil }