M api/go.mod => api/go.mod +4 -2
@@ 4,6 4,7 @@ go 1.14
require (
git.sr.ht/~sircmpwn/core-go v0.0.0-20201126191555-4cc9b34f4ca8
+ git.sr.ht/~sircmpwn/dowork v0.0.0-20201121170652-c2a771442daf // indirect
git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3
github.com/99designs/gqlgen v0.13.0
github.com/Masterminds/squirrel v1.4.0
@@ 13,7 14,6 @@ require (
github.com/fernet/fernet-go v0.0.0-20191111064656-eff2850e6001
github.com/go-chi/chi v4.1.2+incompatible
github.com/go-git/go-git/v5 v5.0.0
- github.com/golang/protobuf v1.4.2 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/gorilla/websocket v1.4.2 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
@@ 23,7 23,8 @@ require (
github.com/matryer/moq v0.0.0-20200310130814-7721994d1b54 // indirect
github.com/mitchellh/mapstructure v1.3.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
- github.com/prometheus/client_golang v1.7.1 // indirect
+ github.com/prometheus/client_golang v1.8.0 // indirect
+ github.com/prometheus/common v0.15.0 // indirect
github.com/urfave/cli/v2 v2.2.0 // indirect
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
github.com/vektah/dataloaden v0.3.0 // indirect
@@ 31,6 32,7 @@ require (
github.com/vektah/gqlparser/v2 v2.1.0
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect
+ golang.org/x/sys v0.0.0-20201126144705-a4b67b81d3d2 // indirect
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
M api/go.sum => api/go.sum +12 -0
@@ 8,6 8,8 @@ git.sr.ht/~sircmpwn/core-go v0.0.0-20201126191555-4cc9b34f4ca8 h1:4Z5v+Xld0h57mH
git.sr.ht/~sircmpwn/core-go v0.0.0-20201126191555-4cc9b34f4ca8/go.mod h1:LLLvDJIgVgmA/sHl0fzj9UvpFiLi0v8a/aiBc3g22ik=
git.sr.ht/~sircmpwn/dowork v0.0.0-20201013160733-35ca012e4dc8 h1:ltrdYYclC4wQEg3QdcG2hgYAFCk+6/l2vU1OXygKXVA=
git.sr.ht/~sircmpwn/dowork v0.0.0-20201013160733-35ca012e4dc8/go.mod h1:8neHEO3503w/rNtttnR0JFpQgM/GFhaafVwvkPsFIDw=
+git.sr.ht/~sircmpwn/dowork v0.0.0-20201121170652-c2a771442daf h1:wRE9o+wlpTSuq/ucFJsfbglAobfGPIgFdaTtZXrk8P0=
+git.sr.ht/~sircmpwn/dowork v0.0.0-20201121170652-c2a771442daf/go.mod h1:8neHEO3503w/rNtttnR0JFpQgM/GFhaafVwvkPsFIDw=
git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3 h1:4wDp4BKF7NQqoh73VXpZsB/t1OEhDpz/zEpmdQfbjDk=
git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw=
git.sr.ht/~sircmpwn/git.sr.ht v0.0.0-20201126161813-85c7338ae566 h1:4MRSyIEfnNfGsLl/R0IjZV7JgYJctGacBMwYJV5RkZE=
@@ 156,6 158,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ 168,6 172,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
+github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
@@ 353,6 358,8 @@ github.com/prometheus/client_golang v1.6.0 h1:YVPodQOcK15POxhgARIvnDRVpLcuK8mgln
github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4=
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.8.0 h1:zvJNkoCFAnYFNC24FV8nW4JdRJ3GIFcLbg65lL/JDcw=
+github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ 368,6 375,8 @@ github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lN
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.14.0 h1:RHRyE8UocrbjU+6UvRzwi6HjiDfxrrBU91TtbKzkGp4=
github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
+github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM=
+github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ 544,6 553,9 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORK
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201013132646-2da7054afaeb h1:HS9IzC4UFbpMBLQUDSQcU+ViVT1vdFCQVjdPVpTlZrs=
golang.org/x/sys v0.0.0-20201013132646-2da7054afaeb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201126144705-a4b67b81d3d2 h1:WFCmm2Hi9I2gYf1kv7LQ8ajKA5x9heC2v9xuUKwvf68=
+golang.org/x/sys v0.0.0-20201126144705-a4b67b81d3d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.4-0.20201021145329-22f1617af38e h1:0kyKOEC0chG7FKmnf/1uNwvDLc3NtNTRip2rXAN9nwI=
M api/graph/api/generated.go => api/graph/api/generated.go +2 -2
@@ 4742,14 4742,14 @@ func (ec *executionContext) _Repository_visibility(ctx context.Context, field gr
Object: "Repository",
Field: field,
Args: nil,
- IsMethod: false,
+ IsMethod: true,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
- return obj.Visibility, nil
+ return obj.Visibility(), nil
})
if err != nil {
ec.Error(ctx, err)
M api/graph/model/repository.go => api/graph/model/repository.go +16 -2
@@ 3,6 3,7 @@ package model
import (
"context"
"database/sql"
+ "fmt"
"time"
"strconv"
@@ 20,17 21,30 @@ type Repository struct {
Updated time.Time `json:"updated"`
Name string `json:"name"`
Description *string `json:"description"`
- Visibility Visibility `json:"visibility"`
UpstreamURL *string `json:"upstreamUrl"`
Path string
OwnerID int
+ RawVisibility string
alias string
repo *RepoWrapper
fields *database.ModelFields
}
+func (r *Repository) Visibility() Visibility {
+ visMap := map[string]Visibility{
+ "public": VisibilityPublic,
+ "unlisted": VisibilityUnlisted,
+ "private": VisibilityPrivate,
+ }
+ vis, ok := visMap[r.RawVisibility]
+ if !ok {
+ panic(fmt.Errorf("Invalid repo visibility %s", r.RawVisibility)) // Invariant
+ }
+ return vis
+}
+
func (r *Repository) Repo() *RepoWrapper {
if r.repo != nil {
return r.repo
@@ 80,7 94,7 @@ func (r *Repository) Fields() *database.ModelFields {
{ "updated", "updated", &r.Updated },
{ "name", "name", &r.Name },
{ "description", "description", &r.Description },
- { "visibility", "visibility", &r.Visibility },
+ { "visibility", "visibility", &r.RawVisibility },
{ "upstream_uri", "upstreamUrl", &r.UpstreamURL },
// Always fetch:
M api/graph/schema.resolvers.go => api/graph/schema.resolvers.go +5 -3
@@ 21,6 21,7 @@ import (
"git.sr.ht/~sircmpwn/git.sr.ht/api/graph/api"
"git.sr.ht/~sircmpwn/git.sr.ht/api/graph/model"
"git.sr.ht/~sircmpwn/git.sr.ht/api/loaders"
+ "git.sr.ht/~sircmpwn/git.sr.ht/api/webhooks"
"github.com/99designs/gqlgen/graphql"
sq "github.com/Masterminds/squirrel"
git "github.com/go-git/go-git/v5"
@@ 119,8 120,8 @@ func (r *mutationResolver) CreateRepository(ctx context.Context, name string, vi
upstream_uri, path, owner_id;
`, name, description, repoPath, dvis, user.UserID)
if err := row.Scan(&repo.ID, &repo.Created, &repo.Updated, &repo.Name,
- &repo.Description, &repo.Visibility, &repo.UpstreamURL, &repo.Path,
- &repo.OwnerID); err != nil {
+ &repo.Description, &repo.RawVisibility, &repo.UpstreamURL,
+ &repo.Path, &repo.OwnerID); err != nil {
if strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
return fmt.Errorf("A repository with this name already exists.")
}
@@ 155,6 156,7 @@ func (r *mutationResolver) CreateRepository(ctx context.Context, name string, vi
}
}
+ webhooks.DeliverLegacyRepoCreate(ctx, &repo)
return nil
}); err != nil {
if repoCreated {
@@ 255,7 257,7 @@ func (r *mutationResolver) UpdateRepository(ctx context.Context, id int, input m
row := query.RunWith(tx).QueryRowContext(ctx)
if err := row.Scan(&repo.ID, &repo.Created, &repo.Updated,
- &repo.Name, &repo.Description, &repo.Visibility,
+ &repo.Name, &repo.Description, &repo.RawVisibility,
&repo.UpstreamURL, &repo.Path, &repo.OwnerID); err != nil {
if err == sql.ErrNoRows {
return fmt.Errorf("No repository by ID %d found for this user", id)
M api/server.go => api/server.go +8 -1
@@ 11,6 11,7 @@ import (
"git.sr.ht/~sircmpwn/git.sr.ht/api/graph/api"
"git.sr.ht/~sircmpwn/git.sr.ht/api/graph/model"
"git.sr.ht/~sircmpwn/git.sr.ht/api/loaders"
+ "git.sr.ht/~sircmpwn/git.sr.ht/api/webhooks"
)
func main() {
@@ 29,9 30,15 @@ func main() {
scopes[i] = s.String()
}
+ legacyWebhooks := webhooks.NewLegacyQueue()
+
server.NewServer("git.sr.ht", appConfig).
WithDefaultMiddleware().
- WithMiddleware(loaders.Middleware).
+ WithMiddleware(
+ loaders.Middleware,
+ webhooks.LegacyMiddleware(legacyWebhooks),
+ ).
WithSchema(schema, scopes).
+ WithQueues(legacyWebhooks.Queue).
Run()
}
A api/webhooks/webhooks.go => api/webhooks/webhooks.go +88 -0
@@ 0,0 1,88 @@
+package webhooks
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "net/http"
+ "time"
+
+ "git.sr.ht/~sircmpwn/core-go/auth"
+ "git.sr.ht/~sircmpwn/core-go/webhooks"
+ sq "github.com/Masterminds/squirrel"
+
+ "git.sr.ht/~sircmpwn/git.sr.ht/api/graph/model"
+)
+
+func NewLegacyQueue() *webhooks.LegacyQueue {
+ return webhooks.NewLegacyQueue()
+}
+
+var legacyUserCtxKey = &contextKey{"legacyUser"}
+
+type contextKey struct {
+ name string
+}
+
+func LegacyMiddleware(
+ queue *webhooks.LegacyQueue) func(next http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ ctx := context.WithValue(r.Context(), legacyUserCtxKey, queue)
+ r = r.WithContext(ctx)
+ next.ServeHTTP(w, r)
+ })
+ }
+}
+
+func DeliverLegacyRepoCreate(ctx context.Context, repo *model.Repository) {
+ q, ok := ctx.Value(legacyUserCtxKey).(*webhooks.LegacyQueue)
+ if !ok {
+ panic(errors.New("No legacy user webhooks worker for this context"))
+ }
+
+ type WebhookPayload struct {
+ ID int `json:"id"`
+ Created time.Time `json:"created"`
+ Updated time.Time `json:"updated"`
+ Name string `json:"name"`
+ Description *string `json:"description"`
+ Visibility string `json:"visibility"`
+
+ Owner struct {
+ CanonicalName string `json:"canonical_name"`
+ Name string `json:"name"`
+ }`json:"owner"`
+ }
+
+ payload := WebhookPayload{
+ ID: repo.ID,
+ Created: repo.Created,
+ Updated: repo.Created,
+ Name: repo.Name,
+ Description: repo.Description,
+ Visibility: repo.RawVisibility,
+ }
+
+ // TODO: User groups
+ user := auth.ForContext(ctx)
+ if user.UserID != repo.OwnerID {
+ // At the time of writing, the only consumers of this function are in a
+ // context where the authenticated user is the owner of this repo. We
+ // can skip the database round-trip if we just grab their auth context.
+ panic(errors.New("TODO: look up user details for this repo"))
+ }
+ payload.Owner.CanonicalName = "~" + user.Username
+ payload.Owner.Name = user.Username
+
+ encoded, err := json.Marshal(&payload)
+ if err != nil {
+ panic(err) // Programmer error
+ }
+
+ query := sq.
+ Select().
+ From("user_webhook_subscription sub").
+ Where("sub.user_id = ?", repo.OwnerID)
+ q.Schedule(query, "user", "repo:create", encoded)
+}