Skip to content

Commit

Permalink
Merge pull request stakwork#2509 from aliraza556/feature/bounty-timin…
Browse files Browse the repository at this point in the history
…g-pause-resume

Add Multi-Cycle Pause and Resume Functionality for Bounty Timing
  • Loading branch information
humansinstitute authored Jan 30, 2025
2 parents 10ed924 + 8028f93 commit f80bb82
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 31 deletions.
40 changes: 40 additions & 0 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -2086,3 +2086,43 @@ func (db database) UpdateFeaturedBounty(bountyID string, bounty FeaturedBounty)
func (db database) DeleteFeaturedBounty(bountyID string) error {
return db.db.Where("bounty_id = ?", bountyID).Delete(&FeaturedBounty{}).Error
}

func (db database) PauseBountyTiming(bountyID uint) error {
timing, err := db.GetBountyTiming(bountyID)
if err != nil {
return fmt.Errorf("failed to get bounty timing: %w", err)
}

if timing.IsPaused {
return nil
}

now := time.Now()
timing.IsPaused = true
timing.LastPausedAt = &now

return db.UpdateBountyTiming(timing)
}

func (db database) ResumeBountyTiming(bountyID uint) error {
timing, err := db.GetBountyTiming(bountyID)
if err != nil {
return fmt.Errorf("failed to get bounty timing: %w", err)
}

if !timing.IsPaused {
return nil
}

now := time.Now()

if timing.LastPausedAt != nil {
pauseDuration := int(now.Sub(*timing.LastPausedAt).Seconds())
timing.AccumulatedPauseSeconds += pauseDuration
}

timing.IsPaused = false
timing.LastPausedAt = nil

return db.UpdateBountyTiming(timing)
}
2 changes: 2 additions & 0 deletions db/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,4 +280,6 @@ type Database interface {
UpdateFileAsset(asset *FileAsset) error
DeleteFileAsset(id uint) error
DeleteTicketGroup(TicketGroupUUID uuid.UUID) error
PauseBountyTiming(bountyID uint) error
ResumeBountyTiming(bountyID uint) error
}
23 changes: 13 additions & 10 deletions db/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1146,16 +1146,19 @@ type ProofOfWork struct {
}

type BountyTiming struct {
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
BountyID uint `json:"bounty_id" gorm:"not null"`
TotalWorkTimeSeconds int `json:"total_work_time_seconds" gorm:"default:0"`
TotalDurationSeconds int `json:"total_duration_seconds" gorm:"default:0"`
TotalAttempts int `json:"total_attempts" gorm:"default:0"`
FirstAssignedAt *time.Time `json:"first_assigned_at"`
LastPoWAt *time.Time `json:"last_pow_at"`
ClosedAt *time.Time `json:"closed_at"`
CreatedAt time.Time `json:"created_at" gorm:"default:current_timestamp"`
UpdatedAt time.Time `json:"updated_at" gorm:"default:current_timestamp"`
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
BountyID uint `json:"bounty_id" gorm:"not null"`
TotalWorkTimeSeconds int `json:"total_work_time_seconds" gorm:"default:0"`
TotalDurationSeconds int `json:"total_duration_seconds" gorm:"default:0"`
TotalAttempts int `json:"total_attempts" gorm:"default:0"`
FirstAssignedAt *time.Time `json:"first_assigned_at"`
LastPoWAt *time.Time `json:"last_pow_at"`
ClosedAt *time.Time `json:"closed_at"`
IsPaused bool `json:"is_paused" gorm:"default:false"`
LastPausedAt *time.Time `json:"last_paused_at"`
AccumulatedPauseSeconds int `json:"accumulated_pause_seconds" gorm:"default:0"`
CreatedAt time.Time `json:"created_at" gorm:"default:current_timestamp"`
UpdatedAt time.Time `json:"updated_at" gorm:"default:current_timestamp"`
}

type FeatureFlag struct {
Expand Down
47 changes: 26 additions & 21 deletions handlers/bounty.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@ import (
)

type BountyTimingResponse struct {
TotalWorkTimeSeconds int `json:"total_work_time_seconds"`
TotalDurationSeconds int `json:"total_duration_seconds"`
TotalAttempts int `json:"total_attempts"`
FirstAssignedAt *time.Time `json:"first_assigned_at"`
LastPoWAt *time.Time `json:"last_pow_at"`
ClosedAt *time.Time `json:"closed_at"`
TotalWorkTimeSeconds int `json:"total_work_time_seconds"`
TotalDurationSeconds int `json:"total_duration_seconds"`
TotalAttempts int `json:"total_attempts"`
FirstAssignedAt *time.Time `json:"first_assigned_at"`
LastPoWAt *time.Time `json:"last_pow_at"`
ClosedAt *time.Time `json:"closed_at"`
IsPaused bool `json:"is_paused"`
LastPausedAt *time.Time `json:"last_paused_at"`
AccumulatedPauseSeconds int `json:"accumulated_pause_seconds"`
}

type bountyHandler struct {
Expand Down Expand Up @@ -1717,6 +1720,10 @@ func (h *bountyHandler) AddProofOfWork(w http.ResponseWriter, r *http.Request) {
return
}

if err := h.db.PauseBountyTiming(proof.BountyID); err != nil {
handleTimingError(w, "pause_timing", err)
}

if err := h.db.UpdateBountyTimingOnProof(proof.BountyID); err != nil {
handleTimingError(w, "update_timing_on_proof", err)
}
Expand Down Expand Up @@ -1798,21 +1805,16 @@ func (h *bountyHandler) UpdateProofStatus(w http.ResponseWriter, r *http.Request
return
}

if statusUpdate.Status == db.AcceptedStatus {
if statusUpdate.Status == db.RejectedStatus || statusUpdate.Status == db.ChangeRequestedStatus {

id, err := utils.ConvertStringToUint(bountyID)
if err != nil {
http.Error(w, "Invalid bounty ID", http.StatusBadRequest)
return
}

_, err = h.db.GetBountyTiming(id)
if err == nil {
if err := h.db.UpdateBountyTimingOnProof(id); err != nil {
http.Error(w, "Failed to update timing", http.StatusInternalServerError)
return
}
} else {
logger.Log.Error(fmt.Sprintf("No bounty timing found for bounty ID %d: %v", id, err))
if err := h.db.ResumeBountyTiming(id); err != nil {
logger.Log.Error(fmt.Sprintf("Failed to resume timing for bounty ID %d: %v", id, err))
}
}

Expand Down Expand Up @@ -1900,12 +1902,15 @@ func (h *bountyHandler) GetBountyTimingStats(w http.ResponseWriter, r *http.Requ
}

response := BountyTimingResponse{
TotalWorkTimeSeconds: timing.TotalWorkTimeSeconds,
TotalDurationSeconds: timing.TotalDurationSeconds,
TotalAttempts: timing.TotalAttempts,
FirstAssignedAt: timing.FirstAssignedAt,
LastPoWAt: timing.LastPoWAt,
ClosedAt: timing.ClosedAt,
TotalWorkTimeSeconds: timing.TotalWorkTimeSeconds,
TotalDurationSeconds: timing.TotalDurationSeconds,
TotalAttempts: timing.TotalAttempts,
FirstAssignedAt: timing.FirstAssignedAt,
LastPoWAt: timing.LastPoWAt,
ClosedAt: timing.ClosedAt,
IsPaused: timing.IsPaused,
LastPausedAt: timing.LastPausedAt,
AccumulatedPauseSeconds: timing.AccumulatedPauseSeconds,
}

w.WriteHeader(http.StatusOK)
Expand Down
85 changes: 85 additions & 0 deletions mocks/Database.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f80bb82

Please sign in to comment.