mirror of
https://github.com/miniflux/v2.git
synced 2025-06-27 16:36:00 +00:00
fix(api): hide_globally
categories field should be a boolean
This commit is contained in:
parent
764212f37c
commit
d33e305af9
16 changed files with 262 additions and 70 deletions
2
Makefile
2
Makefile
|
@ -144,7 +144,7 @@ integration-test:
|
||||||
ADMIN_PASSWORD=test123 \
|
ADMIN_PASSWORD=test123 \
|
||||||
CREATE_ADMIN=1 \
|
CREATE_ADMIN=1 \
|
||||||
RUN_MIGRATIONS=1 \
|
RUN_MIGRATIONS=1 \
|
||||||
DEBUG=1 \
|
LOG_LEVEL=debug \
|
||||||
./miniflux-test >/tmp/miniflux.log 2>&1 & echo "$$!" > "/tmp/miniflux.pid"
|
./miniflux-test >/tmp/miniflux.log 2>&1 & echo "$$!" > "/tmp/miniflux.pid"
|
||||||
|
|
||||||
while ! nc -z localhost 8080; do sleep 1; done
|
while ! nc -z localhost 8080; do sleep 1; done
|
||||||
|
|
|
@ -239,8 +239,8 @@ func (c *Client) Categories() (Categories, error) {
|
||||||
|
|
||||||
// CreateCategory creates a new category.
|
// CreateCategory creates a new category.
|
||||||
func (c *Client) CreateCategory(title string) (*Category, error) {
|
func (c *Client) CreateCategory(title string) (*Category, error) {
|
||||||
body, err := c.request.Post("/v1/categories", map[string]interface{}{
|
body, err := c.request.Post("/v1/categories", &CategoryCreationRequest{
|
||||||
"title": title,
|
Title: title,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -255,10 +255,25 @@ func (c *Client) CreateCategory(title string) (*Category, error) {
|
||||||
return category, nil
|
return category, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateCategoryWithOptions creates a new category with options.
|
||||||
|
func (c *Client) CreateCategoryWithOptions(createRequest *CategoryCreationRequest) (*Category, error) {
|
||||||
|
body, err := c.request.Post("/v1/categories", createRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
var category *Category
|
||||||
|
if err := json.NewDecoder(body).Decode(&category); err != nil {
|
||||||
|
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||||
|
}
|
||||||
|
return category, nil
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateCategory updates a category.
|
// UpdateCategory updates a category.
|
||||||
func (c *Client) UpdateCategory(categoryID int64, title string) (*Category, error) {
|
func (c *Client) UpdateCategory(categoryID int64, title string) (*Category, error) {
|
||||||
body, err := c.request.Put(fmt.Sprintf("/v1/categories/%d", categoryID), map[string]interface{}{
|
body, err := c.request.Put(fmt.Sprintf("/v1/categories/%d", categoryID), &CategoryModificationRequest{
|
||||||
"title": title,
|
Title: SetOptionalField(title),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -273,6 +288,22 @@ func (c *Client) UpdateCategory(categoryID int64, title string) (*Category, erro
|
||||||
return category, nil
|
return category, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateCategoryWithOptions updates a category with options.
|
||||||
|
func (c *Client) UpdateCategoryWithOptions(categoryID int64, categoryChanges *CategoryModificationRequest) (*Category, error) {
|
||||||
|
body, err := c.request.Put(fmt.Sprintf("/v1/categories/%d", categoryID), categoryChanges)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
var category *Category
|
||||||
|
if err := json.NewDecoder(body).Decode(&category); err != nil {
|
||||||
|
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return category, nil
|
||||||
|
}
|
||||||
|
|
||||||
// MarkCategoryAsRead marks all unread entries in a category as read.
|
// MarkCategoryAsRead marks all unread entries in a category as read.
|
||||||
func (c *Client) MarkCategoryAsRead(categoryID int64) error {
|
func (c *Client) MarkCategoryAsRead(categoryID int64) error {
|
||||||
_, err := c.request.Put(fmt.Sprintf("/v1/categories/%d/mark-all-as-read", categoryID), nil)
|
_, err := c.request.Put(fmt.Sprintf("/v1/categories/%d/mark-all-as-read", categoryID), nil)
|
||||||
|
|
|
@ -97,9 +97,12 @@ type Users []User
|
||||||
|
|
||||||
// Category represents a feed category.
|
// Category represents a feed category.
|
||||||
type Category struct {
|
type Category struct {
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title"`
|
||||||
UserID int64 `json:"user_id,omitempty"`
|
UserID int64 `json:"user_id,omitempty"`
|
||||||
|
HideGlobally bool `json:"hide_globally,omitempty"`
|
||||||
|
FeedCount *int `json:"feed_count,omitempty"`
|
||||||
|
TotalUnread *int `json:"total_unread,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Category) String() string {
|
func (c Category) String() string {
|
||||||
|
@ -109,6 +112,18 @@ func (c Category) String() string {
|
||||||
// Categories represents a list of categories.
|
// Categories represents a list of categories.
|
||||||
type Categories []*Category
|
type Categories []*Category
|
||||||
|
|
||||||
|
// CategoryCreationRequest represents the request to create a category.
|
||||||
|
type CategoryCreationRequest struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
HideGlobally bool `json:"hide_globally"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CategoryModificationRequest represents the request to update a category.
|
||||||
|
type CategoryModificationRequest struct {
|
||||||
|
Title *string `json:"title"`
|
||||||
|
HideGlobally *bool `json:"hide_globally"`
|
||||||
|
}
|
||||||
|
|
||||||
// Subscription represents a feed subscription.
|
// Subscription represents a feed subscription.
|
||||||
type Subscription struct {
|
type Subscription struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
|
|
@ -824,6 +824,10 @@ func TestCreateCategoryEndpoint(t *testing.T) {
|
||||||
if category.Title != categoryName {
|
if category.Title != categoryName {
|
||||||
t.Errorf(`Invalid title, got "%v" instead of "%v"`, category.Title, categoryName)
|
t.Errorf(`Invalid title, got "%v" instead of "%v"`, category.Title, categoryName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if category.HideGlobally {
|
||||||
|
t.Errorf(`Invalid hide globally value, got "%v"`, category.HideGlobally)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateCategoryWithEmptyTitle(t *testing.T) {
|
func TestCreateCategoryWithEmptyTitle(t *testing.T) {
|
||||||
|
@ -865,7 +869,49 @@ func TestCannotCreateDuplicatedCategory(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateCatgoryEndpoint(t *testing.T) {
|
func TestCreateCategoryWithOptions(t *testing.T) {
|
||||||
|
testConfig := newIntegrationTestConfig()
|
||||||
|
if !testConfig.isConfigured() {
|
||||||
|
t.Skip(skipIntegrationTestsMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
adminClient := miniflux.NewClient(testConfig.testBaseURL, testConfig.testAdminUsername, testConfig.testAdminPassword)
|
||||||
|
|
||||||
|
regularTestUser, err := adminClient.CreateUser(testConfig.genRandomUsername(), testConfig.testRegularPassword, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer adminClient.DeleteUser(regularTestUser.ID)
|
||||||
|
|
||||||
|
regularUserClient := miniflux.NewClient(testConfig.testBaseURL, regularTestUser.Username, testConfig.testRegularPassword)
|
||||||
|
|
||||||
|
newCategory, err := regularUserClient.CreateCategoryWithOptions(&miniflux.CategoryCreationRequest{
|
||||||
|
Title: "My category",
|
||||||
|
HideGlobally: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`Creating a category with options should not raise an error: %v`, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
categories, err := regularUserClient.Categories()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, category := range categories {
|
||||||
|
if category.ID == newCategory.ID {
|
||||||
|
if category.Title != newCategory.Title {
|
||||||
|
t.Errorf(`Invalid title, got %q instead of %q`, category.Title, newCategory.Title)
|
||||||
|
}
|
||||||
|
if category.HideGlobally != true {
|
||||||
|
t.Errorf(`Invalid hide globally value, got "%v"`, category.HideGlobally)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateCategoryEndpoint(t *testing.T) {
|
||||||
testConfig := newIntegrationTestConfig()
|
testConfig := newIntegrationTestConfig()
|
||||||
if !testConfig.isConfigured() {
|
if !testConfig.isConfigured() {
|
||||||
t.Skip(skipIntegrationTestsMessage)
|
t.Skip(skipIntegrationTestsMessage)
|
||||||
|
@ -903,6 +949,91 @@ func TestUpdateCatgoryEndpoint(t *testing.T) {
|
||||||
if updatedCategory.Title != "new title" {
|
if updatedCategory.Title != "new title" {
|
||||||
t.Errorf(`Invalid title, got "%v" instead of "%v"`, updatedCategory.Title, "new title")
|
t.Errorf(`Invalid title, got "%v" instead of "%v"`, updatedCategory.Title, "new title")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if updatedCategory.HideGlobally {
|
||||||
|
t.Errorf(`Invalid hide globally value, got "%v"`, updatedCategory.HideGlobally)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateCategoryWithOptions(t *testing.T) {
|
||||||
|
testConfig := newIntegrationTestConfig()
|
||||||
|
if !testConfig.isConfigured() {
|
||||||
|
t.Skip(skipIntegrationTestsMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
adminClient := miniflux.NewClient(testConfig.testBaseURL, testConfig.testAdminUsername, testConfig.testAdminPassword)
|
||||||
|
|
||||||
|
regularTestUser, err := adminClient.CreateUser(testConfig.genRandomUsername(), testConfig.testRegularPassword, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer adminClient.DeleteUser(regularTestUser.ID)
|
||||||
|
|
||||||
|
regularUserClient := miniflux.NewClient(testConfig.testBaseURL, regularTestUser.Username, testConfig.testRegularPassword)
|
||||||
|
|
||||||
|
newCategory, err := regularUserClient.CreateCategoryWithOptions(&miniflux.CategoryCreationRequest{
|
||||||
|
Title: "My category",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`Creating a category with options should not raise an error: %v`, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedCategory, err := regularUserClient.UpdateCategoryWithOptions(newCategory.ID, &miniflux.CategoryModificationRequest{
|
||||||
|
Title: miniflux.SetOptionalField("new title"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if updatedCategory.ID != newCategory.ID {
|
||||||
|
t.Errorf(`Invalid categoryID, got "%v"`, updatedCategory.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if updatedCategory.Title != "new title" {
|
||||||
|
t.Errorf(`Invalid title, got "%v" instead of "%v"`, updatedCategory.Title, "new title")
|
||||||
|
}
|
||||||
|
|
||||||
|
if updatedCategory.HideGlobally {
|
||||||
|
t.Errorf(`Invalid hide globally value, got "%v"`, updatedCategory.HideGlobally)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedCategory, err = regularUserClient.UpdateCategoryWithOptions(newCategory.ID, &miniflux.CategoryModificationRequest{
|
||||||
|
HideGlobally: miniflux.SetOptionalField(true),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if updatedCategory.ID != newCategory.ID {
|
||||||
|
t.Errorf(`Invalid categoryID, got "%v"`, updatedCategory.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if updatedCategory.Title != "new title" {
|
||||||
|
t.Errorf(`Invalid title, got "%v" instead of "%v"`, updatedCategory.Title, "new title")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !updatedCategory.HideGlobally {
|
||||||
|
t.Errorf(`Invalid hide globally value, got "%v"`, updatedCategory.HideGlobally)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedCategory, err = regularUserClient.UpdateCategoryWithOptions(newCategory.ID, &miniflux.CategoryModificationRequest{
|
||||||
|
HideGlobally: miniflux.SetOptionalField(false),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if updatedCategory.ID != newCategory.ID {
|
||||||
|
t.Errorf(`Invalid categoryID, got %q`, updatedCategory.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if updatedCategory.Title != "new title" {
|
||||||
|
t.Errorf(`Invalid title, got %q instead of %q`, updatedCategory.Title, "new title")
|
||||||
|
}
|
||||||
|
|
||||||
|
if updatedCategory.HideGlobally {
|
||||||
|
t.Errorf(`Invalid hide globally value, got "%v"`, updatedCategory.HideGlobally)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateInexistingCategory(t *testing.T) {
|
func TestUpdateInexistingCategory(t *testing.T) {
|
||||||
|
|
|
@ -19,18 +19,18 @@ import (
|
||||||
func (h *handler) createCategory(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) createCategory(w http.ResponseWriter, r *http.Request) {
|
||||||
userID := request.UserID(r)
|
userID := request.UserID(r)
|
||||||
|
|
||||||
var categoryRequest model.CategoryRequest
|
var categoryCreationRequest model.CategoryCreationRequest
|
||||||
if err := json_parser.NewDecoder(r.Body).Decode(&categoryRequest); err != nil {
|
if err := json_parser.NewDecoder(r.Body).Decode(&categoryCreationRequest); err != nil {
|
||||||
json.BadRequest(w, r, err)
|
json.BadRequest(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if validationErr := validator.ValidateCategoryCreation(h.store, userID, &categoryRequest); validationErr != nil {
|
if validationErr := validator.ValidateCategoryCreation(h.store, userID, &categoryCreationRequest); validationErr != nil {
|
||||||
json.BadRequest(w, r, validationErr.Error())
|
json.BadRequest(w, r, validationErr.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
category, err := h.store.CreateCategory(userID, &categoryRequest)
|
category, err := h.store.CreateCategory(userID, &categoryCreationRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
json.ServerError(w, r, err)
|
json.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
|
@ -54,20 +54,20 @@ func (h *handler) updateCategory(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var categoryRequest model.CategoryRequest
|
var categoryModificationRequest model.CategoryModificationRequest
|
||||||
if err := json_parser.NewDecoder(r.Body).Decode(&categoryRequest); err != nil {
|
if err := json_parser.NewDecoder(r.Body).Decode(&categoryModificationRequest); err != nil {
|
||||||
json.BadRequest(w, r, err)
|
json.BadRequest(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if validationErr := validator.ValidateCategoryModification(h.store, userID, category.ID, &categoryRequest); validationErr != nil {
|
if validationErr := validator.ValidateCategoryModification(h.store, userID, category.ID, &categoryModificationRequest); validationErr != nil {
|
||||||
json.BadRequest(w, r, validationErr.Error())
|
json.BadRequest(w, r, validationErr.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
categoryRequest.Patch(category)
|
categoryModificationRequest.Patch(category)
|
||||||
err = h.store.UpdateCategory(category)
|
|
||||||
if err != nil {
|
if err := h.store.UpdateCategory(category); err != nil {
|
||||||
json.ServerError(w, r, err)
|
json.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -736,17 +736,16 @@ func getFeed(stream Stream, store *storage.Storage, userID int64) (*model.Feed,
|
||||||
return store.FeedByID(userID, feedID)
|
return store.FeedByID(userID, feedID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOrCreateCategory(category Stream, store *storage.Storage, userID int64) (*model.Category, error) {
|
func getOrCreateCategory(streamCategory Stream, store *storage.Storage, userID int64) (*model.Category, error) {
|
||||||
switch {
|
switch {
|
||||||
case category.ID == "":
|
case streamCategory.ID == "":
|
||||||
return store.FirstCategory(userID)
|
return store.FirstCategory(userID)
|
||||||
case store.CategoryTitleExists(userID, category.ID):
|
case store.CategoryTitleExists(userID, streamCategory.ID):
|
||||||
return store.CategoryByTitle(userID, category.ID)
|
return store.CategoryByTitle(userID, streamCategory.ID)
|
||||||
default:
|
default:
|
||||||
catRequest := model.CategoryRequest{
|
return store.CreateCategory(userID, &model.CategoryCreationRequest{
|
||||||
Title: category.ID,
|
Title: streamCategory.ID,
|
||||||
}
|
})
|
||||||
return store.CreateCategory(userID, &catRequest)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1144,20 +1143,23 @@ func (h *handler) renameTagHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NotFound(w, r)
|
json.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
categoryRequest := model.CategoryRequest{
|
|
||||||
Title: destination.ID,
|
categoryModificationRequest := model.CategoryModificationRequest{
|
||||||
|
Title: model.SetOptionalField(destination.ID),
|
||||||
}
|
}
|
||||||
verr := validator.ValidateCategoryModification(h.store, userID, category.ID, &categoryRequest)
|
|
||||||
if verr != nil {
|
if validationError := validator.ValidateCategoryModification(h.store, userID, category.ID, &categoryModificationRequest); validationError != nil {
|
||||||
json.BadRequest(w, r, verr.Error())
|
json.BadRequest(w, r, validationError.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
categoryRequest.Patch(category)
|
|
||||||
err = h.store.UpdateCategory(category)
|
categoryModificationRequest.Patch(category)
|
||||||
if err != nil {
|
|
||||||
|
if err := h.store.UpdateCategory(category); err != nil {
|
||||||
json.ServerError(w, r, err)
|
json.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
OK(w, r)
|
OK(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,16 +19,24 @@ func (c *Category) String() string {
|
||||||
return fmt.Sprintf("ID=%d, UserID=%d, Title=%s", c.ID, c.UserID, c.Title)
|
return fmt.Sprintf("ID=%d, UserID=%d, Title=%s", c.ID, c.UserID, c.Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CategoryRequest represents the request to create or update a category.
|
type CategoryCreationRequest struct {
|
||||||
type CategoryRequest struct {
|
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
HideGlobally string `json:"hide_globally"`
|
HideGlobally bool `json:"hide_globally"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch updates category fields.
|
type CategoryModificationRequest struct {
|
||||||
func (cr *CategoryRequest) Patch(category *Category) {
|
Title *string `json:"title"`
|
||||||
category.Title = cr.Title
|
HideGlobally *bool `json:"hide_globally"`
|
||||||
category.HideGlobally = cr.HideGlobally != ""
|
}
|
||||||
|
|
||||||
|
func (c *CategoryModificationRequest) Patch(category *Category) {
|
||||||
|
if c.Title != nil {
|
||||||
|
category.Title = *c.Title
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.HideGlobally != nil {
|
||||||
|
category.HideGlobally = *c.HideGlobally
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Categories represents a list of categories.
|
// Categories represents a list of categories.
|
||||||
|
|
|
@ -20,3 +20,7 @@ func OptionalString(value string) *string {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetOptionalField[T any](value T) *T {
|
||||||
|
return &value
|
||||||
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ func (h *Handler) Import(userID int64, data io.Reader) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if category == nil {
|
if category == nil {
|
||||||
category, err = h.store.CreateCategory(userID, &model.CategoryRequest{Title: subscription.CategoryName})
|
category, err = h.store.CreateCategory(userID, &model.CategoryCreationRequest{Title: subscription.CategoryName})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(`opml: unable to create this category: %q`, subscription.CategoryName)
|
return fmt.Errorf(`opml: unable to create this category: %q`, subscription.CategoryName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,27 +165,30 @@ func (s *Storage) CategoriesWithFeedCount(userID int64) (model.Categories, error
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateCategory creates a new category.
|
// CreateCategory creates a new category.
|
||||||
func (s *Storage) CreateCategory(userID int64, request *model.CategoryRequest) (*model.Category, error) {
|
func (s *Storage) CreateCategory(userID int64, request *model.CategoryCreationRequest) (*model.Category, error) {
|
||||||
var category model.Category
|
var category model.Category
|
||||||
|
|
||||||
query := `
|
query := `
|
||||||
INSERT INTO categories
|
INSERT INTO categories
|
||||||
(user_id, title)
|
(user_id, title, hide_globally)
|
||||||
VALUES
|
VALUES
|
||||||
($1, $2)
|
($1, $2, $3)
|
||||||
RETURNING
|
RETURNING
|
||||||
id,
|
id,
|
||||||
user_id,
|
user_id,
|
||||||
title
|
title,
|
||||||
|
hide_globally
|
||||||
`
|
`
|
||||||
err := s.db.QueryRow(
|
err := s.db.QueryRow(
|
||||||
query,
|
query,
|
||||||
userID,
|
userID,
|
||||||
request.Title,
|
request.Title,
|
||||||
|
request.HideGlobally,
|
||||||
).Scan(
|
).Scan(
|
||||||
&category.ID,
|
&category.ID,
|
||||||
&category.UserID,
|
&category.UserID,
|
||||||
&category.Title,
|
&category.Title,
|
||||||
|
&category.HideGlobally,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -197,7 +200,7 @@ func (s *Storage) CreateCategory(userID int64, request *model.CategoryRequest) (
|
||||||
|
|
||||||
// UpdateCategory updates an existing category.
|
// UpdateCategory updates an existing category.
|
||||||
func (s *Storage) UpdateCategory(category *model.Category) error {
|
func (s *Storage) UpdateCategory(category *model.Category) error {
|
||||||
query := `UPDATE categories SET title=$1, hide_globally = $2 WHERE id=$3 AND user_id=$4`
|
query := `UPDATE categories SET title=$1, hide_globally=$2 WHERE id=$3 AND user_id=$4`
|
||||||
_, err := s.db.Exec(
|
_, err := s.db.Exec(
|
||||||
query,
|
query,
|
||||||
category.Title,
|
category.Title,
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
<input type="text" name="title" id="form-title" value="{{ .form.Title }}" required autofocus>
|
<input type="text" name="title" id="form-title" value="{{ .form.Title }}" required autofocus>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" name="hide_globally" {{ if .form.HideGlobally }}checked{{ end }}>
|
<input type="checkbox" name="hide_globally" {{ if .form.HideGlobally }}checked{{ end }} value="1">
|
||||||
{{ t "form.category.hide_globally" }}
|
{{ t "form.category.hide_globally" }}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,7 @@ func (h *handler) showEditCategoryPage(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
categoryID := request.RouteInt64Param(r, "categoryID")
|
category, err := h.store.Category(request.UserID(r), request.RouteInt64Param(r, "categoryID"))
|
||||||
category, err := h.store.Category(request.UserID(r), categoryID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
html.ServerError(w, r, err)
|
html.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
|
@ -34,10 +33,7 @@ func (h *handler) showEditCategoryPage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
categoryForm := form.CategoryForm{
|
categoryForm := form.CategoryForm{
|
||||||
Title: category.Title,
|
Title: category.Title,
|
||||||
HideGlobally: "",
|
HideGlobally: category.HideGlobally,
|
||||||
}
|
|
||||||
if category.HideGlobally {
|
|
||||||
categoryForm.HideGlobally = "checked"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sess := session.New(h.store, request.SessionID(r))
|
sess := session.New(h.store, request.SessionID(r))
|
||||||
|
|
|
@ -33,15 +33,15 @@ func (h *handler) saveCategory(w http.ResponseWriter, r *http.Request) {
|
||||||
view.Set("countUnread", h.store.CountUnreadEntries(loggedUser.ID))
|
view.Set("countUnread", h.store.CountUnreadEntries(loggedUser.ID))
|
||||||
view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(loggedUser.ID))
|
view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(loggedUser.ID))
|
||||||
|
|
||||||
categoryRequest := &model.CategoryRequest{Title: categoryForm.Title}
|
categoryCreationRequest := &model.CategoryCreationRequest{Title: categoryForm.Title}
|
||||||
|
|
||||||
if validationErr := validator.ValidateCategoryCreation(h.store, loggedUser.ID, categoryRequest); validationErr != nil {
|
if validationErr := validator.ValidateCategoryCreation(h.store, loggedUser.ID, categoryCreationRequest); validationErr != nil {
|
||||||
view.Set("errorMessage", validationErr.Translate(loggedUser.Language))
|
view.Set("errorMessage", validationErr.Translate(loggedUser.Language))
|
||||||
html.OK(w, r, view.Render("create_category"))
|
html.OK(w, r, view.Render("create_category"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = h.store.CreateCategory(loggedUser.ID, categoryRequest); err != nil {
|
if _, err = h.store.CreateCategory(loggedUser.ID, categoryCreationRequest); err != nil {
|
||||||
html.ServerError(w, r, err)
|
html.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,9 +46,9 @@ func (h *handler) updateCategory(w http.ResponseWriter, r *http.Request) {
|
||||||
view.Set("countUnread", h.store.CountUnreadEntries(loggedUser.ID))
|
view.Set("countUnread", h.store.CountUnreadEntries(loggedUser.ID))
|
||||||
view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(loggedUser.ID))
|
view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(loggedUser.ID))
|
||||||
|
|
||||||
categoryRequest := &model.CategoryRequest{
|
categoryRequest := &model.CategoryModificationRequest{
|
||||||
Title: categoryForm.Title,
|
Title: model.SetOptionalField(categoryForm.Title),
|
||||||
HideGlobally: categoryForm.HideGlobally,
|
HideGlobally: model.SetOptionalField(categoryForm.HideGlobally),
|
||||||
}
|
}
|
||||||
|
|
||||||
if validationErr := validator.ValidateCategoryModification(h.store, loggedUser.ID, category.ID, categoryRequest); validationErr != nil {
|
if validationErr := validator.ValidateCategoryModification(h.store, loggedUser.ID, category.ID, categoryRequest); validationErr != nil {
|
||||||
|
|
|
@ -10,13 +10,13 @@ import (
|
||||||
// CategoryForm represents a feed form in the UI
|
// CategoryForm represents a feed form in the UI
|
||||||
type CategoryForm struct {
|
type CategoryForm struct {
|
||||||
Title string
|
Title string
|
||||||
HideGlobally string
|
HideGlobally bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCategoryForm returns a new CategoryForm.
|
// NewCategoryForm returns a new CategoryForm.
|
||||||
func NewCategoryForm(r *http.Request) *CategoryForm {
|
func NewCategoryForm(r *http.Request) *CategoryForm {
|
||||||
return &CategoryForm{
|
return &CategoryForm{
|
||||||
Title: r.FormValue("title"),
|
Title: r.FormValue("title"),
|
||||||
HideGlobally: r.FormValue("hide_globally"),
|
HideGlobally: r.FormValue("hide_globally") == "1",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateCategoryCreation validates category creation.
|
// ValidateCategoryCreation validates category creation.
|
||||||
func ValidateCategoryCreation(store *storage.Storage, userID int64, request *model.CategoryRequest) *locale.LocalizedError {
|
func ValidateCategoryCreation(store *storage.Storage, userID int64, request *model.CategoryCreationRequest) *locale.LocalizedError {
|
||||||
if request.Title == "" {
|
if request.Title == "" {
|
||||||
return locale.NewLocalizedError("error.title_required")
|
return locale.NewLocalizedError("error.title_required")
|
||||||
}
|
}
|
||||||
|
@ -23,13 +23,15 @@ func ValidateCategoryCreation(store *storage.Storage, userID int64, request *mod
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateCategoryModification validates category modification.
|
// ValidateCategoryModification validates category modification.
|
||||||
func ValidateCategoryModification(store *storage.Storage, userID, categoryID int64, request *model.CategoryRequest) *locale.LocalizedError {
|
func ValidateCategoryModification(store *storage.Storage, userID, categoryID int64, request *model.CategoryModificationRequest) *locale.LocalizedError {
|
||||||
if request.Title == "" {
|
if request.Title != nil {
|
||||||
return locale.NewLocalizedError("error.title_required")
|
if *request.Title == "" {
|
||||||
}
|
return locale.NewLocalizedError("error.title_required")
|
||||||
|
}
|
||||||
|
|
||||||
if store.AnotherCategoryExists(userID, categoryID, request.Title) {
|
if store.AnotherCategoryExists(userID, categoryID, *request.Title) {
|
||||||
return locale.NewLocalizedError("error.category_already_exists")
|
return locale.NewLocalizedError("error.category_already_exists")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue