card api and db work to support new cards feature

This commit is contained in:
Eoin McAfee 2021-09-24 10:06:26 +01:00
parent 947bcbe92a
commit 85fd11c421
31 changed files with 1976 additions and 5 deletions

View file

@ -79,6 +79,7 @@ tasks:
- cmd: go test -count=1 github.com/drone/drone/store/batch - cmd: go test -count=1 github.com/drone/drone/store/batch
- cmd: go test -count=1 github.com/drone/drone/store/batch2 - cmd: go test -count=1 github.com/drone/drone/store/batch2
- cmd: go test -count=1 github.com/drone/drone/store/build - cmd: go test -count=1 github.com/drone/drone/store/build
- cmd: go test -count=1 github.com/drone/drone/store/card
- cmd: go test -count=1 github.com/drone/drone/store/cron - cmd: go test -count=1 github.com/drone/drone/store/cron
- cmd: go test -count=1 github.com/drone/drone/store/logs - cmd: go test -count=1 github.com/drone/drone/store/logs
- cmd: go test -count=1 github.com/drone/drone/store/perm - cmd: go test -count=1 github.com/drone/drone/store/perm
@ -113,6 +114,7 @@ tasks:
- cmd: go test -count=1 github.com/drone/drone/store/batch - cmd: go test -count=1 github.com/drone/drone/store/batch
- cmd: go test -count=1 github.com/drone/drone/store/batch2 - cmd: go test -count=1 github.com/drone/drone/store/batch2
- cmd: go test -count=1 github.com/drone/drone/store/build - cmd: go test -count=1 github.com/drone/drone/store/build
- cmd: go test -count=1 github.com/drone/drone/store/card
- cmd: go test -count=1 github.com/drone/drone/store/cron - cmd: go test -count=1 github.com/drone/drone/store/cron
- cmd: go test -count=1 github.com/drone/drone/store/logs - cmd: go test -count=1 github.com/drone/drone/store/logs
- cmd: go test -count=1 github.com/drone/drone/store/perm - cmd: go test -count=1 github.com/drone/drone/store/perm

View file

@ -21,6 +21,7 @@ import (
"github.com/drone/drone/store/batch" "github.com/drone/drone/store/batch"
"github.com/drone/drone/store/batch2" "github.com/drone/drone/store/batch2"
"github.com/drone/drone/store/build" "github.com/drone/drone/store/build"
"github.com/drone/drone/store/card"
"github.com/drone/drone/store/cron" "github.com/drone/drone/store/cron"
"github.com/drone/drone/store/logs" "github.com/drone/drone/store/logs"
"github.com/drone/drone/store/perm" "github.com/drone/drone/store/perm"
@ -50,6 +51,7 @@ var storeSet = wire.NewSet(
provideBatchStore, provideBatchStore,
// batch.New, // batch.New,
cron.New, cron.New,
card.New,
perm.New, perm.New,
secret.New, secret.New,
global.New, global.New,

View file

@ -1,6 +1,6 @@
// Code generated by Wire. DO NOT EDIT. // Code generated by Wire. DO NOT EDIT.
//go:generate go run github.com/google/wire/cmd/wire //go:generate wire
//+build !wireinject //+build !wireinject
package main package main
@ -20,6 +20,7 @@ import (
"github.com/drone/drone/service/token" "github.com/drone/drone/service/token"
"github.com/drone/drone/service/transfer" "github.com/drone/drone/service/transfer"
"github.com/drone/drone/service/user" "github.com/drone/drone/service/user"
"github.com/drone/drone/store/card"
"github.com/drone/drone/store/cron" "github.com/drone/drone/store/cron"
"github.com/drone/drone/store/perm" "github.com/drone/drone/store/perm"
"github.com/drone/drone/store/secret" "github.com/drone/drone/store/secret"
@ -86,6 +87,7 @@ func InitializeApplication(config2 config.Config) (application, error) {
secretService := provideSecretPlugin(config2) secretService := provideSecretPlugin(config2)
registryService := provideRegistryPlugin(config2) registryService := provideRegistryPlugin(config2)
runner := provideRunner(buildManager, secretService, registryService, config2) runner := provideRunner(buildManager, secretService, registryService, config2)
cardStore := card.New(db)
hookService := provideHookService(client, renewer, config2) hookService := provideHookService(client, renewer, config2)
licenseService := license.NewService(userStore, repositoryStore, buildStore, coreLicense) licenseService := license.NewService(userStore, repositoryStore, buildStore, coreLicense)
organizationService := provideOrgService(client, renewer) organizationService := provideOrgService(client, renewer)
@ -99,7 +101,7 @@ func InitializeApplication(config2 config.Config) (application, error) {
syncer := provideSyncer(repositoryService, repositoryStore, userStore, batcher, config2) syncer := provideSyncer(repositoryService, repositoryStore, userStore, batcher, config2)
transferer := transfer.New(repositoryStore, permStore) transferer := transfer.New(repositoryStore, permStore)
userService := user.New(client, renewer) userService := user.New(client, renewer)
server := api.New(buildStore, commitService, cronStore, corePubsub, globalSecretStore, hookService, logStore, coreLicense, licenseService, organizationService, permStore, repositoryStore, repositoryService, scheduler, secretStore, stageStore, stepStore, statusService, session, logStream, syncer, system, templateStore, transferer, triggerer, userStore, userService, webhookSender) server := api.New(buildStore, commitService, cardStore, cronStore, corePubsub, globalSecretStore, hookService, logStore, coreLicense, licenseService, organizationService, permStore, repositoryStore, repositoryService, scheduler, secretStore, stageStore, stepStore, statusService, session, logStream, syncer, system, templateStore, transferer, triggerer, userStore, userService, webhookSender)
admissionService := provideAdmissionPlugin(client, organizationService, userService, config2) admissionService := provideAdmissionPlugin(client, organizationService, userService, config2)
hookParser := parser.New(client) hookParser := parser.New(client)
coreLinker := linker.New(client) coreLinker := linker.New(client)

89
core/card.go Normal file
View file

@ -0,0 +1,89 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"context"
"io"
"github.com/drone/drone/handler/api/errors"
)
var (
errCardStepInvalid = errors.New("No Step ID Provided")
errCardBuildInvalid = errors.New("No Build ID Provided")
errCardSchemaInvalid = errors.New("No Card Schema Has Been Provided")
errCardDataInvalid = errors.New("No Card Data Has Been Provided")
)
type Card struct {
Id int64 `json:"id,omitempty"`
Build int64 `json:"build,omitempty"`
Stage int64 `json:"stage,omitempty"`
Step int64 `json:"step,omitempty"`
Schema string `json:"schema,omitempty"`
}
type CreateCard struct {
Id int64 `json:"id,omitempty"`
Build int64 `json:"build,omitempty"`
Stage int64 `json:"stage,omitempty"`
Step int64 `json:"step,omitempty"`
Schema string `json:"schema,omitempty"`
Data []byte `json:"data,omitempty"`
}
type CardData struct {
Id int64 `json:"id,omitempty"`
Data []byte `json:"card_data"`
}
// CardStore manages repository cards.
type CardStore interface {
FindCardByBuild(ctx context.Context, build int64) ([]*Card, error)
FindCard(ctx context.Context, step int64) (*Card, error)
FindCardData(ctx context.Context, id int64) (io.ReadCloser, error)
CreateCard(ctx context.Context, card *CreateCard) error
DeleteCard(ctx context.Context, id int64) error
}
// Validate validates the required fields and formats.
func (c *Card) Validate() error {
switch {
case c.Step == 0:
return errCardStepInvalid
case c.Build == 0:
return errCardBuildInvalid
case len(c.Schema) == 0:
return errCardSchemaInvalid
default:
return nil
}
}
func (c *CreateCard) Validate() error {
switch {
case c.Step == 0:
return errCardStepInvalid
case c.Build == 0:
return errCardBuildInvalid
case len(c.Schema) == 0:
return errCardSchemaInvalid
case len(c.Data) == 0:
return errCardDataInvalid
default:
return nil
}
}

2
go.mod
View file

@ -10,7 +10,7 @@ require (
github.com/aws/aws-sdk-go v1.37.3 github.com/aws/aws-sdk-go v1.37.3
github.com/codegangsta/negroni v1.0.0 // indirect github.com/codegangsta/negroni v1.0.0 // indirect
github.com/coreos/go-semver v0.2.0 github.com/coreos/go-semver v0.2.0
github.com/dchest/authcookie v0.0.0-20120917135355-fbdef6e99866 github.com/dchest/authcookie v0.0.0-20190824115100-f900d2294c8e
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9 github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9
github.com/drone/drone-go v1.4.1-0.20201109202657-b9e58bbbcf27 github.com/drone/drone-go v1.4.1-0.20201109202657-b9e58bbbcf27
github.com/drone/drone-runtime v1.1.1-0.20200623162453-61e33e2cab5d github.com/drone/drone-runtime v1.1.1-0.20200623162453-61e33e2cab5d

2
go.sum
View file

@ -58,6 +58,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/authcookie v0.0.0-20120917135355-fbdef6e99866 h1:98WJ4YCdjmB7uyrdT3P4A2Oa1hiRPKoa/0zInG6UnfQ= github.com/dchest/authcookie v0.0.0-20120917135355-fbdef6e99866 h1:98WJ4YCdjmB7uyrdT3P4A2Oa1hiRPKoa/0zInG6UnfQ=
github.com/dchest/authcookie v0.0.0-20120917135355-fbdef6e99866/go.mod h1:x7AK2h2QzaXVEFi1tbMYMDuvHcCEr1QdMDrg3hkW24Q= github.com/dchest/authcookie v0.0.0-20120917135355-fbdef6e99866/go.mod h1:x7AK2h2QzaXVEFi1tbMYMDuvHcCEr1QdMDrg3hkW24Q=
github.com/dchest/authcookie v0.0.0-20190824115100-f900d2294c8e h1:xizeG5ksKSdyNaom2//2Bow4hLWqXkCql36nrL9iEUI=
github.com/dchest/authcookie v0.0.0-20190824115100-f900d2294c8e/go.mod h1:x7AK2h2QzaXVEFi1tbMYMDuvHcCEr1QdMDrg3hkW24Q=
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9 h1:74lLNRzvsdIlkTgfDSMuaPjBr4cf6k7pwQQANm/yLKU= github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9 h1:74lLNRzvsdIlkTgfDSMuaPjBr4cf6k7pwQQANm/yLKU=
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=

View file

@ -15,7 +15,10 @@
package acl package acl
import ( import (
"github.com/dchest/authcookie"
"net/http" "net/http"
"os"
"strings"
"github.com/drone/drone/core" "github.com/drone/drone/core"
"github.com/drone/drone/handler/api/errors" "github.com/drone/drone/handler/api/errors"
@ -48,6 +51,36 @@ func CheckAdminAccess() func(http.Handler) http.Handler {
return CheckAccess(true, true, true) return CheckAccess(true, true, true)
} }
func CheckInternalAccess() func(http.Handler) http.Handler {
return checkIAccess()
}
// CheckIAccess returns an http.Handler middleware that authorizes only
// allows plugins to communicate with the server
func checkIAccess() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var (
owner = chi.URLParam(r, "owner")
name = chi.URLParam(r, "name")
)
log := logger.FromRequest(r).
WithField("namespace", owner).
WithField("name", name)
secret := []byte(os.Getenv("DRONE_INTERNAL_AUTH_SECRET"))
login := authcookie.Login(extractToken(r), secret)
if login != "" {
log.Debug("api: access granted")
next.ServeHTTP(w, r)
}
render.Unauthorized(w, errors.ErrUnauthorized)
log.Debugln("api: authentication required for access")
return
})
}
}
// CheckAccess returns an http.Handler middleware that authorizes only // CheckAccess returns an http.Handler middleware that authorizes only
// authenticated users with the required read, write or admin access // authenticated users with the required read, write or admin access
// permissions to the requested repository resource. // permissions to the requested repository resource.
@ -144,3 +177,11 @@ func CheckAccess(read, write, admin bool) func(http.Handler) http.Handler {
}) })
} }
} }
func extractToken(r *http.Request) string {
bearer := r.Header.Get("Authorization")
if bearer == "" {
bearer = r.FormValue("access_token")
}
return strings.TrimPrefix(bearer, "Bearer ")
}

View file

@ -23,6 +23,7 @@ import (
"github.com/drone/drone/handler/api/auth" "github.com/drone/drone/handler/api/auth"
"github.com/drone/drone/handler/api/badge" "github.com/drone/drone/handler/api/badge"
globalbuilds "github.com/drone/drone/handler/api/builds" globalbuilds "github.com/drone/drone/handler/api/builds"
"github.com/drone/drone/handler/api/card"
"github.com/drone/drone/handler/api/ccmenu" "github.com/drone/drone/handler/api/ccmenu"
"github.com/drone/drone/handler/api/events" "github.com/drone/drone/handler/api/events"
"github.com/drone/drone/handler/api/queue" "github.com/drone/drone/handler/api/queue"
@ -63,6 +64,7 @@ var corsOpts = cors.Options{
func New( func New(
builds core.BuildStore, builds core.BuildStore,
commits core.CommitService, commits core.CommitService,
card core.CardStore,
cron core.CronStore, cron core.CronStore,
events core.Pubsub, events core.Pubsub,
globals core.GlobalSecretStore, globals core.GlobalSecretStore,
@ -92,6 +94,7 @@ func New(
) Server { ) Server {
return Server{ return Server{
Builds: builds, Builds: builds,
Card: card,
Cron: cron, Cron: cron,
Commits: commits, Commits: commits,
Events: events, Events: events,
@ -125,6 +128,7 @@ func New(
// Server is a http.Handler which exposes drone functionality over HTTP. // Server is a http.Handler which exposes drone functionality over HTTP.
type Server struct { type Server struct {
Builds core.BuildStore Builds core.BuildStore
Card core.CardStore
Cron core.CronStore Cron core.CronStore
Commits core.CommitService Commits core.CommitService
Events core.Pubsub Events core.Pubsub
@ -166,6 +170,11 @@ func (s Server) Handler() http.Handler {
cors := cors.New(corsOpts) cors := cors.New(corsOpts)
r.Use(cors.Handler) r.Use(cors.Handler)
r.Route("/internal/{owner}/{name}", func(r chi.Router) {
r.With(
acl.CheckInternalAccess(),
).Post("/cards/{build}/{stage}/{step}", card.HandleCreate(s.Builds, s.Card, s.Stages, s.Steps, s.Repos))
})
r.Route("/repos", func(r chi.Router) { r.Route("/repos", func(r chi.Router) {
// temporary workaround to enable private mode // temporary workaround to enable private mode
// for the drone server. // for the drone server.
@ -285,6 +294,16 @@ func (s Server) Handler() http.Handler {
acl.CheckAdminAccess(), acl.CheckAdminAccess(),
).Delete("/{member}", collabs.HandleDelete(s.Users, s.Repos, s.Perms)) ).Delete("/{member}", collabs.HandleDelete(s.Users, s.Repos, s.Perms))
}) })
r.Route("/cards", func(r chi.Router) {
r.Get("/{build}", card.HandleFindAll(s.Builds, s.Card, s.Repos))
r.Get("/{build}/{stage}/{step}", card.HandleFind(s.Builds, s.Card, s.Stages, s.Steps, s.Repos))
r.Get("/{build}/{stage}/{step}/json", card.HandleFindData(s.Builds, s.Card, s.Stages, s.Steps, s.Repos))
r.With(
acl.AuthorizeAdmin,
acl.CheckAdminAccess(),
).Delete("/{build}/{stage}/{step}", card.HandleDelete(s.Builds, s.Card, s.Stages, s.Steps, s.Repos))
})
}) })
}) })

108
handler/api/card/create.go Normal file
View file

@ -0,0 +1,108 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
// +build !oss
package card
import (
"encoding/json"
"net/http"
"strconv"
"github.com/drone/drone/core"
"github.com/drone/drone/handler/api/render"
"github.com/go-chi/chi"
)
type cardInput struct {
Schema string `json:"schema"`
Data json.RawMessage `json:"data"`
}
// HandleCreate returns an http.HandlerFunc that processes http
// requests to create a new card.
func HandleCreate(
buildStore core.BuildStore,
cardStore core.CardStore,
stageStore core.StageStore,
stepStore core.StepStore,
repoStore core.RepositoryStore,
) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var (
namespace = chi.URLParam(r, "owner")
name = chi.URLParam(r, "name")
)
buildNumber, err := strconv.ParseInt(chi.URLParam(r, "build"), 10, 64)
if err != nil {
render.BadRequest(w, err)
return
}
stageNumber, err := strconv.Atoi(chi.URLParam(r, "stage"))
if err != nil {
render.BadRequest(w, err)
return
}
stepNumber, err := strconv.Atoi(chi.URLParam(r, "step"))
if err != nil {
render.BadRequest(w, err)
return
}
in := new(cardInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequest(w, err)
return
}
repo, err := repoStore.FindName(r.Context(), namespace, name)
if err != nil {
render.NotFound(w, err)
return
}
build, err := buildStore.FindNumber(r.Context(), repo.ID, buildNumber)
if err != nil {
render.NotFound(w, err)
return
}
stage, err := stageStore.FindNumber(r.Context(), build.ID, stageNumber)
if err != nil {
render.NotFound(w, err)
return
}
step, err := stepStore.FindNumber(r.Context(), stage.ID, stepNumber)
if err != nil {
render.NotFound(w, err)
return
}
c := &core.CreateCard{
Build: build.ID,
Stage: stage.ID,
Step: step.ID,
Schema: in.Schema,
Data: in.Data,
}
err = c.Validate()
if err != nil {
render.BadRequest(w, err)
return
}
err = cardStore.CreateCard(r.Context(), c)
if err != nil {
render.InternalError(w, err)
return
}
render.JSON(w, c, 200)
}
}

View file

@ -0,0 +1,224 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
// +build !oss
package card
import (
"bytes"
"context"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/drone/drone/core"
"github.com/drone/drone/handler/api/errors"
"github.com/drone/drone/mock"
"github.com/go-chi/chi"
"github.com/golang/mock/gomock"
"github.com/google/go-cmp/cmp"
)
var (
dummyRepo = &core.Repository{
ID: 1,
UserID: 1,
Slug: "octocat/hello-world",
}
dummyBuild = &core.Build{
ID: 1,
RepoID: 1,
Number: 1,
}
dummyStage = &core.Stage{
ID: 1,
BuildID: 1,
}
dummyStep = &core.Step{
ID: 1,
StageID: 1,
}
dummyCreateCard = &core.CreateCard{
Schema: "https://myschema.com",
Data: "{\"type\": \"AdaptiveCard\"}",
}
dummyCard = &core.Card{
Id: 1,
Build: 1,
Stage: 1,
Step: 1,
Schema: "https://myschema.com",
}
dummyCardNoData = &core.CreateCard{
Schema: "https://myschema.com",
}
dummyCardList = []*core.Card{
dummyCard,
}
dummyCardData = ioutil.NopCloser(
bytes.NewBuffer([]byte(dummyCreateCard.Data)),
)
)
func TestHandleCreate(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
repos := mock.NewMockRepositoryStore(controller)
repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(dummyRepo, nil)
build := mock.NewMockBuildStore(controller)
build.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyBuild, nil)
stage := mock.NewMockStageStore(controller)
stage.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyStage, nil)
step := mock.NewMockStepStore(controller)
step.EXPECT().FindNumber(gomock.Any(), dummyStage.ID, gomock.Any()).Return(dummyStep, nil)
card := mock.NewMockCardStore(controller)
card.EXPECT().CreateCard(gomock.Any(), gomock.Any()).Return(nil)
c := new(chi.Context)
c.URLParams.Add("owner", "octocat")
c.URLParams.Add("name", "hello-world")
c.URLParams.Add("build", "1")
c.URLParams.Add("stage", "1")
c.URLParams.Add("step", "1")
in := new(bytes.Buffer)
json.NewEncoder(in).Encode(dummyCreateCard)
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", in)
r = r.WithContext(
context.WithValue(context.Background(), chi.RouteCtxKey, c),
)
HandleCreate(build, card, stage, step, repos).ServeHTTP(w, r)
if got, want := w.Code, http.StatusOK; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
}
func TestHandleCreate_ValidationErrorData(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
repos := mock.NewMockRepositoryStore(controller)
repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(dummyRepo, nil)
build := mock.NewMockBuildStore(controller)
build.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyBuild, nil)
stage := mock.NewMockStageStore(controller)
stage.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyStage, nil)
step := mock.NewMockStepStore(controller)
step.EXPECT().FindNumber(gomock.Any(), dummyStage.ID, gomock.Any()).Return(dummyStep, nil)
card := mock.NewMockCardStore(controller)
c := new(chi.Context)
c.URLParams.Add("owner", "octocat")
c.URLParams.Add("name", "hello-world")
c.URLParams.Add("build", "1")
c.URLParams.Add("stage", "1")
c.URLParams.Add("step", "1")
in := new(bytes.Buffer)
json.NewEncoder(in).Encode(dummyCardNoData)
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", in)
r = r.WithContext(
context.WithValue(context.Background(), chi.RouteCtxKey, c),
)
HandleCreate(build, card, stage, step, repos).ServeHTTP(w, r)
if got, want := w.Code, http.StatusBadRequest; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
got, want := &errors.Error{}, &errors.Error{Message: "No Card Data Has Been Provided"}
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}
func TestHandleCreate_BadRequest(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
c := new(chi.Context)
c.URLParams.Add("owner", "octocat")
c.URLParams.Add("name", "hello-world")
c.URLParams.Add("build", "1")
c.URLParams.Add("stage", "1")
c.URLParams.Add("step", "1")
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", nil)
r = r.WithContext(
context.WithValue(context.Background(), chi.RouteCtxKey, c),
)
HandleCreate(nil, nil, nil, nil, nil).ServeHTTP(w, r)
got, want := &errors.Error{}, &errors.Error{Message: "EOF"}
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}
func TestHandleCreate_CreateError(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
repos := mock.NewMockRepositoryStore(controller)
repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(dummyRepo, nil)
build := mock.NewMockBuildStore(controller)
build.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyBuild, nil)
stage := mock.NewMockStageStore(controller)
stage.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyStage, nil)
step := mock.NewMockStepStore(controller)
step.EXPECT().FindNumber(gomock.Any(), dummyStage.ID, gomock.Any()).Return(dummyStep, nil)
card := mock.NewMockCardStore(controller)
card.EXPECT().CreateCard(gomock.Any(), gomock.Any()).Return(errors.ErrNotFound)
c := new(chi.Context)
c.URLParams.Add("owner", "octocat")
c.URLParams.Add("name", "hello-world")
c.URLParams.Add("build", "1")
c.URLParams.Add("stage", "1")
c.URLParams.Add("step", "1")
in := new(bytes.Buffer)
json.NewEncoder(in).Encode(dummyCreateCard)
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", in)
r = r.WithContext(
context.WithValue(context.Background(), chi.RouteCtxKey, c),
)
HandleCreate(build, card, stage, step, repos).ServeHTTP(w, r)
if got, want := w.Code, http.StatusInternalServerError; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
got, want := new(errors.Error), errors.ErrNotFound
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}

View file

@ -0,0 +1,86 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
// +build !oss
package card
import (
"strconv"
"github.com/drone/drone/core"
"github.com/drone/drone/handler/api/render"
"net/http"
"github.com/go-chi/chi"
)
// HandleDelete returns an http.HandlerFunc that processes http
// requests to delete a card.
func HandleDelete(
buildStore core.BuildStore,
cardStore core.CardStore,
stageStore core.StageStore,
stepStore core.StepStore,
repoStore core.RepositoryStore,
) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var (
namespace = chi.URLParam(r, "owner")
name = chi.URLParam(r, "name")
)
buildNumber, err := strconv.ParseInt(chi.URLParam(r, "build"), 10, 64)
if err != nil {
render.BadRequest(w, err)
return
}
stageNumber, err := strconv.Atoi(chi.URLParam(r, "stage"))
if err != nil {
render.BadRequest(w, err)
return
}
stepNumber, err := strconv.Atoi(chi.URLParam(r, "step"))
if err != nil {
render.BadRequest(w, err)
return
}
repo, err := repoStore.FindName(r.Context(), namespace, name)
if err != nil {
render.NotFound(w, err)
return
}
build, err := buildStore.FindNumber(r.Context(), repo.ID, buildNumber)
if err != nil {
render.NotFound(w, err)
return
}
stage, err := stageStore.FindNumber(r.Context(), build.ID, stageNumber)
if err != nil {
render.NotFound(w, err)
return
}
step, err := stepStore.FindNumber(r.Context(), stage.ID, stepNumber)
if err != nil {
render.NotFound(w, err)
return
}
card, err := cardStore.FindCard(r.Context(), step.ID)
if err != nil {
render.NotFound(w, err)
return
}
err = cardStore.DeleteCard(r.Context(), card.Id)
if err != nil {
render.InternalError(w, err)
return
}
w.WriteHeader(http.StatusNoContent)
}
}

View file

@ -0,0 +1,150 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
// +build !oss
package card
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/drone/drone/handler/api/errors"
"github.com/drone/drone/mock"
"github.com/go-chi/chi"
"github.com/golang/mock/gomock"
"github.com/google/go-cmp/cmp"
)
func TestHandleDelete(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
repos := mock.NewMockRepositoryStore(controller)
repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(dummyRepo, nil)
build := mock.NewMockBuildStore(controller)
build.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyBuild, nil)
stage := mock.NewMockStageStore(controller)
stage.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyStage, nil)
step := mock.NewMockStepStore(controller)
step.EXPECT().FindNumber(gomock.Any(), dummyStage.ID, gomock.Any()).Return(dummyStep, nil)
card := mock.NewMockCardStore(controller)
card.EXPECT().FindCard(gomock.Any(), dummyStep.ID).Return(dummyCard, nil)
card.EXPECT().DeleteCard(gomock.Any(), dummyCard.Id).Return(nil)
c := new(chi.Context)
c.URLParams.Add("owner", "octocat")
c.URLParams.Add("name", "hello-world")
c.URLParams.Add("build", "1")
c.URLParams.Add("stage", "1")
c.URLParams.Add("step", "1")
w := httptest.NewRecorder()
r := httptest.NewRequest("DELETE", "/", nil)
r = r.WithContext(
context.WithValue(context.Background(), chi.RouteCtxKey, c),
)
HandleDelete(build, card, stage, step, repos).ServeHTTP(w, r)
if got, want := w.Code, http.StatusNoContent; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
}
func TestHandleDelete_CardNotFound(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
repos := mock.NewMockRepositoryStore(controller)
repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(dummyRepo, nil)
build := mock.NewMockBuildStore(controller)
build.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyBuild, nil)
stage := mock.NewMockStageStore(controller)
stage.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyStage, nil)
step := mock.NewMockStepStore(controller)
step.EXPECT().FindNumber(gomock.Any(), dummyStage.ID, gomock.Any()).Return(dummyStep, nil)
card := mock.NewMockCardStore(controller)
card.EXPECT().FindCard(gomock.Any(), dummyStep.ID).Return(nil, errors.ErrNotFound)
c := new(chi.Context)
c.URLParams.Add("owner", "octocat")
c.URLParams.Add("name", "hello-world")
c.URLParams.Add("build", "1")
c.URLParams.Add("stage", "1")
c.URLParams.Add("step", "1")
w := httptest.NewRecorder()
r := httptest.NewRequest("DELETE", "/", nil)
r = r.WithContext(
context.WithValue(context.Background(), chi.RouteCtxKey, c),
)
HandleDelete(build, card, stage, step, repos).ServeHTTP(w, r)
if got, want := w.Code, http.StatusNotFound; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
got, want := new(errors.Error), errors.ErrNotFound
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}
func TestHandleDelete_DeleteError(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
repos := mock.NewMockRepositoryStore(controller)
repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(dummyRepo, nil)
build := mock.NewMockBuildStore(controller)
build.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyBuild, nil)
stage := mock.NewMockStageStore(controller)
stage.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyStage, nil)
step := mock.NewMockStepStore(controller)
step.EXPECT().FindNumber(gomock.Any(), dummyStage.ID, gomock.Any()).Return(dummyStep, nil)
card := mock.NewMockCardStore(controller)
card.EXPECT().FindCard(gomock.Any(), dummyStep.ID).Return(dummyCard, nil)
card.EXPECT().DeleteCard(gomock.Any(), dummyCard.Id).Return(errors.ErrNotFound)
c := new(chi.Context)
c.URLParams.Add("owner", "octocat")
c.URLParams.Add("name", "hello-world")
c.URLParams.Add("build", "1")
c.URLParams.Add("stage", "1")
c.URLParams.Add("step", "1")
w := httptest.NewRecorder()
r := httptest.NewRequest("DELETE", "/", nil)
r = r.WithContext(
context.WithValue(context.Background(), chi.RouteCtxKey, c),
)
HandleDelete(build, card, stage, step, repos).ServeHTTP(w, r)
if got, want := w.Code, http.StatusInternalServerError; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
got, want := new(errors.Error), errors.ErrNotFound
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}

79
handler/api/card/find.go Normal file
View file

@ -0,0 +1,79 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
// +build !oss
package card
import (
"net/http"
"strconv"
"github.com/drone/drone/core"
"github.com/drone/drone/handler/api/render"
"github.com/go-chi/chi"
)
// HandleFind returns an http.HandlerFunc that writes a json-encoded
func HandleFind(
buildStore core.BuildStore,
cardStore core.CardStore,
stageStore core.StageStore,
stepStore core.StepStore,
repoStore core.RepositoryStore,
) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var (
namespace = chi.URLParam(r, "owner")
name = chi.URLParam(r, "name")
)
buildNumber, err := strconv.ParseInt(chi.URLParam(r, "build"), 10, 64)
if err != nil {
render.BadRequest(w, err)
return
}
stageNumber, err := strconv.Atoi(chi.URLParam(r, "stage"))
if err != nil {
render.BadRequest(w, err)
return
}
stepNumber, err := strconv.Atoi(chi.URLParam(r, "step"))
if err != nil {
render.BadRequest(w, err)
return
}
repo, err := repoStore.FindName(r.Context(), namespace, name)
if err != nil {
render.NotFound(w, err)
return
}
build, err := buildStore.FindNumber(r.Context(), repo.ID, buildNumber)
if err != nil {
render.NotFound(w, err)
return
}
stage, err := stageStore.FindNumber(r.Context(), build.ID, stageNumber)
if err != nil {
render.NotFound(w, err)
return
}
step, err := stepStore.FindNumber(r.Context(), stage.ID, stepNumber)
if err != nil {
render.NotFound(w, err)
return
}
card, err := cardStore.FindCard(r.Context(), step.ID)
if err != nil {
render.NotFound(w, err)
return
}
render.JSON(w, card, 200)
}
}

View file

@ -0,0 +1,55 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
// +build !oss
package card
import (
"net/http"
"strconv"
"github.com/drone/drone/core"
"github.com/drone/drone/handler/api/render"
"github.com/go-chi/chi"
)
// HandleFindAll returns an http.HandlerFunc that writes a json-encoded
func HandleFindAll(
buildStore core.BuildStore,
cardStore core.CardStore,
repoStore core.RepositoryStore,
) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var (
namespace = chi.URLParam(r, "owner")
name = chi.URLParam(r, "name")
)
buildNumber, err := strconv.ParseInt(chi.URLParam(r, "build"), 10, 64)
if err != nil {
render.BadRequest(w, err)
return
}
repo, err := repoStore.FindName(r.Context(), namespace, name)
if err != nil {
render.NotFound(w, err)
return
}
build, err := buildStore.FindNumber(r.Context(), repo.ID, buildNumber)
if err != nil {
render.NotFound(w, err)
return
}
list, err := cardStore.FindCardByBuild(r.Context(), build.ID)
if err != nil {
render.NotFound(w, err)
return
}
render.JSON(w, list, 200)
}
}

View file

@ -0,0 +1,97 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
// +build !oss
package card
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/drone/drone/handler/api/errors"
"github.com/drone/drone/mock"
"github.com/go-chi/chi"
"github.com/golang/mock/gomock"
"github.com/google/go-cmp/cmp"
)
func TestHandleFindAll(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
repos := mock.NewMockRepositoryStore(controller)
repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(dummyRepo, nil)
build := mock.NewMockBuildStore(controller)
build.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyBuild, nil)
card := mock.NewMockCardStore(controller)
card.EXPECT().FindCardByBuild(gomock.Any(), dummyBuild.ID).Return(dummyCardList, nil)
c := new(chi.Context)
c.URLParams.Add("owner", "octocat")
c.URLParams.Add("name", "hello-world")
c.URLParams.Add("build", "1")
in := new(bytes.Buffer)
json.NewEncoder(in).Encode(dummyCreateCard)
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", in)
r = r.WithContext(
context.WithValue(context.Background(), chi.RouteCtxKey, c),
)
HandleFindAll(build, card, repos).ServeHTTP(w, r)
if got, want := w.Code, http.StatusOK; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
}
func TestHandleFindAll_CardsNotFound(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
repos := mock.NewMockRepositoryStore(controller)
repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(dummyRepo, nil)
build := mock.NewMockBuildStore(controller)
build.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyBuild, nil)
card := mock.NewMockCardStore(controller)
card.EXPECT().FindCardByBuild(gomock.Any(), dummyBuild.ID).Return(nil, errors.ErrNotFound)
c := new(chi.Context)
c.URLParams.Add("owner", "octocat")
c.URLParams.Add("name", "hello-world")
c.URLParams.Add("build", "1")
c.URLParams.Add("stage", "1")
c.URLParams.Add("step", "1")
in := new(bytes.Buffer)
json.NewEncoder(in).Encode(dummyCreateCard)
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", in)
r = r.WithContext(
context.WithValue(context.Background(), chi.RouteCtxKey, c),
)
HandleFindAll(build, card, repos).ServeHTTP(w, r)
if got, want := w.Code, http.StatusNotFound; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
got, want := new(errors.Error), errors.ErrNotFound
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}

View file

@ -0,0 +1,87 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
// +build !oss
package card
import (
"io"
"net/http"
"strconv"
"github.com/drone/drone/core"
"github.com/drone/drone/handler/api/render"
"github.com/go-chi/chi"
)
// HandleFindData returns an http.HandlerFunc that writes a json-encoded
func HandleFindData(
buildStore core.BuildStore,
cardStore core.CardStore,
stageStore core.StageStore,
stepStore core.StepStore,
repoStore core.RepositoryStore,
) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var (
namespace = chi.URLParam(r, "owner")
name = chi.URLParam(r, "name")
)
buildNumber, err := strconv.ParseInt(chi.URLParam(r, "build"), 10, 64)
if err != nil {
render.BadRequest(w, err)
return
}
stageNumber, err := strconv.Atoi(chi.URLParam(r, "stage"))
if err != nil {
render.BadRequest(w, err)
return
}
stepNumber, err := strconv.Atoi(chi.URLParam(r, "step"))
if err != nil {
render.BadRequest(w, err)
return
}
repo, err := repoStore.FindName(r.Context(), namespace, name)
if err != nil {
render.NotFound(w, err)
return
}
build, err := buildStore.FindNumber(r.Context(), repo.ID, buildNumber)
if err != nil {
render.NotFound(w, err)
return
}
stage, err := stageStore.FindNumber(r.Context(), build.ID, stageNumber)
if err != nil {
render.NotFound(w, err)
return
}
step, err := stepStore.FindNumber(r.Context(), stage.ID, stepNumber)
if err != nil {
render.NotFound(w, err)
return
}
card, err := cardStore.FindCard(r.Context(), step.ID)
if err != nil {
render.NotFound(w, err)
return
}
cardData, err := cardStore.FindCardData(r.Context(), card.Id)
if err != nil {
render.NotFound(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
io.Copy(w, cardData)
cardData.Close()
}
}

View file

@ -0,0 +1,63 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
// +build !oss
package card
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/drone/drone/mock"
"github.com/go-chi/chi"
"github.com/golang/mock/gomock"
)
func TestHandleFindCardData(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
repos := mock.NewMockRepositoryStore(controller)
repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(dummyRepo, nil)
build := mock.NewMockBuildStore(controller)
build.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyBuild, nil)
stage := mock.NewMockStageStore(controller)
stage.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyStage, nil)
step := mock.NewMockStepStore(controller)
step.EXPECT().FindNumber(gomock.Any(), dummyStage.ID, gomock.Any()).Return(dummyStep, nil)
card := mock.NewMockCardStore(controller)
card.EXPECT().FindCard(gomock.Any(), dummyStep.ID).Return(dummyCard, nil)
card.EXPECT().FindCardData(gomock.Any(), dummyCard.Id).Return(dummyCardData, nil)
c := new(chi.Context)
c.URLParams.Add("owner", "octocat")
c.URLParams.Add("name", "hello-world")
c.URLParams.Add("build", "1")
c.URLParams.Add("stage", "1")
c.URLParams.Add("step", "1")
in := new(bytes.Buffer)
json.NewEncoder(in).Encode(dummyCreateCard)
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", in)
r = r.WithContext(
context.WithValue(context.Background(), chi.RouteCtxKey, c),
)
HandleFindData(build, card, stage, step, repos).ServeHTTP(w, r)
if got, want := w.Code, http.StatusOK; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
}

View file

@ -0,0 +1,111 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
// +build !oss
package card
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/drone/drone/handler/api/errors"
"github.com/drone/drone/mock"
"github.com/go-chi/chi"
"github.com/golang/mock/gomock"
"github.com/google/go-cmp/cmp"
)
func TestHandleFind(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
repos := mock.NewMockRepositoryStore(controller)
repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(dummyRepo, nil)
build := mock.NewMockBuildStore(controller)
build.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyBuild, nil)
stage := mock.NewMockStageStore(controller)
stage.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyStage, nil)
step := mock.NewMockStepStore(controller)
step.EXPECT().FindNumber(gomock.Any(), dummyStage.ID, gomock.Any()).Return(dummyStep, nil)
card := mock.NewMockCardStore(controller)
card.EXPECT().FindCard(gomock.Any(), dummyStep.ID).Return(dummyCard, nil)
c := new(chi.Context)
c.URLParams.Add("owner", "octocat")
c.URLParams.Add("name", "hello-world")
c.URLParams.Add("build", "1")
c.URLParams.Add("stage", "1")
c.URLParams.Add("step", "1")
in := new(bytes.Buffer)
json.NewEncoder(in).Encode(dummyCreateCard)
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", in)
r = r.WithContext(
context.WithValue(context.Background(), chi.RouteCtxKey, c),
)
HandleFind(build, card, stage, step, repos).ServeHTTP(w, r)
if got, want := w.Code, http.StatusOK; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
}
func TestHandleFind_CardNotFound(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
repos := mock.NewMockRepositoryStore(controller)
repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(dummyRepo, nil)
build := mock.NewMockBuildStore(controller)
build.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyBuild, nil)
stage := mock.NewMockStageStore(controller)
stage.EXPECT().FindNumber(gomock.Any(), dummyBuild.ID, gomock.Any()).Return(dummyStage, nil)
step := mock.NewMockStepStore(controller)
step.EXPECT().FindNumber(gomock.Any(), dummyStage.ID, gomock.Any()).Return(dummyStep, nil)
card := mock.NewMockCardStore(controller)
card.EXPECT().FindCard(gomock.Any(), dummyStep.ID).Return(nil, errors.ErrNotFound)
c := new(chi.Context)
c.URLParams.Add("owner", "octocat")
c.URLParams.Add("name", "hello-world")
c.URLParams.Add("build", "1")
c.URLParams.Add("stage", "1")
c.URLParams.Add("step", "1")
in := new(bytes.Buffer)
json.NewEncoder(in).Encode(dummyCreateCard)
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", in)
r = r.WithContext(
context.WithValue(context.Background(), chi.RouteCtxKey, c),
)
HandleFind(build, card, stage, step, repos).ServeHTTP(w, r)
if got, want := w.Code, http.StatusNotFound; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
got, want := new(errors.Error), errors.ErrNotFound
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}

76
handler/api/card/none.go Normal file
View file

@ -0,0 +1,76 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build oss
package card
import (
"net/http"
"github.com/drone/drone/core"
"github.com/drone/drone/handler/api/render"
)
var notImplemented = func(w http.ResponseWriter, r *http.Request) {
render.NotImplemented(w, render.ErrNotImplemented)
}
func HandleCreate(
buildStore core.BuildStore,
cardStore core.CardStore,
stageStore core.StageStore,
stepStore core.StepStore,
repoStore core.RepositoryStore,
) http.HandlerFunc {
return notImplemented
}
func HandleDelete(
buildStore core.BuildStore,
cardStore core.CardStore,
stageStore core.StageStore,
stepStore core.StepStore,
repoStore core.RepositoryStore,
) http.HandlerFunc {
return notImplemented
}
func HandleFind(
buildStore core.BuildStore,
cardStore core.CardStore,
stageStore core.StageStore,
stepStore core.StepStore,
repoStore core.RepositoryStore,
) http.HandlerFunc {
return notImplemented
}
func HandleFindAll(
buildStore core.BuildStore,
cardStore core.CardStore,
repoStore core.RepositoryStore,
) http.HandlerFunc {
return notImplemented
}
func HandleFindData(
buildStore core.BuildStore,
cardStore core.CardStore,
stageStore core.StageStore,
stepStore core.StepStore,
repoStore core.RepositoryStore,
) http.HandlerFunc {
return notImplemented
}

View file

@ -6,4 +6,4 @@
package mock package mock
//go:generate mockgen -package=mock -destination=mock_gen.go github.com/drone/drone/core Pubsub,Canceler,ConvertService,ValidateService,NetrcService,Renewer,HookParser,UserService,RepositoryService,CommitService,StatusService,HookService,FileService,Batcher,BuildStore,CronStore,LogStore,PermStore,SecretStore,GlobalSecretStore,StageStore,StepStore,RepositoryStore,UserStore,Scheduler,Session,OrganizationService,SecretService,RegistryService,ConfigService,Transferer,Triggerer,Syncer,LogStream,WebhookSender,LicenseService,TemplateStore //go:generate mockgen -package=mock -destination=mock_gen.go github.com/drone/drone/core Pubsub,Canceler,ConvertService,ValidateService,NetrcService,Renewer,HookParser,UserService,RepositoryService,CommitService,StatusService,HookService,FileService,Batcher,BuildStore,CronStore,LogStore,PermStore,SecretStore,GlobalSecretStore,StageStore,StepStore,RepositoryStore,UserStore,Scheduler,Session,OrganizationService,SecretService,RegistryService,ConfigService,Transferer,Triggerer,Syncer,LogStream,WebhookSender,LicenseService,TemplateStore,CardStore

View file

@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT. // Code generated by MockGen. DO NOT EDIT.
// Source: github.com/drone/drone/core (interfaces: Pubsub,Canceler,ConvertService,ValidateService,NetrcService,Renewer,HookParser,UserService,RepositoryService,CommitService,StatusService,HookService,FileService,Batcher,BuildStore,CronStore,LogStore,PermStore,SecretStore,GlobalSecretStore,StageStore,StepStore,RepositoryStore,UserStore,Scheduler,Session,OrganizationService,SecretService,RegistryService,ConfigService,Transferer,Triggerer,Syncer,LogStream,WebhookSender,LicenseService,TemplateStore) // Source: github.com/drone/drone/core (interfaces: Pubsub,Canceler,ConvertService,ValidateService,NetrcService,Renewer,HookParser,UserService,RepositoryService,CommitService,StatusService,HookService,FileService,Batcher,BuildStore,CronStore,LogStore,PermStore,SecretStore,GlobalSecretStore,StageStore,StepStore,RepositoryStore,UserStore,Scheduler,Session,OrganizationService,SecretService,RegistryService,ConfigService,Transferer,Triggerer,Syncer,LogStream,WebhookSender,LicenseService,TemplateStore,CardStore)
// Package mock is a generated GoMock package. // Package mock is a generated GoMock package.
package mock package mock
@ -2879,3 +2879,99 @@ func (mr *MockTemplateStoreMockRecorder) Update(arg0, arg1 interface{}) *gomock.
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockTemplateStore)(nil).Update), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockTemplateStore)(nil).Update), arg0, arg1)
} }
// MockCardStore is a mock of CardStore interface.
type MockCardStore struct {
ctrl *gomock.Controller
recorder *MockCardStoreMockRecorder
}
// MockCardStoreMockRecorder is the mock recorder for MockCardStore.
type MockCardStoreMockRecorder struct {
mock *MockCardStore
}
// NewMockCardStore creates a new mock instance.
func NewMockCardStore(ctrl *gomock.Controller) *MockCardStore {
mock := &MockCardStore{ctrl: ctrl}
mock.recorder = &MockCardStoreMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCardStore) EXPECT() *MockCardStoreMockRecorder {
return m.recorder
}
// CreateCard mocks base method.
func (m *MockCardStore) CreateCard(arg0 context.Context, arg1 *core.CreateCard) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateCard", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// CreateCard indicates an expected call of CreateCard.
func (mr *MockCardStoreMockRecorder) CreateCard(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateCard", reflect.TypeOf((*MockCardStore)(nil).CreateCard), arg0, arg1)
}
// DeleteCard mocks base method.
func (m *MockCardStore) DeleteCard(arg0 context.Context, arg1 int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteCard", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteCard indicates an expected call of DeleteCard.
func (mr *MockCardStoreMockRecorder) DeleteCard(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCard", reflect.TypeOf((*MockCardStore)(nil).DeleteCard), arg0, arg1)
}
// FindCard mocks base method.
func (m *MockCardStore) FindCard(arg0 context.Context, arg1 int64) (*core.Card, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindCard", arg0, arg1)
ret0, _ := ret[0].(*core.Card)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindCard indicates an expected call of FindCard.
func (mr *MockCardStoreMockRecorder) FindCard(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindCard", reflect.TypeOf((*MockCardStore)(nil).FindCard), arg0, arg1)
}
// FindCardByBuild mocks base method.
func (m *MockCardStore) FindCardByBuild(arg0 context.Context, arg1 int64) ([]*core.Card, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindCardByBuild", arg0, arg1)
ret0, _ := ret[0].([]*core.Card)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindCardByBuild indicates an expected call of FindCardByBuild.
func (mr *MockCardStoreMockRecorder) FindCardByBuild(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindCardByBuild", reflect.TypeOf((*MockCardStore)(nil).FindCardByBuild), arg0, arg1)
}
// FindCardData mocks base method.
func (m *MockCardStore) FindCardData(arg0 context.Context, arg1 int64) (io.Reader, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindCardData", arg0, arg1)
ret0, _ := ret[0].(io.Reader)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindCardData indicates an expected call of FindCardData.
func (mr *MockCardStoreMockRecorder) FindCardData(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindCardData", reflect.TypeOf((*MockCardStore)(nil).FindCardData), arg0, arg1)
}

193
store/card/card.go Normal file
View file

@ -0,0 +1,193 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
// +build !oss
package card
import (
"bytes"
"context"
"io"
"io/ioutil"
"github.com/drone/drone/core"
"github.com/drone/drone/store/shared/db"
)
// New returns a new card database store.
func New(db *db.DB) core.CardStore {
return &cardStore{
db: db,
}
}
type cardStore struct {
db *db.DB
}
func (c *cardStore) FindCardByBuild(ctx context.Context, build int64) ([]*core.Card, error) {
var out []*core.Card
err := c.db.View(func(queryer db.Queryer, binder db.Binder) error {
params := map[string]interface{}{
"card_build": build,
}
stmt, args, err := binder.BindNamed(queryByBuild, params)
if err != nil {
return err
}
rows, err := queryer.Query(stmt, args...)
if err != nil {
return err
}
out, err = scanRows(rows)
return err
})
return out, err
}
func (c cardStore) FindCard(ctx context.Context, step int64) (*core.Card, error) {
out := &core.Card{Step: step}
err := c.db.View(func(queryer db.Queryer, binder db.Binder) error {
params, err := toParams(out)
if err != nil {
return err
}
query, args, err := binder.BindNamed(queryByStep, params)
if err != nil {
return err
}
row := queryer.QueryRow(query, args...)
return scanRow(row, out)
})
return out, err
}
func (c cardStore) FindCardData(ctx context.Context, id int64) (io.ReadCloser, error) {
out := &core.CardData{}
err := c.db.View(func(queryer db.Queryer, binder db.Binder) error {
params := map[string]interface{}{
"card_id": id,
}
query, args, err := binder.BindNamed(queryKey, params)
if err != nil {
return err
}
row := queryer.QueryRow(query, args...)
return scanRowCardDataOnly(row, out)
})
return ioutil.NopCloser(
bytes.NewBuffer(out.Data),
), err
}
func (c cardStore) CreateCard(ctx context.Context, card *core.CreateCard) error {
if c.db.Driver() == db.Postgres {
return c.createPostgres(ctx, card)
}
return c.create(ctx, card)
}
func (c *cardStore) create(ctx context.Context, card *core.CreateCard) error {
return c.db.Lock(func(execer db.Execer, binder db.Binder) error {
params, err := toSaveCardParams(card)
if err != nil {
return err
}
stmt, args, err := binder.BindNamed(stmtInsert, params)
if err != nil {
return err
}
res, err := execer.Exec(stmt, args...)
if err != nil {
return err
}
card.Id, err = res.LastInsertId()
return err
})
}
func (c *cardStore) createPostgres(ctx context.Context, card *core.CreateCard) error {
return c.db.Lock(func(execer db.Execer, binder db.Binder) error {
params, err := toSaveCardParams(card)
if err != nil {
return err
}
stmt, args, err := binder.BindNamed(stmtInsertPostgres, params)
if err != nil {
return err
}
return execer.QueryRow(stmt, args...).Scan(&card.Id)
})
}
func (c cardStore) DeleteCard(ctx context.Context, id int64) error {
return c.db.Lock(func(execer db.Execer, binder db.Binder) error {
params := map[string]interface{}{
"card_id": id,
}
stmt, args, err := binder.BindNamed(stmtDelete, params)
if err != nil {
return err
}
_, err = execer.Exec(stmt, args...)
return err
})
}
const queryBase = `
SELECT
card_id
,card_build
,card_stage
,card_step
,card_schema
`
const queryCardData = `
SELECT
card_id
,card_data
`
const queryByBuild = queryBase + `
FROM cards
WHERE card_build = :card_build
`
const queryByStep = queryBase + `
FROM cards
WHERE card_step = :card_step
LIMIT 1
`
const queryKey = queryCardData + `
FROM cards
WHERE card_id = :card_id
LIMIT 1
`
const stmtInsert = `
INSERT INTO cards (
card_build
,card_stage
,card_step
,card_schema
,card_data
) VALUES (
:card_build
,:card_stage
,:card_step
,:card_schema
,:card_data
)
`
const stmtDelete = `
DELETE FROM cards
WHERE card_id = :card_id
`
const stmtInsertPostgres = stmtInsert + `
RETURNING card_id
`

51
store/card/card_oss.go Normal file
View file

@ -0,0 +1,51 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build oss
package card
import (
"context"
"io"
"github.com/drone/drone/core"
"github.com/drone/drone/store/shared/db"
)
func New(db *db.DB) core.CardStore {
return new(noop)
}
type noop struct{}
func (noop) FindCardByBuild(ctx context.Context, build int64) ([]*core.Card, error) {
return nil, nil
}
func (noop) FindCard(ctx context.Context, step int64) (*core.Card, error) {
return nil, nil
}
func (noop) FindCardData(ctx context.Context, id int64) (io.Reader, error) {
return nil, nil
}
func (noop) CreateCard(ctx context.Context, card *core.CreateCard) error {
return nil
}
func (noop) DeleteCard(ctx context.Context, id int64) error {
return nil
}

130
store/card/card_test.go Normal file
View file

@ -0,0 +1,130 @@
package card
import (
"context"
"database/sql"
"io/ioutil"
"testing"
"github.com/drone/drone/core"
"github.com/drone/drone/store/shared/db/dbtest"
)
var noContext = context.TODO()
func TestCard(t *testing.T) {
conn, err := dbtest.Connect()
if err != nil {
t.Error(err)
return
}
defer func() {
dbtest.Reset(conn)
dbtest.Disconnect(conn)
}()
store := New(conn).(*cardStore)
t.Run("Create", testCardCreate(store))
}
func testCard(item *core.Card) func(t *testing.T) {
return func(t *testing.T) {
if got, want := item.Schema, "https://myschema.com"; got != want {
t.Errorf("Want card schema %q, got %q", want, got)
}
if got, want := item.Build, int64(1); got != want {
t.Errorf("Want card build number %q, got %q", want, got)
}
if got, want := item.Stage, int64(2); got != want {
t.Errorf("Want card stage number %q, got %q", want, got)
}
if got, want := item.Step, int64(3); got != want {
t.Errorf("Want card step number %q, got %q", want, got)
}
}
}
func testCardCreate(store *cardStore) func(t *testing.T) {
return func(t *testing.T) {
item := &core.CreateCard{
Id: 1,
Build: 1,
Stage: 2,
Step: 3,
Schema: "https://myschema.com",
Data: "{\"type\": \"AdaptiveCard\"}",
}
err := store.CreateCard(noContext, item)
if err != nil {
t.Error(err)
}
if item.Id == 0 {
t.Errorf("Want card Id assigned, got %d", item.Id)
}
t.Run("FindByBuild", testFindCardByBuild(store))
t.Run("FindCard", testFindCard(store))
t.Run("FindCardData", testFindCardData(store))
t.Run("Delete", testCardDelete(store))
}
}
func testFindCardByBuild(card *cardStore) func(t *testing.T) {
return func(t *testing.T) {
item, err := card.FindCardByBuild(noContext, 1)
if err != nil {
t.Error(err)
} else {
t.Run("Fields", testCard(item[0]))
}
}
}
func testFindCard(card *cardStore) func(t *testing.T) {
return func(t *testing.T) {
item, err := card.FindCard(noContext, 3)
if err != nil {
t.Error(err)
} else {
t.Run("Fields", testCard(item))
}
}
}
func testFindCardData(card *cardStore) func(t *testing.T) {
return func(t *testing.T) {
r, err := card.FindCardData(noContext, 1)
if err != nil {
t.Error(err)
} else {
data, err := ioutil.ReadAll(r)
if err != nil {
t.Error(err)
return
}
if got, want := string(data), "{\"type\": \"AdaptiveCard\"}"; got != want {
t.Errorf("Want card data output stream %q, got %q", want, got)
}
}
}
}
func testCardDelete(store *cardStore) func(t *testing.T) {
return func(t *testing.T) {
card, err := store.FindCard(noContext, 3)
if err != nil {
t.Error(err)
return
}
err = store.DeleteCard(noContext, card.Id)
if err != nil {
t.Error(err)
return
}
_, err = store.FindCard(noContext, card.Step)
if got, want := sql.ErrNoRows, err; got != want {
t.Errorf("Want sql.ErrNoRows, got %v", got)
return
}
}
}

79
store/card/scan.go Normal file
View file

@ -0,0 +1,79 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
// +build !oss
package card
import (
"database/sql"
"github.com/drone/drone/core"
"github.com/drone/drone/store/shared/db"
)
// helper function converts the card structure to a set
// of named query parameters.
func toParams(card *core.Card) (map[string]interface{}, error) {
return map[string]interface{}{
"card_id": card.Id,
"card_build": card.Build,
"card_stage": card.Stage,
"card_step": card.Step,
"card_schema": card.Schema,
}, nil
}
// helper function converts the card structure to a set
// of named query parameters.
func toSaveCardParams(card *core.CreateCard) (map[string]interface{}, error) {
return map[string]interface{}{
"card_id": card.Id,
"card_build": card.Build,
"card_stage": card.Stage,
"card_step": card.Step,
"card_schema": card.Schema,
"card_data": card.Data,
}, nil
}
// helper function scans the sql.Row and copies the column
// values to the destination object.
func scanRow(scanner db.Scanner, dst *core.Card) error {
err := scanner.Scan(
&dst.Id,
&dst.Build,
&dst.Stage,
&dst.Step,
&dst.Schema,
)
if err != nil {
return err
}
return nil
}
func scanRowCardDataOnly(scanner db.Scanner, dst *core.CardData) error {
return scanner.Scan(
&dst.Id,
&dst.Data,
)
}
// helper function scans the sql.Row and copies the column
// values to the destination object.
func scanRows(rows *sql.Rows) ([]*core.Card, error) {
defer rows.Close()
card := []*core.Card{}
for rows.Next() {
tem := new(core.Card)
err := scanRow(rows, tem)
if err != nil {
return nil, err
}
card = append(card, tem)
}
return card, nil
}

View file

@ -180,6 +180,14 @@ var migrations = []struct {
name: "alter-table-steps-add-column-step-detached", name: "alter-table-steps-add-column-step-detached",
stmt: alterTableStepsAddColumnStepDetached, stmt: alterTableStepsAddColumnStepDetached,
}, },
{
name: "create-table-cards",
stmt: createTableCards,
},
{
name: "create-index-cards-card_build",
stmt: createIndexCardsCardbuild,
},
} }
// Migrate performs the database migration. If the migration fails // Migrate performs the database migration. If the migration fails
@ -722,3 +730,22 @@ ALTER TABLE steps ADD COLUMN step_image VARCHAR(1000) NOT NULL DEFAULT '';
var alterTableStepsAddColumnStepDetached = ` var alterTableStepsAddColumnStepDetached = `
ALTER TABLE steps ADD COLUMN step_detached BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE steps ADD COLUMN step_detached BOOLEAN NOT NULL DEFAULT FALSE;
` `
//
// 017_create_table_cards.sql
//
var createTableCards = `
CREATE TABLE IF NOT EXISTS cards (
card_id INTEGER PRIMARY KEY AUTO_INCREMENT
,card_build INTEGER
,card_stage INTEGER
,card_step INTEGER
,card_schema TEXT
,card_data TEXT
);
`
var createIndexCardsCardbuild = `
CREATE INDEX ix_cards_build ON cards (card_build);
`

View file

@ -0,0 +1,16 @@
-- name: create-table-cards
CREATE TABLE IF NOT EXISTS cards (
card_id INTEGER PRIMARY KEY AUTO_INCREMENT
,card_build INTEGER
,card_stage INTEGER
,card_step INTEGER
,card_schema TEXT
,card_data TEXT
);
-- name: create-index-cards-card-build
CREATE INDEX ix_cards_build ON cards (card_build);
-- name: create-index-cards-card_step
CREATE INDEX ix_cards_step ON cards (card_step);

View file

@ -172,6 +172,14 @@ var migrations = []struct {
name: "alter-table-steps-add-column-step-detached", name: "alter-table-steps-add-column-step-detached",
stmt: alterTableStepsAddColumnStepDetached, stmt: alterTableStepsAddColumnStepDetached,
}, },
{
name: "create-table-cards",
stmt: createTableCards,
},
{
name: "create-index-cards-card_build",
stmt: createIndexCardsCardbuild,
},
} }
// Migrate performs the database migration. If the migration fails // Migrate performs the database migration. If the migration fails
@ -694,3 +702,22 @@ ALTER TABLE steps ADD COLUMN step_image VARCHAR(1000) NOT NULL DEFAULT '';
var alterTableStepsAddColumnStepDetached = ` var alterTableStepsAddColumnStepDetached = `
ALTER TABLE steps ADD COLUMN step_detached BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE steps ADD COLUMN step_detached BOOLEAN NOT NULL DEFAULT FALSE;
` `
//
// 018_create_table_cards.sql
//
var createTableCards = `
CREATE TABLE IF NOT EXISTS cards (
card_id SERIAL PRIMARY KEY
,card_build INTEGER
,card_stage INTEGER
,card_step INTEGER
,card_schema TEXT
,card_data TEXT
);
`
var createIndexCardsCardbuild = `
CREATE INDEX IF NOT EXISTS ix_cards_build ON cards (card_build);
`

View file

@ -0,0 +1,16 @@
-- name: create-table-cards
CREATE TABLE IF NOT EXISTS cards (
card_id SERIAL PRIMARY KEY
,card_build INTEGER
,card_stage INTEGER
,card_step INTEGER
,card_schema TEXT
,card_data TEXT
);
-- name: create-index-cards-card_build
CREATE INDEX IF NOT EXISTS ix_cards_build ON cards (card_build);
-- name: create-index-cards-card_step
CREATE INDEX IF NOT EXISTS ix_cards_step ON cards (card_step);

View file

@ -172,6 +172,14 @@ var migrations = []struct {
name: "alter-table-steps-add-column-step-detached", name: "alter-table-steps-add-column-step-detached",
stmt: alterTableStepsAddColumnStepDetached, stmt: alterTableStepsAddColumnStepDetached,
}, },
{
name: "create-table-cards",
stmt: createTableCards,
},
{
name: "create-index-cards-card_build",
stmt: createIndexCardsCardbuild,
},
} }
// Migrate performs the database migration. If the migration fails // Migrate performs the database migration. If the migration fails
@ -696,3 +704,22 @@ ALTER TABLE steps ADD COLUMN step_image TEXT NOT NULL DEFAULT '';
var alterTableStepsAddColumnStepDetached = ` var alterTableStepsAddColumnStepDetached = `
ALTER TABLE steps ADD COLUMN step_detached BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE steps ADD COLUMN step_detached BOOLEAN NOT NULL DEFAULT FALSE;
` `
//
// 017_create_table_cards.sql
//
var createTableCards = `
CREATE TABLE IF NOT EXISTS cards (
card_id INTEGER PRIMARY KEY AUTOINCREMENT
,card_build INTEGER
,card_stage INTEGER
,card_step INTEGER
,card_schema TEXT
,card_data TEXT
);
`
var createIndexCardsCardbuild = `
CREATE INDEX IF NOT EXISTS ix_cards_build ON cards (card_build);
`

View file

@ -0,0 +1,16 @@
-- name: create-table-cards
CREATE TABLE IF NOT EXISTS cards (
card_id INTEGER PRIMARY KEY AUTOINCREMENT
,card_build INTEGER
,card_stage INTEGER
,card_step INTEGER
,card_schema TEXT
,card_data TEXT
);
-- name: create-index-cards-card_build
CREATE INDEX IF NOT EXISTS ix_cards_build ON cards (card_build);
-- name: create-index-cards-card_step
CREATE INDEX IF NOT EXISTS ix_cards_step ON cards (card_step);