diff --git a/.woodpecker.yml b/.woodpecker.yml index 6b1331e..2a93771 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -1,6 +1,6 @@ platform: linux/amd64 steps: - build: + build-frontend: image: gitea.elara.ws/elara6331/node environment: - LURE_WEB_API_URL=https://api.lure.elara.ws @@ -10,8 +10,8 @@ steps: - mkdir app - mv node_modules build package.json app - tar czf app.tar.gz app/ - - publish: + + publish-frontend: image: gitea.elara.ws/elara6331/crane secrets: [ registry_password ] commands: @@ -22,6 +22,19 @@ steps: - crane append -b gitea.elara.ws/elara6331/node -f app.tar.gz -t gitea.elara.ws/elara6331/lure-web:riscv64 --platform=linux/riscv64 - crane index append -m gitea.elara.ws/elara6331/lure-web:amd64 -m gitea.elara.ws/elara6331/lure-web:arm64 -m gitea.elara.ws/elara6331/lure-web:riscv64 -t gitea.elara.ws/elara6331/lure-web:latest + build-backend: + image: gitea.elara.ws/elara6331/builder + secrets: [ registry_password ] + directory: lure-backend + commands: + - registry-login + - ko build -B --platform=linux/amd64,linux/arm64,linux/riscv64 --sbom=none + environment: + - REGISTRY=gitea.elara.ws + - REGISTRY_USERNAME=Elara6331 + - KO_DOCKER_REPO=gitea.elara.ws/elara6331 + - KO_DEFAULTBASEIMAGE=gitea.elara.ws/elara6331/static + deploy: image: loq9/drone-nomad secrets: [lure_api_github_secret] diff --git a/lure-backend/README.md b/lure-backend/README.md new file mode 100644 index 0000000..2b3fa16 --- /dev/null +++ b/lure-backend/README.md @@ -0,0 +1,3 @@ +# lure-api-server + +`lure-api-server` is the backend API server for lure-web, the web interface for LURE. \ No newline at end of file diff --git a/lure-backend/api.go b/lure-backend/api.go new file mode 100644 index 0000000..b2e86dc --- /dev/null +++ b/lure-backend/api.go @@ -0,0 +1,166 @@ +/* + * LURE - Linux User REpository + * Copyright (C) 2023 Elara Musayelyan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package main + +import ( + "context" + "io" + + "github.com/twitchtv/twirp" + "go.elara.ws/lure-web/lure-backend/internal/api" + "go.elara.ws/logger/log" + "go.elara.ws/lure/pkg/search" + "golang.org/x/text/language" +) + +type lureWebAPI struct{} + +func (l lureWebAPI) Search(ctx context.Context, req *api.SearchRequest) (*api.SearchResponse, error) { + pkgs, err := search.Search(search.Options{ + Filter: search.Filter(req.FilterType), + SortBy: search.SortBy(req.SortBy), + Limit: req.Limit, + Query: req.Query, + }) + return &api.SearchResponse{Packages: searchPkgsToAPI(ctx, pkgs)}, err +} + +func (l lureWebAPI) GetPkg(ctx context.Context, req *api.GetPackageRequest) (*api.Package, error) { + pkg, err := search.GetPkg(req.Repository, req.Name) + if err != nil { + return nil, err + } + return searchPkgToAPI(ctx, pkg), nil +} + +func (l lureWebAPI) GetBuildScript(ctx context.Context, req *api.GetBuildScriptRequest) (*api.GetBuildScriptResponse, error) { + r, err := search.GetScript(req.Repository, req.Name) + if err == search.ErrScriptNotFound { + return nil, twirp.NewError(twirp.NotFound, err.Error()) + } else if err == search.ErrInvalidArgument { + return nil, twirp.NewError(twirp.InvalidArgument, err.Error()) + } else if err != nil { + return nil, err + } + + data, err := io.ReadAll(r) + if err != nil { + return nil, err + } + + return &api.GetBuildScriptResponse{Script: string(data)}, nil +} + +func searchPkgsToAPI(ctx context.Context, pkgs []search.Package) []*api.Package { + out := make([]*api.Package, len(pkgs)) + for i, pkg := range pkgs { + out[i] = searchPkgToAPI(ctx, pkg) + } + return out +} + +func searchPkgToAPI(ctx context.Context, pkg search.Package) *api.Package { + return &api.Package{ + Name: pkg.Name, + Repository: pkg.Repository, + Version: pkg.Version, + Release: int64(pkg.Release), + Epoch: ptr(int64(pkg.Epoch)), + Description: performTranslation(ctx, pkg.Description), + Homepage: performTranslation(ctx, pkg.Homepage), + Maintainer: performTranslation(ctx, pkg.Maintainer), + Architectures: pkg.Architectures, + Licenses: pkg.Licenses, + Provides: pkg.Provides, + Conflicts: pkg.Conflicts, + Replaces: pkg.Replaces, + Depends: mapToAPI(pkg.Depends), + BuildDepends: mapToAPI(pkg.BuildDepends), + } +} + +func ptr[T any](v T) *T { + return &v +} + +func mapToAPI(m map[string][]string) map[string]*api.StringList { + out := make(map[string]*api.StringList, len(m)) + for override, list := range m { + out[override] = &api.StringList{Entries: list} + } + return out +} + +func performTranslation(ctx context.Context, v map[string]string) *string { + alVal := ctx.Value(acceptLanguageKey{}) + langVal := ctx.Value(langParameterKey{}) + + if alVal == nil && langVal == nil { + val, ok := v[""] + if !ok { + return ptr("") + } + return &val + } + + al, _ := alVal.(string) + lang, _ := langVal.(string) + + tags, _, err := language.ParseAcceptLanguage(al) + if err != nil { + log.Warn("Error parsing Accept-Language header").Err(err).Send() + } + + var bases []string + if lang != "" { + langTag, err := language.Parse(lang) + if err != nil { + log.Warn("Error parsing lang parameter").Err(err).Send() + bases = getLangBases(tags) + } else { + bases = getLangBases(append([]language.Tag{langTag}, tags...)) + } + } else { + bases = getLangBases(tags) + } + + if len(bases) == 1 { + bases = []string{"en", ""} + } + + for _, name := range bases { + val, ok := v[name] + if !ok { + continue + } + return &val + } + + return ptr("") +} + +func getLangBases(langs []language.Tag) []string { + out := make([]string, len(langs)+1) + for i, lang := range langs { + base, _ := lang.Base() + out[i] = base.String() + } + out[len(out)-1] = "" + return out +} diff --git a/lure-backend/badge-logo.txt b/lure-backend/badge-logo.txt new file mode 100644 index 0000000..37767cc --- /dev/null +++ b/lure-backend/badge-logo.txt @@ -0,0 +1 @@ +data:image/png;base64,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 \ No newline at end of file diff --git a/lure-backend/badge.go b/lure-backend/badge.go new file mode 100644 index 0000000..b926cb7 --- /dev/null +++ b/lure-backend/badge.go @@ -0,0 +1,58 @@ +package main + +import ( + _ "embed" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/go-chi/chi/v5" + "go.elara.ws/lure/pkg/search" +) + +//go:embed badge-logo.txt +var logoData string + +var _ http.HandlerFunc + +func handleBadge() http.HandlerFunc { + return func(res http.ResponseWriter, req *http.Request) { + repo := chi.URLParam(req, "repo") + name := chi.URLParam(req, "pkg") + + pkg, err := search.GetPkg(repo, name) + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + + http.Redirect(res, req, genBadgeURL(pkg.Name, genVersion(pkg)), http.StatusFound) + } +} + +func genVersion(pkg search.Package) string { + sb := strings.Builder{} + if pkg.Epoch != 0 { + sb.WriteString(strconv.Itoa(int(pkg.Epoch))) + sb.WriteByte(':') + } + + sb.WriteString(pkg.Version) + + if pkg.Release != 0 { + sb.WriteByte('-') + sb.WriteString(strconv.Itoa(pkg.Release)) + } + return sb.String() +} + +func genBadgeURL(pkgName, pkgVersion string) string { + v := url.Values{} + v.Set("label", pkgName) + v.Set("message", pkgVersion) + v.Set("logo", logoData) + v.Set("color", "blue") + u := &url.URL{Scheme: "https", Host: "img.shields.io", Path: "/static/v1", RawQuery: v.Encode()} + return u.String() +} diff --git a/lure-backend/go.mod b/lure-backend/go.mod new file mode 100644 index 0000000..db5b59d --- /dev/null +++ b/lure-backend/go.mod @@ -0,0 +1,64 @@ +module go.elara.ws/lure-web/lure-backend + +go 1.21.1 + +require ( + github.com/go-chi/chi/v5 v5.0.10 + github.com/twitchtv/twirp v8.1.3+incompatible + go.elara.ws/logger v0.0.0-20230421022458-e80700db2090 + go.elara.ws/lure v0.0.10-0.20230922223953-02a6104fb042 + golang.org/x/text v0.13.0 + google.golang.org/protobuf v1.31.0 +) + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/acomagu/bufpipe v1.0.4 // indirect + github.com/cloudflare/circl v1.3.3 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/go-git/go-git/v5 v5.9.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/gookit/color v1.5.1 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jmoiron/sqlx v1.3.5 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/matryer/is v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/sergi/go-diff v1.2.0 // indirect + github.com/skeema/knownhosts v1.2.0 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect + go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 // indirect + golang.org/x/crypto v0.13.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/term v0.12.0 // indirect + golang.org/x/tools v0.13.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + lukechampine.com/uint128 v1.2.0 // indirect + modernc.org/cc/v3 v3.40.0 // indirect + modernc.org/ccgo/v3 v3.16.13 // indirect + modernc.org/libc v1.24.1 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.6.0 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/sqlite v1.25.0 // indirect + modernc.org/strutil v1.1.3 // indirect + modernc.org/token v1.0.1 // indirect + mvdan.cc/sh/v3 v3.7.0 // indirect +) diff --git a/lure-backend/go.sum b/lure-backend/go.sum new file mode 100644 index 0000000..7162411 --- /dev/null +++ b/lure-backend/go.sum @@ -0,0 +1,238 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= +github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= +github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= +github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= +github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= +github.com/go-git/go-git/v5 v5.9.0 h1:cD9SFA7sHVRdJ7AYck1ZaAa/yeuBvGPxwXDL8cxrObY= +github.com/go-git/go-git/v5 v5.9.0/go.mod h1:RKIqga24sWdMGZF+1Ekv9kylsDz6LzdTSI2s/OsZWE0= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ= +github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM= +github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU= +github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.elara.ws/logger v0.0.0-20230421022458-e80700db2090 h1:RVC8XvWo6Yw4HUshqx4TSzuBDScDghafU6QFRJ4xPZg= +go.elara.ws/logger v0.0.0-20230421022458-e80700db2090/go.mod h1:qng49owViqsW5Aey93lwBXONw20oGbJIoLVscB16mPM= +go.elara.ws/lure v0.0.10-0.20230922223953-02a6104fb042 h1:NNaaf58btpzlvuaJWgd43H9UcD7nNEX+ZNFVVNy8/WM= +go.elara.ws/lure v0.0.10-0.20230922223953-02a6104fb042/go.mod h1:xFbBPYZYEmcogIkN+L/GdG4G5/eFFK2h8lHig4YEp94= +go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 h1:Ep54XceQlKhcCHl9awG+wWP4kz4kIP3c3Lzw/Gc/zwY= +go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4/go.mod h1:/7PNW7nFnDR5W7UXZVc04gdVLR/wBNgkm33KgIz0OBk= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM= +modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o= +modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA= +modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU= +modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= +modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c= +modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= +modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= +modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= +mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= +mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= diff --git a/lure-backend/internal/api/gen.go b/lure-backend/internal/api/gen.go new file mode 100644 index 0000000..00575f5 --- /dev/null +++ b/lure-backend/internal/api/gen.go @@ -0,0 +1,22 @@ +/* + * LURE - Linux User REpository + * Copyright (C) 2023 Elara Musayelyan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package api + +//go:generate protoc --twirp_out=. lure.proto +//go:generate protoc --go_out=. lure.proto diff --git a/lure-backend/internal/api/lure.pb.go b/lure-backend/internal/api/lure.pb.go new file mode 100644 index 0000000..c2e43a9 --- /dev/null +++ b/lure-backend/internal/api/lure.pb.go @@ -0,0 +1,888 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc v4.24.2 +// source: lure.proto + +package api + +import ( + reflect "reflect" + sync "sync" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// SORT_BY represents possible things to sort packages by +type SORT_BY int32 + +const ( + SORT_BY_UNSORTED SORT_BY = 0 + SORT_BY_NAME SORT_BY = 1 + SORT_BY_REPOSITORY SORT_BY = 2 + SORT_BY_VERSION SORT_BY = 3 +) + +// Enum value maps for SORT_BY. +var ( + SORT_BY_name = map[int32]string{ + 0: "UNSORTED", + 1: "NAME", + 2: "REPOSITORY", + 3: "VERSION", + } + SORT_BY_value = map[string]int32{ + "UNSORTED": 0, + "NAME": 1, + "REPOSITORY": 2, + "VERSION": 3, + } +) + +func (x SORT_BY) Enum() *SORT_BY { + p := new(SORT_BY) + *p = x + return p +} + +func (x SORT_BY) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SORT_BY) Descriptor() protoreflect.EnumDescriptor { + return file_lure_proto_enumTypes[0].Descriptor() +} + +func (SORT_BY) Type() protoreflect.EnumType { + return &file_lure_proto_enumTypes[0] +} + +func (x SORT_BY) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SORT_BY.Descriptor instead. +func (SORT_BY) EnumDescriptor() ([]byte, []int) { + return file_lure_proto_rawDescGZIP(), []int{0} +} + +// FILTER_TYPE represents possible filters for packages +type FILTER_TYPE int32 + +const ( + FILTER_TYPE_NO_FILTER FILTER_TYPE = 0 + FILTER_TYPE_IN_REPOSITORY FILTER_TYPE = 1 + FILTER_TYPE_SUPPORTS_ARCH FILTER_TYPE = 2 +) + +// Enum value maps for FILTER_TYPE. +var ( + FILTER_TYPE_name = map[int32]string{ + 0: "NO_FILTER", + 1: "IN_REPOSITORY", + 2: "SUPPORTS_ARCH", + } + FILTER_TYPE_value = map[string]int32{ + "NO_FILTER": 0, + "IN_REPOSITORY": 1, + "SUPPORTS_ARCH": 2, + } +) + +func (x FILTER_TYPE) Enum() *FILTER_TYPE { + p := new(FILTER_TYPE) + *p = x + return p +} + +func (x FILTER_TYPE) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (FILTER_TYPE) Descriptor() protoreflect.EnumDescriptor { + return file_lure_proto_enumTypes[1].Descriptor() +} + +func (FILTER_TYPE) Type() protoreflect.EnumType { + return &file_lure_proto_enumTypes[1] +} + +func (x FILTER_TYPE) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use FILTER_TYPE.Descriptor instead. +func (FILTER_TYPE) EnumDescriptor() ([]byte, []int) { + return file_lure_proto_rawDescGZIP(), []int{1} +} + +// SearchRequest is a request to search for packages +type SearchRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` + Limit int64 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + SortBy SORT_BY `protobuf:"varint,3,opt,name=sort_by,json=sortBy,proto3,enum=lure.SORT_BY" json:"sort_by,omitempty"` + FilterType FILTER_TYPE `protobuf:"varint,4,opt,name=filter_type,json=filterType,proto3,enum=lure.FILTER_TYPE" json:"filter_type,omitempty"` + FilterValue *string `protobuf:"bytes,5,opt,name=filter_value,json=filterValue,proto3,oneof" json:"filter_value,omitempty"` +} + +func (x *SearchRequest) Reset() { + *x = SearchRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_lure_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SearchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchRequest) ProtoMessage() {} + +func (x *SearchRequest) ProtoReflect() protoreflect.Message { + mi := &file_lure_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SearchRequest.ProtoReflect.Descriptor instead. +func (*SearchRequest) Descriptor() ([]byte, []int) { + return file_lure_proto_rawDescGZIP(), []int{0} +} + +func (x *SearchRequest) GetQuery() string { + if x != nil { + return x.Query + } + return "" +} + +func (x *SearchRequest) GetLimit() int64 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *SearchRequest) GetSortBy() SORT_BY { + if x != nil { + return x.SortBy + } + return SORT_BY_UNSORTED +} + +func (x *SearchRequest) GetFilterType() FILTER_TYPE { + if x != nil { + return x.FilterType + } + return FILTER_TYPE_NO_FILTER +} + +func (x *SearchRequest) GetFilterValue() string { + if x != nil && x.FilterValue != nil { + return *x.FilterValue + } + return "" +} + +// StringList contains a list of strings +type StringList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Entries []string `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"` +} + +func (x *StringList) Reset() { + *x = StringList{} + if protoimpl.UnsafeEnabled { + mi := &file_lure_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StringList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StringList) ProtoMessage() {} + +func (x *StringList) ProtoReflect() protoreflect.Message { + mi := &file_lure_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StringList.ProtoReflect.Descriptor instead. +func (*StringList) Descriptor() ([]byte, []int) { + return file_lure_proto_rawDescGZIP(), []int{1} +} + +func (x *StringList) GetEntries() []string { + if x != nil { + return x.Entries + } + return nil +} + +// Package represents a LURE package +type Package struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Repository string `protobuf:"bytes,2,opt,name=repository,proto3" json:"repository,omitempty"` + Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` + Release int64 `protobuf:"varint,4,opt,name=release,proto3" json:"release,omitempty"` + Epoch *int64 `protobuf:"varint,5,opt,name=epoch,proto3,oneof" json:"epoch,omitempty"` + Description *string `protobuf:"bytes,6,opt,name=description,proto3,oneof" json:"description,omitempty"` + Homepage *string `protobuf:"bytes,7,opt,name=homepage,proto3,oneof" json:"homepage,omitempty"` + Maintainer *string `protobuf:"bytes,8,opt,name=maintainer,proto3,oneof" json:"maintainer,omitempty"` + Architectures []string `protobuf:"bytes,9,rep,name=architectures,proto3" json:"architectures,omitempty"` + Licenses []string `protobuf:"bytes,10,rep,name=licenses,proto3" json:"licenses,omitempty"` + Provides []string `protobuf:"bytes,11,rep,name=provides,proto3" json:"provides,omitempty"` + Conflicts []string `protobuf:"bytes,12,rep,name=conflicts,proto3" json:"conflicts,omitempty"` + Replaces []string `protobuf:"bytes,13,rep,name=replaces,proto3" json:"replaces,omitempty"` + Depends map[string]*StringList `protobuf:"bytes,14,rep,name=depends,proto3" json:"depends,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + BuildDepends map[string]*StringList `protobuf:"bytes,15,rep,name=build_depends,json=buildDepends,proto3" json:"build_depends,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *Package) Reset() { + *x = Package{} + if protoimpl.UnsafeEnabled { + mi := &file_lure_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Package) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Package) ProtoMessage() {} + +func (x *Package) ProtoReflect() protoreflect.Message { + mi := &file_lure_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Package.ProtoReflect.Descriptor instead. +func (*Package) Descriptor() ([]byte, []int) { + return file_lure_proto_rawDescGZIP(), []int{2} +} + +func (x *Package) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Package) GetRepository() string { + if x != nil { + return x.Repository + } + return "" +} + +func (x *Package) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *Package) GetRelease() int64 { + if x != nil { + return x.Release + } + return 0 +} + +func (x *Package) GetEpoch() int64 { + if x != nil && x.Epoch != nil { + return *x.Epoch + } + return 0 +} + +func (x *Package) GetDescription() string { + if x != nil && x.Description != nil { + return *x.Description + } + return "" +} + +func (x *Package) GetHomepage() string { + if x != nil && x.Homepage != nil { + return *x.Homepage + } + return "" +} + +func (x *Package) GetMaintainer() string { + if x != nil && x.Maintainer != nil { + return *x.Maintainer + } + return "" +} + +func (x *Package) GetArchitectures() []string { + if x != nil { + return x.Architectures + } + return nil +} + +func (x *Package) GetLicenses() []string { + if x != nil { + return x.Licenses + } + return nil +} + +func (x *Package) GetProvides() []string { + if x != nil { + return x.Provides + } + return nil +} + +func (x *Package) GetConflicts() []string { + if x != nil { + return x.Conflicts + } + return nil +} + +func (x *Package) GetReplaces() []string { + if x != nil { + return x.Replaces + } + return nil +} + +func (x *Package) GetDepends() map[string]*StringList { + if x != nil { + return x.Depends + } + return nil +} + +func (x *Package) GetBuildDepends() map[string]*StringList { + if x != nil { + return x.BuildDepends + } + return nil +} + +type GetPackageRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Repository string `protobuf:"bytes,2,opt,name=repository,proto3" json:"repository,omitempty"` +} + +func (x *GetPackageRequest) Reset() { + *x = GetPackageRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_lure_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetPackageRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPackageRequest) ProtoMessage() {} + +func (x *GetPackageRequest) ProtoReflect() protoreflect.Message { + mi := &file_lure_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPackageRequest.ProtoReflect.Descriptor instead. +func (*GetPackageRequest) Descriptor() ([]byte, []int) { + return file_lure_proto_rawDescGZIP(), []int{3} +} + +func (x *GetPackageRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetPackageRequest) GetRepository() string { + if x != nil { + return x.Repository + } + return "" +} + +// SearchResponse contains returned packages +type SearchResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Packages []*Package `protobuf:"bytes,1,rep,name=packages,proto3" json:"packages,omitempty"` +} + +func (x *SearchResponse) Reset() { + *x = SearchResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_lure_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SearchResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchResponse) ProtoMessage() {} + +func (x *SearchResponse) ProtoReflect() protoreflect.Message { + mi := &file_lure_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SearchResponse.ProtoReflect.Descriptor instead. +func (*SearchResponse) Descriptor() ([]byte, []int) { + return file_lure_proto_rawDescGZIP(), []int{4} +} + +func (x *SearchResponse) GetPackages() []*Package { + if x != nil { + return x.Packages + } + return nil +} + +type GetBuildScriptRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Repository string `protobuf:"bytes,2,opt,name=repository,proto3" json:"repository,omitempty"` +} + +func (x *GetBuildScriptRequest) Reset() { + *x = GetBuildScriptRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_lure_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetBuildScriptRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBuildScriptRequest) ProtoMessage() {} + +func (x *GetBuildScriptRequest) ProtoReflect() protoreflect.Message { + mi := &file_lure_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBuildScriptRequest.ProtoReflect.Descriptor instead. +func (*GetBuildScriptRequest) Descriptor() ([]byte, []int) { + return file_lure_proto_rawDescGZIP(), []int{5} +} + +func (x *GetBuildScriptRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetBuildScriptRequest) GetRepository() string { + if x != nil { + return x.Repository + } + return "" +} + +type GetBuildScriptResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Script string `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"` +} + +func (x *GetBuildScriptResponse) Reset() { + *x = GetBuildScriptResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_lure_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetBuildScriptResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBuildScriptResponse) ProtoMessage() {} + +func (x *GetBuildScriptResponse) ProtoReflect() protoreflect.Message { + mi := &file_lure_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBuildScriptResponse.ProtoReflect.Descriptor instead. +func (*GetBuildScriptResponse) Descriptor() ([]byte, []int) { + return file_lure_proto_rawDescGZIP(), []int{6} +} + +func (x *GetBuildScriptResponse) GetScript() string { + if x != nil { + return x.Script + } + return "" +} + +var File_lure_proto protoreflect.FileDescriptor + +var file_lure_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x6c, 0x75, + 0x72, 0x65, 0x22, 0xd0, 0x01, 0x0a, 0x0d, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x12, 0x26, 0x0a, 0x07, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x62, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x0d, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59, + 0x52, 0x06, 0x73, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x12, 0x32, 0x0a, 0x0b, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, + 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x52, 0x0a, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x26, 0x0a, 0x0c, + 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x88, 0x01, 0x01, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0xe4, 0x05, + 0x0a, 0x07, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, + 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x18, 0x0a, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61, + 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, + 0x65, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x48, 0x00, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x01, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x68, 0x6f, 0x6d, 0x65, 0x70, 0x61, 0x67, 0x65, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x08, 0x68, 0x6f, 0x6d, 0x65, 0x70, 0x61, 0x67, + 0x65, 0x88, 0x01, 0x01, 0x12, 0x23, 0x0a, 0x0a, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x0a, 0x6d, 0x61, 0x69, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x0d, 0x61, 0x72, 0x63, + 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x0d, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, + 0x1a, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x6c, + 0x69, 0x63, 0x74, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x66, + 0x6c, 0x69, 0x63, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, + 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, + 0x73, 0x12, 0x34, 0x0a, 0x07, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x0e, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, + 0x65, 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, + 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x12, 0x44, 0x0a, 0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x5f, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, + 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x2e, 0x42, 0x75, + 0x69, 0x6c, 0x64, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x1a, 0x4c, 0x0a, + 0x0c, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x26, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x51, 0x0a, 0x11, 0x42, + 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x26, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x08, + 0x0a, 0x06, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x68, 0x6f, 0x6d, + 0x65, 0x70, 0x61, 0x67, 0x65, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x22, 0x47, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x61, + 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, + 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x3b, 0x0a, + 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x29, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, + 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x22, 0x4b, 0x0a, 0x15, 0x47, 0x65, + 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x30, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x42, 0x75, + 0x69, 0x6c, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2a, 0x3e, 0x0a, 0x07, 0x53, 0x4f, 0x52, + 0x54, 0x5f, 0x42, 0x59, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x4e, 0x53, 0x4f, 0x52, 0x54, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x41, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, + 0x52, 0x45, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x4f, 0x52, 0x59, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, + 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x2a, 0x42, 0x0a, 0x0b, 0x46, 0x49, 0x4c, + 0x54, 0x45, 0x52, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f, 0x46, + 0x49, 0x4c, 0x54, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x5f, 0x52, 0x45, + 0x50, 0x4f, 0x53, 0x49, 0x54, 0x4f, 0x52, 0x59, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x55, + 0x50, 0x50, 0x4f, 0x52, 0x54, 0x53, 0x5f, 0x41, 0x52, 0x43, 0x48, 0x10, 0x02, 0x32, 0xb9, 0x01, + 0x0a, 0x03, 0x41, 0x50, 0x49, 0x12, 0x33, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, + 0x13, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x06, 0x47, 0x65, + 0x74, 0x50, 0x6b, 0x67, 0x12, 0x17, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x50, + 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, + 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x4b, 0x0a, 0x0e, + 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x1b, + 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x75, + 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2e, 0x2f, + 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_lure_proto_rawDescOnce sync.Once + file_lure_proto_rawDescData = file_lure_proto_rawDesc +) + +func file_lure_proto_rawDescGZIP() []byte { + file_lure_proto_rawDescOnce.Do(func() { + file_lure_proto_rawDescData = protoimpl.X.CompressGZIP(file_lure_proto_rawDescData) + }) + return file_lure_proto_rawDescData +} + +var ( + file_lure_proto_enumTypes = make([]protoimpl.EnumInfo, 2) + file_lure_proto_msgTypes = make([]protoimpl.MessageInfo, 9) + file_lure_proto_goTypes = []interface{}{ + (SORT_BY)(0), // 0: lure.SORT_BY + (FILTER_TYPE)(0), // 1: lure.FILTER_TYPE + (*SearchRequest)(nil), // 2: lure.SearchRequest + (*StringList)(nil), // 3: lure.StringList + (*Package)(nil), // 4: lure.Package + (*GetPackageRequest)(nil), // 5: lure.GetPackageRequest + (*SearchResponse)(nil), // 6: lure.SearchResponse + (*GetBuildScriptRequest)(nil), // 7: lure.GetBuildScriptRequest + (*GetBuildScriptResponse)(nil), // 8: lure.GetBuildScriptResponse + nil, // 9: lure.Package.DependsEntry + nil, // 10: lure.Package.BuildDependsEntry + } +) +var file_lure_proto_depIdxs = []int32{ + 0, // 0: lure.SearchRequest.sort_by:type_name -> lure.SORT_BY + 1, // 1: lure.SearchRequest.filter_type:type_name -> lure.FILTER_TYPE + 9, // 2: lure.Package.depends:type_name -> lure.Package.DependsEntry + 10, // 3: lure.Package.build_depends:type_name -> lure.Package.BuildDependsEntry + 4, // 4: lure.SearchResponse.packages:type_name -> lure.Package + 3, // 5: lure.Package.DependsEntry.value:type_name -> lure.StringList + 3, // 6: lure.Package.BuildDependsEntry.value:type_name -> lure.StringList + 2, // 7: lure.API.Search:input_type -> lure.SearchRequest + 5, // 8: lure.API.GetPkg:input_type -> lure.GetPackageRequest + 7, // 9: lure.API.GetBuildScript:input_type -> lure.GetBuildScriptRequest + 6, // 10: lure.API.Search:output_type -> lure.SearchResponse + 4, // 11: lure.API.GetPkg:output_type -> lure.Package + 8, // 12: lure.API.GetBuildScript:output_type -> lure.GetBuildScriptResponse + 10, // [10:13] is the sub-list for method output_type + 7, // [7:10] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name +} + +func init() { file_lure_proto_init() } +func file_lure_proto_init() { + if File_lure_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_lure_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SearchRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lure_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StringList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lure_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Package); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lure_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetPackageRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lure_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SearchResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lure_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBuildScriptRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lure_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBuildScriptResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_lure_proto_msgTypes[0].OneofWrappers = []interface{}{} + file_lure_proto_msgTypes[2].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_lure_proto_rawDesc, + NumEnums: 2, + NumMessages: 9, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_lure_proto_goTypes, + DependencyIndexes: file_lure_proto_depIdxs, + EnumInfos: file_lure_proto_enumTypes, + MessageInfos: file_lure_proto_msgTypes, + }.Build() + File_lure_proto = out.File + file_lure_proto_rawDesc = nil + file_lure_proto_goTypes = nil + file_lure_proto_depIdxs = nil +} diff --git a/lure-backend/internal/api/lure.proto b/lure-backend/internal/api/lure.proto new file mode 100644 index 0000000..06c2105 --- /dev/null +++ b/lure-backend/internal/api/lure.proto @@ -0,0 +1,82 @@ +syntax = "proto3"; +package lure; + +// Slight hack to provide protoc with a package name +option go_package = "../api"; + +// SORT_BY represents possible things to sort packages by +enum SORT_BY { + UNSORTED = 0; + NAME = 1; + REPOSITORY = 2; + VERSION = 3; +} + +// FILTER_TYPE represents possible filters for packages +enum FILTER_TYPE { + NO_FILTER = 0; + IN_REPOSITORY = 1; + SUPPORTS_ARCH = 2; +} + +// SearchRequest is a request to search for packages +message SearchRequest { + string query = 1; + int64 limit = 2; + SORT_BY sort_by = 3; + FILTER_TYPE filter_type = 4; + optional string filter_value = 5; +} + +// StringList contains a list of strings +message StringList { + repeated string entries = 1; +} + +// Package represents a LURE package +message Package { + string name = 1; + string repository = 2; + string version = 3; + int64 release = 4; + optional int64 epoch = 5; + optional string description = 6; + optional string homepage = 7; + optional string maintainer = 8; + repeated string architectures = 9; + repeated string licenses = 10; + repeated string provides = 11; + repeated string conflicts = 12; + repeated string replaces = 13; + map depends = 14; + map build_depends = 15; +} + +message GetPackageRequest { + string name = 1; + string repository = 2; +} + +// SearchResponse contains returned packages +message SearchResponse { + repeated Package packages = 1; +} + +message GetBuildScriptRequest { + string name = 1; + string repository = 2; +} + +message GetBuildScriptResponse { + string script = 1; +} + +// Web is the LURE Web service +service API { + // Search searches through LURE packages in the database + rpc Search(SearchRequest) returns (SearchResponse); + // GetPkg gets a single LURE package from the database + rpc GetPkg(GetPackageRequest) returns (Package); + // GetBuildScript returns the build script for the given package + rpc GetBuildScript(GetBuildScriptRequest) returns (GetBuildScriptResponse); +} \ No newline at end of file diff --git a/lure-backend/internal/api/lure.twirp.go b/lure-backend/internal/api/lure.twirp.go new file mode 100644 index 0000000..c2e1927 --- /dev/null +++ b/lure-backend/internal/api/lure.twirp.go @@ -0,0 +1,1709 @@ +// Code generated by protoc-gen-twirp v8.1.3, DO NOT EDIT. +// source: lure.proto + +package api + +import ( + context "context" + fmt "fmt" + http "net/http" + io "io" + json "encoding/json" + strconv "strconv" + strings "strings" +) + +import ( + protojson "google.golang.org/protobuf/encoding/protojson" + proto "google.golang.org/protobuf/proto" + twirp "github.com/twitchtv/twirp" + ctxsetters "github.com/twitchtv/twirp/ctxsetters" +) + +import ( + bytes "bytes" + errors "errors" + path "path" + url "net/url" +) + +// Version compatibility assertion. +// If the constant is not defined in the package, that likely means +// the package needs to be updated to work with this generated code. +// See https://twitchtv.github.io/twirp/docs/version_matrix.html +const _ = twirp.TwirpPackageMinVersion_8_1_0 + +// ============= +// API Interface +// ============= + +// Web is the LURE Web service +type API interface { + // Search searches through LURE packages in the database + Search(context.Context, *SearchRequest) (*SearchResponse, error) + + // GetPkg gets a single LURE package from the database + GetPkg(context.Context, *GetPackageRequest) (*Package, error) + + // GetBuildScript returns the build script for the given package + GetBuildScript(context.Context, *GetBuildScriptRequest) (*GetBuildScriptResponse, error) +} + +// =================== +// API Protobuf Client +// =================== + +type aPIProtobufClient struct { + client HTTPClient + urls [3]string + interceptor twirp.Interceptor + opts twirp.ClientOptions +} + +// NewAPIProtobufClient creates a Protobuf client that implements the API interface. +// It communicates using Protobuf and can be configured with a custom HTTPClient. +func NewAPIProtobufClient(baseURL string, client HTTPClient, opts ...twirp.ClientOption) API { + if c, ok := client.(*http.Client); ok { + client = withoutRedirects(c) + } + + clientOpts := twirp.ClientOptions{} + for _, o := range opts { + o(&clientOpts) + } + + // Using ReadOpt allows backwards and forwards compatibility with new options in the future + literalURLs := false + _ = clientOpts.ReadOpt("literalURLs", &literalURLs) + var pathPrefix string + if ok := clientOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { + pathPrefix = "/twirp" // default prefix + } + + // Build method URLs: []/./ + serviceURL := sanitizeBaseURL(baseURL) + serviceURL += baseServicePath(pathPrefix, "lure", "API") + urls := [3]string{ + serviceURL + "Search", + serviceURL + "GetPkg", + serviceURL + "GetBuildScript", + } + + return &aPIProtobufClient{ + client: client, + urls: urls, + interceptor: twirp.ChainInterceptors(clientOpts.Interceptors...), + opts: clientOpts, + } +} + +func (c *aPIProtobufClient) Search(ctx context.Context, in *SearchRequest) (*SearchResponse, error) { + ctx = ctxsetters.WithPackageName(ctx, "lure") + ctx = ctxsetters.WithServiceName(ctx, "API") + ctx = ctxsetters.WithMethodName(ctx, "Search") + caller := c.callSearch + if c.interceptor != nil { + caller = func(ctx context.Context, req *SearchRequest) (*SearchResponse, error) { + resp, err := c.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*SearchRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*SearchRequest) when calling interceptor") + } + return c.callSearch(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*SearchResponse) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*SearchResponse) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + return caller(ctx, in) +} + +func (c *aPIProtobufClient) callSearch(ctx context.Context, in *SearchRequest) (*SearchResponse, error) { + out := new(SearchResponse) + ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) + if err != nil { + twerr, ok := err.(twirp.Error) + if !ok { + twerr = twirp.InternalErrorWith(err) + } + callClientError(ctx, c.opts.Hooks, twerr) + return nil, err + } + + callClientResponseReceived(ctx, c.opts.Hooks) + + return out, nil +} + +func (c *aPIProtobufClient) GetPkg(ctx context.Context, in *GetPackageRequest) (*Package, error) { + ctx = ctxsetters.WithPackageName(ctx, "lure") + ctx = ctxsetters.WithServiceName(ctx, "API") + ctx = ctxsetters.WithMethodName(ctx, "GetPkg") + caller := c.callGetPkg + if c.interceptor != nil { + caller = func(ctx context.Context, req *GetPackageRequest) (*Package, error) { + resp, err := c.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*GetPackageRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*GetPackageRequest) when calling interceptor") + } + return c.callGetPkg(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*Package) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*Package) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + return caller(ctx, in) +} + +func (c *aPIProtobufClient) callGetPkg(ctx context.Context, in *GetPackageRequest) (*Package, error) { + out := new(Package) + ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[1], in, out) + if err != nil { + twerr, ok := err.(twirp.Error) + if !ok { + twerr = twirp.InternalErrorWith(err) + } + callClientError(ctx, c.opts.Hooks, twerr) + return nil, err + } + + callClientResponseReceived(ctx, c.opts.Hooks) + + return out, nil +} + +func (c *aPIProtobufClient) GetBuildScript(ctx context.Context, in *GetBuildScriptRequest) (*GetBuildScriptResponse, error) { + ctx = ctxsetters.WithPackageName(ctx, "lure") + ctx = ctxsetters.WithServiceName(ctx, "API") + ctx = ctxsetters.WithMethodName(ctx, "GetBuildScript") + caller := c.callGetBuildScript + if c.interceptor != nil { + caller = func(ctx context.Context, req *GetBuildScriptRequest) (*GetBuildScriptResponse, error) { + resp, err := c.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*GetBuildScriptRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*GetBuildScriptRequest) when calling interceptor") + } + return c.callGetBuildScript(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*GetBuildScriptResponse) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*GetBuildScriptResponse) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + return caller(ctx, in) +} + +func (c *aPIProtobufClient) callGetBuildScript(ctx context.Context, in *GetBuildScriptRequest) (*GetBuildScriptResponse, error) { + out := new(GetBuildScriptResponse) + ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[2], in, out) + if err != nil { + twerr, ok := err.(twirp.Error) + if !ok { + twerr = twirp.InternalErrorWith(err) + } + callClientError(ctx, c.opts.Hooks, twerr) + return nil, err + } + + callClientResponseReceived(ctx, c.opts.Hooks) + + return out, nil +} + +// =============== +// API JSON Client +// =============== + +type aPIJSONClient struct { + client HTTPClient + urls [3]string + interceptor twirp.Interceptor + opts twirp.ClientOptions +} + +// NewAPIJSONClient creates a JSON client that implements the API interface. +// It communicates using JSON and can be configured with a custom HTTPClient. +func NewAPIJSONClient(baseURL string, client HTTPClient, opts ...twirp.ClientOption) API { + if c, ok := client.(*http.Client); ok { + client = withoutRedirects(c) + } + + clientOpts := twirp.ClientOptions{} + for _, o := range opts { + o(&clientOpts) + } + + // Using ReadOpt allows backwards and forwards compatibility with new options in the future + literalURLs := false + _ = clientOpts.ReadOpt("literalURLs", &literalURLs) + var pathPrefix string + if ok := clientOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { + pathPrefix = "/twirp" // default prefix + } + + // Build method URLs: []/./ + serviceURL := sanitizeBaseURL(baseURL) + serviceURL += baseServicePath(pathPrefix, "lure", "API") + urls := [3]string{ + serviceURL + "Search", + serviceURL + "GetPkg", + serviceURL + "GetBuildScript", + } + + return &aPIJSONClient{ + client: client, + urls: urls, + interceptor: twirp.ChainInterceptors(clientOpts.Interceptors...), + opts: clientOpts, + } +} + +func (c *aPIJSONClient) Search(ctx context.Context, in *SearchRequest) (*SearchResponse, error) { + ctx = ctxsetters.WithPackageName(ctx, "lure") + ctx = ctxsetters.WithServiceName(ctx, "API") + ctx = ctxsetters.WithMethodName(ctx, "Search") + caller := c.callSearch + if c.interceptor != nil { + caller = func(ctx context.Context, req *SearchRequest) (*SearchResponse, error) { + resp, err := c.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*SearchRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*SearchRequest) when calling interceptor") + } + return c.callSearch(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*SearchResponse) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*SearchResponse) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + return caller(ctx, in) +} + +func (c *aPIJSONClient) callSearch(ctx context.Context, in *SearchRequest) (*SearchResponse, error) { + out := new(SearchResponse) + ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) + if err != nil { + twerr, ok := err.(twirp.Error) + if !ok { + twerr = twirp.InternalErrorWith(err) + } + callClientError(ctx, c.opts.Hooks, twerr) + return nil, err + } + + callClientResponseReceived(ctx, c.opts.Hooks) + + return out, nil +} + +func (c *aPIJSONClient) GetPkg(ctx context.Context, in *GetPackageRequest) (*Package, error) { + ctx = ctxsetters.WithPackageName(ctx, "lure") + ctx = ctxsetters.WithServiceName(ctx, "API") + ctx = ctxsetters.WithMethodName(ctx, "GetPkg") + caller := c.callGetPkg + if c.interceptor != nil { + caller = func(ctx context.Context, req *GetPackageRequest) (*Package, error) { + resp, err := c.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*GetPackageRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*GetPackageRequest) when calling interceptor") + } + return c.callGetPkg(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*Package) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*Package) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + return caller(ctx, in) +} + +func (c *aPIJSONClient) callGetPkg(ctx context.Context, in *GetPackageRequest) (*Package, error) { + out := new(Package) + ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[1], in, out) + if err != nil { + twerr, ok := err.(twirp.Error) + if !ok { + twerr = twirp.InternalErrorWith(err) + } + callClientError(ctx, c.opts.Hooks, twerr) + return nil, err + } + + callClientResponseReceived(ctx, c.opts.Hooks) + + return out, nil +} + +func (c *aPIJSONClient) GetBuildScript(ctx context.Context, in *GetBuildScriptRequest) (*GetBuildScriptResponse, error) { + ctx = ctxsetters.WithPackageName(ctx, "lure") + ctx = ctxsetters.WithServiceName(ctx, "API") + ctx = ctxsetters.WithMethodName(ctx, "GetBuildScript") + caller := c.callGetBuildScript + if c.interceptor != nil { + caller = func(ctx context.Context, req *GetBuildScriptRequest) (*GetBuildScriptResponse, error) { + resp, err := c.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*GetBuildScriptRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*GetBuildScriptRequest) when calling interceptor") + } + return c.callGetBuildScript(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*GetBuildScriptResponse) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*GetBuildScriptResponse) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + return caller(ctx, in) +} + +func (c *aPIJSONClient) callGetBuildScript(ctx context.Context, in *GetBuildScriptRequest) (*GetBuildScriptResponse, error) { + out := new(GetBuildScriptResponse) + ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[2], in, out) + if err != nil { + twerr, ok := err.(twirp.Error) + if !ok { + twerr = twirp.InternalErrorWith(err) + } + callClientError(ctx, c.opts.Hooks, twerr) + return nil, err + } + + callClientResponseReceived(ctx, c.opts.Hooks) + + return out, nil +} + +// ================== +// API Server Handler +// ================== + +type aPIServer struct { + API + interceptor twirp.Interceptor + hooks *twirp.ServerHooks + pathPrefix string // prefix for routing + jsonSkipDefaults bool // do not include unpopulated fields (default values) in the response + jsonCamelCase bool // JSON fields are serialized as lowerCamelCase rather than keeping the original proto names +} + +// NewAPIServer builds a TwirpServer that can be used as an http.Handler to handle +// HTTP requests that are routed to the right method in the provided svc implementation. +// The opts are twirp.ServerOption modifiers, for example twirp.WithServerHooks(hooks). +func NewAPIServer(svc API, opts ...interface{}) TwirpServer { + serverOpts := newServerOpts(opts) + + // Using ReadOpt allows backwards and forwards compatibility with new options in the future + jsonSkipDefaults := false + _ = serverOpts.ReadOpt("jsonSkipDefaults", &jsonSkipDefaults) + jsonCamelCase := false + _ = serverOpts.ReadOpt("jsonCamelCase", &jsonCamelCase) + var pathPrefix string + if ok := serverOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { + pathPrefix = "/twirp" // default prefix + } + + return &aPIServer{ + API: svc, + hooks: serverOpts.Hooks, + interceptor: twirp.ChainInterceptors(serverOpts.Interceptors...), + pathPrefix: pathPrefix, + jsonSkipDefaults: jsonSkipDefaults, + jsonCamelCase: jsonCamelCase, + } +} + +// writeError writes an HTTP response with a valid Twirp error format, and triggers hooks. +// If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err) +func (s *aPIServer) writeError(ctx context.Context, resp http.ResponseWriter, err error) { + writeError(ctx, resp, err, s.hooks) +} + +// handleRequestBodyError is used to handle error when the twirp server cannot read request +func (s *aPIServer) handleRequestBodyError(ctx context.Context, resp http.ResponseWriter, msg string, err error) { + if context.Canceled == ctx.Err() { + s.writeError(ctx, resp, twirp.NewError(twirp.Canceled, "failed to read request: context canceled")) + return + } + if context.DeadlineExceeded == ctx.Err() { + s.writeError(ctx, resp, twirp.NewError(twirp.DeadlineExceeded, "failed to read request: deadline exceeded")) + return + } + s.writeError(ctx, resp, twirp.WrapError(malformedRequestError(msg), err)) +} + +// APIPathPrefix is a convenience constant that may identify URL paths. +// Should be used with caution, it only matches routes generated by Twirp Go clients, +// with the default "/twirp" prefix and default CamelCase service and method names. +// More info: https://twitchtv.github.io/twirp/docs/routing.html +const APIPathPrefix = "/twirp/lure.API/" + +func (s *aPIServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) { + ctx := req.Context() + ctx = ctxsetters.WithPackageName(ctx, "lure") + ctx = ctxsetters.WithServiceName(ctx, "API") + ctx = ctxsetters.WithResponseWriter(ctx, resp) + + var err error + ctx, err = callRequestReceived(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + if req.Method != "POST" { + msg := fmt.Sprintf("unsupported method %q (only POST is allowed)", req.Method) + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) + return + } + + // Verify path format: []/./ + prefix, pkgService, method := parseTwirpPath(req.URL.Path) + if pkgService != "lure.API" { + msg := fmt.Sprintf("no handler for path %q", req.URL.Path) + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) + return + } + if prefix != s.pathPrefix { + msg := fmt.Sprintf("invalid path prefix %q, expected %q, on path %q", prefix, s.pathPrefix, req.URL.Path) + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) + return + } + + switch method { + case "Search": + s.serveSearch(ctx, resp, req) + return + case "GetPkg": + s.serveGetPkg(ctx, resp, req) + return + case "GetBuildScript": + s.serveGetBuildScript(ctx, resp, req) + return + default: + msg := fmt.Sprintf("no handler for path %q", req.URL.Path) + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) + return + } +} + +func (s *aPIServer) serveSearch(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + header := req.Header.Get("Content-Type") + i := strings.Index(header, ";") + if i == -1 { + i = len(header) + } + switch strings.TrimSpace(strings.ToLower(header[:i])) { + case "application/json": + s.serveSearchJSON(ctx, resp, req) + case "application/protobuf": + s.serveSearchProtobuf(ctx, resp, req) + default: + msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) + twerr := badRouteError(msg, req.Method, req.URL.Path) + s.writeError(ctx, resp, twerr) + } +} + +func (s *aPIServer) serveSearchJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + var err error + ctx = ctxsetters.WithMethodName(ctx, "Search") + ctx, err = callRequestRouted(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + d := json.NewDecoder(req.Body) + rawReqBody := json.RawMessage{} + if err := d.Decode(&rawReqBody); err != nil { + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) + return + } + reqContent := new(SearchRequest) + unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} + if err = unmarshaler.Unmarshal(rawReqBody, reqContent); err != nil { + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) + return + } + + handler := s.API.Search + if s.interceptor != nil { + handler = func(ctx context.Context, req *SearchRequest) (*SearchResponse, error) { + resp, err := s.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*SearchRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*SearchRequest) when calling interceptor") + } + return s.API.Search(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*SearchResponse) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*SearchResponse) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + + // Call service method + var respContent *SearchResponse + func() { + defer ensurePanicResponses(ctx, resp, s.hooks) + respContent, err = handler(ctx, reqContent) + }() + + if err != nil { + s.writeError(ctx, resp, err) + return + } + if respContent == nil { + s.writeError(ctx, resp, twirp.InternalError("received a nil *SearchResponse and nil error while calling Search. nil responses are not supported")) + return + } + + ctx = callResponsePrepared(ctx, s.hooks) + + marshaler := &protojson.MarshalOptions{UseProtoNames: !s.jsonCamelCase, EmitUnpopulated: !s.jsonSkipDefaults} + respBytes, err := marshaler.Marshal(respContent) + if err != nil { + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) + return + } + + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) + resp.Header().Set("Content-Type", "application/json") + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) + resp.WriteHeader(http.StatusOK) + + if n, err := resp.Write(respBytes); err != nil { + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) + twerr := twirp.NewError(twirp.Unknown, msg) + ctx = callError(ctx, s.hooks, twerr) + } + callResponseSent(ctx, s.hooks) +} + +func (s *aPIServer) serveSearchProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + var err error + ctx = ctxsetters.WithMethodName(ctx, "Search") + ctx, err = callRequestRouted(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + buf, err := io.ReadAll(req.Body) + if err != nil { + s.handleRequestBodyError(ctx, resp, "failed to read request body", err) + return + } + reqContent := new(SearchRequest) + if err = proto.Unmarshal(buf, reqContent); err != nil { + s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) + return + } + + handler := s.API.Search + if s.interceptor != nil { + handler = func(ctx context.Context, req *SearchRequest) (*SearchResponse, error) { + resp, err := s.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*SearchRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*SearchRequest) when calling interceptor") + } + return s.API.Search(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*SearchResponse) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*SearchResponse) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + + // Call service method + var respContent *SearchResponse + func() { + defer ensurePanicResponses(ctx, resp, s.hooks) + respContent, err = handler(ctx, reqContent) + }() + + if err != nil { + s.writeError(ctx, resp, err) + return + } + if respContent == nil { + s.writeError(ctx, resp, twirp.InternalError("received a nil *SearchResponse and nil error while calling Search. nil responses are not supported")) + return + } + + ctx = callResponsePrepared(ctx, s.hooks) + + respBytes, err := proto.Marshal(respContent) + if err != nil { + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) + return + } + + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) + resp.Header().Set("Content-Type", "application/protobuf") + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) + resp.WriteHeader(http.StatusOK) + if n, err := resp.Write(respBytes); err != nil { + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) + twerr := twirp.NewError(twirp.Unknown, msg) + ctx = callError(ctx, s.hooks, twerr) + } + callResponseSent(ctx, s.hooks) +} + +func (s *aPIServer) serveGetPkg(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + header := req.Header.Get("Content-Type") + i := strings.Index(header, ";") + if i == -1 { + i = len(header) + } + switch strings.TrimSpace(strings.ToLower(header[:i])) { + case "application/json": + s.serveGetPkgJSON(ctx, resp, req) + case "application/protobuf": + s.serveGetPkgProtobuf(ctx, resp, req) + default: + msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) + twerr := badRouteError(msg, req.Method, req.URL.Path) + s.writeError(ctx, resp, twerr) + } +} + +func (s *aPIServer) serveGetPkgJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + var err error + ctx = ctxsetters.WithMethodName(ctx, "GetPkg") + ctx, err = callRequestRouted(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + d := json.NewDecoder(req.Body) + rawReqBody := json.RawMessage{} + if err := d.Decode(&rawReqBody); err != nil { + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) + return + } + reqContent := new(GetPackageRequest) + unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} + if err = unmarshaler.Unmarshal(rawReqBody, reqContent); err != nil { + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) + return + } + + handler := s.API.GetPkg + if s.interceptor != nil { + handler = func(ctx context.Context, req *GetPackageRequest) (*Package, error) { + resp, err := s.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*GetPackageRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*GetPackageRequest) when calling interceptor") + } + return s.API.GetPkg(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*Package) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*Package) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + + // Call service method + var respContent *Package + func() { + defer ensurePanicResponses(ctx, resp, s.hooks) + respContent, err = handler(ctx, reqContent) + }() + + if err != nil { + s.writeError(ctx, resp, err) + return + } + if respContent == nil { + s.writeError(ctx, resp, twirp.InternalError("received a nil *Package and nil error while calling GetPkg. nil responses are not supported")) + return + } + + ctx = callResponsePrepared(ctx, s.hooks) + + marshaler := &protojson.MarshalOptions{UseProtoNames: !s.jsonCamelCase, EmitUnpopulated: !s.jsonSkipDefaults} + respBytes, err := marshaler.Marshal(respContent) + if err != nil { + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) + return + } + + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) + resp.Header().Set("Content-Type", "application/json") + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) + resp.WriteHeader(http.StatusOK) + + if n, err := resp.Write(respBytes); err != nil { + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) + twerr := twirp.NewError(twirp.Unknown, msg) + ctx = callError(ctx, s.hooks, twerr) + } + callResponseSent(ctx, s.hooks) +} + +func (s *aPIServer) serveGetPkgProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + var err error + ctx = ctxsetters.WithMethodName(ctx, "GetPkg") + ctx, err = callRequestRouted(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + buf, err := io.ReadAll(req.Body) + if err != nil { + s.handleRequestBodyError(ctx, resp, "failed to read request body", err) + return + } + reqContent := new(GetPackageRequest) + if err = proto.Unmarshal(buf, reqContent); err != nil { + s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) + return + } + + handler := s.API.GetPkg + if s.interceptor != nil { + handler = func(ctx context.Context, req *GetPackageRequest) (*Package, error) { + resp, err := s.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*GetPackageRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*GetPackageRequest) when calling interceptor") + } + return s.API.GetPkg(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*Package) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*Package) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + + // Call service method + var respContent *Package + func() { + defer ensurePanicResponses(ctx, resp, s.hooks) + respContent, err = handler(ctx, reqContent) + }() + + if err != nil { + s.writeError(ctx, resp, err) + return + } + if respContent == nil { + s.writeError(ctx, resp, twirp.InternalError("received a nil *Package and nil error while calling GetPkg. nil responses are not supported")) + return + } + + ctx = callResponsePrepared(ctx, s.hooks) + + respBytes, err := proto.Marshal(respContent) + if err != nil { + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) + return + } + + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) + resp.Header().Set("Content-Type", "application/protobuf") + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) + resp.WriteHeader(http.StatusOK) + if n, err := resp.Write(respBytes); err != nil { + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) + twerr := twirp.NewError(twirp.Unknown, msg) + ctx = callError(ctx, s.hooks, twerr) + } + callResponseSent(ctx, s.hooks) +} + +func (s *aPIServer) serveGetBuildScript(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + header := req.Header.Get("Content-Type") + i := strings.Index(header, ";") + if i == -1 { + i = len(header) + } + switch strings.TrimSpace(strings.ToLower(header[:i])) { + case "application/json": + s.serveGetBuildScriptJSON(ctx, resp, req) + case "application/protobuf": + s.serveGetBuildScriptProtobuf(ctx, resp, req) + default: + msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) + twerr := badRouteError(msg, req.Method, req.URL.Path) + s.writeError(ctx, resp, twerr) + } +} + +func (s *aPIServer) serveGetBuildScriptJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + var err error + ctx = ctxsetters.WithMethodName(ctx, "GetBuildScript") + ctx, err = callRequestRouted(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + d := json.NewDecoder(req.Body) + rawReqBody := json.RawMessage{} + if err := d.Decode(&rawReqBody); err != nil { + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) + return + } + reqContent := new(GetBuildScriptRequest) + unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} + if err = unmarshaler.Unmarshal(rawReqBody, reqContent); err != nil { + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) + return + } + + handler := s.API.GetBuildScript + if s.interceptor != nil { + handler = func(ctx context.Context, req *GetBuildScriptRequest) (*GetBuildScriptResponse, error) { + resp, err := s.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*GetBuildScriptRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*GetBuildScriptRequest) when calling interceptor") + } + return s.API.GetBuildScript(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*GetBuildScriptResponse) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*GetBuildScriptResponse) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + + // Call service method + var respContent *GetBuildScriptResponse + func() { + defer ensurePanicResponses(ctx, resp, s.hooks) + respContent, err = handler(ctx, reqContent) + }() + + if err != nil { + s.writeError(ctx, resp, err) + return + } + if respContent == nil { + s.writeError(ctx, resp, twirp.InternalError("received a nil *GetBuildScriptResponse and nil error while calling GetBuildScript. nil responses are not supported")) + return + } + + ctx = callResponsePrepared(ctx, s.hooks) + + marshaler := &protojson.MarshalOptions{UseProtoNames: !s.jsonCamelCase, EmitUnpopulated: !s.jsonSkipDefaults} + respBytes, err := marshaler.Marshal(respContent) + if err != nil { + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) + return + } + + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) + resp.Header().Set("Content-Type", "application/json") + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) + resp.WriteHeader(http.StatusOK) + + if n, err := resp.Write(respBytes); err != nil { + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) + twerr := twirp.NewError(twirp.Unknown, msg) + ctx = callError(ctx, s.hooks, twerr) + } + callResponseSent(ctx, s.hooks) +} + +func (s *aPIServer) serveGetBuildScriptProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + var err error + ctx = ctxsetters.WithMethodName(ctx, "GetBuildScript") + ctx, err = callRequestRouted(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + + buf, err := io.ReadAll(req.Body) + if err != nil { + s.handleRequestBodyError(ctx, resp, "failed to read request body", err) + return + } + reqContent := new(GetBuildScriptRequest) + if err = proto.Unmarshal(buf, reqContent); err != nil { + s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) + return + } + + handler := s.API.GetBuildScript + if s.interceptor != nil { + handler = func(ctx context.Context, req *GetBuildScriptRequest) (*GetBuildScriptResponse, error) { + resp, err := s.interceptor( + func(ctx context.Context, req interface{}) (interface{}, error) { + typedReq, ok := req.(*GetBuildScriptRequest) + if !ok { + return nil, twirp.InternalError("failed type assertion req.(*GetBuildScriptRequest) when calling interceptor") + } + return s.API.GetBuildScript(ctx, typedReq) + }, + )(ctx, req) + if resp != nil { + typedResp, ok := resp.(*GetBuildScriptResponse) + if !ok { + return nil, twirp.InternalError("failed type assertion resp.(*GetBuildScriptResponse) when calling interceptor") + } + return typedResp, err + } + return nil, err + } + } + + // Call service method + var respContent *GetBuildScriptResponse + func() { + defer ensurePanicResponses(ctx, resp, s.hooks) + respContent, err = handler(ctx, reqContent) + }() + + if err != nil { + s.writeError(ctx, resp, err) + return + } + if respContent == nil { + s.writeError(ctx, resp, twirp.InternalError("received a nil *GetBuildScriptResponse and nil error while calling GetBuildScript. nil responses are not supported")) + return + } + + ctx = callResponsePrepared(ctx, s.hooks) + + respBytes, err := proto.Marshal(respContent) + if err != nil { + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) + return + } + + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) + resp.Header().Set("Content-Type", "application/protobuf") + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) + resp.WriteHeader(http.StatusOK) + if n, err := resp.Write(respBytes); err != nil { + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) + twerr := twirp.NewError(twirp.Unknown, msg) + ctx = callError(ctx, s.hooks, twerr) + } + callResponseSent(ctx, s.hooks) +} + +func (s *aPIServer) ServiceDescriptor() ([]byte, int) { + return twirpFileDescriptor0, 0 +} + +func (s *aPIServer) ProtocGenTwirpVersion() string { + return "v8.1.3" +} + +// PathPrefix returns the base service path, in the form: "//./" +// that is everything in a Twirp route except for the . This can be used for routing, +// for example to identify the requests that are targeted to this service in a mux. +func (s *aPIServer) PathPrefix() string { + return baseServicePath(s.pathPrefix, "lure", "API") +} + +// ===== +// Utils +// ===== + +// HTTPClient is the interface used by generated clients to send HTTP requests. +// It is fulfilled by *(net/http).Client, which is sufficient for most users. +// Users can provide their own implementation for special retry policies. +// +// HTTPClient implementations should not follow redirects. Redirects are +// automatically disabled if *(net/http).Client is passed to client +// constructors. See the withoutRedirects function in this file for more +// details. +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} + +// TwirpServer is the interface generated server structs will support: they're +// HTTP handlers with additional methods for accessing metadata about the +// service. Those accessors are a low-level API for building reflection tools. +// Most people can think of TwirpServers as just http.Handlers. +type TwirpServer interface { + http.Handler + + // ServiceDescriptor returns gzipped bytes describing the .proto file that + // this service was generated from. Once unzipped, the bytes can be + // unmarshalled as a + // google.golang.org/protobuf/types/descriptorpb.FileDescriptorProto. + // + // The returned integer is the index of this particular service within that + // FileDescriptorProto's 'Service' slice of ServiceDescriptorProtos. This is a + // low-level field, expected to be used for reflection. + ServiceDescriptor() ([]byte, int) + + // ProtocGenTwirpVersion is the semantic version string of the version of + // twirp used to generate this file. + ProtocGenTwirpVersion() string + + // PathPrefix returns the HTTP URL path prefix for all methods handled by this + // service. This can be used with an HTTP mux to route Twirp requests. + // The path prefix is in the form: "//./" + // that is, everything in a Twirp route except for the at the end. + PathPrefix() string +} + +func newServerOpts(opts []interface{}) *twirp.ServerOptions { + serverOpts := &twirp.ServerOptions{} + for _, opt := range opts { + switch o := opt.(type) { + case twirp.ServerOption: + o(serverOpts) + case *twirp.ServerHooks: // backwards compatibility, allow to specify hooks as an argument + twirp.WithServerHooks(o)(serverOpts) + case nil: // backwards compatibility, allow nil value for the argument + continue + default: + panic(fmt.Sprintf("Invalid option type %T, please use a twirp.ServerOption", o)) + } + } + return serverOpts +} + +// WriteError writes an HTTP response with a valid Twirp error format (code, msg, meta). +// Useful outside of the Twirp server (e.g. http middleware), but does not trigger hooks. +// If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err) +func WriteError(resp http.ResponseWriter, err error) { + writeError(context.Background(), resp, err, nil) +} + +// writeError writes Twirp errors in the response and triggers hooks. +func writeError(ctx context.Context, resp http.ResponseWriter, err error, hooks *twirp.ServerHooks) { + // Convert to a twirp.Error. Non-twirp errors are converted to internal errors. + var twerr twirp.Error + if !errors.As(err, &twerr) { + twerr = twirp.InternalErrorWith(err) + } + + statusCode := twirp.ServerHTTPStatusFromErrorCode(twerr.Code()) + ctx = ctxsetters.WithStatusCode(ctx, statusCode) + ctx = callError(ctx, hooks, twerr) + + respBody := marshalErrorToJSON(twerr) + + resp.Header().Set("Content-Type", "application/json") // Error responses are always JSON + resp.Header().Set("Content-Length", strconv.Itoa(len(respBody))) + resp.WriteHeader(statusCode) // set HTTP status code and send response + + _, writeErr := resp.Write(respBody) + if writeErr != nil { + // We have three options here. We could log the error, call the Error + // hook, or just silently ignore the error. + // + // Logging is unacceptable because we don't have a user-controlled + // logger; writing out to stderr without permission is too rude. + // + // Calling the Error hook would confuse users: it would mean the Error + // hook got called twice for one request, which is likely to lead to + // duplicated log messages and metrics, no matter how well we document + // the behavior. + // + // Silently ignoring the error is our least-bad option. It's highly + // likely that the connection is broken and the original 'err' says + // so anyway. + _ = writeErr + } + + callResponseSent(ctx, hooks) +} + +// sanitizeBaseURL parses the the baseURL, and adds the "http" scheme if needed. +// If the URL is unparsable, the baseURL is returned unchanged. +func sanitizeBaseURL(baseURL string) string { + u, err := url.Parse(baseURL) + if err != nil { + return baseURL // invalid URL will fail later when making requests + } + if u.Scheme == "" { + u.Scheme = "http" + } + return u.String() +} + +// baseServicePath composes the path prefix for the service (without ). +// e.g.: baseServicePath("/twirp", "my.pkg", "MyService") +// +// returns => "/twirp/my.pkg.MyService/" +// +// e.g.: baseServicePath("", "", "MyService") +// +// returns => "/MyService/" +func baseServicePath(prefix, pkg, service string) string { + fullServiceName := service + if pkg != "" { + fullServiceName = pkg + "." + service + } + return path.Join("/", prefix, fullServiceName) + "/" +} + +// parseTwirpPath extracts path components form a valid Twirp route. +// Expected format: "[]/./" +// e.g.: prefix, pkgService, method := parseTwirpPath("/twirp/pkg.Svc/MakeHat") +func parseTwirpPath(path string) (string, string, string) { + parts := strings.Split(path, "/") + if len(parts) < 2 { + return "", "", "" + } + method := parts[len(parts)-1] + pkgService := parts[len(parts)-2] + prefix := strings.Join(parts[0:len(parts)-2], "/") + return prefix, pkgService, method +} + +// getCustomHTTPReqHeaders retrieves a copy of any headers that are set in +// a context through the twirp.WithHTTPRequestHeaders function. +// If there are no headers set, or if they have the wrong type, nil is returned. +func getCustomHTTPReqHeaders(ctx context.Context) http.Header { + header, ok := twirp.HTTPRequestHeaders(ctx) + if !ok || header == nil { + return nil + } + copied := make(http.Header) + for k, vv := range header { + if vv == nil { + copied[k] = nil + continue + } + copied[k] = make([]string, len(vv)) + copy(copied[k], vv) + } + return copied +} + +// newRequest makes an http.Request from a client, adding common headers. +func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType string) (*http.Request, error) { + req, err := http.NewRequest("POST", url, reqBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if customHeader := getCustomHTTPReqHeaders(ctx); customHeader != nil { + req.Header = customHeader + } + req.Header.Set("Accept", contentType) + req.Header.Set("Content-Type", contentType) + req.Header.Set("Twirp-Version", "v8.1.3") + return req, nil +} + +// JSON serialization for errors +type twerrJSON struct { + Code string `json:"code"` + Msg string `json:"msg"` + Meta map[string]string `json:"meta,omitempty"` +} + +// marshalErrorToJSON returns JSON from a twirp.Error, that can be used as HTTP error response body. +// If serialization fails, it will use a descriptive Internal error instead. +func marshalErrorToJSON(twerr twirp.Error) []byte { + // make sure that msg is not too large + msg := twerr.Msg() + if len(msg) > 1e6 { + msg = msg[:1e6] + } + + tj := twerrJSON{ + Code: string(twerr.Code()), + Msg: msg, + Meta: twerr.MetaMap(), + } + + buf, err := json.Marshal(&tj) + if err != nil { + buf = []byte("{\"type\": \"" + twirp.Internal + "\", \"msg\": \"There was an error but it could not be serialized into JSON\"}") // fallback + } + + return buf +} + +// errorFromResponse builds a twirp.Error from a non-200 HTTP response. +// If the response has a valid serialized Twirp error, then it's returned. +// If not, the response status code is used to generate a similar twirp +// error. See twirpErrorFromIntermediary for more info on intermediary errors. +func errorFromResponse(resp *http.Response) twirp.Error { + statusCode := resp.StatusCode + statusText := http.StatusText(statusCode) + + if isHTTPRedirect(statusCode) { + // Unexpected redirect: it must be an error from an intermediary. + // Twirp clients don't follow redirects automatically, Twirp only handles + // POST requests, redirects should only happen on GET and HEAD requests. + location := resp.Header.Get("Location") + msg := fmt.Sprintf("unexpected HTTP status code %d %q received, Location=%q", statusCode, statusText, location) + return twirpErrorFromIntermediary(statusCode, msg, location) + } + + respBodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return wrapInternal(err, "failed to read server error response body") + } + + var tj twerrJSON + dec := json.NewDecoder(bytes.NewReader(respBodyBytes)) + dec.DisallowUnknownFields() + if err := dec.Decode(&tj); err != nil || tj.Code == "" { + // Invalid JSON response; it must be an error from an intermediary. + msg := fmt.Sprintf("Error from intermediary with HTTP status code %d %q", statusCode, statusText) + return twirpErrorFromIntermediary(statusCode, msg, string(respBodyBytes)) + } + + errorCode := twirp.ErrorCode(tj.Code) + if !twirp.IsValidErrorCode(errorCode) { + msg := "invalid type returned from server error response: " + tj.Code + return twirp.InternalError(msg).WithMeta("body", string(respBodyBytes)) + } + + twerr := twirp.NewError(errorCode, tj.Msg) + for k, v := range tj.Meta { + twerr = twerr.WithMeta(k, v) + } + return twerr +} + +// twirpErrorFromIntermediary maps HTTP errors from non-twirp sources to twirp errors. +// The mapping is similar to gRPC: https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md. +// Returned twirp Errors have some additional metadata for inspection. +func twirpErrorFromIntermediary(status int, msg string, bodyOrLocation string) twirp.Error { + var code twirp.ErrorCode + if isHTTPRedirect(status) { // 3xx + code = twirp.Internal + } else { + switch status { + case 400: // Bad Request + code = twirp.Internal + case 401: // Unauthorized + code = twirp.Unauthenticated + case 403: // Forbidden + code = twirp.PermissionDenied + case 404: // Not Found + code = twirp.BadRoute + case 429: // Too Many Requests + code = twirp.ResourceExhausted + case 502, 503, 504: // Bad Gateway, Service Unavailable, Gateway Timeout + code = twirp.Unavailable + default: // All other codes + code = twirp.Unknown + } + } + + twerr := twirp.NewError(code, msg) + twerr = twerr.WithMeta("http_error_from_intermediary", "true") // to easily know if this error was from intermediary + twerr = twerr.WithMeta("status_code", strconv.Itoa(status)) + if isHTTPRedirect(status) { + twerr = twerr.WithMeta("location", bodyOrLocation) + } else { + twerr = twerr.WithMeta("body", bodyOrLocation) + } + return twerr +} + +func isHTTPRedirect(status int) bool { + return status >= 300 && status <= 399 +} + +// wrapInternal wraps an error with a prefix as an Internal error. +// The original error cause is accessible by github.com/pkg/errors.Cause. +func wrapInternal(err error, prefix string) twirp.Error { + return twirp.InternalErrorWith(&wrappedError{prefix: prefix, cause: err}) +} + +type wrappedError struct { + prefix string + cause error +} + +func (e *wrappedError) Error() string { return e.prefix + ": " + e.cause.Error() } +func (e *wrappedError) Unwrap() error { return e.cause } // for go1.13 + errors.Is/As +func (e *wrappedError) Cause() error { return e.cause } // for github.com/pkg/errors + +// ensurePanicResponses makes sure that rpc methods causing a panic still result in a Twirp Internal +// error response (status 500), and error hooks are properly called with the panic wrapped as an error. +// The panic is re-raised so it can be handled normally with middleware. +func ensurePanicResponses(ctx context.Context, resp http.ResponseWriter, hooks *twirp.ServerHooks) { + if r := recover(); r != nil { + // Wrap the panic as an error so it can be passed to error hooks. + // The original error is accessible from error hooks, but not visible in the response. + err := errFromPanic(r) + twerr := &internalWithCause{msg: "Internal service panic", cause: err} + // Actually write the error + writeError(ctx, resp, twerr, hooks) + // If possible, flush the error to the wire. + f, ok := resp.(http.Flusher) + if ok { + f.Flush() + } + + panic(r) + } +} + +// errFromPanic returns the typed error if the recovered panic is an error, otherwise formats as error. +func errFromPanic(p interface{}) error { + if err, ok := p.(error); ok { + return err + } + return fmt.Errorf("panic: %v", p) +} + +// internalWithCause is a Twirp Internal error wrapping an original error cause, +// but the original error message is not exposed on Msg(). The original error +// can be checked with go1.13+ errors.Is/As, and also by (github.com/pkg/errors).Unwrap +type internalWithCause struct { + msg string + cause error +} + +func (e *internalWithCause) Unwrap() error { return e.cause } // for go1.13 + errors.Is/As +func (e *internalWithCause) Cause() error { return e.cause } // for github.com/pkg/errors +func (e *internalWithCause) Error() string { return e.msg + ": " + e.cause.Error() } +func (e *internalWithCause) Code() twirp.ErrorCode { return twirp.Internal } +func (e *internalWithCause) Msg() string { return e.msg } +func (e *internalWithCause) Meta(key string) string { return "" } +func (e *internalWithCause) MetaMap() map[string]string { return nil } +func (e *internalWithCause) WithMeta(key string, val string) twirp.Error { return e } + +// malformedRequestError is used when the twirp server cannot unmarshal a request +func malformedRequestError(msg string) twirp.Error { + return twirp.NewError(twirp.Malformed, msg) +} + +// badRouteError is used when the twirp server cannot route a request +func badRouteError(msg string, method, url string) twirp.Error { + err := twirp.NewError(twirp.BadRoute, msg) + err = err.WithMeta("twirp_invalid_route", method+" "+url) + return err +} + +// withoutRedirects makes sure that the POST request can not be redirected. +// The standard library will, by default, redirect requests (including POSTs) if it gets a 302 or +// 303 response, and also 301s in go1.8. It redirects by making a second request, changing the +// method to GET and removing the body. This produces very confusing error messages, so instead we +// set a redirect policy that always errors. This stops Go from executing the redirect. +// +// We have to be a little careful in case the user-provided http.Client has its own CheckRedirect +// policy - if so, we'll run through that policy first. +// +// Because this requires modifying the http.Client, we make a new copy of the client and return it. +func withoutRedirects(in *http.Client) *http.Client { + copy := *in + copy.CheckRedirect = func(req *http.Request, via []*http.Request) error { + if in.CheckRedirect != nil { + // Run the input's redirect if it exists, in case it has side effects, but ignore any error it + // returns, since we want to use ErrUseLastResponse. + err := in.CheckRedirect(req, via) + _ = err // Silly, but this makes sure generated code passes errcheck -blank, which some people use. + } + return http.ErrUseLastResponse + } + return © +} + +// doProtobufRequest makes a Protobuf request to the remote Twirp service. +func doProtobufRequest(ctx context.Context, client HTTPClient, hooks *twirp.ClientHooks, url string, in, out proto.Message) (_ context.Context, err error) { + reqBodyBytes, err := proto.Marshal(in) + if err != nil { + return ctx, wrapInternal(err, "failed to marshal proto request") + } + reqBody := bytes.NewBuffer(reqBodyBytes) + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + + req, err := newRequest(ctx, url, reqBody, "application/protobuf") + if err != nil { + return ctx, wrapInternal(err, "could not build request") + } + ctx, err = callClientRequestPrepared(ctx, hooks, req) + if err != nil { + return ctx, err + } + + req = req.WithContext(ctx) + resp, err := client.Do(req) + if err != nil { + return ctx, wrapInternal(err, "failed to do request") + } + defer func() { _ = resp.Body.Close() }() + + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + + if resp.StatusCode != 200 { + return ctx, errorFromResponse(resp) + } + + respBodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return ctx, wrapInternal(err, "failed to read response body") + } + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + + if err = proto.Unmarshal(respBodyBytes, out); err != nil { + return ctx, wrapInternal(err, "failed to unmarshal proto response") + } + return ctx, nil +} + +// doJSONRequest makes a JSON request to the remote Twirp service. +func doJSONRequest(ctx context.Context, client HTTPClient, hooks *twirp.ClientHooks, url string, in, out proto.Message) (_ context.Context, err error) { + marshaler := &protojson.MarshalOptions{UseProtoNames: true} + reqBytes, err := marshaler.Marshal(in) + if err != nil { + return ctx, wrapInternal(err, "failed to marshal json request") + } + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + + req, err := newRequest(ctx, url, bytes.NewReader(reqBytes), "application/json") + if err != nil { + return ctx, wrapInternal(err, "could not build request") + } + ctx, err = callClientRequestPrepared(ctx, hooks, req) + if err != nil { + return ctx, err + } + + req = req.WithContext(ctx) + resp, err := client.Do(req) + if err != nil { + return ctx, wrapInternal(err, "failed to do request") + } + + defer func() { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = wrapInternal(cerr, "failed to close response body") + } + }() + + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + + if resp.StatusCode != 200 { + return ctx, errorFromResponse(resp) + } + + d := json.NewDecoder(resp.Body) + rawRespBody := json.RawMessage{} + if err := d.Decode(&rawRespBody); err != nil { + return ctx, wrapInternal(err, "failed to unmarshal json response") + } + unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} + if err = unmarshaler.Unmarshal(rawRespBody, out); err != nil { + return ctx, wrapInternal(err, "failed to unmarshal json response") + } + if err = ctx.Err(); err != nil { + return ctx, wrapInternal(err, "aborted because context was done") + } + return ctx, nil +} + +// Call twirp.ServerHooks.RequestReceived if the hook is available +func callRequestReceived(ctx context.Context, h *twirp.ServerHooks) (context.Context, error) { + if h == nil || h.RequestReceived == nil { + return ctx, nil + } + return h.RequestReceived(ctx) +} + +// Call twirp.ServerHooks.RequestRouted if the hook is available +func callRequestRouted(ctx context.Context, h *twirp.ServerHooks) (context.Context, error) { + if h == nil || h.RequestRouted == nil { + return ctx, nil + } + return h.RequestRouted(ctx) +} + +// Call twirp.ServerHooks.ResponsePrepared if the hook is available +func callResponsePrepared(ctx context.Context, h *twirp.ServerHooks) context.Context { + if h == nil || h.ResponsePrepared == nil { + return ctx + } + return h.ResponsePrepared(ctx) +} + +// Call twirp.ServerHooks.ResponseSent if the hook is available +func callResponseSent(ctx context.Context, h *twirp.ServerHooks) { + if h == nil || h.ResponseSent == nil { + return + } + h.ResponseSent(ctx) +} + +// Call twirp.ServerHooks.Error if the hook is available +func callError(ctx context.Context, h *twirp.ServerHooks, err twirp.Error) context.Context { + if h == nil || h.Error == nil { + return ctx + } + return h.Error(ctx, err) +} + +func callClientResponseReceived(ctx context.Context, h *twirp.ClientHooks) { + if h == nil || h.ResponseReceived == nil { + return + } + h.ResponseReceived(ctx) +} + +func callClientRequestPrepared(ctx context.Context, h *twirp.ClientHooks, req *http.Request) (context.Context, error) { + if h == nil || h.RequestPrepared == nil { + return ctx, nil + } + return h.RequestPrepared(ctx, req) +} + +func callClientError(ctx context.Context, h *twirp.ClientHooks, err twirp.Error) { + if h == nil || h.Error == nil { + return + } + h.Error(ctx, err) +} + +var twirpFileDescriptor0 = []byte{ + // 791 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0xdd, 0x6e, 0xdb, 0x36, + 0x14, 0x8e, 0xac, 0xf8, 0xef, 0xc8, 0x72, 0x1d, 0xae, 0xeb, 0x34, 0xaf, 0x58, 0x0d, 0x6f, 0x0b, + 0xbc, 0x5e, 0x78, 0x85, 0xbb, 0x8b, 0x61, 0x03, 0x06, 0x44, 0x8b, 0x97, 0x18, 0xcd, 0x6c, 0x8f, + 0x72, 0x0b, 0x64, 0x37, 0x82, 0x22, 0x9f, 0x26, 0x44, 0x14, 0x49, 0x25, 0xe9, 0x00, 0x7e, 0x83, + 0xbe, 0xd2, 0xde, 0x62, 0x0f, 0xb1, 0x07, 0x19, 0x48, 0x4a, 0xae, 0xbc, 0xec, 0x6a, 0xbd, 0xd3, + 0xf7, 0x73, 0x0e, 0xc9, 0x73, 0x0e, 0x29, 0x80, 0x64, 0xc3, 0x71, 0x9c, 0xf3, 0x4c, 0x66, 0xe4, + 0x50, 0x7d, 0x0f, 0xff, 0xb2, 0xc0, 0x0d, 0x30, 0xe2, 0xf1, 0x0d, 0xc5, 0x77, 0x1b, 0x14, 0x92, + 0x3c, 0x86, 0xfa, 0xbb, 0x0d, 0xf2, 0xad, 0x67, 0x0d, 0xac, 0x51, 0x9b, 0x1a, 0xa0, 0xd8, 0x84, + 0xdd, 0x31, 0xe9, 0xd5, 0x06, 0xd6, 0xc8, 0xa6, 0x06, 0x90, 0x63, 0x68, 0x8a, 0x8c, 0xcb, 0xf0, + 0x6a, 0xeb, 0xd9, 0x03, 0x6b, 0xd4, 0x9d, 0xb8, 0x63, 0xbd, 0x42, 0xb0, 0xa0, 0xab, 0xd0, 0xbf, + 0xa4, 0x0d, 0xa5, 0xfa, 0x5b, 0x32, 0x01, 0xe7, 0x2d, 0x4b, 0x24, 0xf2, 0x50, 0x6e, 0x73, 0xf4, + 0x0e, 0xb5, 0xf7, 0xc8, 0x78, 0x7f, 0x9d, 0x5d, 0xac, 0xa6, 0x34, 0x5c, 0x5d, 0x2e, 0xa7, 0x14, + 0x8c, 0x6b, 0xb5, 0xcd, 0x91, 0x1c, 0x43, 0xa7, 0x88, 0xb9, 0x8f, 0x92, 0x0d, 0x7a, 0x75, 0xb5, + 0x9d, 0xf3, 0x03, 0x5a, 0x64, 0x7a, 0xa3, 0xc8, 0xf7, 0x96, 0xe5, 0x3f, 0x02, 0x37, 0xac, 0x1a, + 0x87, 0xc7, 0x00, 0x81, 0xe4, 0x2c, 0xbd, 0xbe, 0x60, 0x42, 0x12, 0x0f, 0x9a, 0x98, 0x4a, 0xce, + 0x50, 0x78, 0xd6, 0xc0, 0x1e, 0xb5, 0x69, 0x09, 0x87, 0x7f, 0xd7, 0xa1, 0xb9, 0x8c, 0xe2, 0xdb, + 0xe8, 0x1a, 0x09, 0x81, 0xc3, 0x34, 0xba, 0xc3, 0xe2, 0xcc, 0xfa, 0x9b, 0x7c, 0x09, 0xc0, 0x31, + 0xcf, 0x04, 0x93, 0x19, 0xdf, 0xea, 0x73, 0xb7, 0x69, 0x85, 0x51, 0x99, 0xef, 0x91, 0x0b, 0x96, + 0xa5, 0xfa, 0xf0, 0x6d, 0x5a, 0x42, 0xa5, 0x70, 0x4c, 0x30, 0x12, 0xe6, 0xa8, 0x36, 0x2d, 0x21, + 0xf9, 0x1c, 0xea, 0x98, 0x67, 0xf1, 0x8d, 0x3e, 0x8d, 0x7d, 0x7e, 0x40, 0x0d, 0x7c, 0x6f, 0x59, + 0xe4, 0x1b, 0x70, 0xd6, 0x28, 0x62, 0xce, 0x72, 0xa9, 0x52, 0x36, 0xf4, 0x71, 0x2d, 0x5a, 0x25, + 0x95, 0xed, 0x19, 0xb4, 0x6e, 0xb2, 0x3b, 0xcc, 0xa3, 0x6b, 0xf4, 0x9a, 0xda, 0x53, 0xa3, 0x3b, + 0x46, 0x19, 0xbe, 0x02, 0xb8, 0x8b, 0x58, 0x2a, 0x23, 0x96, 0x22, 0xf7, 0x5a, 0xda, 0x62, 0xd3, + 0x0a, 0xa7, 0x4c, 0x5f, 0x83, 0xab, 0x7a, 0xce, 0x24, 0xc6, 0x72, 0xc3, 0x51, 0x78, 0x6d, 0x5d, + 0x9b, 0x7d, 0x92, 0xf4, 0xa1, 0x95, 0xb0, 0x18, 0x53, 0x81, 0xc2, 0x03, 0x6d, 0xd8, 0x61, 0xa5, + 0xe5, 0x3c, 0xbb, 0x67, 0x6b, 0x14, 0x9e, 0x63, 0xb4, 0x12, 0x93, 0xa7, 0xd0, 0x8e, 0xb3, 0xf4, + 0x6d, 0xc2, 0x62, 0x29, 0xbc, 0x8e, 0x16, 0x3f, 0x10, 0x2a, 0x92, 0x63, 0x9e, 0x44, 0x31, 0x0a, + 0xcf, 0x35, 0x91, 0x25, 0x26, 0xdf, 0x43, 0x73, 0x8d, 0x39, 0xa6, 0x6b, 0xe1, 0x75, 0x07, 0xf6, + 0xc8, 0x99, 0xf4, 0xcd, 0x90, 0x14, 0x7d, 0x1a, 0x9f, 0x1a, 0x71, 0x9a, 0x4a, 0xbe, 0xa5, 0xa5, + 0x95, 0x9c, 0x82, 0x7b, 0xb5, 0x61, 0xc9, 0x3a, 0x2c, 0x63, 0x1f, 0xe9, 0xd8, 0x67, 0xfb, 0xb1, + 0xbe, 0xb2, 0xec, 0x25, 0xe8, 0x5c, 0x55, 0xa8, 0xfe, 0x05, 0x74, 0xaa, 0x2a, 0xe9, 0x81, 0x7d, + 0x8b, 0xe5, 0x35, 0x50, 0x9f, 0xe4, 0x18, 0xea, 0x66, 0x16, 0xd5, 0x30, 0x38, 0x93, 0x5e, 0x31, + 0xec, 0xbb, 0x61, 0xa3, 0x46, 0xfe, 0xb1, 0xf6, 0x83, 0xd5, 0xff, 0x1d, 0x8e, 0x1e, 0x2c, 0xf8, + 0x71, 0x29, 0xfd, 0x16, 0x34, 0x42, 0x3d, 0x2e, 0x7e, 0x17, 0x3a, 0x61, 0x65, 0x2e, 0x7c, 0x07, + 0xda, 0x61, 0x39, 0x03, 0xbe, 0x0b, 0x4e, 0xf8, 0xa1, 0xdb, 0xc3, 0x33, 0x38, 0x3a, 0x43, 0x59, + 0x14, 0xa1, 0xbc, 0xe4, 0xff, 0x63, 0xde, 0x87, 0x3f, 0x41, 0xb7, 0x7c, 0x29, 0x44, 0x9e, 0xa5, + 0x02, 0xc9, 0xb7, 0xd0, 0xca, 0x4d, 0x5e, 0x73, 0xb9, 0x9c, 0xf2, 0xfe, 0x97, 0xab, 0xed, 0xe4, + 0xe1, 0x2b, 0xf8, 0xf4, 0x0c, 0xa5, 0xae, 0x48, 0xa0, 0xb7, 0xfd, 0x31, 0x3b, 0x79, 0x01, 0x4f, + 0xfe, 0x9d, 0xac, 0xd8, 0xd1, 0x13, 0x68, 0x98, 0xaa, 0x14, 0xf9, 0x0a, 0xf4, 0xfc, 0x67, 0x68, + 0x16, 0x6f, 0x12, 0xe9, 0x40, 0xeb, 0xf5, 0x5c, 0x81, 0xe9, 0x69, 0xef, 0x80, 0xb4, 0xe0, 0x70, + 0x7e, 0xf2, 0xdb, 0xb4, 0x67, 0x91, 0x2e, 0x00, 0x9d, 0x2e, 0x17, 0xc1, 0x6c, 0xb5, 0xa0, 0x97, + 0xbd, 0x1a, 0x71, 0xa0, 0xf9, 0x66, 0x4a, 0x83, 0xd9, 0x62, 0xde, 0xb3, 0x9f, 0xfb, 0xe0, 0x54, + 0xde, 0x29, 0xe2, 0x42, 0x7b, 0xbe, 0x08, 0x0d, 0xd3, 0x3b, 0x20, 0x47, 0xe0, 0xce, 0xe6, 0x61, + 0x25, 0xda, 0x52, 0x54, 0xf0, 0x7a, 0xb9, 0x5c, 0xd0, 0x55, 0x10, 0x9e, 0xd0, 0x5f, 0xce, 0x7b, + 0xb5, 0xc9, 0x9f, 0x16, 0xd8, 0x27, 0xcb, 0x19, 0x79, 0x09, 0x0d, 0x53, 0x47, 0xf2, 0x49, 0xd1, + 0xed, 0xea, 0xfb, 0xdb, 0x7f, 0xbc, 0x4f, 0x16, 0x07, 0x7b, 0x01, 0x0d, 0xd5, 0xc5, 0xdb, 0x6b, + 0xf2, 0x99, 0xd1, 0x1f, 0xf4, 0xb4, 0xbf, 0x5f, 0x7b, 0xf2, 0x0a, 0xba, 0xfb, 0x45, 0x22, 0x5f, + 0xec, 0x22, 0x1f, 0xf6, 0xa1, 0xff, 0xf4, 0xbf, 0x45, 0xb3, 0xbc, 0xdf, 0xfa, 0xa3, 0x31, 0x1e, + 0x7f, 0x17, 0xe5, 0xec, 0xaa, 0xa1, 0xff, 0x1e, 0x2f, 0xff, 0x09, 0x00, 0x00, 0xff, 0xff, 0xed, + 0x6c, 0xfc, 0x7a, 0x4b, 0x06, 0x00, 0x00, +} diff --git a/lure-backend/main.go b/lure-backend/main.go new file mode 100644 index 0000000..f2396c9 --- /dev/null +++ b/lure-backend/main.go @@ -0,0 +1,120 @@ +/* + * LURE - Linux User REpository + * Copyright (C) 2023 Elara Musayelyan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package main + +import ( + "context" + "flag" + "net" + "net/http" + "os" + + "github.com/go-chi/chi/v5" + "github.com/twitchtv/twirp" + "go.elara.ws/logger" + "go.elara.ws/lure-web/lure-backend/internal/api" + "go.elara.ws/logger/log" + "go.elara.ws/lure/pkg/repos" + lurelog "go.elara.ws/lure/pkg/log" +) + +func init() { + log.Logger = logger.NewPretty(os.Stderr) + lurelog.SetLogger(log.Logger) +} + +func main() { + ctx := context.Background() + + addr := flag.String("a", ":8080", "Listen address for API server") + logFile := flag.String("l", "", "Output file for JSON log") + flag.Parse() + + if *logFile != "" { + fl, err := os.Create(*logFile) + if err != nil { + log.Fatal("Error creating log file").Err(err).Send() + } + defer fl.Close() + + log.Logger = logger.NewMulti(log.Logger, logger.NewJSON(fl)) + } + + err := repos.Pull(ctx, nil) + if err != nil { + log.Fatal("Error pulling repositories").Err(err).Send() + } + + sigCh := make(chan struct{}, 200) + go repoPullWorker(ctx, sigCh) + + apiServer := api.NewAPIServer( + lureWebAPI{}, + twirp.WithServerPathPrefix(""), + ) + + r := chi.NewRouter() + r.With(allowAllCORSHandler, withAcceptLanguage).Handle("/*", apiServer) + r.Post("/webhook", handleWebhook(sigCh)) + r.Get("/badge/{repo}/{pkg}", handleBadge()) + + ln, err := net.Listen("tcp", *addr) + if err != nil { + log.Fatal("Error starting listener").Err(err).Send() + } + + log.Info("Starting HTTP API server").Str("addr", ln.Addr().String()).Send() + + err = http.Serve(ln, r) + if err != nil { + log.Fatal("Error while running server").Err(err).Send() + } +} + +func allowAllCORSHandler(h http.Handler) http.Handler { + return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + res.Header().Set("Access-Control-Allow-Origin", "*") + res.Header().Set("Access-Control-Allow-Headers", "*") + if req.Method == http.MethodOptions { + return + } + h.ServeHTTP(res, req) + }) +} + +type ( + acceptLanguageKey struct{} + langParameterKey struct{} +) + +func withAcceptLanguage(h http.Handler) http.Handler { + return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + ctx := req.Context() + + langs := req.Header.Get("Accept-Language") + ctx = context.WithValue(ctx, acceptLanguageKey{}, langs) + + lang := req.URL.Query().Get("lang") + ctx = context.WithValue(ctx, langParameterKey{}, lang) + + req = req.WithContext(ctx) + + h.ServeHTTP(res, req) + }) +} diff --git a/lure-backend/webhook.go b/lure-backend/webhook.go new file mode 100644 index 0000000..32e92d7 --- /dev/null +++ b/lure-backend/webhook.go @@ -0,0 +1,98 @@ +/* + * LURE - Linux User REpository + * Copyright (C) 2023 Elara Musayelyan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package main + +import ( + "context" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "errors" + "io" + "net/http" + "os" + "strings" + + "go.elara.ws/logger/log" + "go.elara.ws/lure/pkg/repos" +) + +func handleWebhook(sigCh chan<- struct{}) http.HandlerFunc { + return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + if req.Header.Get("X-GitHub-Event") != "push" { + http.Error(res, "Only push events are accepted by this bot", http.StatusBadRequest) + return + } + + err := verifySecure(req) + if err != nil { + http.Error(res, err.Error(), http.StatusForbidden) + return + } + + sigCh <- struct{}{} + return + }) +} + +func verifySecure(req *http.Request) error { + sigStr := req.Header.Get("X-Hub-Signature-256") + sig, err := hex.DecodeString(strings.TrimPrefix(sigStr, "sha256=")) + if err != nil { + return err + } + + secretStr, ok := os.LookupEnv("LURE_API_GITHUB_SECRET") + if !ok { + return errors.New("LURE_API_GITHUB_SECRET must be set to the secret used for setting up the github webhook\n\n") + } + secret := []byte(secretStr) + + h := hmac.New(sha256.New, secret) + _, err = io.Copy(h, req.Body) + if err != nil { + return err + } + + if !hmac.Equal(h.Sum(nil), sig) { + log.Warn("Insecure webhook request"). + Str("from", req.RemoteAddr). + Bytes("sig", sig). + Bytes("hmac", h.Sum(nil)). + Send() + + return errors.New("webhook signature mismatch") + } + + return nil +} + +func repoPullWorker(ctx context.Context, sigCh <-chan struct{}) { + for { + select { + case <-sigCh: + err := repos.Pull(ctx, nil) + if err != nil { + log.Warn("Error while pulling repositories").Err(err).Send() + } + case <-ctx.Done(): + return + } + } +} diff --git a/template.nomad b/template.nomad index b97feb2..97bbf59 100644 --- a/template.nomad +++ b/template.nomad @@ -14,7 +14,7 @@ job "lure-web" { } } - task "lure-api-server" { + task "lure-backend" { driver = "docker" env { @@ -26,12 +26,12 @@ job "lure-web" { } config { - image = "elara6331/lure-api-server:latest" + image = "gitea.elara.ws/elara6331/lure-backend:latest" ports = ["api"] } service { - name = "lure-api-server" + name = "lure-backend" port = "api" tags = [ "traefik.enable=true", @@ -73,6 +73,6 @@ job "lure-web" { constraint { attribute = "${attr.cpu.arch}" operator = "set_contains_any" - value = "amd64,arm64" + value = "amd64,arm64,riscv64" } }