Skip to content

Commit

Permalink
Merge pull request #2310 from aliraza556/feature/bounty-timing-tracking
Browse files Browse the repository at this point in the history
Feature: Enhanced Bounty Timing and Proof of Work Tracking
  • Loading branch information
humansinstitute authored Jan 1, 2025
2 parents 0c58a67 + 35afa05 commit 057d69a
Show file tree
Hide file tree
Showing 7 changed files with 464 additions and 189 deletions.
1 change: 1 addition & 0 deletions db/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func InitDB() {
db.AutoMigrate(&ChatMessage{})
db.AutoMigrate(&Chat{})
db.AutoMigrate(&ProofOfWork{})
db.AutoMigrate(&BountyTiming{})
db.AutoMigrate(&FeatureFlag{})
db.AutoMigrate(&Endpoint{})

Expand Down
74 changes: 74 additions & 0 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -1903,3 +1903,77 @@ func (db database) DeleteProof(proofID string) error {
func (db database) UpdateProofStatus(proofID string, status ProofOfWorkStatus) error {
return db.db.Model(&ProofOfWork{}).Where("id = ?", proofID).Update("status", status).Error
}

func (db database) CreateBountyTiming(bountyID uint) (*BountyTiming, error) {
timing := &BountyTiming{
BountyID: bountyID,
}
err := db.db.Create(timing).Error
return timing, err
}

func (db database) GetBountyTiming(bountyID uint) (*BountyTiming, error) {
var timing BountyTiming
err := db.db.Where("bounty_id = ?", bountyID).First(&timing).Error
if err != nil {
return nil, err
}
return &timing, nil
}

func (db database) UpdateBountyTiming(timing *BountyTiming) error {
return db.db.Save(timing).Error
}

func (db database) StartBountyTiming(bountyID uint) error {
now := time.Now()
timing, err := db.GetBountyTiming(bountyID)
if err != nil {

timing, err = db.CreateBountyTiming(bountyID)
if err != nil {
return err
}
}

if timing.FirstAssignedAt == nil {
timing.FirstAssignedAt = &now
return db.UpdateBountyTiming(timing)
}
return nil
}

func (db database) CloseBountyTiming(bountyID uint) error {
timing, err := db.GetBountyTiming(bountyID)
if err != nil {
return err
}

now := time.Now()
timing.ClosedAt = &now

if timing.FirstAssignedAt != nil {
timing.TotalDurationSeconds = int(now.Sub(*timing.FirstAssignedAt).Seconds())
}

return db.UpdateBountyTiming(timing)
}

func (db database) UpdateBountyTimingOnProof(bountyID uint) error {
timing, err := db.GetBountyTiming(bountyID)
if err != nil {
return err
}

now := time.Now()

if timing.LastPoWAt != nil {
workTime := int(now.Sub(*timing.LastPoWAt).Seconds())
timing.TotalWorkTimeSeconds += workTime
}

timing.LastPoWAt = &now
timing.TotalAttempts++

return db.UpdateBountyTiming(timing)
}
6 changes: 6 additions & 0 deletions db/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,10 @@ type Database interface {
CreateProof(proof ProofOfWork) error
DeleteProof(proofID string) error
UpdateProofStatus(proofID string, status ProofOfWorkStatus) error
CreateBountyTiming(bountyID uint) (*BountyTiming, error)
GetBountyTiming(bountyID uint) (*BountyTiming, error)
UpdateBountyTiming(timing *BountyTiming) error
StartBountyTiming(bountyID uint) error
CloseBountyTiming(bountyID uint) error
UpdateBountyTimingOnProof(bountyID uint) error
}
13 changes: 13 additions & 0 deletions db/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,19 @@ type ProofOfWork struct {
SubmittedAt time.Time `json:"submitted_at" gorm:"type:timestamp;default:current_timestamp"`
}

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"`
}

type FeatureFlag struct {
UUID uuid.UUID `gorm:"type:uuid;primaryKey" json:"uuid"`
Name string `gorm:"type:varchar(255);unique;not null" json:"name"`
Expand Down
87 changes: 86 additions & 1 deletion handlers/bounty.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bytes"
"encoding/json"
"fmt"
"github.com/google/uuid"
"io"
"log"
"net/http"
Expand All @@ -13,6 +12,8 @@ import (
"sync"
"time"

"github.com/google/uuid"

"github.com/go-chi/chi"
"github.com/stakwork/sphinx-tribes/auth"
"github.com/stakwork/sphinx-tribes/config"
Expand All @@ -22,6 +23,15 @@ import (
"gorm.io/gorm"
)

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"`
}

type bountyHandler struct {
httpClient HttpClient
db db.Database
Expand Down Expand Up @@ -1607,6 +1617,8 @@ func (h *bountyHandler) DeleteProof(w http.ResponseWriter, r *http.Request) {

func (h *bountyHandler) UpdateProofStatus(w http.ResponseWriter, r *http.Request) {
proofID := chi.URLParam(r, "proofId")
bountyID := chi.URLParam(r, "id")

var statusUpdate struct {
Status db.ProofOfWorkStatus `json:"status"`
}
Expand All @@ -1628,6 +1640,19 @@ func (h *bountyHandler) UpdateProofStatus(w http.ResponseWriter, r *http.Request
return
}

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

if err := h.db.UpdateBountyTimingOnProof(id); err != nil {
http.Error(w, "Failed to update timing", http.StatusInternalServerError)
return
}
}

if err := h.db.UpdateProofStatus(proofID, statusUpdate.Status); err != nil {
http.Error(w, "Failed to update status", http.StatusInternalServerError)
return
Expand Down Expand Up @@ -1692,3 +1717,63 @@ func (h *bountyHandler) DeleteBountyAssignee(w http.ResponseWriter, r *http.Requ
json.NewEncoder(w).Encode(deletedAssignee)

}

func (h *bountyHandler) GetBountyTimingStats(w http.ResponseWriter, r *http.Request) {
bountyID := chi.URLParam(r, "id")
id, err := utils.ConvertStringToUint(bountyID)
if err != nil {
http.Error(w, "Invalid bounty ID", http.StatusBadRequest)
return
}

timing, err := h.db.GetBountyTiming(id)
if err != nil {
http.Error(w, "Failed to get timing stats", http.StatusInternalServerError)
return
}

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

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
}

func (h *bountyHandler) StartBountyTiming(w http.ResponseWriter, r *http.Request) {
bountyID := chi.URLParam(r, "id")

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

if err := h.db.StartBountyTiming(id); err != nil {
http.Error(w, "Failed to start timing", http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusOK)
}

func (h *bountyHandler) CloseBountyTiming(w http.ResponseWriter, r *http.Request) {
bountyID := chi.URLParam(r, "id")
id, err := utils.ConvertStringToUint(bountyID)
if err != nil {
http.Error(w, "Invalid bounty ID", http.StatusBadRequest)
return
}

if err := h.db.CloseBountyTiming(id); err != nil {
http.Error(w, "Failed to close timing", http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusOK)
}
Loading

0 comments on commit 057d69a

Please sign in to comment.