Added docs via swagger

This commit is contained in:
kolaente 2018-06-13 13:45:22 +02:00
parent 0652125c25
commit 4589f3b4bb
No known key found for this signature in database
GPG key ID: F40E70337AB24C9B
17 changed files with 410 additions and 48 deletions

View file

@ -153,4 +153,27 @@ release-os-package:
.PHONY: release-zip .PHONY: release-zip
release-zip: release-zip:
$(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),cd $(file); zip -r ../../zip/$(shell basename $(file)).zip *; cd ../../../; ) $(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),cd $(file); zip -r ../../zip/$(shell basename $(file)).zip *; cd ../../../; )
.PHONY: generate-swagger
generate-swagger:
@hash swagger > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) get -u github.com/go-swagger/go-swagger/cmd/swagger; \
fi
swagger generate spec -o ./public/swagger.v1.json
.PHONY: swagger-check
swagger-check: generate-swagger
@diff=$$(git diff public/swagger.v1.json); \
if [ -n "$$diff" ]; then \
echo "Please run 'make generate-swagger' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi;
.PHONY: swagger-validate
swagger-validate:
@hash swagger > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) get -u github.com/go-swagger/go-swagger/cmd/swagger; \
fi
swagger validate ./public/swagger.v1.json

View file

@ -159,7 +159,6 @@ func (err ErrNeedToBeListOwner) Error() string {
return fmt.Sprintf("You need to be list owner to do that [ListID: %d, UserID: %d]", err.ListID, err.UserID) return fmt.Sprintf("You need to be list owner to do that [ListID: %d, UserID: %d]", err.ListID, err.UserID)
} }
// ================ // ================
// List item errors // List item errors
// ================ // ================
@ -178,7 +177,7 @@ func (err ErrListItemCannotBeEmpty) Error() string {
} }
// ErrListItemCannotBeEmpty represents a "ErrListDoesNotExist" kind of error. Used if the list does not exist. // ErrListItemCannotBeEmpty represents a "ErrListDoesNotExist" kind of error. Used if the list does not exist.
type ErrListItemDoesNotExist struct{ type ErrListItemDoesNotExist struct {
ID int64 ID int64
} }
@ -206,4 +205,4 @@ func IsErrNeedToBeItemOwner(err error) bool {
func (err ErrNeedToBeItemOwner) Error() string { func (err ErrNeedToBeItemOwner) Error() string {
return fmt.Sprintf("You need to be item owner to do that [ItemID: %d, UserID: %d]", err.ItemID, err.UserID) return fmt.Sprintf("You need to be item owner to do that [ItemID: %d, UserID: %d]", err.ItemID, err.UserID)
} }

View file

@ -9,7 +9,7 @@ func DeleteListByID(listID int64, doer *User) (err error) {
} }
if list.Owner.ID != doer.ID { if list.Owner.ID != doer.ID {
return ErrNeedToBeListOwner{ListID:listID, UserID:doer.ID} return ErrNeedToBeListOwner{ListID: listID, UserID: doer.ID}
} }
// Delete the list // Delete the list

View file

@ -95,7 +95,7 @@ func DeleteListItemByID(itemID int64, doer *User) (err error) {
// Check if the user hat the right to delete that item // Check if the user hat the right to delete that item
if listitem.CreatedByID != doer.ID { if listitem.CreatedByID != doer.ID {
return ErrNeedToBeItemOwner{ItemID:itemID, UserID: doer.ID} return ErrNeedToBeItemOwner{ItemID: itemID, UserID: doer.ID}
} }
_, err = x.ID(itemID).Delete(ListItem{}) _, err = x.ID(itemID).Delete(ListItem{})

View file

@ -17,21 +17,23 @@ func CreateOrUpdateListItem(item *ListItem) (newItem *ListItem, err error) {
item.CreatedByID = item.CreatedBy.ID item.CreatedByID = item.CreatedBy.ID
item.CreatedBy = user item.CreatedBy = user
// TODO: Check if the user has the right to add/update an item to that list
if item.ID != 0 { if item.ID != 0 {
_, err = x.ID(item.ID).Update(item) _, err = x.ID(item.ID).Update(item)
if err != nil { if err != nil {
return return
} }
} else { } else {
_, err = x.Insert(item)
if err != nil {
return
}
// Check if we have at least a text // Check if we have at least a text
if item.Text == "" { if item.Text == "" {
return newItem, ErrListItemCannotBeEmpty{} return newItem, ErrListItemCannotBeEmpty{}
} }
_, err = x.Insert(item)
if err != nil {
return
}
} }
// Get the new/updated item // Get the new/updated item

View file

@ -27,6 +27,23 @@ func (User) TableName() string {
return "users" return "users"
} }
// ApiUserPassword represents a user object without timestamps and a json password field.
type ApiUserPassword struct {
ID int64 `json:"id"`
Username string `json:"username"`
Password string `json:"password"`
Email string `json:"email"`
}
func (apiUser *ApiUserPassword) APIFormat() User {
return User{
ID: apiUser.ID,
Username: apiUser.Username,
Password: apiUser.Password,
Email: apiUser.Email,
}
}
// GetUserByID gets informations about a user by its ID // GetUserByID gets informations about a user by its ID
func GetUserByID(id int64) (user User, exists bool, err error) { func GetUserByID(id int64) (user User, exists bool, err error) {
// Apparently xorm does otherwise look for all users but return only one, which leads to returing one even if the ID is 0 // Apparently xorm does otherwise look for all users but return only one, which leads to returing one even if the ID is 0

View file

@ -1,13 +1,38 @@
package v1 package v1
import ( import (
"github.com/labstack/echo"
"strconv"
"net/http"
"git.kolaente.de/konrad/list/models" "git.kolaente.de/konrad/list/models"
"github.com/labstack/echo"
"net/http"
"strconv"
) )
func DeleteListItemByIDtemByID(c echo.Context) error { func DeleteListItemByIDtemByID(c echo.Context) error {
// swagger:operation DELETE /item/{itemID} lists deleteListItem
// ---
// summary: Deletes a list item
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: itemID
// in: path
// description: ID of the list item to delete
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/Message"
// "400":
// "$ref": "#/responses/Message"
// "403":
// "$ref": "#/responses/Message"
// "404":
// "$ref": "#/responses/Message"
// "500":
// "$ref": "#/responses/Message"
// Check if we have our ID // Check if we have our ID
id := c.Param("id") id := c.Param("id")
// Make int // Make int
@ -36,4 +61,4 @@ func DeleteListItemByIDtemByID(c echo.Context) error {
} }
return c.JSON(http.StatusOK, models.Message{"The item was deleted with success."}) return c.JSON(http.StatusOK, models.Message{"The item was deleted with success."})
} }

View file

@ -1,13 +1,38 @@
package v1 package v1
import ( import (
"github.com/labstack/echo"
"strconv"
"net/http"
"git.kolaente.de/konrad/list/models" "git.kolaente.de/konrad/list/models"
"github.com/labstack/echo"
"net/http"
"strconv"
) )
func DeleteListByID(c echo.Context) error { func DeleteListByID(c echo.Context) error {
// swagger:operation DELETE /lists/{listID} lists deleteList
// ---
// summary: Deletes a list with all items on it
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: listID
// in: path
// description: ID of the list to delete
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/Message"
// "400":
// "$ref": "#/responses/Message"
// "403":
// "$ref": "#/responses/Message"
// "404":
// "$ref": "#/responses/Message"
// "500":
// "$ref": "#/responses/Message"
// Check if we have our ID // Check if we have our ID
id := c.Param("id") id := c.Param("id")
// Make int // Make int
@ -36,4 +61,4 @@ func DeleteListByID(c echo.Context) error {
} }
return c.JSON(http.StatusOK, models.Message{"The list was deleted with success."}) return c.JSON(http.StatusOK, models.Message{"The list was deleted with success."})
} }

View file

@ -8,6 +8,33 @@ import (
) )
func AddListItem(c echo.Context) error { func AddListItem(c echo.Context) error {
// swagger:operation PUT /lists/{listID} lists addListItem
// ---
// summary: Adds an item to a list
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: listID
// in: path
// description: ID of the list to use
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/ListItem"
// responses:
// "200":
// "$ref": "#/responses/ListItem"
// "400":
// "$ref": "#/responses/Message"
// "500":
// "$ref": "#/responses/Message"
// TODO: return 403 if you dont have the right to add an item to that list
// Get the list ID // Get the list ID
id := c.Param("id") id := c.Param("id")
// Make int // Make int
@ -20,6 +47,31 @@ func AddListItem(c echo.Context) error {
} }
func UpdateListItem(c echo.Context) error { func UpdateListItem(c echo.Context) error {
// swagger:operation PUT /item/{itemID} lists updateListItem
// ---
// summary: Updates a list item
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: itemID
// in: path
// description: ID of the item to update
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/ListItem"
// responses:
// "200":
// "$ref": "#/responses/ListItem"
// "400":
// "$ref": "#/responses/Message"
// "500":
// "$ref": "#/responses/Message"
// Get the item ID // Get the item ID
id := c.Param("id") id := c.Param("id")
// Make int // Make int
@ -73,4 +125,4 @@ func updateOrCreateListItemHelper(listID, itemID int64, c echo.Context) error {
} }
return c.JSON(http.StatusOK, finalItem) return c.JSON(http.StatusOK, finalItem)
} }

View file

@ -9,6 +9,27 @@ import (
// AddOrUpdateList Adds or updates a new list // AddOrUpdateList Adds or updates a new list
func GetListByID(c echo.Context) error { func GetListByID(c echo.Context) error {
// swagger:operation GET /lists/{listID} lists getList
// ---
// summary: gets one list with all todo items
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: listID
// in: path
// description: ID of the list to show
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/List"
// "400":
// "$ref": "#/responses/Message"
// "500":
// "$ref": "#/responses/Message"
// Check if we have our ID // Check if we have our ID
id := c.Param("id") id := c.Param("id")
// Make int // Make int

View file

@ -7,8 +7,65 @@ import (
"strconv" "strconv"
) )
func AddList(c echo.Context) error {
// swagger:operation PUT /lists lists addList
// ---
// summary: Creates a new list owned by the currently logged in user
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/List"
// responses:
// "200":
// "$ref": "#/responses/List"
// "400":
// "$ref": "#/responses/Message"
// "403":
// "$ref": "#/responses/Message"
// "500":
// "$ref": "#/responses/Message"
return addOrUpdateList(c)
}
func UpdateList(c echo.Context) error {
// swagger:operation POST /lists/{listID} lists upadteList
// ---
// summary: Updates a list
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: listID
// in: path
// description: ID of the list to update
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/List"
// responses:
// "200":
// "$ref": "#/responses/List"
// "400":
// "$ref": "#/responses/Message"
// "403":
// "$ref": "#/responses/Message"
// "500":
// "$ref": "#/responses/Message"
return addOrUpdateList(c)
}
// AddOrUpdateList Adds or updates a new list // AddOrUpdateList Adds or updates a new list
func AddOrUpdateList(c echo.Context) error { func addOrUpdateList(c echo.Context) error {
// Get the list // Get the list
var list *models.List var list *models.List

View file

@ -8,6 +8,18 @@ import (
// GetListsByUser gets all lists a user owns // GetListsByUser gets all lists a user owns
func GetListsByUser(c echo.Context) error { func GetListsByUser(c echo.Context) error {
// swagger:operation GET /lists lists getLists
// ---
// summary: Gets all lists owned by the current user
// consumes:
// - application/json
// produces:
// - application/json
// responses:
// "200":
// "$ref": "#/responses/List"
// "500":
// "$ref": "#/responses/Message"
currentUser, err := models.GetCurrentUser(c) currentUser, err := models.GetCurrentUser(c)
if err != nil { if err != nil {

View file

@ -12,6 +12,26 @@ import (
// Login is the login handler // Login is the login handler
func Login(c echo.Context) error { func Login(c echo.Context) error {
// swagger:operation POST /login user login
// ---
// summary: Logs a user in. Returns a JWT-Token to authenticate requests
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/UserLogin"
// responses:
// "200":
// "$ref": "#/responses/Token"
// "400":
// "$ref": "#/responses/Message"
// "403":
// "$ref": "#/responses/Message"
u := new(models.UserLogin) u := new(models.UserLogin)
if err := c.Bind(u); err != nil { if err := c.Bind(u); err != nil {
return c.JSON(http.StatusBadRequest, models.Message{"Please provide a username and password."}) return c.JSON(http.StatusBadRequest, models.Message{"Please provide a username and password."})

View file

@ -0,0 +1,22 @@
package swagger
import "git.kolaente.de/konrad/list/models"
// not actually a response, just a hack to get go-swagger to include definitions
// of the various XYZOption structs
// parameterBodies
// swagger:response parameterBodies
type swaggerParameterBodies struct {
// in:body
UserLogin models.UserLogin
// in:body
ApiUserPassword models.ApiUserPassword
// in:body
List models.List
// in:body
ListItem models.ListItem
}

View file

@ -0,0 +1,52 @@
package swagger
import "git.kolaente.de/konrad/list/models"
// Message
// swagger:response Message
type swaggerResponseMessage struct {
// in:body
Body models.Message `json:"body"`
}
// ================
// User definitions
// ================
// User Object
// swagger:response User
type swaggerResponseUser struct {
// in:body
Body models.User `json:"body"`
}
// Token
// swagger:response Token
type swaggerResponseToken struct {
// The body message
// in:body
Body struct {
// The token
//
// Required: true
Token string `json:"token"`
} `json:"body"`
}
// ================
// List definitions
// ================
// List
// swagger:response List
type swaggerResponseLIst struct {
// in:body
Body models.List `json:"body"`
}
// ListItem
// swagger:response ListItem
type swaggerResponseLIstItem struct {
// in:body
Body models.ListItem `json:"body"`
}

View file

@ -1,36 +1,47 @@
package v1 package v1
import ( import (
"encoding/json"
"git.kolaente.de/konrad/list/models" "git.kolaente.de/konrad/list/models"
"github.com/labstack/echo" "github.com/labstack/echo"
"net/http" "net/http"
"strconv" "strconv"
"strings"
) )
// UserAddOrUpdate is the handler to add a user func RegisterUser(c echo.Context) error {
func UserAddOrUpdate(c echo.Context) error {
// swagger:operation POST /register user register
// ---
// summary: Creates a new user account
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/ApiUserPassword"
// responses:
// "200":
// "$ref": "#/responses/User"
// "400":
// "$ref": "#/responses/Message"
// "500":
// "$ref": "#/responses/Message"
return userAddOrUpdate(c)
}
// userAddOrUpdate is the handler to add a user
func userAddOrUpdate(c echo.Context) error {
// TODO: prevent everyone from updating users // TODO: prevent everyone from updating users
// Check for Request Content // Check for Request Content
userFromString := c.FormValue("user") var datUser *models.ApiUserPassword
var datUser *models.User
if userFromString == "" { if err := c.Bind(&datUser); err != nil {
// b := new(models.User) return c.JSON(http.StatusBadRequest, models.Message{"No user model provided."})
if err := c.Bind(&datUser); err != nil {
return c.JSON(http.StatusBadRequest, models.Message{"No user model provided."})
}
} else {
// Decode the JSON
dec := json.NewDecoder(strings.NewReader(userFromString))
err := dec.Decode(&datUser)
if err != nil {
return c.JSON(http.StatusBadRequest, models.Message{"Error decoding user: " + err.Error()})
}
} }
// Check if we have an ID other than the one in the struct // Check if we have an ID other than the one in the struct
@ -54,9 +65,9 @@ func UserAddOrUpdate(c echo.Context) error {
// Insert or update the user // Insert or update the user
var newUser models.User var newUser models.User
if exists { if exists {
newUser, err = models.UpdateUser(*datUser) newUser, err = models.UpdateUser(datUser.APIFormat())
} else { } else {
newUser, err = models.CreateUser(*datUser) newUser, err = models.CreateUser(datUser.APIFormat())
} }
if err != nil { if err != nil {
@ -88,8 +99,5 @@ func UserAddOrUpdate(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, models.Message{"Error"}) return c.JSON(http.StatusInternalServerError, models.Message{"Error"})
} }
// Obfuscate his password
newUser.Password = ""
return c.JSON(http.StatusOK, newUser) return c.JSON(http.StatusOK, newUser)
} }

View file

@ -1,3 +1,28 @@
// Package v1 List API.
//
// This documentation describes the List API.
//
// Schemes: http, https
// BasePath: /api/v1
// Version: 0.1
// License: GPLv3
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// Security:
// - AuthorizationHeaderToken :
//
// SecurityDefinitions:
// AuthorizationHeaderToken:
// type: apiKey
// name: Authorization
// in: header
//
// swagger:meta
package routes package routes
import ( import (
@ -6,6 +31,7 @@ import (
"git.kolaente.de/konrad/list/models" "git.kolaente.de/konrad/list/models"
apiv1 "git.kolaente.de/konrad/list/routes/api/v1" apiv1 "git.kolaente.de/konrad/list/routes/api/v1"
_ "git.kolaente.de/konrad/list/routes/api/v1/swagger" // for docs generation
) )
// NewEcho registers a new Echo instance // NewEcho registers a new Echo instance
@ -23,6 +49,7 @@ func NewEcho() *echo.Echo {
// RegisterRoutes registers all routes for the application // RegisterRoutes registers all routes for the application
func RegisterRoutes(e *echo.Echo) { func RegisterRoutes(e *echo.Echo) {
// Middleware for cors
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
res := c.Response() res := c.Response()
@ -46,17 +73,17 @@ func RegisterRoutes(e *echo.Echo) {
a.OPTIONS("/lists/:id", SetCORSHeader) a.OPTIONS("/lists/:id", SetCORSHeader)
a.POST("/login", apiv1.Login) a.POST("/login", apiv1.Login)
a.POST("/register", apiv1.UserAddOrUpdate) a.POST("/register", apiv1.RegisterUser)
// ===== Routes with Authetification ===== // ===== Routes with Authetification =====
// Authetification // Authetification
a.Use(middleware.JWT(models.Config.JWTLoginSecret)) a.Use(middleware.JWT(models.Config.JWTLoginSecret))
a.POST("/tokenTest", apiv1.CheckToken) a.POST("/tokenTest", apiv1.CheckToken)
a.PUT("/lists", apiv1.AddOrUpdateList)
a.GET("/lists", apiv1.GetListsByUser) a.GET("/lists", apiv1.GetListsByUser)
a.PUT("/lists", apiv1.AddList)
a.GET("/lists/:id", apiv1.GetListByID) a.GET("/lists/:id", apiv1.GetListByID)
a.POST("/lists/:id", apiv1.AddOrUpdateList) a.POST("/lists/:id", apiv1.UpdateList)
a.PUT("/lists/:id", apiv1.AddListItem) a.PUT("/lists/:id", apiv1.AddListItem)
a.DELETE("/lists/:id", apiv1.DeleteListByID) a.DELETE("/lists/:id", apiv1.DeleteListByID)