Merge pull request #1556 from bradrydzewski/master

relocate api handlers, add swagger annotations
This commit is contained in:
Brad Rydzewski 2016-03-30 23:50:34 -07:00
commit 1f336b456c
21 changed files with 303 additions and 198 deletions

2
.gitignore vendored
View file

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

View file

@ -1,9 +1,11 @@
package controller
package api
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
@ -20,6 +22,18 @@ import (
"github.com/drone/drone/router/middleware/session"
)
var (
droneYml = os.Getenv("BUILD_CONFIG_FILE")
droneSec string
)
func init() {
if droneYml == "" {
droneYml = ".drone.yml"
}
droneSec = fmt.Sprintf("%s.sec", strings.TrimSuffix(droneYml, filepath.Ext(droneYml)))
}
func GetBuilds(c *gin.Context) {
repo := session.Repo(c)
builds, err := store.GetBuildList(c, repo)
@ -231,10 +245,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)
}

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 (
"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) {
}

View file

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

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 (
"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)
}

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,6 @@
package model
// swagger:model build
type Build struct {
ID int64 `json:"id" meddler:"build_id,pk"`
RepoID int64 `json:"-" meddler:"build_repo_id"`

View file

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

View file

@ -1,5 +1,6 @@
package model
// swagger:model job
type Job struct {
ID int64 `json:"id" meddler:"job_id,pk"`
BuildID int64 `json:"-" meddler:"job_build_id"`

View file

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

View file

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

View file

@ -6,13 +6,14 @@ import (
"github.com/gin-gonic/gin"
"github.com/drone/drone/controller"
"github.com/drone/drone/api"
"github.com/drone/drone/router/middleware/header"
"github.com/drone/drone/router/middleware/location"
"github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/router/middleware/token"
"github.com/drone/drone/static"
"github.com/drone/drone/template"
"github.com/drone/drone/web"
)
func Load(middleware ...gin.HandlerFunc) http.Handler {
@ -28,17 +29,17 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
e.Use(session.SetUser())
e.Use(token.Refresh)
e.GET("/", controller.ShowIndex)
e.GET("/login", controller.ShowLogin)
e.GET("/login/form", controller.ShowLoginForm)
e.GET("/logout", controller.GetLogout)
e.GET("/", web.ShowIndex)
e.GET("/login", web.ShowLogin)
e.GET("/login/form", web.ShowLoginForm)
e.GET("/logout", web.GetLogout)
settings := e.Group("/settings")
{
settings.Use(session.MustUser())
settings.GET("/profile", controller.ShowUser)
settings.GET("/people", session.MustAdmin(), controller.ShowUsers)
settings.GET("/nodes", session.MustAdmin(), controller.ShowNodes)
settings.GET("/profile", web.ShowUser)
settings.GET("/people", session.MustAdmin(), web.ShowUsers)
settings.GET("/nodes", session.MustAdmin(), web.ShowNodes)
}
repo := e.Group("/repos/:owner/:name")
{
@ -46,48 +47,48 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
repo.Use(session.SetPerm())
repo.Use(session.MustPull)
repo.GET("", controller.ShowRepo)
repo.GET("/builds/:number", controller.ShowBuild)
repo.GET("/builds/:number/:job", controller.ShowBuild)
repo.GET("", web.ShowRepo)
repo.GET("/builds/:number", web.ShowBuild)
repo.GET("/builds/:number/:job", web.ShowBuild)
repo_settings := repo.Group("/settings")
{
repo_settings.GET("", session.MustPush, controller.ShowRepoConf)
repo_settings.GET("/encrypt", session.MustPush, controller.ShowRepoEncrypt)
repo_settings.GET("/badges", controller.ShowRepoBadges)
repo_settings.GET("", session.MustPush, web.ShowRepoConf)
repo_settings.GET("/encrypt", session.MustPush, web.ShowRepoEncrypt)
repo_settings.GET("/badges", web.ShowRepoBadges)
}
}
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,60 +96,60 @@ 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("/builds", controller.GetBuilds)
repo.GET("/builds/:number", controller.GetBuild)
repo.GET("/logs/:number/:job", controller.GetBuildLogs)
repo.GET("", api.GetRepo)
repo.GET("/key", api.GetRepoKey)
repo.POST("/key", api.PostRepoKey)
repo.GET("/builds", api.GetBuilds)
repo.GET("/builds/:number", api.GetBuild)
repo.GET("/logs/:number/:job", api.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)
repo.POST("/builds/:number", session.MustPush, api.PostBuild)
repo.DELETE("/builds/:number/:job", session.MustPush, api.DeleteBuild)
}
}
badges := e.Group("/api/badges/:owner/:name")
{
badges.GET("/status.svg", controller.GetBadge)
badges.GET("/cc.xml", controller.GetCC)
badges.GET("/status.svg", web.GetBadge)
badges.GET("/cc.xml", web.GetCC)
}
e.POST("/hook", controller.PostHook)
e.POST("/api/hook", controller.PostHook)
e.POST("/hook", web.PostHook)
e.POST("/api/hook", web.PostHook)
stream := e.Group("/api/stream")
{
stream.Use(session.SetRepo())
stream.Use(session.SetPerm())
stream.Use(session.MustPull)
stream.GET("/:owner/:name", controller.GetRepoEvents)
stream.GET("/:owner/:name/:build/:number", controller.GetStream)
stream.GET("/:owner/:name", web.GetRepoEvents)
stream.GET("/:owner/:name/:build/:number", web.GetStream)
}
auth := e.Group("/authorize")
{
auth.GET("", controller.GetLogin)
auth.POST("", controller.GetLogin)
auth.POST("/token", controller.GetLoginToken)
auth.GET("", web.GetLogin)
auth.POST("", web.GetLogin)
auth.POST("/token", web.GetLoginToken)
}
gitlab := e.Group("/gitlab/:owner/:name")
{
gitlab.Use(session.SetRepo())
gitlab.GET("/commits/:sha", controller.GetCommit)
gitlab.GET("/pulls/:number", controller.GetPullRequest)
gitlab.GET("/commits/:sha", web.GetCommit)
gitlab.GET("/pulls/:number", web.GetPullRequest)
redirects := gitlab.Group("/redirect")
{
redirects.GET("/commits/:sha", controller.RedirectSha)
redirects.GET("/pulls/:number", controller.RedirectPullRequest)
redirects.GET("/commits/:sha", web.RedirectSha)
redirects.GET("/pulls/:number", web.RedirectPullRequest)
}
}

View file

@ -1,4 +1,4 @@
package controller
package web
import (
"fmt"

View file

@ -1,4 +1,4 @@
package controller
package web
import (
"fmt"

View file

@ -1,4 +1,4 @@
package controller
package web
import (
"fmt"

View file

@ -1,4 +1,4 @@
package controller
package web
import (
"net/http"

View file

@ -1,4 +1,4 @@
package controller
package web
import (
"net/http"
@ -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})
}

View file

@ -1,4 +1,4 @@
package controller
package web
import (
"io"