Skip to content

Commit

Permalink
Merge pull request #2507 from MirzaHanan/feature/add-delete-bounty-ti…
Browse files Browse the repository at this point in the history
…ming-endpoint

Feature: Add Endpoint to Delete Bounty Timing Records
  • Loading branch information
humansinstitute authored Jan 30, 2025
2 parents f80bb82 + 6eb0b58 commit 2e50e82
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 1 deletion.
13 changes: 13 additions & 0 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -2126,3 +2126,16 @@ func (db database) ResumeBountyTiming(bountyID uint) error {

return db.UpdateBountyTiming(timing)
}

func (db database) DeleteBountyTiming(bountyID uint) error {
result := db.db.Where("bounty_id = ?", bountyID).Delete(&BountyTiming{})
if result.Error != nil {
return fmt.Errorf("failed to delete bounty timing: %w", result.Error)
}

if result.RowsAffected == 0 {
return fmt.Errorf("no timing record found for bounty %d", bountyID)
}

return nil
}
1 change: 1 addition & 0 deletions db/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ type Database interface {
ListFileAssets(params ListFileAssetsParams) ([]FileAsset, int64, error)
UpdateFileAsset(asset *FileAsset) error
DeleteFileAsset(id uint) error
DeleteBountyTiming(bountyID uint) error
DeleteTicketGroup(TicketGroupUUID uuid.UUID) error
PauseBountyTiming(bountyID uint) error
ResumeBountyTiming(bountyID uint) error
Expand Down
2 changes: 2 additions & 0 deletions db/test_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ func DeleteAllFeatureStories() {
func CleanTestData() {
TestDB.db.Exec("DELETE FROM bounty")

TestDB.db.Exec("DELETE FROM bounty_timings")

TestDB.db.Exec("DELETE FROM workspaces")

TestDB.db.Exec("DELETE FROM workspace_features")
Expand Down
35 changes: 35 additions & 0 deletions handlers/bounty.go
Original file line number Diff line number Diff line change
Expand Up @@ -2100,3 +2100,38 @@ func (h *bountyHandler) DeleteFeaturedBounty(w http.ResponseWriter, r *http.Requ

w.WriteHeader(http.StatusNoContent)
}

func (h *bountyHandler) DeleteBountyTiming(w http.ResponseWriter, r *http.Request) {

ctx := r.Context()
pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string)
if pubKeyFromAuth == "" {
logger.Log.Info("no pubkey from auth")
w.WriteHeader(http.StatusUnauthorized)
return
}

bountyID := chi.URLParam(r, "id")
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 {
logger.Log.Error(fmt.Sprintf("No bounty timing found for bounty ID %d: %v", id, err))
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w).Encode(map[string]string{"error": "No timing record found"})
return
}

if err := h.db.DeleteBountyTiming(id); err != nil {
logger.Log.Error(fmt.Sprintf("Failed to delete bounty timing for bounty ID %d: %v", id, err))
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{"error": "Failed to delete bounty timing"})
return
}

w.WriteHeader(http.StatusNoContent)
}
113 changes: 113 additions & 0 deletions handlers/bounty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4258,3 +4258,116 @@ func TestWorkspaceIsolation(t *testing.T) {
}
})
}

func TestDeleteBountyTiming(t *testing.T) {
teardownSuite := SetupSuite(t)
defer teardownSuite(t)

db.CleanTestData()

bHandler := NewBountyHandler(http.DefaultClient, db.TestDB)

testBounty := db.NewBounty{
Type: "coding",
Title: "Test Bounty for Timing Deletion",
Description: "Test bounty description",
WorkspaceUuid: "test-workspace",
OwnerID: bountyOwner.OwnerPubKey,
Created: time.Now().Unix(),
}

createdBounty, err := db.TestDB.CreateOrEditBounty(testBounty)
assert.NoError(t, err)

_, err = db.TestDB.CreateBountyTiming(createdBounty.ID)
assert.NoError(t, err)

t.Run("should return 401 if no pubkey in context", func(t *testing.T) {
rr := httptest.NewRecorder()
handler := http.HandlerFunc(bHandler.DeleteBountyTiming)

rctx := chi.NewRouteContext()
rctx.URLParams.Add("id", strconv.FormatUint(uint64(createdBounty.ID), 10))
req, err := http.NewRequestWithContext(
context.WithValue(context.Background(), chi.RouteCtxKey, rctx),
http.MethodDelete,
"/timing",
nil,
)
assert.NoError(t, err)

handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusUnauthorized, rr.Code)
})

t.Run("should return 400 for invalid bounty ID", func(t *testing.T) {
rr := httptest.NewRecorder()
handler := http.HandlerFunc(bHandler.DeleteBountyTiming)

ctx := context.WithValue(context.Background(), auth.ContextKey, bountyOwner.OwnerPubKey)

rctx := chi.NewRouteContext()
rctx.URLParams.Add("id", "invalid")
req, err := http.NewRequestWithContext(
context.WithValue(ctx, chi.RouteCtxKey, rctx),
http.MethodDelete,
"/timing",
nil,
)
assert.NoError(t, err)

handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
})

t.Run("should return 404 when no timing record exists", func(t *testing.T) {

db.CleanTestData()

rr := httptest.NewRecorder()
handler := http.HandlerFunc(bHandler.DeleteBountyTiming)

ctx := context.WithValue(context.Background(), auth.ContextKey, bountyOwner.OwnerPubKey)

rctx := chi.NewRouteContext()
rctx.URLParams.Add("id", strconv.FormatUint(uint64(createdBounty.ID), 10))
req, err := http.NewRequestWithContext(
context.WithValue(ctx, chi.RouteCtxKey, rctx),
http.MethodDelete,
"/timing",
nil,
)
assert.NoError(t, err)

handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusNotFound, rr.Code)
})

t.Run("should successfully delete bounty timing", func(t *testing.T) {

_, err := db.TestDB.CreateBountyTiming(createdBounty.ID)
assert.NoError(t, err)

rr := httptest.NewRecorder()
handler := http.HandlerFunc(bHandler.DeleteBountyTiming)

ctx := context.WithValue(context.Background(), auth.ContextKey, bountyOwner.OwnerPubKey)

rctx := chi.NewRouteContext()
rctx.URLParams.Add("id", strconv.FormatUint(uint64(createdBounty.ID), 10))
req, err := http.NewRequestWithContext(
context.WithValue(ctx, chi.RouteCtxKey, rctx),
http.MethodDelete,
"/timing",
nil,
)
assert.NoError(t, err)

handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusNoContent, rr.Code)

timing, err := db.TestDB.GetBountyTiming(createdBounty.ID)
assert.Error(t, err)
assert.Nil(t, timing)
})
}
43 changes: 43 additions & 0 deletions mocks/Database.go

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

4 changes: 3 additions & 1 deletion routes/bounty.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package routes

import (
"github.com/stakwork/sphinx-tribes/auth"
"net/http"

"github.com/stakwork/sphinx-tribes/auth"

"github.com/go-chi/chi"
"github.com/stakwork/sphinx-tribes/db"
"github.com/stakwork/sphinx-tribes/handlers"
Expand Down Expand Up @@ -60,6 +61,7 @@ func BountyRoutes() chi.Router {
r.Get("/{id}/timing", bountyHandler.GetBountyTimingStats)
r.Put("/{id}/timing/start", bountyHandler.StartBountyTiming)
r.Put("/{id}/timing/close", bountyHandler.CloseBountyTiming)
r.Delete("/{id}/timing", bountyHandler.DeleteBountyTiming)
})
return r
}

0 comments on commit 2e50e82

Please sign in to comment.