1
0

chore(web): move web handler package to Vikunja

(cherry picked from commit 2063da9eecf8d0980a62106a627d7f00da172138)
This commit is contained in:
kolaente
2024-08-29 16:15:28 +02:00
parent cfa58ae599
commit 4c73c74587
111 changed files with 1016 additions and 136 deletions

57
pkg/web/handler/config.go Normal file
View File

@ -0,0 +1,57 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package handler
import (
"code.vikunja.io/api/pkg/web"
"github.com/op/go-logging"
"xorm.io/xorm"
)
// Config contains the config for the web handler
type Config struct {
AuthProvider *web.Auths
LoggingProvider *logging.Logger
MaxItemsPerPage int
SessionFactory func() *xorm.Session
}
var config *Config
func init() {
config = &Config{}
}
// SetAuthProvider sets the auth provider in config
func SetAuthProvider(provider *web.Auths) {
config.AuthProvider = provider
}
// SetLoggingProvider sets the logging provider in the config
func SetLoggingProvider(logger *logging.Logger) {
config.LoggingProvider = logger
}
// SetMaxItemsPerPage sets the max number of items per page in the config
func SetMaxItemsPerPage(maxItemsPerPage int) {
config.MaxItemsPerPage = maxItemsPerPage
}
// SetSessionFactory sets the session factory
func SetSessionFactory(sessionFactory func() *xorm.Session) {
config.SessionFactory = sessionFactory
}

89
pkg/web/handler/create.go Normal file
View File

@ -0,0 +1,89 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package handler
import (
"fmt"
"net/http"
"github.com/labstack/echo/v4"
)
// CreateWeb is the handler to create an object
func (c *WebHandler) CreateWeb(ctx echo.Context) error {
// Get our model
currentStruct := c.EmptyStruct()
// Get the object & bind params to struct
if err := ctx.Bind(currentStruct); err != nil {
config.LoggingProvider.Debugf("Invalid model error. Internal error was: %s", err.Error())
if he, is := err.(*echo.HTTPError); is {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message))
}
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided."))
}
// Validate the struct
if err := ctx.Validate(currentStruct); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
// Get the user to pass for later checks
currentAuth, err := config.AuthProvider.AuthObject(ctx)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Could not determine the current user.")
}
// Create the db session
s := config.SessionFactory()
defer func() {
err = s.Close()
if err != nil {
config.LoggingProvider.Errorf("Could not close session: %s", err)
}
}()
// Check rights
canCreate, err := currentStruct.CanCreate(s, currentAuth)
if err != nil {
_ = s.Rollback()
return HandleHTTPError(err, ctx)
}
if !canCreate {
_ = s.Rollback()
config.LoggingProvider.Noticef("Tried to create while not having the rights for it (User: %v)", currentAuth)
return echo.NewHTTPError(http.StatusForbidden)
}
// Create
err = currentStruct.Create(s, currentAuth)
if err != nil {
_ = s.Rollback()
return HandleHTTPError(err, ctx)
}
err = s.Commit()
if err != nil {
return HandleHTTPError(err, ctx)
}
err = ctx.JSON(http.StatusCreated, currentStruct)
if err != nil {
return HandleHTTPError(err, ctx)
}
return err
}

87
pkg/web/handler/delete.go Normal file
View File

@ -0,0 +1,87 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package handler
import (
"fmt"
"net/http"
"github.com/labstack/echo/v4"
)
type message struct {
Message string `json:"message"`
}
// DeleteWeb is the web handler to delete something
func (c *WebHandler) DeleteWeb(ctx echo.Context) error {
// Get our model
currentStruct := c.EmptyStruct()
// Bind params to struct
if err := ctx.Bind(currentStruct); err != nil {
config.LoggingProvider.Debugf("Invalid model error. Internal error was: %s", err.Error())
if he, is := err.(*echo.HTTPError); is {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message))
}
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided."))
}
// Check if the user has the right to delete
currentAuth, err := config.AuthProvider.AuthObject(ctx)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError)
}
// Create the db session
s := config.SessionFactory()
defer func() {
err = s.Close()
if err != nil {
config.LoggingProvider.Errorf("Could not close session: %s", err)
}
}()
canDelete, err := currentStruct.CanDelete(s, currentAuth)
if err != nil {
_ = s.Rollback()
return HandleHTTPError(err, ctx)
}
if !canDelete {
_ = s.Rollback()
config.LoggingProvider.Noticef("Tried to delete while not having the rights for it (User: %v)", currentAuth)
return echo.NewHTTPError(http.StatusForbidden)
}
err = currentStruct.Delete(s, currentAuth)
if err != nil {
_ = s.Rollback()
return HandleHTTPError(err, ctx)
}
err = s.Commit()
if err != nil {
return HandleHTTPError(err, ctx)
}
err = ctx.JSON(http.StatusOK, message{"Successfully deleted."})
if err != nil {
return HandleHTTPError(err, ctx)
}
return err
}

46
pkg/web/handler/helper.go Normal file
View File

@ -0,0 +1,46 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package handler
import (
"net/http"
"code.vikunja.io/api/pkg/web"
"github.com/labstack/echo/v4"
)
// WebHandler defines the webhandler object
// This does web stuff, aka returns json etc. Uses CRUDable Methods to get the data
type WebHandler struct {
EmptyStruct func() CObject
}
// CObject is the definition of our object, holds the structs
type CObject interface {
web.CRUDable
web.Rights
}
// HandleHTTPError does what it says
func HandleHTTPError(err error, ctx echo.Context) *echo.HTTPError {
config.LoggingProvider.Error(err.Error())
if a, has := err.(web.HTTPErrorProcessor); has {
errDetails := a.HTTPError()
return echo.NewHTTPError(errDetails.HTTPCode, errDetails)
}
return echo.NewHTTPError(http.StatusInternalServerError)
}

129
pkg/web/handler/read_all.go Normal file
View File

@ -0,0 +1,129 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package handler
import (
"fmt"
"math"
"net/http"
"strconv"
"github.com/labstack/echo/v4"
)
// ReadAllWeb is the webhandler to get all objects of a type
func (c *WebHandler) ReadAllWeb(ctx echo.Context) error {
// Get our model
currentStruct := c.EmptyStruct()
currentAuth, err := config.AuthProvider.AuthObject(ctx)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Could not determine the current user.")
}
// Get the object & bind params to struct
if err := ctx.Bind(currentStruct); err != nil {
config.LoggingProvider.Debugf("Invalid model error. Internal error was: %s", err.Error())
if he, is := err.(*echo.HTTPError); is {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message))
}
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided."))
}
// Pagination
page := ctx.QueryParam("page")
if page == "" {
page = "1"
}
pageNumber, err := strconv.Atoi(page)
if err != nil {
config.LoggingProvider.Error(err.Error())
return echo.NewHTTPError(http.StatusBadRequest, "Bad page requested.")
}
if pageNumber < 0 {
return echo.NewHTTPError(http.StatusBadRequest, "Page number cannot be negative.")
}
// Items per page
var perPageNumber int
perPage := ctx.QueryParam("per_page")
// If we dont have an "items per page" parameter, we want to use the default.
// To prevent Atoi from failing, we check this here.
if perPage != "" {
perPageNumber, err = strconv.Atoi(perPage)
if err != nil {
config.LoggingProvider.Error(err.Error())
return echo.NewHTTPError(http.StatusBadRequest, "Bad per page amount requested.")
}
}
// Set default page count
if perPageNumber == 0 {
perPageNumber = config.MaxItemsPerPage
}
if perPageNumber < 1 {
return echo.NewHTTPError(http.StatusBadRequest, "Per page amount cannot be negative.")
}
if perPageNumber > config.MaxItemsPerPage {
perPageNumber = config.MaxItemsPerPage
}
// Create the db session
s := config.SessionFactory()
defer func() {
err = s.Close()
if err != nil {
config.LoggingProvider.Errorf("Could not close session: %s", err)
}
}()
// Search
search := ctx.QueryParam("s")
result, resultCount, numberOfItems, err := currentStruct.ReadAll(s, currentAuth, search, pageNumber, perPageNumber)
if err != nil {
_ = s.Rollback()
return HandleHTTPError(err, ctx)
}
// Calculate the number of pages from the number of items
// We always round up, because if we don't have a number of items which is exactly dividable by the number of items per page,
// we would get a result that is one page off.
var numberOfPages = math.Ceil(float64(numberOfItems) / float64(perPageNumber))
// If we return all results, we only have one page
if pageNumber < 0 {
numberOfPages = 1
}
// If we don't have results, we don't have a page
if resultCount == 0 {
numberOfPages = 0
}
ctx.Response().Header().Set("x-pagination-total-pages", strconv.FormatFloat(numberOfPages, 'f', 0, 64))
ctx.Response().Header().Set("x-pagination-result-count", strconv.FormatInt(int64(resultCount), 10))
ctx.Response().Header().Set("Access-Control-Expose-Headers", "x-pagination-total-pages, x-pagination-result-count")
err = s.Commit()
if err != nil {
return HandleHTTPError(err, ctx)
}
err = ctx.JSON(http.StatusOK, result)
if err != nil {
return HandleHTTPError(err, ctx)
}
return err
}

View File

@ -0,0 +1,90 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package handler
import (
"fmt"
"net/http"
"strconv"
"github.com/labstack/echo/v4"
)
// ReadOneWeb is the webhandler to get one object
func (c *WebHandler) ReadOneWeb(ctx echo.Context) error {
// Get our model
currentStruct := c.EmptyStruct()
// Get the object & bind params to struct
if err := ctx.Bind(currentStruct); err != nil {
config.LoggingProvider.Debugf("Invalid model error. Internal error was: %s", err.Error())
if he, is := err.(*echo.HTTPError); is {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message))
}
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided."))
}
// Check rights
currentAuth, err := config.AuthProvider.AuthObject(ctx)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Could not determine the current user.")
}
// Create the db session
s := config.SessionFactory()
defer func() {
err = s.Close()
if err != nil {
config.LoggingProvider.Errorf("Could not close session: %s", err)
}
}()
canRead, maxRight, err := currentStruct.CanRead(s, currentAuth)
if err != nil {
_ = s.Rollback()
return HandleHTTPError(err, ctx)
}
if !canRead {
_ = s.Rollback()
config.LoggingProvider.Noticef("Tried to read while not having the rights for it (User: %v)", currentAuth)
return echo.NewHTTPError(http.StatusForbidden, "You don't have the right to see this")
}
// Get our object
err = currentStruct.ReadOne(s, currentAuth)
if err != nil {
_ = s.Rollback()
return HandleHTTPError(err, ctx)
}
// Set the headers
if canRead {
ctx.Response().Header().Set("x-max-right", strconv.FormatInt(int64(maxRight), 10))
ctx.Response().Header().Set("Access-Control-Expose-Headers", "x-max-right")
}
err = s.Commit()
if err != nil {
return HandleHTTPError(err, ctx)
}
err = ctx.JSON(http.StatusOK, currentStruct)
if err != nil {
return HandleHTTPError(err, ctx)
}
return err
}

89
pkg/web/handler/update.go Normal file
View File

@ -0,0 +1,89 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package handler
import (
"fmt"
"net/http"
"github.com/labstack/echo/v4"
)
// UpdateWeb is the webhandler to update an object
func (c *WebHandler) UpdateWeb(ctx echo.Context) error {
// Get our model
currentStruct := c.EmptyStruct()
// Get the object & bind params to struct
if err := ctx.Bind(currentStruct); err != nil {
config.LoggingProvider.Debugf("Invalid model error. Internal error was: %s", err.Error())
if he, is := err.(*echo.HTTPError); is {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message))
}
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided."))
}
// Validate the struct
if err := ctx.Validate(currentStruct); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
// Check if the user has the right to do that
currentAuth, err := config.AuthProvider.AuthObject(ctx)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Could not determine the current user.")
}
// Create the db session
s := config.SessionFactory()
defer func() {
err = s.Close()
if err != nil {
config.LoggingProvider.Errorf("Could not close session: %s", err)
}
}()
canUpdate, err := currentStruct.CanUpdate(s, currentAuth)
if err != nil {
_ = s.Rollback()
return HandleHTTPError(err, ctx)
}
if !canUpdate {
_ = s.Rollback()
config.LoggingProvider.Noticef("Tried to update while not having the rights for it (User: %v)", currentAuth)
return echo.NewHTTPError(http.StatusForbidden)
}
// Do the update
err = currentStruct.Update(s, currentAuth)
if err != nil {
_ = s.Rollback()
return HandleHTTPError(err, ctx)
}
err = s.Commit()
if err != nil {
return HandleHTTPError(err, ctx)
}
err = ctx.JSON(http.StatusOK, currentStruct)
if err != nil {
return HandleHTTPError(err, ctx)
}
return err
}