diff --git a/pkg/cli/cmd/sync/sync.go b/pkg/cli/cmd/sync/sync.go index 1e4a792c..de0739fe 100644 --- a/pkg/cli/cmd/sync/sync.go +++ b/pkg/cli/cmd/sync/sync.go @@ -631,7 +631,8 @@ func stepSync(ctx context.DnoteCtx, tx *database.DB, afterUSN int) error { func sendBooks(ctx context.DnoteCtx, tx *database.DB) (bool, error) { isBehind := false - rows, err := tx.Query("SELECT uuid, label, usn, deleted FROM books WHERE dirty") + // send deleted books first to allow users to remain within the plan limit + rows, err := tx.Query("SELECT uuid, label, usn, deleted FROM books WHERE dirty ORDER BY deleted DESC") if err != nil { return isBehind, errors.Wrap(err, "getting syncable books") } diff --git a/pkg/server/handlers/routes.go b/pkg/server/handlers/routes.go index 198b6640..badf7198 100644 --- a/pkg/server/handlers/routes.go +++ b/pkg/server/handlers/routes.go @@ -399,7 +399,7 @@ func NewRouter(app *App) (*mux.Router, error) { {"OPTIONS", "/v3/books", cors(app.BooksOptions), true}, {"GET", "/v3/books", cors(app.auth(app.GetBooks, nil)), true}, {"GET", "/v3/books/{bookUUID}", cors(app.auth(app.GetBook, nil)), true}, - {"POST", "/v3/books", cors(app.auth(app.CreateBook, &checkPlanLimit)), false}, + {"POST", "/v3/books", cors(app.auth(app.CreateBook, nil)), false}, {"PATCH", "/v3/books/{bookUUID}", cors(app.auth(app.UpdateBook, &checkPlanLimit)), false}, {"DELETE", "/v3/books/{bookUUID}", cors(app.auth(app.DeleteBook, nil)), false}, {"OPTIONS", "/v3/notes", cors(app.NotesOptions), true}, diff --git a/pkg/server/handlers/v3_books.go b/pkg/server/handlers/v3_books.go index 84d5dd76..f58c2bf2 100644 --- a/pkg/server/handlers/v3_books.go +++ b/pkg/server/handlers/v3_books.go @@ -27,6 +27,7 @@ import ( "github.com/dnote/dnote/pkg/server/database" "github.com/dnote/dnote/pkg/server/helpers" "github.com/dnote/dnote/pkg/server/operations" + "github.com/dnote/dnote/pkg/server/permissions" "github.com/dnote/dnote/pkg/server/presenters" "github.com/gorilla/mux" "github.com/jinzhu/gorm" @@ -71,9 +72,19 @@ func (a *App) CreateBook(w http.ResponseWriter, r *http.Request) { } var book database.Book - conn := a.DB.Model(database.Book{}).Where("user_id = ? AND label = ?", user.ID, params.Name).First(&book) + conn := a.DB.Debug().Model(database.Book{}).Where("user_id = ? AND label = ?", user.ID, params.Name).First(&book) if conn.RecordNotFound() { + allowed, err := permissions.CanCreateBook(a.DB, user) + if err != nil { + HandleError(w, "checking plan threshold", err, http.StatusInternalServerError) + return + } + if !allowed { + a.respondPlanLimitExceeded(w) + return + } + b, err := operations.CreateBook(a.DB, user, a.Clock, params.Name) if err != nil { HandleError(w, "inserting book", err, http.StatusInternalServerError) diff --git a/pkg/server/permissions/plan.go b/pkg/server/permissions/plan.go index c3430735..88565e0f 100644 --- a/pkg/server/permissions/plan.go +++ b/pkg/server/permissions/plan.go @@ -18,9 +18,23 @@ func CheckPlanAllowance(db *gorm.DB, user database.User) (bool, error) { return false, errors.Wrap(err, "checking plan threshold") } - if bookCount >= 5 { + if bookCount > 5 { return false, nil } return true, nil } + +// CanCreateBook checks if the given user can create a book +func CanCreateBook(db *gorm.DB, user database.User) (bool, error) { + if user.Cloud { + return true, nil + } + + var bookCount int + if err := db.Model(database.Book{}).Where("user_id = ? AND NOT deleted", user.ID).Count(&bookCount).Error; err != nil { + return false, errors.Wrap(err, "checking plan threshold") + } + + return bookCount < 5, nil +}