Skip to content

Commit

Permalink
Merge pull request #2308 from MahtabBukhari/Add_Proof_of_Work_Feature…
Browse files Browse the repository at this point in the history
…_to_Bounties

Add Proof of Work Feature to Bounties
  • Loading branch information
humansinstitute authored Dec 30, 2024
2 parents f90ff76 + a660340 commit b9abf1a
Show file tree
Hide file tree
Showing 9 changed files with 533 additions and 3 deletions.
1 change: 1 addition & 0 deletions db/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func InitDB() {
db.AutoMigrate(&Tickets{})
db.AutoMigrate(&ChatMessage{})
db.AutoMigrate(&Chat{})
db.AutoMigrate(&ProofOfWork{})

DB.MigrateTablesWithOrgUuid()
DB.MigrateOrganizationToWorkspace()
Expand Down
18 changes: 18 additions & 0 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -1885,3 +1885,21 @@ func (db database) DeleteAllBounties() error {
}
return nil
}

func (db database) GetProofsByBountyID(bountyID uint) []ProofOfWork {
var proofs []ProofOfWork
db.db.Where("bounty_id = ?", bountyID).Find(&proofs)
return proofs
}

func (db database) CreateProof(proof ProofOfWork) error {
return db.db.Create(&proof).Error
}

func (db database) DeleteProof(proofID string) error {
return db.db.Delete(&ProofOfWork{}, "id = ?", proofID).Error
}

func (db database) UpdateProofStatus(proofID string, status ProofOfWorkStatus) error {
return db.db.Model(&ProofOfWork{}).Where("id = ?", proofID).Update("status", status).Error
}
4 changes: 4 additions & 0 deletions db/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,8 @@ type Database interface {
ProcessUpdateTicketsWithoutGroup()
GetNewHunters(r PaymentDateRange) int64
TotalPeopleByPeriod(r PaymentDateRange) int64
GetProofsByBountyID(bountyID uint) []ProofOfWork
CreateProof(proof ProofOfWork) error
DeleteProof(proofID string) error
UpdateProofStatus(proofID string, status ProofOfWorkStatus) error
}
19 changes: 19 additions & 0 deletions db/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ type BountyResponse struct {
Owner Person `json:"owner"`
Organization WorkspaceShort `json:"organization"`
Workspace WorkspaceShort `json:"workspace"`
Proofs []ProofOfWork `json:"proofs,omitempty"`
}

type BountyCountResponse struct {
Expand Down Expand Up @@ -1085,6 +1086,24 @@ type Chat struct {
UpdatedAt time.Time `json:"updatedAt"`
}

type ProofOfWorkStatus string

const (
NewStatus ProofOfWorkStatus = "New"
AcceptedStatus ProofOfWorkStatus = "Accepted"
RejectedStatus ProofOfWorkStatus = "Rejected"
ChangeRequestedStatus ProofOfWorkStatus = "Change Requested"
)

type ProofOfWork struct {
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
BountyID uint `json:"bounty_id"`
Description string `json:"description" gorm:"type:text;not null"`
Status ProofOfWorkStatus `json:"status" gorm:"type:varchar(20);default:'New'"`
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:current_timestamp"`
SubmittedAt time.Time `json:"submitted_at" gorm:"type:timestamp;default:current_timestamp"`
}

func (Person) TableName() string {
return "people"
}
Expand Down
110 changes: 110 additions & 0 deletions handlers/bounty.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"github.com/google/uuid"
"io"
"log"
"net/http"
Expand Down Expand Up @@ -440,6 +441,8 @@ func (h *bountyHandler) GenerateBountyResponse(bounties []db.NewBounty) []db.Bou
assignee := h.db.GetPersonByPubkey(bounty.Assignee)
workspace := h.db.GetWorkspaceByUuid(bounty.WorkspaceUuid)

proofs := h.db.GetProofsByBountyID(bounty.ID)

b := db.BountyResponse{
Bounty: db.NewBounty{
ID: bounty.ID,
Expand Down Expand Up @@ -519,6 +522,11 @@ func (h *bountyHandler) GenerateBountyResponse(bounties []db.NewBounty) []db.Bou
Img: workspace.Img,
},
}

if len(proofs) > 0 {
b.Proofs = proofs
}

bountyResponse = append(bountyResponse, b)
}

Expand Down Expand Up @@ -1535,6 +1543,107 @@ func (h *bountyHandler) GenerateBountyCardResponse(bounties []db.NewBounty) []db
return bountyCardResponse
}

func (h *bountyHandler) AddProofOfWork(w http.ResponseWriter, r *http.Request) {
bountyID := chi.URLParam(r, "id")
var proof db.ProofOfWork

body, err := io.ReadAll(r.Body)
defer r.Body.Close()
if err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}

err = json.Unmarshal(body, &proof)
if err != nil || proof.Description == "" {
http.Error(w, "Description is required", http.StatusBadRequest)
return
}

proof.ID = uuid.New()
proof.BountyID, _ = utils.ConvertStringToUint(bountyID)
proof.CreatedAt = time.Now()
proof.SubmittedAt = time.Now()

if err := h.db.CreateProof(proof); err != nil {
http.Error(w, "Failed to add proof", http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(proof)
}

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

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

proofs := h.db.GetProofsByBountyID(bountyUUID)

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

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

if _, err := uuid.Parse(proofID); err != nil {
http.Error(w, "Invalid proof ID", http.StatusBadRequest)
return
}

if err := h.db.DeleteProof(proofID); err != nil {
http.Error(w, "Failed to delete proof", http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusNoContent)
}

func (h *bountyHandler) UpdateProofStatus(w http.ResponseWriter, r *http.Request) {
proofID := chi.URLParam(r, "proofId")
var statusUpdate struct {
Status db.ProofOfWorkStatus `json:"status"`
}

if _, err := uuid.Parse(proofID); err != nil {
http.Error(w, "Invalid proof ID", http.StatusBadRequest)
return
}

body, err := io.ReadAll(r.Body)
defer r.Body.Close()
if err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}

if err := json.Unmarshal(body, &statusUpdate); err != nil || !isValidProofStatus(statusUpdate.Status) {
http.Error(w, "Invalid status", http.StatusBadRequest)
return
}

if err := h.db.UpdateProofStatus(proofID, statusUpdate.Status); err != nil {
http.Error(w, "Failed to update status", http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusOK)
}

func isValidProofStatus(status db.ProofOfWorkStatus) bool {
switch status {
case db.NewStatus, db.AcceptedStatus, db.RejectedStatus, db.ChangeRequestedStatus:
return true
}
return false
}

func (h *bountyHandler) DeleteBountyAssignee(w http.ResponseWriter, r *http.Request) {
invoice := db.DeleteBountyAssignee{}
body, err := io.ReadAll(r.Body)
Expand Down Expand Up @@ -1581,4 +1690,5 @@ func (h *bountyHandler) DeleteBountyAssignee(w http.ResponseWriter, r *http.Requ

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(deletedAssignee)

}
1 change: 0 additions & 1 deletion handlers/chat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -828,7 +828,6 @@ func TestProcessChatResponse(t *testing.T) {
}
}


func TestGetChatHistory(t *testing.T) {
teardownSuite := SetupSuite(t)
defer teardownSuite(t)
Expand Down
Loading

0 comments on commit b9abf1a

Please sign in to comment.