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