diff --git a/pkg/server/handlers/v3_books.go b/pkg/server/handlers/v3_books.go index 84d5dd76..45c89298 100644 --- a/pkg/server/handlers/v3_books.go +++ b/pkg/server/handlers/v3_books.go @@ -70,6 +70,21 @@ func (a *App) CreateBook(w http.ResponseWriter, r *http.Request) { return } + // check for the plan allowance + if !user.Cloud { + var bookCount int + if err := a.DB.Model(database.Book{}).Where("user_id = ? AND NOT deleted", user.ID).Count(&bookCount).Error; err != nil { + HandleError(w, "checking plan threshold", err, http.StatusInternalServerError) + return + } + + if bookCount >= 5 { + msg := fmt.Sprintf("Your plan has reached the limit for the total number of books. Please upgrade at %s", a.WebURL) + http.Error(w, msg, http.StatusForbidden) + return + } + } + var book database.Book conn := a.DB.Model(database.Book{}).Where("user_id = ? AND label = ?", user.ID, params.Name).First(&book) diff --git a/pkg/server/handlers/v3_books_test.go b/pkg/server/handlers/v3_books_test.go index 4c12cfa3..1f2f23aa 100644 --- a/pkg/server/handlers/v3_books_test.go +++ b/pkg/server/handlers/v3_books_test.go @@ -33,7 +33,6 @@ import ( ) func TestGetBooks(t *testing.T) { - defer testutils.ClearData() // Setup @@ -112,7 +111,6 @@ func TestGetBooks(t *testing.T) { } func TestGetBooksByName(t *testing.T) { - defer testutils.ClearData() // Setup @@ -508,6 +506,56 @@ func TestCreateBookIdempotent(t *testing.T) { assert.Equal(t, userRecord.MaxUSN, 101, "user max_usn mismatch") } +func TestCreateBookPlanAllowance(t *testing.T) { + // Setup + server := MustNewServer(t, &App{ + Clock: clock.NewMock(), + }) + defer server.Close() + + createBook := func(t *testing.T, u database.User, name string, expectedStatusCode int) { + req := testutils.MakeReq(server, "POST", "/v3/books", fmt.Sprintf(`{"name": "%s"}`, name)) + res := testutils.HTTPAuthDo(t, req, u) + assert.StatusCodeEquals(t, res, expectedStatusCode, "") + } + + t.Run("pro plan", func(t *testing.T) { + defer testutils.ClearData() + + user := testutils.SetupUserData() + testutils.MustExec(t, testutils.DB.Model(&user).Update("cloud", true), "preparing user") + + createBook(t, user, "js1", http.StatusCreated) + createBook(t, user, "js2", http.StatusCreated) + createBook(t, user, "js3", http.StatusCreated) + createBook(t, user, "js4", http.StatusCreated) + createBook(t, user, "js5", http.StatusCreated) + createBook(t, user, "js6", http.StatusCreated) + + var bookCount int + testutils.MustExec(t, testutils.DB.Model(&database.Book{}).Count(&bookCount), "counting books") + assert.Equal(t, bookCount, 6, "book count mismatch") + }) + + t.Run("core plan", func(t *testing.T) { + defer testutils.ClearData() + + user := testutils.SetupUserData() + testutils.MustExec(t, testutils.DB.Model(&user).Update("cloud", false), "preparing user") + + createBook(t, user, "js1", http.StatusCreated) + createBook(t, user, "js2", http.StatusCreated) + createBook(t, user, "js3", http.StatusCreated) + createBook(t, user, "js4", http.StatusCreated) + createBook(t, user, "js5", http.StatusCreated) + createBook(t, user, "js6", http.StatusForbidden) + + var bookCount int + testutils.MustExec(t, testutils.DB.Model(&database.Book{}).Count(&bookCount), "counting books") + assert.Equal(t, bookCount, 5, "book count mismatch") + }) +} + func TestUpdateBook(t *testing.T) { updatedLabel := "updated-label"