diff --git a/.gitignore b/.gitignore index 07e6cf21..be026243 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ drone_* .env temp/ +api/swagger/files/* + # vendored repositories that we don't actually need # to vendor. so exclude them diff --git a/api/build.go b/api/build.go new file mode 100644 index 00000000..778f64ec --- /dev/null +++ b/api/build.go @@ -0,0 +1 @@ +package api diff --git a/api/doc.go b/api/doc.go new file mode 100644 index 00000000..6c00e13e --- /dev/null +++ b/api/doc.go @@ -0,0 +1,16 @@ +// Package classification Drone API. +// +// Schemes: http, https +// BasePath: /api +// Version: 1.0.0 +// +// Consumes: +// - application/json +// +// Produces: +// - application/json +// +// swagger:meta +package api + +//go:generate swagger generate spec -o swagger/files/swagger.json diff --git a/controller/node.go b/api/node.go similarity index 79% rename from controller/node.go rename to api/node.go index 1ee3bfe5..90b73179 100644 --- a/controller/node.go +++ b/api/node.go @@ -1,4 +1,4 @@ -package controller +package api import ( "net/http" @@ -8,8 +8,6 @@ import ( "github.com/drone/drone/model" "github.com/drone/drone/router/middleware/context" - "github.com/drone/drone/router/middleware/session" - "github.com/drone/drone/shared/token" "github.com/drone/drone/store" ) @@ -22,13 +20,6 @@ func GetNodes(c *gin.Context) { } } -func ShowNodes(c *gin.Context) { - user := session.User(c) - nodes, _ := store.GetNodeList(c) - token, _ := token.New(token.CsrfToken, user.Login).Sign(user.Hash) - c.HTML(http.StatusOK, "nodes.html", gin.H{"User": user, "Nodes": nodes, "Csrf": token}) -} - func GetNode(c *gin.Context) { } diff --git a/controller/repo.go b/api/repo.go similarity index 98% rename from controller/repo.go rename to api/repo.go index 53c93755..59a0fb4c 100644 --- a/controller/repo.go +++ b/api/repo.go @@ -1,4 +1,4 @@ -package controller +package api import ( "bytes" @@ -150,11 +150,11 @@ func PatchRepo(c *gin.Context) { return } - c.IndentedJSON(http.StatusOK, repo) + c.JSON(http.StatusOK, repo) } func GetRepo(c *gin.Context) { - c.IndentedJSON(http.StatusOK, session.Repo(c)) + c.JSON(http.StatusOK, session.Repo(c)) } func GetRepoKey(c *gin.Context) { diff --git a/api/swagger/swagger.go b/api/swagger/swagger.go new file mode 100644 index 00000000..7921031e --- /dev/null +++ b/api/swagger/swagger.go @@ -0,0 +1,3 @@ +package swagger + +//go:generate go-bindata -pkg swagger -o swagger_gen.go files/ diff --git a/api/user.go b/api/user.go new file mode 100644 index 00000000..d7a7cde3 --- /dev/null +++ b/api/user.go @@ -0,0 +1,113 @@ +package api + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/drone/drone/cache" + "github.com/drone/drone/model" + "github.com/drone/drone/router/middleware/session" + "github.com/drone/drone/shared/token" + "github.com/drone/drone/store" +) + +// swagger:route GET /user user getUser +// +// Get the currently authenticated user. +// +// Responses: +// 200: user +// +func GetSelf(c *gin.Context) { + c.JSON(200, session.User(c)) +} + +// swagger:route GET /user/feed user getUserFeed +// +// Get the currently authenticated user's build feed. +// +// Responses: +// 200: feed +// +func GetFeed(c *gin.Context) { + repos, err := cache.GetRepos(c, session.User(c)) + if err != nil { + c.String(500, "Error fetching repository list. %s", err) + return + } + + feed, err := store.GetUserFeed(c, repos) + if err != nil { + c.String(500, "Error fetching feed. %s", err) + return + } + c.JSON(200, feed) +} + +// swagger:route GET /user/repos user getUserRepos +// +// Get the currently authenticated user's active repository list. +// +// Responses: +// 200: repos +// +func GetRepos(c *gin.Context) { + repos, err := cache.GetRepos(c, session.User(c)) + if err != nil { + c.String(500, "Error fetching repository list. %s", err) + return + } + + repos_, err := store.GetRepoListOf(c, repos) + if err != nil { + c.String(500, "Error fetching repository list. %s", err) + return + } + c.JSON(http.StatusOK, repos_) +} + +func GetRemoteRepos(c *gin.Context) { + repos, err := cache.GetRepos(c, session.User(c)) + if err != nil { + c.String(500, "Error fetching repository list. %s", err) + return + } + c.JSON(http.StatusOK, repos) +} + +func PostToken(c *gin.Context) { + user := session.User(c) + + token := token.New(token.UserToken, user.Login) + tokenstr, err := token.Sign(user.Hash) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + c.String(http.StatusOK, tokenstr) +} + +// swagger:response user +type userResp struct { + // in: body + Body model.User +} + +// swagger:response users +type usersResp struct { + // in: body + Body []model.User +} + +// swagger:response feed +type feedResp struct { + // in: body + Body []model.Feed +} + +// swagger:response repos +type reposResp struct { + // in: body + Body []model.Repo +} diff --git a/controller/users.go b/api/users.go similarity index 61% rename from controller/users.go rename to api/users.go index b2fed5db..81756aec 100644 --- a/controller/users.go +++ b/api/users.go @@ -1,4 +1,4 @@ -package controller +package api import ( "net/http" @@ -6,33 +6,43 @@ import ( "github.com/gin-gonic/gin" "github.com/drone/drone/model" - "github.com/drone/drone/router/middleware/session" "github.com/drone/drone/shared/crypto" "github.com/drone/drone/store" ) +// swagger:route GET /users user getUserList +// +// Get the list of all registered users. +// +// Responses: +// 200: user +// func GetUsers(c *gin.Context) { users, err := store.GetUserList(c) if err != nil { - c.AbortWithStatus(http.StatusInternalServerError) - return + c.String(500, "Error getting user list. %s", err) + } else { + c.JSON(200, users) } - - c.IndentedJSON(http.StatusOK, users) } +// swagger:route GET /users/{login} user getUserLogin +// +// Get the user with the matching login. +// +// Responses: +// 200: user +// func GetUser(c *gin.Context) { user, err := store.GetUserLogin(c, c.Param("login")) if err != nil { - c.AbortWithStatus(http.StatusNotFound) - return + c.String(404, "Cannot find user. %s", err) + } else { + c.JSON(200, user) } - - c.IndentedJSON(http.StatusOK, user) } func PatchUser(c *gin.Context) { - me := session.User(c) in := &model.User{} err := c.Bind(in) if err != nil { @@ -48,19 +58,13 @@ func PatchUser(c *gin.Context) { user.Admin = in.Admin user.Active = in.Active - // cannot update self - if me.ID == user.ID { - c.AbortWithStatus(http.StatusForbidden) - return - } - err = store.UpdateUser(c, user) if err != nil { c.AbortWithStatus(http.StatusConflict) return } - c.IndentedJSON(http.StatusOK, user) + c.JSON(http.StatusOK, user) } func PostUser(c *gin.Context) { @@ -85,29 +89,25 @@ func PostUser(c *gin.Context) { return } - c.IndentedJSON(http.StatusOK, user) + c.JSON(http.StatusOK, user) } +// swagger:route DELETE /users/{login} user deleteUserLogin +// +// Delete the user with the matching login. +// +// Responses: +// 200: user +// func DeleteUser(c *gin.Context) { - me := session.User(c) - user, err := store.GetUserLogin(c, c.Param("login")) if err != nil { - c.AbortWithStatus(http.StatusNotFound) + c.String(404, "Cannot find user. %s", err) return } - - // cannot delete self - if me.ID == user.ID { - c.AbortWithStatus(http.StatusForbidden) - return + if err = store.DeleteUser(c, user); err != nil { + c.String(500, "Error deleting user. %s", err) + } else { + c.String(200, "") } - - err = store.DeleteUser(c, user) - if err != nil { - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - c.Writer.WriteHeader(http.StatusNoContent) } diff --git a/controller/build.go b/controller/build.go index 96e7ffd4..066f8aee 100644 --- a/controller/build.go +++ b/controller/build.go @@ -231,10 +231,10 @@ func PostBuild(c *gin.Context) { event := c.DefaultQuery("event", build.Event) if event == model.EventPush || - event == model.EventPull || - event == model.EventTag || - event == model.EventDeploy { - build.Event = event + event == model.EventPull || + event == model.EventTag || + event == model.EventDeploy { + build.Event = event } build.Deploy = c.DefaultQuery("deploy_to", build.Deploy) } diff --git a/controller/pages.go b/controller/pages.go index 251e4418..00da8908 100644 --- a/controller/pages.go +++ b/controller/pages.go @@ -200,3 +200,10 @@ func ShowBuild(c *gin.Context) { "Csrf": csrf, }) } + +func ShowNodes(c *gin.Context) { + user := session.User(c) + nodes, _ := store.GetNodeList(c) + token, _ := token.New(token.CsrfToken, user.Login).Sign(user.Hash) + c.HTML(http.StatusOK, "nodes.html", gin.H{"User": user, "Nodes": nodes, "Csrf": token}) +} diff --git a/controller/user.go b/controller/user.go deleted file mode 100644 index 92540b94..00000000 --- a/controller/user.go +++ /dev/null @@ -1,78 +0,0 @@ -package controller - -import ( - "net/http" - - "github.com/gin-gonic/gin" - - "github.com/drone/drone/cache" - "github.com/drone/drone/router/middleware/session" - "github.com/drone/drone/shared/token" - "github.com/drone/drone/store" -) - -func GetSelf(c *gin.Context) { - c.IndentedJSON(200, session.User(c)) -} - -func GetFeed(c *gin.Context) { - user := session.User(c) - - // get the repository list from the cache - repos, err := cache.GetRepos(c, user) - if err != nil { - c.String(400, err.Error()) - return - } - - feed, err := store.GetUserFeed(c, repos) - if err != nil { - c.String(400, err.Error()) - return - } - c.JSON(200, feed) -} - -func GetRepos(c *gin.Context) { - user := session.User(c) - - repos, err := cache.GetRepos(c, user) - if err != nil { - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - // for each repository in the remote system we get - // the intersection of those repostiories in Drone - repos_, err := store.GetRepoListOf(c, repos) - if err != nil { - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - c.IndentedJSON(http.StatusOK, repos_) -} - -func GetRemoteRepos(c *gin.Context) { - user := session.User(c) - - repos, err := cache.GetRepos(c, user) - if err != nil { - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - c.IndentedJSON(http.StatusOK, repos) -} - -func PostToken(c *gin.Context) { - user := session.User(c) - - token := token.New(token.UserToken, user.Login) - tokenstr, err := token.Sign(user.Hash) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - } else { - c.String(http.StatusOK, tokenstr) - } -} diff --git a/model/feed.go b/model/feed.go index 2a2de523..56dd8d0a 100644 --- a/model/feed.go +++ b/model/feed.go @@ -1,5 +1,8 @@ package model +// Feed represents an item in the user's feed or timeline. +// +// swagger:model feed type Feed struct { Owner string `json:"owner" meddler:"repo_owner"` Name string `json:"name" meddler:"repo_name"` diff --git a/model/repo.go b/model/repo.go index e44af1a5..062f5313 100644 --- a/model/repo.go +++ b/model/repo.go @@ -7,6 +7,9 @@ type RepoLite struct { Avatar string `json:"avatar_url"` } +// Repo represents a repository. +// +// swagger:model repo type Repo struct { ID int64 `json:"id" meddler:"repo_id,pk"` UserID int64 `json:"-" meddler:"repo_user_id"` diff --git a/model/user.go b/model/user.go index 59db0e4b..e33d1fc3 100644 --- a/model/user.go +++ b/model/user.go @@ -1,14 +1,42 @@ package model +// User represents a registered user. +// +// swagger:model user type User struct { - ID int64 `json:"id" meddler:"user_id,pk"` - Login string `json:"login" meddler:"user_login"` - Token string `json:"-" meddler:"user_token"` - Secret string `json:"-" meddler:"user_secret"` - Expiry int64 `json:"-" meddler:"user_expiry"` - Email string `json:"email" meddler:"user_email"` + // the id for this user. + // + // required: true + ID int64 `json:"id" meddler:"user_id,pk"` + + // Login is the username for this user. + // + // required: true + Login string `json:"login" meddler:"user_login"` + + // Token is the oauth2 token. + Token string `json:"-" meddler:"user_token"` + + // Secret is the oauth2 token secret. + Secret string `json:"-" meddler:"user_secret"` + + // Expiry is the token and secret expriation timestamp. + Expiry int64 `json:"-" meddler:"user_expiry"` + + // Email is the email address for this user. + // + // required: true + Email string `json:"email" meddler:"user_email"` + + // the avatar url for this user. Avatar string `json:"avatar_url" meddler:"user_avatar"` - Active bool `json:"active," meddler:"user_active"` - Admin bool `json:"admin," meddler:"user_admin"` - Hash string `json:"-" meddler:"user_hash"` + + // Activate indicates the user is active in the system. + Active bool `json:"active," meddler:"user_active"` + + // Admin indicates the user is a system administrator. + Admin bool `json:"admin," meddler:"user_admin"` + + // Hash is a unique token used to sign tokens. + Hash string `json:"-" meddler:"user_hash"` } diff --git a/router/router.go b/router/router.go index 5f64f664..112f62ac 100644 --- a/router/router.go +++ b/router/router.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" + "github.com/drone/drone/api" "github.com/drone/drone/controller" "github.com/drone/drone/router/middleware/header" "github.com/drone/drone/router/middleware/location" @@ -60,34 +61,34 @@ func Load(middleware ...gin.HandlerFunc) http.Handler { user := e.Group("/api/user") { user.Use(session.MustUser()) - user.GET("", controller.GetSelf) - user.GET("/feed", controller.GetFeed) - user.GET("/repos", controller.GetRepos) - user.GET("/repos/remote", controller.GetRemoteRepos) - user.POST("/token", controller.PostToken) + user.GET("", api.GetSelf) + user.GET("/feed", api.GetFeed) + user.GET("/repos", api.GetRepos) + user.GET("/repos/remote", api.GetRemoteRepos) + user.POST("/token", api.PostToken) } users := e.Group("/api/users") { users.Use(session.MustAdmin()) - users.GET("", controller.GetUsers) - users.POST("", controller.PostUser) - users.GET("/:login", controller.GetUser) - users.PATCH("/:login", controller.PatchUser) - users.DELETE("/:login", controller.DeleteUser) + users.GET("", api.GetUsers) + users.POST("", api.PostUser) + users.GET("/:login", api.GetUser) + users.PATCH("/:login", api.PatchUser) + users.DELETE("/:login", api.DeleteUser) } nodes := e.Group("/api/nodes") { nodes.Use(session.MustAdmin()) - nodes.GET("", controller.GetNodes) - nodes.POST("", controller.PostNode) - nodes.DELETE("/:node", controller.DeleteNode) + nodes.GET("", api.GetNodes) + nodes.POST("", api.PostNode) + nodes.DELETE("/:node", api.DeleteNode) } repos := e.Group("/api/repos/:owner/:name") { - repos.POST("", controller.PostRepo) + repos.POST("", api.PostRepo) repo := repos.Group("") { @@ -95,19 +96,19 @@ func Load(middleware ...gin.HandlerFunc) http.Handler { repo.Use(session.SetPerm()) repo.Use(session.MustPull) - repo.GET("", controller.GetRepo) - repo.GET("/key", controller.GetRepoKey) - repo.POST("/key", controller.PostRepoKey) + repo.GET("", api.GetRepo) + repo.GET("/key", api.GetRepoKey) + repo.POST("/key", api.PostRepoKey) repo.GET("/builds", controller.GetBuilds) repo.GET("/builds/:number", controller.GetBuild) repo.GET("/logs/:number/:job", controller.GetBuildLogs) // requires authenticated user - repo.POST("/encrypt", session.MustUser(), controller.PostSecure) + repo.POST("/encrypt", session.MustUser(), api.PostSecure) // requires push permissions - repo.PATCH("", session.MustPush, controller.PatchRepo) - repo.DELETE("", session.MustPush, controller.DeleteRepo) + repo.PATCH("", session.MustPush, api.PatchRepo) + repo.DELETE("", session.MustPush, api.DeleteRepo) repo.POST("/builds/:number", session.MustPush, controller.PostBuild) repo.DELETE("/builds/:number/:job", session.MustPush, controller.DeleteBuild)