-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add /v3 DELETE endpoint for cleanup (#862)
- Loading branch information
1 parent
3ef1669
commit ebb9ca5
Showing
8 changed files
with
230 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package controllers | ||
|
||
import ( | ||
"net/http" | ||
|
||
"github.com/envelope-zero/backend/v3/pkg/httperrors" | ||
"github.com/envelope-zero/backend/v3/pkg/models" | ||
"github.com/gin-gonic/gin" | ||
) | ||
|
||
// CleanupV3 permanently deletes all resources in the database | ||
// | ||
// @Summary Delete everything | ||
// @Description Permanently deletes all resources | ||
// @Tags v3 | ||
// @Success 204 | ||
// @Failure 400 {object} httperrors.HTTPError | ||
// @Failure 500 {object} httperrors.HTTPError | ||
// @Param confirm query string false "Confirmation to delete all resources. Must have the value 'yes-please-delete-everything'" | ||
// @Router /v3 [delete] | ||
func (co Controller) CleanupV3(c *gin.Context) { | ||
var params struct { | ||
Confirm string `form:"confirm"` | ||
} | ||
|
||
err := c.Bind(¶ms) | ||
if err != nil || params.Confirm != "yes-please-delete-everything" { | ||
c.JSON(http.StatusBadRequest, httperrors.HTTPError{ | ||
Error: httperrors.ErrCleanupConfirmation.Error(), | ||
}) | ||
return | ||
} | ||
|
||
// The order is important here since there are foreign keys to consider! | ||
models := []models.Model{ | ||
models.Allocation{}, | ||
models.MatchRule{}, | ||
models.Transaction{}, | ||
models.MonthConfig{}, | ||
models.Envelope{}, | ||
models.Category{}, | ||
models.Account{}, | ||
models.Budget{}, | ||
} | ||
|
||
// Use a transaction so that we can roll back if errors happen | ||
tx := co.DB.Begin() | ||
|
||
for _, model := range models { | ||
err := tx.Unscoped().Where("true").Delete(&model).Error | ||
if err != nil { | ||
c.JSON(http.StatusInternalServerError, httperrors.HTTPError{ | ||
Error: err.Error(), | ||
}) | ||
tx.Rollback() | ||
return | ||
} | ||
} | ||
|
||
tx.Commit() | ||
c.JSON(http.StatusNoContent, nil) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package controllers_test | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"testing" | ||
"time" | ||
|
||
"github.com/envelope-zero/backend/v3/internal/types" | ||
"github.com/envelope-zero/backend/v3/pkg/models" | ||
"github.com/envelope-zero/backend/v3/test" | ||
"github.com/shopspring/decimal" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func (suite *TestSuiteStandard) TestCleanupV3() { | ||
_ = suite.createTestBudget(models.BudgetCreate{}) | ||
account := suite.createTestAccount(models.AccountCreate{Name: "TestCleanup"}) | ||
_ = suite.createTestCategory(models.CategoryCreate{}) | ||
envelope := suite.createTestEnvelope(models.EnvelopeCreate{}) | ||
_ = suite.createTestAllocation(models.AllocationCreate{}) | ||
_ = suite.createTestTransaction(models.TransactionCreate{Amount: decimal.NewFromFloat(17.32)}) | ||
_ = suite.createTestMonthConfig(envelope.Data.ID, types.NewMonth(time.Now().Year(), time.Now().Month()), models.MonthConfigCreate{}) | ||
_ = suite.createTestMatchRule(suite.T(), models.MatchRuleCreate{AccountID: account.Data.ID, Match: "Delete me"}) | ||
|
||
tests := []string{ | ||
"http://example.com/v3/budgets", | ||
"http://example.com/v1/accounts", | ||
"http://example.com/v1/categories", | ||
"http://example.com/v3/transactions", | ||
"http://example.com/v1/envelopes", | ||
"http://example.com/v1/allocations", | ||
"http://example.com/v1/month-configs", | ||
"http://example.com/v3/match-rules", | ||
} | ||
|
||
// Delete | ||
recorder := test.Request(suite.controller, suite.T(), http.MethodDelete, "http://example.com/v3?confirm=yes-please-delete-everything", "") | ||
assertHTTPStatus(suite.T(), &recorder, http.StatusNoContent) | ||
|
||
// Verify | ||
for _, tt := range tests { | ||
suite.T().Run(tt, func(t *testing.T) { | ||
recorder := test.Request(suite.controller, suite.T(), http.MethodGet, tt, "") | ||
assertHTTPStatus(suite.T(), &recorder, http.StatusOK) | ||
|
||
var response struct { | ||
Data []any `json:"data"` | ||
} | ||
|
||
suite.decodeResponse(&recorder, &response) | ||
assert.Len(t, response.Data, 0, "There are resources left for type %s", tt) | ||
}) | ||
} | ||
} | ||
|
||
func (suite *TestSuiteStandard) TestCleanupV3Fails() { | ||
tests := []struct { | ||
name string | ||
path string | ||
}{ | ||
{"Invalid path", "confirm=2"}, | ||
{"Confirmation wrong", "confirm=invalid-confirmation"}, | ||
} | ||
|
||
for _, tt := range tests { | ||
suite.T().Run(tt.name, func(t *testing.T) { | ||
recorder := test.Request(suite.controller, t, http.MethodDelete, fmt.Sprintf("http://example.com/v3?%s", tt.path), "") | ||
assertHTTPStatus(suite.T(), &recorder, http.StatusBadRequest) | ||
}) | ||
} | ||
} | ||
|
||
func (suite *TestSuiteStandard) TestCleanupV3DBError() { | ||
suite.CloseDB() | ||
|
||
recorder := test.Request(suite.controller, suite.T(), http.MethodDelete, "http://example.com/v3?confirm=yes-please-delete-everything", "") | ||
assertHTTPStatus(suite.T(), &recorder, http.StatusInternalServerError) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters