moving API to api package, swagger annotatoins

This commit is contained in:
Brad Rydzewski 2016-03-30 13:15:28 -07:00
parent e64ec7cf88
commit 0d04fa67e3
15 changed files with 249 additions and 159 deletions

2
.gitignore vendored
View file

@ -13,6 +13,8 @@ drone_*
.env .env
temp/ temp/
api/swagger/files/*
# vendored repositories that we don't actually need # vendored repositories that we don't actually need
# to vendor. so exclude them # to vendor. so exclude them

1
api/build.go Normal file
View file

@ -0,0 +1 @@
package api

16
api/doc.go Normal file
View file

@ -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

View file

@ -1,4 +1,4 @@
package controller package api
import ( import (
"net/http" "net/http"
@ -8,8 +8,6 @@ import (
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/router/middleware/context" "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" "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) { func GetNode(c *gin.Context) {
} }

View file

@ -1,4 +1,4 @@
package controller package api
import ( import (
"bytes" "bytes"
@ -150,11 +150,11 @@ func PatchRepo(c *gin.Context) {
return return
} }
c.IndentedJSON(http.StatusOK, repo) c.JSON(http.StatusOK, repo)
} }
func GetRepo(c *gin.Context) { func GetRepo(c *gin.Context) {
c.IndentedJSON(http.StatusOK, session.Repo(c)) c.JSON(http.StatusOK, session.Repo(c))
} }
func GetRepoKey(c *gin.Context) { func GetRepoKey(c *gin.Context) {

3
api/swagger/swagger.go Normal file
View file

@ -0,0 +1,3 @@
package swagger
//go:generate go-bindata -pkg swagger -o swagger_gen.go files/

113
api/user.go Normal file
View file

@ -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
}

View file

@ -1,4 +1,4 @@
package controller package api
import ( import (
"net/http" "net/http"
@ -6,33 +6,43 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/shared/crypto" "github.com/drone/drone/shared/crypto"
"github.com/drone/drone/store" "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) { func GetUsers(c *gin.Context) {
users, err := store.GetUserList(c) users, err := store.GetUserList(c)
if err != nil { if err != nil {
c.AbortWithStatus(http.StatusInternalServerError) c.String(500, "Error getting user list. %s", err)
return } 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) { func GetUser(c *gin.Context) {
user, err := store.GetUserLogin(c, c.Param("login")) user, err := store.GetUserLogin(c, c.Param("login"))
if err != nil { if err != nil {
c.AbortWithStatus(http.StatusNotFound) c.String(404, "Cannot find user. %s", err)
return } else {
c.JSON(200, user)
} }
c.IndentedJSON(http.StatusOK, user)
} }
func PatchUser(c *gin.Context) { func PatchUser(c *gin.Context) {
me := session.User(c)
in := &model.User{} in := &model.User{}
err := c.Bind(in) err := c.Bind(in)
if err != nil { if err != nil {
@ -48,19 +58,13 @@ func PatchUser(c *gin.Context) {
user.Admin = in.Admin user.Admin = in.Admin
user.Active = in.Active user.Active = in.Active
// cannot update self
if me.ID == user.ID {
c.AbortWithStatus(http.StatusForbidden)
return
}
err = store.UpdateUser(c, user) err = store.UpdateUser(c, user)
if err != nil { if err != nil {
c.AbortWithStatus(http.StatusConflict) c.AbortWithStatus(http.StatusConflict)
return return
} }
c.IndentedJSON(http.StatusOK, user) c.JSON(http.StatusOK, user)
} }
func PostUser(c *gin.Context) { func PostUser(c *gin.Context) {
@ -85,29 +89,25 @@ func PostUser(c *gin.Context) {
return 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) { func DeleteUser(c *gin.Context) {
me := session.User(c)
user, err := store.GetUserLogin(c, c.Param("login")) user, err := store.GetUserLogin(c, c.Param("login"))
if err != nil { if err != nil {
c.AbortWithStatus(http.StatusNotFound) c.String(404, "Cannot find user. %s", err)
return return
} }
if err = store.DeleteUser(c, user); err != nil {
// cannot delete self c.String(500, "Error deleting user. %s", err)
if me.ID == user.ID { } else {
c.AbortWithStatus(http.StatusForbidden) c.String(200, "")
return
} }
err = store.DeleteUser(c, user)
if err != nil {
c.AbortWithStatus(http.StatusInternalServerError)
return
}
c.Writer.WriteHeader(http.StatusNoContent)
} }

View file

@ -231,10 +231,10 @@ func PostBuild(c *gin.Context) {
event := c.DefaultQuery("event", build.Event) event := c.DefaultQuery("event", build.Event)
if event == model.EventPush || if event == model.EventPush ||
event == model.EventPull || event == model.EventPull ||
event == model.EventTag || event == model.EventTag ||
event == model.EventDeploy { event == model.EventDeploy {
build.Event = event build.Event = event
} }
build.Deploy = c.DefaultQuery("deploy_to", build.Deploy) build.Deploy = c.DefaultQuery("deploy_to", build.Deploy)
} }

View file

@ -200,3 +200,10 @@ func ShowBuild(c *gin.Context) {
"Csrf": csrf, "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})
}

View file

@ -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)
}
}

View file

@ -1,5 +1,8 @@
package model package model
// Feed represents an item in the user's feed or timeline.
//
// swagger:model feed
type Feed struct { type Feed struct {
Owner string `json:"owner" meddler:"repo_owner"` Owner string `json:"owner" meddler:"repo_owner"`
Name string `json:"name" meddler:"repo_name"` Name string `json:"name" meddler:"repo_name"`

View file

@ -7,6 +7,9 @@ type RepoLite struct {
Avatar string `json:"avatar_url"` Avatar string `json:"avatar_url"`
} }
// Repo represents a repository.
//
// swagger:model repo
type Repo struct { type Repo struct {
ID int64 `json:"id" meddler:"repo_id,pk"` ID int64 `json:"id" meddler:"repo_id,pk"`
UserID int64 `json:"-" meddler:"repo_user_id"` UserID int64 `json:"-" meddler:"repo_user_id"`

View file

@ -1,14 +1,42 @@
package model package model
// User represents a registered user.
//
// swagger:model user
type User struct { type User struct {
ID int64 `json:"id" meddler:"user_id,pk"` // the id for this user.
Login string `json:"login" meddler:"user_login"` //
Token string `json:"-" meddler:"user_token"` // required: true
Secret string `json:"-" meddler:"user_secret"` ID int64 `json:"id" meddler:"user_id,pk"`
Expiry int64 `json:"-" meddler:"user_expiry"`
Email string `json:"email" meddler:"user_email"` // 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"` Avatar string `json:"avatar_url" meddler:"user_avatar"`
Active bool `json:"active," meddler:"user_active"`
Admin bool `json:"admin," meddler:"user_admin"` // Activate indicates the user is active in the system.
Hash string `json:"-" meddler:"user_hash"` 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"`
} }

View file

@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/drone/drone/api"
"github.com/drone/drone/controller" "github.com/drone/drone/controller"
"github.com/drone/drone/router/middleware/header" "github.com/drone/drone/router/middleware/header"
"github.com/drone/drone/router/middleware/location" "github.com/drone/drone/router/middleware/location"
@ -60,34 +61,34 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
user := e.Group("/api/user") user := e.Group("/api/user")
{ {
user.Use(session.MustUser()) user.Use(session.MustUser())
user.GET("", controller.GetSelf) user.GET("", api.GetSelf)
user.GET("/feed", controller.GetFeed) user.GET("/feed", api.GetFeed)
user.GET("/repos", controller.GetRepos) user.GET("/repos", api.GetRepos)
user.GET("/repos/remote", controller.GetRemoteRepos) user.GET("/repos/remote", api.GetRemoteRepos)
user.POST("/token", controller.PostToken) user.POST("/token", api.PostToken)
} }
users := e.Group("/api/users") users := e.Group("/api/users")
{ {
users.Use(session.MustAdmin()) users.Use(session.MustAdmin())
users.GET("", controller.GetUsers) users.GET("", api.GetUsers)
users.POST("", controller.PostUser) users.POST("", api.PostUser)
users.GET("/:login", controller.GetUser) users.GET("/:login", api.GetUser)
users.PATCH("/:login", controller.PatchUser) users.PATCH("/:login", api.PatchUser)
users.DELETE("/:login", controller.DeleteUser) users.DELETE("/:login", api.DeleteUser)
} }
nodes := e.Group("/api/nodes") nodes := e.Group("/api/nodes")
{ {
nodes.Use(session.MustAdmin()) nodes.Use(session.MustAdmin())
nodes.GET("", controller.GetNodes) nodes.GET("", api.GetNodes)
nodes.POST("", controller.PostNode) nodes.POST("", api.PostNode)
nodes.DELETE("/:node", controller.DeleteNode) nodes.DELETE("/:node", api.DeleteNode)
} }
repos := e.Group("/api/repos/:owner/:name") repos := e.Group("/api/repos/:owner/:name")
{ {
repos.POST("", controller.PostRepo) repos.POST("", api.PostRepo)
repo := repos.Group("") repo := repos.Group("")
{ {
@ -95,19 +96,19 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
repo.Use(session.SetPerm()) repo.Use(session.SetPerm())
repo.Use(session.MustPull) repo.Use(session.MustPull)
repo.GET("", controller.GetRepo) repo.GET("", api.GetRepo)
repo.GET("/key", controller.GetRepoKey) repo.GET("/key", api.GetRepoKey)
repo.POST("/key", controller.PostRepoKey) repo.POST("/key", api.PostRepoKey)
repo.GET("/builds", controller.GetBuilds) repo.GET("/builds", controller.GetBuilds)
repo.GET("/builds/:number", controller.GetBuild) repo.GET("/builds/:number", controller.GetBuild)
repo.GET("/logs/:number/:job", controller.GetBuildLogs) repo.GET("/logs/:number/:job", controller.GetBuildLogs)
// requires authenticated user // requires authenticated user
repo.POST("/encrypt", session.MustUser(), controller.PostSecure) repo.POST("/encrypt", session.MustUser(), api.PostSecure)
// requires push permissions // requires push permissions
repo.PATCH("", session.MustPush, controller.PatchRepo) repo.PATCH("", session.MustPush, api.PatchRepo)
repo.DELETE("", session.MustPush, controller.DeleteRepo) repo.DELETE("", session.MustPush, api.DeleteRepo)
repo.POST("/builds/:number", session.MustPush, controller.PostBuild) repo.POST("/builds/:number", session.MustPush, controller.PostBuild)
repo.DELETE("/builds/:number/:job", session.MustPush, controller.DeleteBuild) repo.DELETE("/builds/:number/:job", session.MustPush, controller.DeleteBuild)