diff --git a/.github/workflows/docker-publish-rootless.yaml b/.github/workflows/docker-publish-rootless.yaml index d4726a7e..745c089b 100644 --- a/.github/workflows/docker-publish-rootless.yaml +++ b/.github/workflows/docker-publish-rootless.yaml @@ -2,7 +2,7 @@ name: Docker publish rootless on: schedule: - - cron: '00 6 * * *' + - cron: '00 0 * * *' push: branches: [ "main" ] paths: diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 4e4dbe78..cf05d993 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -2,7 +2,7 @@ name: Docker publish on: schedule: - - cron: '00 6 * * *' + - cron: '00 0 * * *' push: branches: [ "main" ] paths: diff --git a/README.md b/README.md index c8f6237e..e45676f6 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@
- Docs + Docs | - Demo + Demo | Discord
@@ -24,7 +24,7 @@ Check out screenshots of the project [here](https://imgur.com/a/5gLWt2j). ## Quick Start -[Configuration & Docker Compose](https://homebox.sysadminsmedia.com/en/quick-start.html) +[Configuration & Docker Compose](https://homebox.software/en/quick-start.html) ```bash # If using the rootless image, ensure data diff --git a/backend/app/api/providers/extractors.go b/backend/app/api/providers/extractors.go index c7b8fd66..1ad7010b 100644 --- a/backend/app/api/providers/extractors.go +++ b/backend/app/api/providers/extractors.go @@ -2,6 +2,7 @@ package providers import ( "errors" + "github.com/sysadminsmedia/homebox/backend/internal/core/services" "net/http" "github.com/hay-kot/httpkit/server" @@ -53,3 +54,41 @@ func getLoginForm(r *http.Request) (LoginForm, error) { return loginForm, nil } + +func getOAuthForm(r *http.Request) (services.OAuthValidate, error) { + var oauthForm services.OAuthValidate + switch r.Header.Get("Content-Type") { + case "application/x-www-form-urlencoded": + err := r.ParseForm() + if err != nil { + return oauthForm, errors.New("failed to parse form") + } + + oauthForm.Issuer = r.PostFormValue("issuer") + oauthForm.Code = r.PostFormValue("code") + oauthForm.State = r.PostFormValue("state") + case "application/json": + err := server.Decode(r, &oauthForm) + if err != nil { + log.Err(err).Msg("failed to decode OAuth form") + return oauthForm, err + } + default: + return oauthForm, errors.New("invalid content type") + } + + if oauthForm.Issuer == "" || oauthForm.Code == "" { + return oauthForm, validate.NewFieldErrors( + validate.FieldError{ + Field: "iss", + Error: "Issuer is empty", + }, + validate.FieldError{ + Field: "code", + Error: "Code is missing", + }, + ) + } + + return oauthForm, nil +} diff --git a/backend/app/api/providers/oauth.go b/backend/app/api/providers/oauth.go new file mode 100644 index 00000000..20b0bc43 --- /dev/null +++ b/backend/app/api/providers/oauth.go @@ -0,0 +1,67 @@ +package providers + +import ( + "context" + "errors" + "fmt" + "github.com/coreos/go-oidc/v3/oidc" + "github.com/rs/zerolog/log" + "github.com/sysadminsmedia/homebox/backend/internal/core/services" + "golang.org/x/oauth2" + "net/http" + "os" + "strings" +) + +type OAuthProvider struct { + name string + service *services.OAuthService + config *services.OAuthConfig +} + +func NewOAuthProvider(ctx context.Context, service *services.OAuthService, name string) (*OAuthProvider, error) { + upperName := strings.ToUpper(name) + clientId := os.Getenv(fmt.Sprintf("HBOX_OAUTH_%s_ID", upperName)) + clientSecret := os.Getenv(fmt.Sprintf("HBOX_OAUTH_%s_SECRET", upperName)) + redirectUri := os.Getenv(fmt.Sprintf("HBOX_OAUTH_%s_REDIRECT", upperName)) + + providerUrl := os.Getenv(fmt.Sprintf("HBOX_OAUTH_%s_URL", upperName)) + // TODO: fallback for all variabnles if no well known is supported + if providerUrl == "" { + return nil, errors.New("Provider url not given") + } + provider, err := oidc.NewProvider(ctx, providerUrl) + if err != nil { + return nil, err + } + log.Debug().Str("AuthUrl", provider.Endpoint().AuthURL).Msg("discovered oauth provider") + + return &OAuthProvider{ + name: name, + service: service, + config: &services.OAuthConfig{ + Config: &oauth2.Config{ + ClientID: clientId, + ClientSecret: clientSecret, + Endpoint: provider.Endpoint(), + RedirectURL: redirectUri, + Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, + }, + Provider: provider, + Verifier: provider.Verifier(&oidc.Config{ClientID: clientId}), + }, + }, nil +} + +func (p *OAuthProvider) Name() string { + return p.name +} + +func (p *OAuthProvider) Authenticate(w http.ResponseWriter, r *http.Request) (services.UserAuthTokenDetail, error) { + oauthForm, err := getOAuthForm(r) + if err != nil { + return services.UserAuthTokenDetail{}, err + } + + return p.service.Login(r.Context(), p.config, oauthForm) +} diff --git a/backend/app/api/routes.go b/backend/app/api/routes.go index 03288df5..f6510bc5 100644 --- a/backend/app/api/routes.go +++ b/backend/app/api/routes.go @@ -1,6 +1,7 @@ package main import ( + "context" "embed" "errors" "fmt" @@ -16,6 +17,7 @@ import ( "io" "mime" "net/http" + "os" "path" "path/filepath" ) @@ -66,12 +68,19 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR r.Get("/currencies", chain.ToHandlerFunc(v1Ctrl.HandleCurrency())) - providers := []v1.AuthProvider{ + providerList := []v1.AuthProvider{ providers.NewLocalProvider(a.services.User), } + if _, exist := os.LookupEnv("HBOX_OAUTH_OIDC_URL"); exist { + provider, err := providers.NewOAuthProvider(context.Background(), a.services.OAuth, "oidc") + if err != nil { + panic(err) + } + providerList = append(providerList, provider) + } r.Post("/users/register", chain.ToHandlerFunc(v1Ctrl.HandleUserRegistration())) - r.Post("/users/login", chain.ToHandlerFunc(v1Ctrl.HandleAuthLogin(providers...))) + r.Post("/users/login", chain.ToHandlerFunc(v1Ctrl.HandleAuthLogin(providerList...))) userMW := []errchain.Middleware{ a.mwAuthToken, diff --git a/backend/go.mod b/backend/go.mod index ce9e8d63..e9f6af8b 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -1,31 +1,31 @@ module github.com/sysadminsmedia/homebox/backend -go 1.22 - -toolchain go1.22.0 +go 1.23.0 require ( - ariga.io/atlas v0.19.1 - entgo.io/ent v0.12.5 - github.com/ardanlabs/conf/v3 v3.1.7 + ariga.io/atlas v0.27.0 + entgo.io/ent v0.14.1 + github.com/ardanlabs/conf/v3 v3.1.8 github.com/containrrr/shoutrrr v0.8.0 - github.com/go-chi/chi/v5 v5.0.12 - github.com/go-playground/validator/v10 v10.18.0 - github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a + github.com/coreos/go-oidc/v3 v3.11.0 + github.com/go-chi/chi/v5 v5.1.0 + github.com/go-playground/validator/v10 v10.22.1 + github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 github.com/google/uuid v1.6.0 github.com/gorilla/schema v1.4.1 - github.com/hay-kot/httpkit v0.0.9 - github.com/mattn/go-sqlite3 v1.14.22 - github.com/olahol/melody v1.1.4 + github.com/hay-kot/httpkit v0.0.11 + github.com/mattn/go-sqlite3 v1.14.23 + github.com/olahol/melody v1.2.1 github.com/pkg/errors v0.9.1 - github.com/rs/zerolog v1.32.0 - github.com/stretchr/testify v1.8.4 + github.com/rs/zerolog v1.33.0 + github.com/stretchr/testify v1.9.0 github.com/swaggo/http-swagger/v2 v2.0.2 github.com/swaggo/swag v1.16.3 - github.com/yeqown/go-qrcode/v2 v2.2.2 - github.com/yeqown/go-qrcode/writer/standard v1.2.2 - golang.org/x/crypto v0.23.0 - modernc.org/sqlite v1.29.2 + github.com/yeqown/go-qrcode/v2 v2.2.4 + github.com/yeqown/go-qrcode/writer/standard v1.2.4 + golang.org/x/crypto v0.27.0 + golang.org/x/oauth2 v0.23.0 + modernc.org/sqlite v1.33.1 ) require ( @@ -34,21 +34,22 @@ require ( github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/fatih/color v1.16.0 // indirect + github.com/fatih/color v1.17.0 // indirect github.com/fogleman/gg v1.3.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/go-openapi/inflect v0.19.0 // indirect - github.com/go-openapi/jsonpointer v0.20.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/spec v0.20.9 // indirect - github.com/go-openapi/swag v0.22.4 // indirect + github.com/gabriel-vasile/mimetype v1.4.5 // indirect + github.com/go-jose/go-jose/v4 v4.0.4 // indirect + github.com/go-openapi/inflect v0.21.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/gorilla/websocket v1.5.1 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/hashicorp/hcl/v2 v2.19.1 // indirect + github.com/hashicorp/hcl/v2 v2.22.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -59,20 +60,21 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect - github.com/swaggo/files/v2 v2.0.0 // indirect + github.com/swaggo/files/v2 v2.0.1 // indirect github.com/yeqown/reedsolomon v1.0.0 // indirect - github.com/zclconf/go-cty v1.14.1 // indirect - golang.org/x/image v0.18.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + github.com/zclconf/go-cty v1.15.0 // indirect + golang.org/x/image v0.20.0 // indirect + golang.org/x/mod v0.21.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect + golang.org/x/tools v0.25.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect - modernc.org/libc v1.41.0 // indirect + modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a // indirect + modernc.org/libc v1.60.1 // indirect modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.7.2 // indirect + modernc.org/memory v1.8.0 // indirect modernc.org/strutil v1.2.0 // indirect modernc.org/token v1.1.0 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index ac151ff6..fa8eb5cd 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,7 +1,11 @@ ariga.io/atlas v0.19.1 h1:QzBHkakwzEhmPWOzNhw8Yr/Bbicj6Iq5hwEoNI/Jr9A= ariga.io/atlas v0.19.1/go.mod h1:VPlcXdd4w2KqKnH54yEZcry79UAhpaWaxEsmn5JRNoE= +ariga.io/atlas v0.27.0 h1:UHUQMTRx2Vz8/acJxEfcC/zL2wY739/vkZzt7L8HL8I= +ariga.io/atlas v0.27.0/go.mod h1:KPLc7Zj+nzoXfWshrcY1RwlOh94dsATQEy4UPrF2RkM= entgo.io/ent v0.12.5 h1:KREM5E4CSoej4zeGa88Ou/gfturAnpUv0mzAjch1sj4= entgo.io/ent v0.12.5/go.mod h1:Y3JVAjtlIk8xVZYSn3t3mf8xlZIn5SAOXZQxD6kKI+Q= +entgo.io/ent v0.14.1 h1:fUERL506Pqr92EPHJqr8EYxbPioflJo6PudkrEA8a/s= +entgo.io/ent v0.14.1/go.mod h1:MH6XLG0KXpkcDQhKiHfANZSzR55TJyPL5IGNpI8wpco= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= @@ -12,8 +16,12 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/ardanlabs/conf/v3 v3.1.7 h1:p232cF68TafoA5U9ZlbxUIhGJtGNdKHBXF80Fdqb5t0= github.com/ardanlabs/conf/v3 v3.1.7/go.mod h1:zclexWKe0NVj6LHQ8NgDDZ7bQ1spE0KeKPFficdtAjU= +github.com/ardanlabs/conf/v3 v3.1.8 h1:r0KUV9/Hni5XdeWR2+A1BiedIDnry5CjezoqgJ0rnFQ= +github.com/ardanlabs/conf/v3 v3.1.8/go.mod h1:OIi6NK95fj8jKFPdZ/UmcPlY37JBg99hdP9o5XmNK9c= github.com/containrrr/shoutrrr v0.8.0 h1:mfG2ATzIS7NR2Ec6XL+xyoHzN97H8WPjir8aYzJUSec= github.com/containrrr/shoutrrr v0.8.0/go.mod h1:ioyQAyu1LJY6sILuNyKaQaw+9Ttik5QePU8atnAdO2o= +github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= +github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -23,31 +31,51 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= +github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= +github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= +github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= +github.com/go-openapi/inflect v0.21.0 h1:FoBjBTQEcbg2cJUWX6uwL9OyIW8eqc9k4KhN4lfbeYk= +github.com/go-openapi/inflect v0.21.0/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -56,12 +84,16 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U= github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a h1:RYfmiM0zluBJOiPDJseKLEN4BapJ42uSi9SZBQ2YyiA= github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= +github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ= +github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= @@ -78,12 +110,20 @@ github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI= github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= +github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M= +github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hay-kot/httpkit v0.0.9 h1:hu2TPY9awmIYWXxWGubaXl2U61pPvaVsm9YwboBRGu0= github.com/hay-kot/httpkit v0.0.9/go.mod h1:AD22YluZrvBDxmtB3Pw2SOyp3A2PZqcmBZa0+COrhoU= +github.com/hay-kot/httpkit v0.0.11 h1:ZdB2uqsFBSDpfUoClGK5c5orjBjQkEVSXh7fZX5FKEk= +github.com/hay-kot/httpkit v0.0.11/go.mod h1:0kZdk5/swzdfqfg2c6pBWimcgeJ9PTyO97EbHnYl2Sw= github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -113,6 +153,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0= +github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= @@ -120,6 +162,8 @@ github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJm github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/olahol/melody v1.1.4 h1:RQHfKZkQmDxI0+SLZRNBCn4LiXdqxLKRGSkT8Dyoe/E= github.com/olahol/melody v1.1.4/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4= +github.com/olahol/melody v1.2.1 h1:xdwRkzHxf+B0w4TKbGpUSSkV516ZucQZJIWLztOWICQ= +github.com/olahol/melody v1.2.1/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4= github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= @@ -135,6 +179,8 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -147,39 +193,68 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 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/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= +github.com/swaggo/files/v2 v2.0.1 h1:XCVJO/i/VosCDsJu1YLpdejGsGnBE9deRMpjN4pJLHk= +github.com/swaggo/files/v2 v2.0.1/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg= github.com/swaggo/http-swagger/v2 v2.0.2/go.mod h1:r7/GBkAWIfK6E/OLnE8fXnviHiDeAHmgIyooa4xm3AQ= github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= github.com/yeqown/go-qrcode/v2 v2.2.2 h1:0comk6jEwi0oWNhKEmzx4JI+Q7XIneAApmFSMKWmSVc= github.com/yeqown/go-qrcode/v2 v2.2.2/go.mod h1:2Qsk2APUCPne0TsRo40DIkI5MYnbzYKCnKGEFWrxd24= +github.com/yeqown/go-qrcode/v2 v2.2.4 h1:cXdYlrhzHzVAnJHiwr/T6lAUmS9MtEStjEZBjArrvnc= +github.com/yeqown/go-qrcode/v2 v2.2.4/go.mod h1:uHpt9CM0V1HeXLz+Wg5MN50/sI/fQhfkZlOM+cOTHxw= github.com/yeqown/go-qrcode/writer/standard v1.2.2 h1:gyzunKXgC0ZUpKqQFUImbAEwewAiwNCkxFEKZV80Kt4= github.com/yeqown/go-qrcode/writer/standard v1.2.2/go.mod h1:bbVRiBJSRPj4UBZP/biLG7JSd9kHqXjErk1eakAMnRA= +github.com/yeqown/go-qrcode/writer/standard v1.2.4 h1:41e/aLr1AMVWlug6oUMkDg2r0+dv5ofB7UaTkekKZBc= +github.com/yeqown/go-qrcode/writer/standard v1.2.4/go.mod h1:H8nLSGYUWBpNyBPjDcJzAanMzYBBYMFtrU2lwoSRn+k= github.com/yeqown/reedsolomon v1.0.0 h1:x1h/Ej/uJnNu8jaX7GLHBWmZKCAWjEJTetkqaabr4B0= github.com/yeqown/reedsolomon v1.0.0/go.mod h1:P76zpcn2TCuL0ul1Fso373qHRc69LKwAw/Iy6g1WiiM= github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA= github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= +github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= +golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw= +golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -196,14 +271,22 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a h1:CfbpOLEo2IwNzJdMvE8aiRbPMxoTpgAJeyePh0SmO8M= +modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk= modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY= +modernc.org/libc v1.60.1 h1:at373l8IFRTkJIkAU85BIuUoBM4T1b51ds0E1ovPG2s= +modernc.org/libc v1.60.1/go.mod h1:xJuobKuNxKH3RUatS7GjR+suWj+5c2K7bi4m/S5arOY= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= modernc.org/sqlite v1.29.2 h1:xgBSyA3gemwgP31PWFfFjtBorQNYpeypGdoSDjXhrgI= modernc.org/sqlite v1.29.2/go.mod h1:hG41jCYxOAOoO6BRK66AdRlmOcDzXf7qnwlwjUIOqa0= +modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM= +modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/backend/internal/core/services/all.go b/backend/internal/core/services/all.go index 87cd3feb..a8f399da 100644 --- a/backend/internal/core/services/all.go +++ b/backend/internal/core/services/all.go @@ -8,6 +8,7 @@ import ( type AllServices struct { User *UserService + OAuth *OAuthService Group *GroupService Items *ItemService BackgroundService *BackgroundService @@ -54,8 +55,11 @@ func New(repos *repo.AllRepos, opts ...OptionsFunc) *AllServices { opt(options) } + user := &UserService{repos} + return &AllServices{ - User: &UserService{repos}, + User: user, + OAuth: &OAuthService{repos: repos, user: user}, Group: &GroupService{repos}, Items: &ItemService{ repo: repos, diff --git a/backend/internal/core/services/reporting/io_sheet.go b/backend/internal/core/services/reporting/io_sheet.go index 21d07d1b..cc97bd9b 100644 --- a/backend/internal/core/services/reporting/io_sheet.go +++ b/backend/internal/core/services/reporting/io_sheet.go @@ -222,6 +222,7 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid. SoldPrice: item.SoldPrice, SoldNotes: item.SoldNotes, + Notes: item.Notes, Fields: customFields, } } diff --git a/backend/internal/core/services/service_oauth.go b/backend/internal/core/services/service_oauth.go new file mode 100644 index 00000000..92d00da7 --- /dev/null +++ b/backend/internal/core/services/service_oauth.go @@ -0,0 +1,182 @@ +package services + +import ( + "context" + "errors" + "github.com/coreos/go-oidc/v3/oidc" + "github.com/google/uuid" + "github.com/rs/zerolog/log" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent" + "github.com/sysadminsmedia/homebox/backend/internal/data/repo" + "golang.org/x/oauth2" +) + +type OAuthService struct { + repos *repo.AllRepos + user *UserService +} + +type ( + OAuthConfig struct { + Config *oauth2.Config + Provider *oidc.Provider + Verifier *oidc.IDTokenVerifier + } + OAuthValidate struct { + Issuer string `json:"iss"` + Code string `json:"code"` + State string `json:"state"` + } + OAuthUserRegistration struct { + Issuer string `json:"iss"` + Subject string `json:"sub"` + Email string `json:"email"` + Name string `json:"name"` + } + OAuthIdClaims struct { + Email string `json:"email"` + EmailVerified bool `json:"email_verified"` + Name string `json:"name"` + } +) + +func (svc *OAuthService) Login(ctx context.Context, config *OAuthConfig, data OAuthValidate) (UserAuthTokenDetail, error) { + usr, err := svc.ValidateCode(ctx, config, data) + if err != nil { + return UserAuthTokenDetail{}, ErrorInvalidLogin + } + + return svc.user.createSessionToken(ctx, usr.ID, false) +} + +func (svc *OAuthService) ValidateCode(ctx context.Context, config *OAuthConfig, data OAuthValidate) (repo.UserOut, error) { + log.Debug().Str("ClientId", config.Config.ClientID).Msg("Exchanging code") + token, err := config.Config.Exchange(ctx, data.Code) + if err != nil { + log.Debug().Err(err).Msg("Failed to exchange code") + return repo.UserOut{}, err + } + + user, ok, err := svc.LoginWithIdToken(ctx, config, token) + if err != nil { + return repo.UserOut{}, err + } + if !ok { + panic("Id token check not ok") // TODO: fallback to user info + } + return user, nil +} + +func (svc *OAuthService) LoginWithIdToken(ctx context.Context, config *OAuthConfig, token *oauth2.Token) (repo.UserOut, bool, error) { + if config.Verifier == nil { + return repo.UserOut{}, false, nil + } + rawIDToken, ok := token.Extra("id_token").(string) + if !ok { + return repo.UserOut{}, ok, nil + } + + idToken, err := config.Verifier.Verify(ctx, rawIDToken) + if err != nil { + return repo.UserOut{}, false, err + } + + log.Debug(). + Str("iss", idToken.Issuer). + Str("sub", idToken.Subject). + Msg("Searching for user") + + user, err := svc.repos.OAuth.GetUserFromToken(ctx, idToken.Issuer, idToken.Subject) + if err != nil { + var notFoundError *ent.NotFoundError + notFound := errors.As(err, ¬FoundError) + if notFound { + // User does not exist, create and link a new one + user, err := svc.CreateUserIdToken(ctx, idToken) + return user, true, err + } + } + return user, true, err +} + +func (svc *OAuthService) CreateUserIdToken(ctx context.Context, token *oidc.IDToken) (repo.UserOut, error) { + var claims OAuthIdClaims + if err := token.Claims(&claims); err != nil { + return repo.UserOut{}, err + } + + // Check that user does not yet exist so that we don't clash + if _, err := svc.repos.Users.GetOneEmail(ctx, claims.Email); err != nil { + var notFoundError *ent.NotFoundError + if notFound := errors.As(err, ¬FoundError); !notFound { + return repo.UserOut{}, err + } + } else { + return repo.UserOut{}, errors.New("cannot create OAuth connection") + } + + registration := OAuthUserRegistration{ + Issuer: token.Issuer, + Subject: token.Subject, + Email: claims.Email, + Name: claims.Name, + } + return svc.CreateUser(ctx, registration) +} + +func (svc *OAuthService) CreateUser(ctx context.Context, registration OAuthUserRegistration) (repo.UserOut, error) { + log.Debug(). + Str("Subject", registration.Subject). + Str("Issuer", registration.Issuer). + Str("name", registration.Name). + Msg("Registering new OAuth user") + + var groupId uuid.UUID + if group, err := svc.repos.Groups.GroupByName(ctx, "OAuth"); err == nil { + log.Debug().Msg("joining existing oauth group") + groupId = group.ID + } else { + var notFoundError *ent.NotFoundError + if notFound := errors.As(err, ¬FoundError); notFound { + log.Debug().Msg("Creating new oauth group") + group, err := svc.repos.Groups.GroupCreate(ctx, "OAuth") + if err != nil { + log.Err(err).Msg("Failed to create group") + return repo.UserOut{}, err + } + groupId = group.ID + err = createDefaultLabels(ctx, svc.repos, groupId) + if err != nil { + return repo.UserOut{}, err + } + } else { + return repo.UserOut{}, err + } + } + + usrCreate := repo.UserCreate{ + Name: registration.Name, + Email: registration.Email, + IsSuperuser: false, // TODO: use role to check if superuser + GroupID: groupId, + IsOwner: false, // TODO: use role to check if owner? + } + usr, err := svc.repos.Users.Create(ctx, usrCreate) + if err != nil { + log.Debug().Err(err).Msg("Failed to create user") + return repo.UserOut{}, err + } + + oauthCreate := repo.OAuthCreate{ + Provider: registration.Issuer, + Subject: registration.Subject, + UserId: usr.ID, + } + _, err = svc.repos.OAuth.Create(ctx, oauthCreate) + if err != nil { + return repo.UserOut{}, err + } + + log.Debug().Msg("OAuth User created") + return usr, nil +} diff --git a/backend/internal/core/services/service_user.go b/backend/internal/core/services/service_user.go index da5d893f..60a116a9 100644 --- a/backend/internal/core/services/service_user.go +++ b/backend/internal/core/services/service_user.go @@ -96,20 +96,9 @@ func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration) // Create the default labels and locations for the group. if creatingGroup { - log.Debug().Msg("creating default labels") - for _, label := range defaultLabels() { - _, err := svc.repos.Labels.Create(ctx, usr.GroupID, label) - if err != nil { - return repo.UserOut{}, err - } - } - - log.Debug().Msg("creating default locations") - for _, location := range defaultLocations() { - _, err := svc.repos.Locations.Create(ctx, usr.GroupID, location) - if err != nil { - return repo.UserOut{}, err - } + err = createDefaultLabels(ctx, svc.repos, usr.GroupID) + if err != nil { + return repo.UserOut{}, err } } diff --git a/backend/internal/core/services/service_user_defaults.go b/backend/internal/core/services/service_user_defaults.go index 62a429dc..df00d3cc 100644 --- a/backend/internal/core/services/service_user_defaults.go +++ b/backend/internal/core/services/service_user_defaults.go @@ -1,6 +1,9 @@ package services import ( + "context" + "github.com/google/uuid" + "github.com/rs/zerolog/log" "github.com/sysadminsmedia/homebox/backend/internal/data/repo" ) @@ -55,3 +58,22 @@ func defaultLabels() []repo.LabelCreate { }, } } + +func createDefaultLabels(ctx context.Context, repos *repo.AllRepos, groupId uuid.UUID) error { + log.Debug().Msg("creating default labels") + for _, label := range defaultLabels() { + _, err := repos.Labels.Create(ctx, groupId, label) + if err != nil { + return err + } + } + + log.Debug().Msg("creating default locations") + for _, location := range defaultLocations() { + _, err := repos.Locations.Create(ctx, groupId, location) + if err != nil { + return err + } + } + return nil +} diff --git a/backend/internal/data/ent/client.go b/backend/internal/data/ent/client.go index ed34e984..a713c063 100644 --- a/backend/internal/data/ent/client.go +++ b/backend/internal/data/ent/client.go @@ -28,6 +28,7 @@ import ( "github.com/sysadminsmedia/homebox/backend/internal/data/ent/location" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/maintenanceentry" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/notifier" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/oauth" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/user" ) @@ -60,6 +61,8 @@ type Client struct { MaintenanceEntry *MaintenanceEntryClient // Notifier is the client for interacting with the Notifier builders. Notifier *NotifierClient + // OAuth is the client for interacting with the OAuth builders. + OAuth *OAuthClient // User is the client for interacting with the User builders. User *UserClient } @@ -85,6 +88,7 @@ func (c *Client) init() { c.Location = NewLocationClient(c.config) c.MaintenanceEntry = NewMaintenanceEntryClient(c.config) c.Notifier = NewNotifierClient(c.config) + c.OAuth = NewOAuthClient(c.config) c.User = NewUserClient(c.config) } @@ -190,6 +194,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) { Location: NewLocationClient(cfg), MaintenanceEntry: NewMaintenanceEntryClient(cfg), Notifier: NewNotifierClient(cfg), + OAuth: NewOAuthClient(cfg), User: NewUserClient(cfg), }, nil } @@ -222,6 +227,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) Location: NewLocationClient(cfg), MaintenanceEntry: NewMaintenanceEntryClient(cfg), Notifier: NewNotifierClient(cfg), + OAuth: NewOAuthClient(cfg), User: NewUserClient(cfg), }, nil } @@ -254,7 +260,7 @@ func (c *Client) Use(hooks ...Hook) { for _, n := range []interface{ Use(...Hook) }{ c.Attachment, c.AuthRoles, c.AuthTokens, c.Document, c.Group, c.GroupInvitationToken, c.Item, c.ItemField, c.Label, c.Location, - c.MaintenanceEntry, c.Notifier, c.User, + c.MaintenanceEntry, c.Notifier, c.OAuth, c.User, } { n.Use(hooks...) } @@ -266,7 +272,7 @@ func (c *Client) Intercept(interceptors ...Interceptor) { for _, n := range []interface{ Intercept(...Interceptor) }{ c.Attachment, c.AuthRoles, c.AuthTokens, c.Document, c.Group, c.GroupInvitationToken, c.Item, c.ItemField, c.Label, c.Location, - c.MaintenanceEntry, c.Notifier, c.User, + c.MaintenanceEntry, c.Notifier, c.OAuth, c.User, } { n.Intercept(interceptors...) } @@ -299,6 +305,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { return c.MaintenanceEntry.mutate(ctx, m) case *NotifierMutation: return c.Notifier.mutate(ctx, m) + case *OAuthMutation: + return c.OAuth.mutate(ctx, m) case *UserMutation: return c.User.mutate(ctx, m) default: @@ -2430,6 +2438,155 @@ func (c *NotifierClient) mutate(ctx context.Context, m *NotifierMutation) (Value } } +// OAuthClient is a client for the OAuth schema. +type OAuthClient struct { + config +} + +// NewOAuthClient returns a client for the OAuth from the given config. +func NewOAuthClient(c config) *OAuthClient { + return &OAuthClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `oauth.Hooks(f(g(h())))`. +func (c *OAuthClient) Use(hooks ...Hook) { + c.hooks.OAuth = append(c.hooks.OAuth, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `oauth.Intercept(f(g(h())))`. +func (c *OAuthClient) Intercept(interceptors ...Interceptor) { + c.inters.OAuth = append(c.inters.OAuth, interceptors...) +} + +// Create returns a builder for creating a OAuth entity. +func (c *OAuthClient) Create() *OAuthCreate { + mutation := newOAuthMutation(c.config, OpCreate) + return &OAuthCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of OAuth entities. +func (c *OAuthClient) CreateBulk(builders ...*OAuthCreate) *OAuthCreateBulk { + return &OAuthCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *OAuthClient) MapCreateBulk(slice any, setFunc func(*OAuthCreate, int)) *OAuthCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &OAuthCreateBulk{err: fmt.Errorf("calling to OAuthClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*OAuthCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &OAuthCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for OAuth. +func (c *OAuthClient) Update() *OAuthUpdate { + mutation := newOAuthMutation(c.config, OpUpdate) + return &OAuthUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *OAuthClient) UpdateOne(o *OAuth) *OAuthUpdateOne { + mutation := newOAuthMutation(c.config, OpUpdateOne, withOAuth(o)) + return &OAuthUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *OAuthClient) UpdateOneID(id uuid.UUID) *OAuthUpdateOne { + mutation := newOAuthMutation(c.config, OpUpdateOne, withOAuthID(id)) + return &OAuthUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for OAuth. +func (c *OAuthClient) Delete() *OAuthDelete { + mutation := newOAuthMutation(c.config, OpDelete) + return &OAuthDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *OAuthClient) DeleteOne(o *OAuth) *OAuthDeleteOne { + return c.DeleteOneID(o.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *OAuthClient) DeleteOneID(id uuid.UUID) *OAuthDeleteOne { + builder := c.Delete().Where(oauth.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &OAuthDeleteOne{builder} +} + +// Query returns a query builder for OAuth. +func (c *OAuthClient) Query() *OAuthQuery { + return &OAuthQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeOAuth}, + inters: c.Interceptors(), + } +} + +// Get returns a OAuth entity by its id. +func (c *OAuthClient) Get(ctx context.Context, id uuid.UUID) (*OAuth, error) { + return c.Query().Where(oauth.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *OAuthClient) GetX(ctx context.Context, id uuid.UUID) *OAuth { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryUser queries the user edge of a OAuth. +func (c *OAuthClient) QueryUser(o *OAuth) *UserQuery { + query := (&UserClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := o.ID + step := sqlgraph.NewStep( + sqlgraph.From(oauth.Table, oauth.FieldID, id), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, oauth.UserTable, oauth.UserColumn), + ) + fromV = sqlgraph.Neighbors(o.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *OAuthClient) Hooks() []Hook { + return c.hooks.OAuth +} + +// Interceptors returns the client interceptors. +func (c *OAuthClient) Interceptors() []Interceptor { + return c.inters.OAuth +} + +func (c *OAuthClient) mutate(ctx context.Context, m *OAuthMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&OAuthCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&OAuthUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&OAuthUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&OAuthDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown OAuth mutation op: %q", m.Op()) + } +} + // UserClient is a client for the User schema. type UserClient struct { config @@ -2586,6 +2743,22 @@ func (c *UserClient) QueryNotifiers(u *User) *NotifierQuery { return query } +// QueryOauth queries the oauth edge of a User. +func (c *UserClient) QueryOauth(u *User) *OAuthQuery { + query := (&OAuthClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := u.ID + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, id), + sqlgraph.To(oauth.Table, oauth.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, user.OauthTable, user.OauthColumn), + ) + fromV = sqlgraph.Neighbors(u.driver.Dialect(), step) + return fromV, nil + } + return query +} + // Hooks returns the client hooks. func (c *UserClient) Hooks() []Hook { return c.hooks.User @@ -2615,10 +2788,11 @@ func (c *UserClient) mutate(ctx context.Context, m *UserMutation) (Value, error) type ( hooks struct { Attachment, AuthRoles, AuthTokens, Document, Group, GroupInvitationToken, Item, - ItemField, Label, Location, MaintenanceEntry, Notifier, User []ent.Hook + ItemField, Label, Location, MaintenanceEntry, Notifier, OAuth, User []ent.Hook } inters struct { Attachment, AuthRoles, AuthTokens, Document, Group, GroupInvitationToken, Item, - ItemField, Label, Location, MaintenanceEntry, Notifier, User []ent.Interceptor + ItemField, Label, Location, MaintenanceEntry, Notifier, OAuth, + User []ent.Interceptor } ) diff --git a/backend/internal/data/ent/ent.go b/backend/internal/data/ent/ent.go index 8a0ddc4e..93c77fc7 100644 --- a/backend/internal/data/ent/ent.go +++ b/backend/internal/data/ent/ent.go @@ -24,6 +24,7 @@ import ( "github.com/sysadminsmedia/homebox/backend/internal/data/ent/location" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/maintenanceentry" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/notifier" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/oauth" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/user" ) @@ -97,6 +98,7 @@ func checkColumn(table, column string) error { location.Table: location.ValidColumn, maintenanceentry.Table: maintenanceentry.ValidColumn, notifier.Table: notifier.ValidColumn, + oauth.Table: oauth.ValidColumn, user.Table: user.ValidColumn, }) }) diff --git a/backend/internal/data/ent/has_id.go b/backend/internal/data/ent/has_id.go index 0877caac..1c6ef0b1 100644 --- a/backend/internal/data/ent/has_id.go +++ b/backend/internal/data/ent/has_id.go @@ -52,6 +52,10 @@ func (n *Notifier) GetID() uuid.UUID { return n.ID } +func (o *OAuth) GetID() uuid.UUID { + return o.ID +} + func (u *User) GetID() uuid.UUID { return u.ID } diff --git a/backend/internal/data/ent/hook/hook.go b/backend/internal/data/ent/hook/hook.go index 00e3adfb..e388d00b 100644 --- a/backend/internal/data/ent/hook/hook.go +++ b/backend/internal/data/ent/hook/hook.go @@ -153,6 +153,18 @@ func (f NotifierFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, er return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.NotifierMutation", m) } +// The OAuthFunc type is an adapter to allow the use of ordinary +// function as OAuth mutator. +type OAuthFunc func(context.Context, *ent.OAuthMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f OAuthFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.OAuthMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.OAuthMutation", m) +} + // The UserFunc type is an adapter to allow the use of ordinary // function as User mutator. type UserFunc func(context.Context, *ent.UserMutation) (ent.Value, error) diff --git a/backend/internal/data/ent/migrate/schema.go b/backend/internal/data/ent/migrate/schema.go index 2b588380..4a68acba 100644 --- a/backend/internal/data/ent/migrate/schema.go +++ b/backend/internal/data/ent/migrate/schema.go @@ -398,6 +398,36 @@ var ( }, }, } + // OauthsColumns holds the columns for the "oauths" table. + OauthsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeUUID}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, + {Name: "provider", Type: field.TypeString}, + {Name: "sub", Type: field.TypeString}, + {Name: "user_oauth", Type: field.TypeUUID, Nullable: true}, + } + // OauthsTable holds the schema information for the "oauths" table. + OauthsTable = &schema.Table{ + Name: "oauths", + Columns: OauthsColumns, + PrimaryKey: []*schema.Column{OauthsColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "oauths_users_oauth", + Columns: []*schema.Column{OauthsColumns[5]}, + RefColumns: []*schema.Column{UsersColumns[0]}, + OnDelete: schema.Cascade, + }, + }, + Indexes: []*schema.Index{ + { + Name: "oauth_provider_sub", + Unique: false, + Columns: []*schema.Column{OauthsColumns[3], OauthsColumns[4]}, + }, + }, + } // UsersColumns holds the columns for the "users" table. UsersColumns = []*schema.Column{ {Name: "id", Type: field.TypeUUID}, @@ -405,7 +435,7 @@ var ( {Name: "updated_at", Type: field.TypeTime}, {Name: "name", Type: field.TypeString, Size: 255}, {Name: "email", Type: field.TypeString, Unique: true, Size: 255}, - {Name: "password", Type: field.TypeString, Size: 255}, + {Name: "password", Type: field.TypeString, Nullable: true, Size: 255}, {Name: "is_superuser", Type: field.TypeBool, Default: false}, {Name: "superuser", Type: field.TypeBool, Default: false}, {Name: "role", Type: field.TypeEnum, Enums: []string{"user", "owner"}, Default: "user"}, @@ -465,6 +495,7 @@ var ( LocationsTable, MaintenanceEntriesTable, NotifiersTable, + OauthsTable, UsersTable, LabelItemsTable, } @@ -487,6 +518,7 @@ func init() { MaintenanceEntriesTable.ForeignKeys[0].RefTable = ItemsTable NotifiersTable.ForeignKeys[0].RefTable = GroupsTable NotifiersTable.ForeignKeys[1].RefTable = UsersTable + OauthsTable.ForeignKeys[0].RefTable = UsersTable UsersTable.ForeignKeys[0].RefTable = GroupsTable LabelItemsTable.ForeignKeys[0].RefTable = LabelsTable LabelItemsTable.ForeignKeys[1].RefTable = ItemsTable diff --git a/backend/internal/data/ent/mutation.go b/backend/internal/data/ent/mutation.go index 228de48e..3a4a0503 100644 --- a/backend/internal/data/ent/mutation.go +++ b/backend/internal/data/ent/mutation.go @@ -24,6 +24,7 @@ import ( "github.com/sysadminsmedia/homebox/backend/internal/data/ent/location" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/maintenanceentry" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/notifier" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/oauth" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/user" ) @@ -49,6 +50,7 @@ const ( TypeLocation = "Location" TypeMaintenanceEntry = "MaintenanceEntry" TypeNotifier = "Notifier" + TypeOAuth = "OAuth" TypeUser = "User" ) @@ -10669,6 +10671,567 @@ func (m *NotifierMutation) ResetEdge(name string) error { return fmt.Errorf("unknown Notifier edge %s", name) } +// OAuthMutation represents an operation that mutates the OAuth nodes in the graph. +type OAuthMutation struct { + config + op Op + typ string + id *uuid.UUID + created_at *time.Time + updated_at *time.Time + provider *string + sub *string + clearedFields map[string]struct{} + user *uuid.UUID + cleareduser bool + done bool + oldValue func(context.Context) (*OAuth, error) + predicates []predicate.OAuth +} + +var _ ent.Mutation = (*OAuthMutation)(nil) + +// oauthOption allows management of the mutation configuration using functional options. +type oauthOption func(*OAuthMutation) + +// newOAuthMutation creates new mutation for the OAuth entity. +func newOAuthMutation(c config, op Op, opts ...oauthOption) *OAuthMutation { + m := &OAuthMutation{ + config: c, + op: op, + typ: TypeOAuth, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withOAuthID sets the ID field of the mutation. +func withOAuthID(id uuid.UUID) oauthOption { + return func(m *OAuthMutation) { + var ( + err error + once sync.Once + value *OAuth + ) + m.oldValue = func(ctx context.Context) (*OAuth, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().OAuth.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withOAuth sets the old OAuth of the mutation. +func withOAuth(node *OAuth) oauthOption { + return func(m *OAuthMutation) { + m.oldValue = func(context.Context) (*OAuth, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m OAuthMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m OAuthMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of OAuth entities. +func (m *OAuthMutation) SetID(id uuid.UUID) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *OAuthMutation) ID() (id uuid.UUID, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *OAuthMutation) IDs(ctx context.Context) ([]uuid.UUID, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []uuid.UUID{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().OAuth.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *OAuthMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *OAuthMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the OAuth entity. +// If the OAuth object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *OAuthMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *OAuthMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *OAuthMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *OAuthMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the OAuth entity. +// If the OAuth object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *OAuthMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *OAuthMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// SetProvider sets the "provider" field. +func (m *OAuthMutation) SetProvider(s string) { + m.provider = &s +} + +// Provider returns the value of the "provider" field in the mutation. +func (m *OAuthMutation) Provider() (r string, exists bool) { + v := m.provider + if v == nil { + return + } + return *v, true +} + +// OldProvider returns the old "provider" field's value of the OAuth entity. +// If the OAuth object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *OAuthMutation) OldProvider(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldProvider is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldProvider requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldProvider: %w", err) + } + return oldValue.Provider, nil +} + +// ResetProvider resets all changes to the "provider" field. +func (m *OAuthMutation) ResetProvider() { + m.provider = nil +} + +// SetSub sets the "sub" field. +func (m *OAuthMutation) SetSub(s string) { + m.sub = &s +} + +// Sub returns the value of the "sub" field in the mutation. +func (m *OAuthMutation) Sub() (r string, exists bool) { + v := m.sub + if v == nil { + return + } + return *v, true +} + +// OldSub returns the old "sub" field's value of the OAuth entity. +// If the OAuth object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *OAuthMutation) OldSub(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSub is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSub requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSub: %w", err) + } + return oldValue.Sub, nil +} + +// ResetSub resets all changes to the "sub" field. +func (m *OAuthMutation) ResetSub() { + m.sub = nil +} + +// SetUserID sets the "user" edge to the User entity by id. +func (m *OAuthMutation) SetUserID(id uuid.UUID) { + m.user = &id +} + +// ClearUser clears the "user" edge to the User entity. +func (m *OAuthMutation) ClearUser() { + m.cleareduser = true +} + +// UserCleared reports if the "user" edge to the User entity was cleared. +func (m *OAuthMutation) UserCleared() bool { + return m.cleareduser +} + +// UserID returns the "user" edge ID in the mutation. +func (m *OAuthMutation) UserID() (id uuid.UUID, exists bool) { + if m.user != nil { + return *m.user, true + } + return +} + +// UserIDs returns the "user" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// UserID instead. It exists only for internal usage by the builders. +func (m *OAuthMutation) UserIDs() (ids []uuid.UUID) { + if id := m.user; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetUser resets all changes to the "user" edge. +func (m *OAuthMutation) ResetUser() { + m.user = nil + m.cleareduser = false +} + +// Where appends a list predicates to the OAuthMutation builder. +func (m *OAuthMutation) Where(ps ...predicate.OAuth) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the OAuthMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *OAuthMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.OAuth, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *OAuthMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *OAuthMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (OAuth). +func (m *OAuthMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *OAuthMutation) Fields() []string { + fields := make([]string, 0, 4) + if m.created_at != nil { + fields = append(fields, oauth.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, oauth.FieldUpdatedAt) + } + if m.provider != nil { + fields = append(fields, oauth.FieldProvider) + } + if m.sub != nil { + fields = append(fields, oauth.FieldSub) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *OAuthMutation) Field(name string) (ent.Value, bool) { + switch name { + case oauth.FieldCreatedAt: + return m.CreatedAt() + case oauth.FieldUpdatedAt: + return m.UpdatedAt() + case oauth.FieldProvider: + return m.Provider() + case oauth.FieldSub: + return m.Sub() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *OAuthMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case oauth.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case oauth.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case oauth.FieldProvider: + return m.OldProvider(ctx) + case oauth.FieldSub: + return m.OldSub(ctx) + } + return nil, fmt.Errorf("unknown OAuth field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *OAuthMutation) SetField(name string, value ent.Value) error { + switch name { + case oauth.FieldCreatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case oauth.FieldUpdatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case oauth.FieldProvider: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetProvider(v) + return nil + case oauth.FieldSub: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSub(v) + return nil + } + return fmt.Errorf("unknown OAuth field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *OAuthMutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *OAuthMutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *OAuthMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown OAuth numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *OAuthMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *OAuthMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *OAuthMutation) ClearField(name string) error { + return fmt.Errorf("unknown OAuth nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *OAuthMutation) ResetField(name string) error { + switch name { + case oauth.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case oauth.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case oauth.FieldProvider: + m.ResetProvider() + return nil + case oauth.FieldSub: + m.ResetSub() + return nil + } + return fmt.Errorf("unknown OAuth field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *OAuthMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.user != nil { + edges = append(edges, oauth.EdgeUser) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *OAuthMutation) AddedIDs(name string) []ent.Value { + switch name { + case oauth.EdgeUser: + if id := m.user; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *OAuthMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *OAuthMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *OAuthMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.cleareduser { + edges = append(edges, oauth.EdgeUser) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *OAuthMutation) EdgeCleared(name string) bool { + switch name { + case oauth.EdgeUser: + return m.cleareduser + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *OAuthMutation) ClearEdge(name string) error { + switch name { + case oauth.EdgeUser: + m.ClearUser() + return nil + } + return fmt.Errorf("unknown OAuth unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *OAuthMutation) ResetEdge(name string) error { + switch name { + case oauth.EdgeUser: + m.ResetUser() + return nil + } + return fmt.Errorf("unknown OAuth edge %s", name) +} + // UserMutation represents an operation that mutates the User nodes in the graph. type UserMutation struct { config @@ -10693,6 +11256,9 @@ type UserMutation struct { notifiers map[uuid.UUID]struct{} removednotifiers map[uuid.UUID]struct{} clearednotifiers bool + oauth map[uuid.UUID]struct{} + removedoauth map[uuid.UUID]struct{} + clearedoauth bool done bool oldValue func(context.Context) (*User, error) predicates []predicate.User @@ -10977,9 +11543,22 @@ func (m *UserMutation) OldPassword(ctx context.Context) (v string, err error) { return oldValue.Password, nil } +// ClearPassword clears the value of the "password" field. +func (m *UserMutation) ClearPassword() { + m.password = nil + m.clearedFields[user.FieldPassword] = struct{}{} +} + +// PasswordCleared returns if the "password" field was cleared in this mutation. +func (m *UserMutation) PasswordCleared() bool { + _, ok := m.clearedFields[user.FieldPassword] + return ok +} + // ResetPassword resets all changes to the "password" field. func (m *UserMutation) ResetPassword() { m.password = nil + delete(m.clearedFields, user.FieldPassword) } // SetIsSuperuser sets the "is_superuser" field. @@ -11286,6 +11865,60 @@ func (m *UserMutation) ResetNotifiers() { m.removednotifiers = nil } +// AddOauthIDs adds the "oauth" edge to the OAuth entity by ids. +func (m *UserMutation) AddOauthIDs(ids ...uuid.UUID) { + if m.oauth == nil { + m.oauth = make(map[uuid.UUID]struct{}) + } + for i := range ids { + m.oauth[ids[i]] = struct{}{} + } +} + +// ClearOauth clears the "oauth" edge to the OAuth entity. +func (m *UserMutation) ClearOauth() { + m.clearedoauth = true +} + +// OauthCleared reports if the "oauth" edge to the OAuth entity was cleared. +func (m *UserMutation) OauthCleared() bool { + return m.clearedoauth +} + +// RemoveOauthIDs removes the "oauth" edge to the OAuth entity by IDs. +func (m *UserMutation) RemoveOauthIDs(ids ...uuid.UUID) { + if m.removedoauth == nil { + m.removedoauth = make(map[uuid.UUID]struct{}) + } + for i := range ids { + delete(m.oauth, ids[i]) + m.removedoauth[ids[i]] = struct{}{} + } +} + +// RemovedOauth returns the removed IDs of the "oauth" edge to the OAuth entity. +func (m *UserMutation) RemovedOauthIDs() (ids []uuid.UUID) { + for id := range m.removedoauth { + ids = append(ids, id) + } + return +} + +// OauthIDs returns the "oauth" edge IDs in the mutation. +func (m *UserMutation) OauthIDs() (ids []uuid.UUID) { + for id := range m.oauth { + ids = append(ids, id) + } + return +} + +// ResetOauth resets all changes to the "oauth" edge. +func (m *UserMutation) ResetOauth() { + m.oauth = nil + m.clearedoauth = false + m.removedoauth = nil +} + // Where appends a list predicates to the UserMutation builder. func (m *UserMutation) Where(ps ...predicate.User) { m.predicates = append(m.predicates, ps...) @@ -11503,6 +12136,9 @@ func (m *UserMutation) AddField(name string, value ent.Value) error { // mutation. func (m *UserMutation) ClearedFields() []string { var fields []string + if m.FieldCleared(user.FieldPassword) { + fields = append(fields, user.FieldPassword) + } if m.FieldCleared(user.FieldActivatedOn) { fields = append(fields, user.FieldActivatedOn) } @@ -11520,6 +12156,9 @@ func (m *UserMutation) FieldCleared(name string) bool { // error if the field is not defined in the schema. func (m *UserMutation) ClearField(name string) error { switch name { + case user.FieldPassword: + m.ClearPassword() + return nil case user.FieldActivatedOn: m.ClearActivatedOn() return nil @@ -11564,7 +12203,7 @@ func (m *UserMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *UserMutation) AddedEdges() []string { - edges := make([]string, 0, 3) + edges := make([]string, 0, 4) if m.group != nil { edges = append(edges, user.EdgeGroup) } @@ -11574,6 +12213,9 @@ func (m *UserMutation) AddedEdges() []string { if m.notifiers != nil { edges = append(edges, user.EdgeNotifiers) } + if m.oauth != nil { + edges = append(edges, user.EdgeOauth) + } return edges } @@ -11597,19 +12239,28 @@ func (m *UserMutation) AddedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case user.EdgeOauth: + ids := make([]ent.Value, 0, len(m.oauth)) + for id := range m.oauth { + ids = append(ids, id) + } + return ids } return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *UserMutation) RemovedEdges() []string { - edges := make([]string, 0, 3) + edges := make([]string, 0, 4) if m.removedauth_tokens != nil { edges = append(edges, user.EdgeAuthTokens) } if m.removednotifiers != nil { edges = append(edges, user.EdgeNotifiers) } + if m.removedoauth != nil { + edges = append(edges, user.EdgeOauth) + } return edges } @@ -11629,13 +12280,19 @@ func (m *UserMutation) RemovedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case user.EdgeOauth: + ids := make([]ent.Value, 0, len(m.removedoauth)) + for id := range m.removedoauth { + ids = append(ids, id) + } + return ids } return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *UserMutation) ClearedEdges() []string { - edges := make([]string, 0, 3) + edges := make([]string, 0, 4) if m.clearedgroup { edges = append(edges, user.EdgeGroup) } @@ -11645,6 +12302,9 @@ func (m *UserMutation) ClearedEdges() []string { if m.clearednotifiers { edges = append(edges, user.EdgeNotifiers) } + if m.clearedoauth { + edges = append(edges, user.EdgeOauth) + } return edges } @@ -11658,6 +12318,8 @@ func (m *UserMutation) EdgeCleared(name string) bool { return m.clearedauth_tokens case user.EdgeNotifiers: return m.clearednotifiers + case user.EdgeOauth: + return m.clearedoauth } return false } @@ -11686,6 +12348,9 @@ func (m *UserMutation) ResetEdge(name string) error { case user.EdgeNotifiers: m.ResetNotifiers() return nil + case user.EdgeOauth: + m.ResetOauth() + return nil } return fmt.Errorf("unknown User edge %s", name) } diff --git a/backend/internal/data/ent/oauth.go b/backend/internal/data/ent/oauth.go new file mode 100644 index 00000000..d8ef56a5 --- /dev/null +++ b/backend/internal/data/ent/oauth.go @@ -0,0 +1,181 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/google/uuid" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/oauth" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/user" +) + +// OAuth is the model entity for the OAuth schema. +type OAuth struct { + config `json:"-"` + // ID of the ent. + ID uuid.UUID `json:"id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt time.Time `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt time.Time `json:"updated_at,omitempty"` + // Provider holds the value of the "provider" field. + Provider string `json:"provider,omitempty"` + // Sub holds the value of the "sub" field. + Sub string `json:"sub,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the OAuthQuery when eager-loading is set. + Edges OAuthEdges `json:"edges"` + user_oauth *uuid.UUID + selectValues sql.SelectValues +} + +// OAuthEdges holds the relations/edges for other nodes in the graph. +type OAuthEdges struct { + // User holds the value of the user edge. + User *User `json:"user,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// UserOrErr returns the User value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e OAuthEdges) UserOrErr() (*User, error) { + if e.loadedTypes[0] { + if e.User == nil { + // Edge was loaded but was not found. + return nil, &NotFoundError{label: user.Label} + } + return e.User, nil + } + return nil, &NotLoadedError{edge: "user"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*OAuth) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case oauth.FieldProvider, oauth.FieldSub: + values[i] = new(sql.NullString) + case oauth.FieldCreatedAt, oauth.FieldUpdatedAt: + values[i] = new(sql.NullTime) + case oauth.FieldID: + values[i] = new(uuid.UUID) + case oauth.ForeignKeys[0]: // user_oauth + values[i] = &sql.NullScanner{S: new(uuid.UUID)} + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the OAuth fields. +func (o *OAuth) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case oauth.FieldID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value != nil { + o.ID = *value + } + case oauth.FieldCreatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + o.CreatedAt = value.Time + } + case oauth.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + o.UpdatedAt = value.Time + } + case oauth.FieldProvider: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field provider", values[i]) + } else if value.Valid { + o.Provider = value.String + } + case oauth.FieldSub: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field sub", values[i]) + } else if value.Valid { + o.Sub = value.String + } + case oauth.ForeignKeys[0]: + if value, ok := values[i].(*sql.NullScanner); !ok { + return fmt.Errorf("unexpected type %T for field user_oauth", values[i]) + } else if value.Valid { + o.user_oauth = new(uuid.UUID) + *o.user_oauth = *value.S.(*uuid.UUID) + } + default: + o.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the OAuth. +// This includes values selected through modifiers, order, etc. +func (o *OAuth) Value(name string) (ent.Value, error) { + return o.selectValues.Get(name) +} + +// QueryUser queries the "user" edge of the OAuth entity. +func (o *OAuth) QueryUser() *UserQuery { + return NewOAuthClient(o.config).QueryUser(o) +} + +// Update returns a builder for updating this OAuth. +// Note that you need to call OAuth.Unwrap() before calling this method if this OAuth +// was returned from a transaction, and the transaction was committed or rolled back. +func (o *OAuth) Update() *OAuthUpdateOne { + return NewOAuthClient(o.config).UpdateOne(o) +} + +// Unwrap unwraps the OAuth entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (o *OAuth) Unwrap() *OAuth { + _tx, ok := o.config.driver.(*txDriver) + if !ok { + panic("ent: OAuth is not a transactional entity") + } + o.config.driver = _tx.drv + return o +} + +// String implements the fmt.Stringer. +func (o *OAuth) String() string { + var builder strings.Builder + builder.WriteString("OAuth(") + builder.WriteString(fmt.Sprintf("id=%v, ", o.ID)) + builder.WriteString("created_at=") + builder.WriteString(o.CreatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(o.UpdatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("provider=") + builder.WriteString(o.Provider) + builder.WriteString(", ") + builder.WriteString("sub=") + builder.WriteString(o.Sub) + builder.WriteByte(')') + return builder.String() +} + +// OAuths is a parsable slice of OAuth. +type OAuths []*OAuth diff --git a/backend/internal/data/ent/oauth/oauth.go b/backend/internal/data/ent/oauth/oauth.go new file mode 100644 index 00000000..666c85f1 --- /dev/null +++ b/backend/internal/data/ent/oauth/oauth.go @@ -0,0 +1,124 @@ +// Code generated by ent, DO NOT EDIT. + +package oauth + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/google/uuid" +) + +const ( + // Label holds the string label denoting the oauth type in the database. + Label = "oauth" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldProvider holds the string denoting the provider field in the database. + FieldProvider = "provider" + // FieldSub holds the string denoting the sub field in the database. + FieldSub = "sub" + // EdgeUser holds the string denoting the user edge name in mutations. + EdgeUser = "user" + // Table holds the table name of the oauth in the database. + Table = "oauths" + // UserTable is the table that holds the user relation/edge. + UserTable = "oauths" + // UserInverseTable is the table name for the User entity. + // It exists in this package in order to avoid circular dependency with the "user" package. + UserInverseTable = "users" + // UserColumn is the table column denoting the user relation/edge. + UserColumn = "user_oauth" +) + +// Columns holds all SQL columns for oauth fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldProvider, + FieldSub, +} + +// ForeignKeys holds the SQL foreign-keys that are owned by the "oauths" +// table and are not defined as standalone fields in the schema. +var ForeignKeys = []string{ + "user_oauth", +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + for i := range ForeignKeys { + if column == ForeignKeys[i] { + return true + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() time.Time + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() time.Time + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() time.Time + // ProviderValidator is a validator for the "provider" field. It is called by the builders before save. + ProviderValidator func(string) error + // SubValidator is a validator for the "sub" field. It is called by the builders before save. + SubValidator func(string) error + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() uuid.UUID +) + +// OrderOption defines the ordering options for the OAuth queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByProvider orders the results by the provider field. +func ByProvider(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldProvider, opts...).ToFunc() +} + +// BySub orders the results by the sub field. +func BySub(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSub, opts...).ToFunc() +} + +// ByUserField orders the results by user field. +func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...)) + } +} +func newUserStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(UserInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn), + ) +} diff --git a/backend/internal/data/ent/oauth/where.go b/backend/internal/data/ent/oauth/where.go new file mode 100644 index 00000000..c7476643 --- /dev/null +++ b/backend/internal/data/ent/oauth/where.go @@ -0,0 +1,325 @@ +// Code generated by ent, DO NOT EDIT. + +package oauth + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/google/uuid" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id uuid.UUID) predicate.OAuth { + return predicate.OAuth(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id uuid.UUID) predicate.OAuth { + return predicate.OAuth(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id uuid.UUID) predicate.OAuth { + return predicate.OAuth(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...uuid.UUID) predicate.OAuth { + return predicate.OAuth(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...uuid.UUID) predicate.OAuth { + return predicate.OAuth(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id uuid.UUID) predicate.OAuth { + return predicate.OAuth(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id uuid.UUID) predicate.OAuth { + return predicate.OAuth(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id uuid.UUID) predicate.OAuth { + return predicate.OAuth(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id uuid.UUID) predicate.OAuth { + return predicate.OAuth(sql.FieldLTE(FieldID, id)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// Provider applies equality check predicate on the "provider" field. It's identical to ProviderEQ. +func Provider(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldEQ(FieldProvider, v)) +} + +// Sub applies equality check predicate on the "sub" field. It's identical to SubEQ. +func Sub(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldEQ(FieldSub, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.OAuth { + return predicate.OAuth(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// ProviderEQ applies the EQ predicate on the "provider" field. +func ProviderEQ(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldEQ(FieldProvider, v)) +} + +// ProviderNEQ applies the NEQ predicate on the "provider" field. +func ProviderNEQ(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldNEQ(FieldProvider, v)) +} + +// ProviderIn applies the In predicate on the "provider" field. +func ProviderIn(vs ...string) predicate.OAuth { + return predicate.OAuth(sql.FieldIn(FieldProvider, vs...)) +} + +// ProviderNotIn applies the NotIn predicate on the "provider" field. +func ProviderNotIn(vs ...string) predicate.OAuth { + return predicate.OAuth(sql.FieldNotIn(FieldProvider, vs...)) +} + +// ProviderGT applies the GT predicate on the "provider" field. +func ProviderGT(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldGT(FieldProvider, v)) +} + +// ProviderGTE applies the GTE predicate on the "provider" field. +func ProviderGTE(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldGTE(FieldProvider, v)) +} + +// ProviderLT applies the LT predicate on the "provider" field. +func ProviderLT(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldLT(FieldProvider, v)) +} + +// ProviderLTE applies the LTE predicate on the "provider" field. +func ProviderLTE(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldLTE(FieldProvider, v)) +} + +// ProviderContains applies the Contains predicate on the "provider" field. +func ProviderContains(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldContains(FieldProvider, v)) +} + +// ProviderHasPrefix applies the HasPrefix predicate on the "provider" field. +func ProviderHasPrefix(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldHasPrefix(FieldProvider, v)) +} + +// ProviderHasSuffix applies the HasSuffix predicate on the "provider" field. +func ProviderHasSuffix(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldHasSuffix(FieldProvider, v)) +} + +// ProviderEqualFold applies the EqualFold predicate on the "provider" field. +func ProviderEqualFold(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldEqualFold(FieldProvider, v)) +} + +// ProviderContainsFold applies the ContainsFold predicate on the "provider" field. +func ProviderContainsFold(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldContainsFold(FieldProvider, v)) +} + +// SubEQ applies the EQ predicate on the "sub" field. +func SubEQ(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldEQ(FieldSub, v)) +} + +// SubNEQ applies the NEQ predicate on the "sub" field. +func SubNEQ(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldNEQ(FieldSub, v)) +} + +// SubIn applies the In predicate on the "sub" field. +func SubIn(vs ...string) predicate.OAuth { + return predicate.OAuth(sql.FieldIn(FieldSub, vs...)) +} + +// SubNotIn applies the NotIn predicate on the "sub" field. +func SubNotIn(vs ...string) predicate.OAuth { + return predicate.OAuth(sql.FieldNotIn(FieldSub, vs...)) +} + +// SubGT applies the GT predicate on the "sub" field. +func SubGT(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldGT(FieldSub, v)) +} + +// SubGTE applies the GTE predicate on the "sub" field. +func SubGTE(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldGTE(FieldSub, v)) +} + +// SubLT applies the LT predicate on the "sub" field. +func SubLT(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldLT(FieldSub, v)) +} + +// SubLTE applies the LTE predicate on the "sub" field. +func SubLTE(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldLTE(FieldSub, v)) +} + +// SubContains applies the Contains predicate on the "sub" field. +func SubContains(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldContains(FieldSub, v)) +} + +// SubHasPrefix applies the HasPrefix predicate on the "sub" field. +func SubHasPrefix(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldHasPrefix(FieldSub, v)) +} + +// SubHasSuffix applies the HasSuffix predicate on the "sub" field. +func SubHasSuffix(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldHasSuffix(FieldSub, v)) +} + +// SubEqualFold applies the EqualFold predicate on the "sub" field. +func SubEqualFold(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldEqualFold(FieldSub, v)) +} + +// SubContainsFold applies the ContainsFold predicate on the "sub" field. +func SubContainsFold(v string) predicate.OAuth { + return predicate.OAuth(sql.FieldContainsFold(FieldSub, v)) +} + +// HasUser applies the HasEdge predicate on the "user" edge. +func HasUser() predicate.OAuth { + return predicate.OAuth(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates). +func HasUserWith(preds ...predicate.User) predicate.OAuth { + return predicate.OAuth(func(s *sql.Selector) { + step := newUserStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.OAuth) predicate.OAuth { + return predicate.OAuth(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.OAuth) predicate.OAuth { + return predicate.OAuth(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.OAuth) predicate.OAuth { + return predicate.OAuth(sql.NotPredicates(p)) +} diff --git a/backend/internal/data/ent/oauth_create.go b/backend/internal/data/ent/oauth_create.go new file mode 100644 index 00000000..ebe601b8 --- /dev/null +++ b/backend/internal/data/ent/oauth_create.go @@ -0,0 +1,324 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/google/uuid" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/oauth" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/user" +) + +// OAuthCreate is the builder for creating a OAuth entity. +type OAuthCreate struct { + config + mutation *OAuthMutation + hooks []Hook +} + +// SetCreatedAt sets the "created_at" field. +func (oc *OAuthCreate) SetCreatedAt(t time.Time) *OAuthCreate { + oc.mutation.SetCreatedAt(t) + return oc +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (oc *OAuthCreate) SetNillableCreatedAt(t *time.Time) *OAuthCreate { + if t != nil { + oc.SetCreatedAt(*t) + } + return oc +} + +// SetUpdatedAt sets the "updated_at" field. +func (oc *OAuthCreate) SetUpdatedAt(t time.Time) *OAuthCreate { + oc.mutation.SetUpdatedAt(t) + return oc +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (oc *OAuthCreate) SetNillableUpdatedAt(t *time.Time) *OAuthCreate { + if t != nil { + oc.SetUpdatedAt(*t) + } + return oc +} + +// SetProvider sets the "provider" field. +func (oc *OAuthCreate) SetProvider(s string) *OAuthCreate { + oc.mutation.SetProvider(s) + return oc +} + +// SetSub sets the "sub" field. +func (oc *OAuthCreate) SetSub(s string) *OAuthCreate { + oc.mutation.SetSub(s) + return oc +} + +// SetID sets the "id" field. +func (oc *OAuthCreate) SetID(u uuid.UUID) *OAuthCreate { + oc.mutation.SetID(u) + return oc +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (oc *OAuthCreate) SetNillableID(u *uuid.UUID) *OAuthCreate { + if u != nil { + oc.SetID(*u) + } + return oc +} + +// SetUserID sets the "user" edge to the User entity by ID. +func (oc *OAuthCreate) SetUserID(id uuid.UUID) *OAuthCreate { + oc.mutation.SetUserID(id) + return oc +} + +// SetNillableUserID sets the "user" edge to the User entity by ID if the given value is not nil. +func (oc *OAuthCreate) SetNillableUserID(id *uuid.UUID) *OAuthCreate { + if id != nil { + oc = oc.SetUserID(*id) + } + return oc +} + +// SetUser sets the "user" edge to the User entity. +func (oc *OAuthCreate) SetUser(u *User) *OAuthCreate { + return oc.SetUserID(u.ID) +} + +// Mutation returns the OAuthMutation object of the builder. +func (oc *OAuthCreate) Mutation() *OAuthMutation { + return oc.mutation +} + +// Save creates the OAuth in the database. +func (oc *OAuthCreate) Save(ctx context.Context) (*OAuth, error) { + oc.defaults() + return withHooks(ctx, oc.sqlSave, oc.mutation, oc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (oc *OAuthCreate) SaveX(ctx context.Context) *OAuth { + v, err := oc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (oc *OAuthCreate) Exec(ctx context.Context) error { + _, err := oc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (oc *OAuthCreate) ExecX(ctx context.Context) { + if err := oc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (oc *OAuthCreate) defaults() { + if _, ok := oc.mutation.CreatedAt(); !ok { + v := oauth.DefaultCreatedAt() + oc.mutation.SetCreatedAt(v) + } + if _, ok := oc.mutation.UpdatedAt(); !ok { + v := oauth.DefaultUpdatedAt() + oc.mutation.SetUpdatedAt(v) + } + if _, ok := oc.mutation.ID(); !ok { + v := oauth.DefaultID() + oc.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (oc *OAuthCreate) check() error { + if _, ok := oc.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "OAuth.created_at"`)} + } + if _, ok := oc.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "OAuth.updated_at"`)} + } + if _, ok := oc.mutation.Provider(); !ok { + return &ValidationError{Name: "provider", err: errors.New(`ent: missing required field "OAuth.provider"`)} + } + if v, ok := oc.mutation.Provider(); ok { + if err := oauth.ProviderValidator(v); err != nil { + return &ValidationError{Name: "provider", err: fmt.Errorf(`ent: validator failed for field "OAuth.provider": %w`, err)} + } + } + if _, ok := oc.mutation.Sub(); !ok { + return &ValidationError{Name: "sub", err: errors.New(`ent: missing required field "OAuth.sub"`)} + } + if v, ok := oc.mutation.Sub(); ok { + if err := oauth.SubValidator(v); err != nil { + return &ValidationError{Name: "sub", err: fmt.Errorf(`ent: validator failed for field "OAuth.sub": %w`, err)} + } + } + return nil +} + +func (oc *OAuthCreate) sqlSave(ctx context.Context) (*OAuth, error) { + if err := oc.check(); err != nil { + return nil, err + } + _node, _spec := oc.createSpec() + if err := sqlgraph.CreateNode(ctx, oc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(*uuid.UUID); ok { + _node.ID = *id + } else if err := _node.ID.Scan(_spec.ID.Value); err != nil { + return nil, err + } + } + oc.mutation.id = &_node.ID + oc.mutation.done = true + return _node, nil +} + +func (oc *OAuthCreate) createSpec() (*OAuth, *sqlgraph.CreateSpec) { + var ( + _node = &OAuth{config: oc.config} + _spec = sqlgraph.NewCreateSpec(oauth.Table, sqlgraph.NewFieldSpec(oauth.FieldID, field.TypeUUID)) + ) + if id, ok := oc.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = &id + } + if value, ok := oc.mutation.CreatedAt(); ok { + _spec.SetField(oauth.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := oc.mutation.UpdatedAt(); ok { + _spec.SetField(oauth.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + if value, ok := oc.mutation.Provider(); ok { + _spec.SetField(oauth.FieldProvider, field.TypeString, value) + _node.Provider = value + } + if value, ok := oc.mutation.Sub(); ok { + _spec.SetField(oauth.FieldSub, field.TypeString, value) + _node.Sub = value + } + if nodes := oc.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: oauth.UserTable, + Columns: []string{oauth.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.user_oauth = &nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// OAuthCreateBulk is the builder for creating many OAuth entities in bulk. +type OAuthCreateBulk struct { + config + err error + builders []*OAuthCreate +} + +// Save creates the OAuth entities in the database. +func (ocb *OAuthCreateBulk) Save(ctx context.Context) ([]*OAuth, error) { + if ocb.err != nil { + return nil, ocb.err + } + specs := make([]*sqlgraph.CreateSpec, len(ocb.builders)) + nodes := make([]*OAuth, len(ocb.builders)) + mutators := make([]Mutator, len(ocb.builders)) + for i := range ocb.builders { + func(i int, root context.Context) { + builder := ocb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*OAuthMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, ocb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, ocb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, ocb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (ocb *OAuthCreateBulk) SaveX(ctx context.Context) []*OAuth { + v, err := ocb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (ocb *OAuthCreateBulk) Exec(ctx context.Context) error { + _, err := ocb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ocb *OAuthCreateBulk) ExecX(ctx context.Context) { + if err := ocb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/internal/data/ent/oauth_delete.go b/backend/internal/data/ent/oauth_delete.go new file mode 100644 index 00000000..68fcb901 --- /dev/null +++ b/backend/internal/data/ent/oauth_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/oauth" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate" +) + +// OAuthDelete is the builder for deleting a OAuth entity. +type OAuthDelete struct { + config + hooks []Hook + mutation *OAuthMutation +} + +// Where appends a list predicates to the OAuthDelete builder. +func (od *OAuthDelete) Where(ps ...predicate.OAuth) *OAuthDelete { + od.mutation.Where(ps...) + return od +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (od *OAuthDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, od.sqlExec, od.mutation, od.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (od *OAuthDelete) ExecX(ctx context.Context) int { + n, err := od.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (od *OAuthDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(oauth.Table, sqlgraph.NewFieldSpec(oauth.FieldID, field.TypeUUID)) + if ps := od.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, od.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + od.mutation.done = true + return affected, err +} + +// OAuthDeleteOne is the builder for deleting a single OAuth entity. +type OAuthDeleteOne struct { + od *OAuthDelete +} + +// Where appends a list predicates to the OAuthDelete builder. +func (odo *OAuthDeleteOne) Where(ps ...predicate.OAuth) *OAuthDeleteOne { + odo.od.mutation.Where(ps...) + return odo +} + +// Exec executes the deletion query. +func (odo *OAuthDeleteOne) Exec(ctx context.Context) error { + n, err := odo.od.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{oauth.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (odo *OAuthDeleteOne) ExecX(ctx context.Context) { + if err := odo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/internal/data/ent/oauth_query.go b/backend/internal/data/ent/oauth_query.go new file mode 100644 index 00000000..bd663426 --- /dev/null +++ b/backend/internal/data/ent/oauth_query.go @@ -0,0 +1,614 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/google/uuid" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/oauth" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/user" +) + +// OAuthQuery is the builder for querying OAuth entities. +type OAuthQuery struct { + config + ctx *QueryContext + order []oauth.OrderOption + inters []Interceptor + predicates []predicate.OAuth + withUser *UserQuery + withFKs bool + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the OAuthQuery builder. +func (oq *OAuthQuery) Where(ps ...predicate.OAuth) *OAuthQuery { + oq.predicates = append(oq.predicates, ps...) + return oq +} + +// Limit the number of records to be returned by this query. +func (oq *OAuthQuery) Limit(limit int) *OAuthQuery { + oq.ctx.Limit = &limit + return oq +} + +// Offset to start from. +func (oq *OAuthQuery) Offset(offset int) *OAuthQuery { + oq.ctx.Offset = &offset + return oq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (oq *OAuthQuery) Unique(unique bool) *OAuthQuery { + oq.ctx.Unique = &unique + return oq +} + +// Order specifies how the records should be ordered. +func (oq *OAuthQuery) Order(o ...oauth.OrderOption) *OAuthQuery { + oq.order = append(oq.order, o...) + return oq +} + +// QueryUser chains the current query on the "user" edge. +func (oq *OAuthQuery) QueryUser() *UserQuery { + query := (&UserClient{config: oq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := oq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := oq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(oauth.Table, oauth.FieldID, selector), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, oauth.UserTable, oauth.UserColumn), + ) + fromU = sqlgraph.SetNeighbors(oq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first OAuth entity from the query. +// Returns a *NotFoundError when no OAuth was found. +func (oq *OAuthQuery) First(ctx context.Context) (*OAuth, error) { + nodes, err := oq.Limit(1).All(setContextOp(ctx, oq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{oauth.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (oq *OAuthQuery) FirstX(ctx context.Context) *OAuth { + node, err := oq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first OAuth ID from the query. +// Returns a *NotFoundError when no OAuth ID was found. +func (oq *OAuthQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = oq.Limit(1).IDs(setContextOp(ctx, oq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{oauth.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (oq *OAuthQuery) FirstIDX(ctx context.Context) uuid.UUID { + id, err := oq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single OAuth entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one OAuth entity is found. +// Returns a *NotFoundError when no OAuth entities are found. +func (oq *OAuthQuery) Only(ctx context.Context) (*OAuth, error) { + nodes, err := oq.Limit(2).All(setContextOp(ctx, oq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{oauth.Label} + default: + return nil, &NotSingularError{oauth.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (oq *OAuthQuery) OnlyX(ctx context.Context) *OAuth { + node, err := oq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only OAuth ID in the query. +// Returns a *NotSingularError when more than one OAuth ID is found. +// Returns a *NotFoundError when no entities are found. +func (oq *OAuthQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = oq.Limit(2).IDs(setContextOp(ctx, oq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{oauth.Label} + default: + err = &NotSingularError{oauth.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (oq *OAuthQuery) OnlyIDX(ctx context.Context) uuid.UUID { + id, err := oq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of OAuths. +func (oq *OAuthQuery) All(ctx context.Context) ([]*OAuth, error) { + ctx = setContextOp(ctx, oq.ctx, "All") + if err := oq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*OAuth, *OAuthQuery]() + return withInterceptors[[]*OAuth](ctx, oq, qr, oq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (oq *OAuthQuery) AllX(ctx context.Context) []*OAuth { + nodes, err := oq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of OAuth IDs. +func (oq *OAuthQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) { + if oq.ctx.Unique == nil && oq.path != nil { + oq.Unique(true) + } + ctx = setContextOp(ctx, oq.ctx, "IDs") + if err = oq.Select(oauth.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (oq *OAuthQuery) IDsX(ctx context.Context) []uuid.UUID { + ids, err := oq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (oq *OAuthQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, oq.ctx, "Count") + if err := oq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, oq, querierCount[*OAuthQuery](), oq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (oq *OAuthQuery) CountX(ctx context.Context) int { + count, err := oq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (oq *OAuthQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, oq.ctx, "Exist") + switch _, err := oq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (oq *OAuthQuery) ExistX(ctx context.Context) bool { + exist, err := oq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the OAuthQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (oq *OAuthQuery) Clone() *OAuthQuery { + if oq == nil { + return nil + } + return &OAuthQuery{ + config: oq.config, + ctx: oq.ctx.Clone(), + order: append([]oauth.OrderOption{}, oq.order...), + inters: append([]Interceptor{}, oq.inters...), + predicates: append([]predicate.OAuth{}, oq.predicates...), + withUser: oq.withUser.Clone(), + // clone intermediate query. + sql: oq.sql.Clone(), + path: oq.path, + } +} + +// WithUser tells the query-builder to eager-load the nodes that are connected to +// the "user" edge. The optional arguments are used to configure the query builder of the edge. +func (oq *OAuthQuery) WithUser(opts ...func(*UserQuery)) *OAuthQuery { + query := (&UserClient{config: oq.config}).Query() + for _, opt := range opts { + opt(query) + } + oq.withUser = query + return oq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CreatedAt time.Time `json:"created_at,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.OAuth.Query(). +// GroupBy(oauth.FieldCreatedAt). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (oq *OAuthQuery) GroupBy(field string, fields ...string) *OAuthGroupBy { + oq.ctx.Fields = append([]string{field}, fields...) + grbuild := &OAuthGroupBy{build: oq} + grbuild.flds = &oq.ctx.Fields + grbuild.label = oauth.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CreatedAt time.Time `json:"created_at,omitempty"` +// } +// +// client.OAuth.Query(). +// Select(oauth.FieldCreatedAt). +// Scan(ctx, &v) +func (oq *OAuthQuery) Select(fields ...string) *OAuthSelect { + oq.ctx.Fields = append(oq.ctx.Fields, fields...) + sbuild := &OAuthSelect{OAuthQuery: oq} + sbuild.label = oauth.Label + sbuild.flds, sbuild.scan = &oq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a OAuthSelect configured with the given aggregations. +func (oq *OAuthQuery) Aggregate(fns ...AggregateFunc) *OAuthSelect { + return oq.Select().Aggregate(fns...) +} + +func (oq *OAuthQuery) prepareQuery(ctx context.Context) error { + for _, inter := range oq.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, oq); err != nil { + return err + } + } + } + for _, f := range oq.ctx.Fields { + if !oauth.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if oq.path != nil { + prev, err := oq.path(ctx) + if err != nil { + return err + } + oq.sql = prev + } + return nil +} + +func (oq *OAuthQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*OAuth, error) { + var ( + nodes = []*OAuth{} + withFKs = oq.withFKs + _spec = oq.querySpec() + loadedTypes = [1]bool{ + oq.withUser != nil, + } + ) + if oq.withUser != nil { + withFKs = true + } + if withFKs { + _spec.Node.Columns = append(_spec.Node.Columns, oauth.ForeignKeys...) + } + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*OAuth).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &OAuth{config: oq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, oq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := oq.withUser; query != nil { + if err := oq.loadUser(ctx, query, nodes, nil, + func(n *OAuth, e *User) { n.Edges.User = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (oq *OAuthQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*OAuth, init func(*OAuth), assign func(*OAuth, *User)) error { + ids := make([]uuid.UUID, 0, len(nodes)) + nodeids := make(map[uuid.UUID][]*OAuth) + for i := range nodes { + if nodes[i].user_oauth == nil { + continue + } + fk := *nodes[i].user_oauth + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(user.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "user_oauth" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (oq *OAuthQuery) sqlCount(ctx context.Context) (int, error) { + _spec := oq.querySpec() + _spec.Node.Columns = oq.ctx.Fields + if len(oq.ctx.Fields) > 0 { + _spec.Unique = oq.ctx.Unique != nil && *oq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, oq.driver, _spec) +} + +func (oq *OAuthQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(oauth.Table, oauth.Columns, sqlgraph.NewFieldSpec(oauth.FieldID, field.TypeUUID)) + _spec.From = oq.sql + if unique := oq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if oq.path != nil { + _spec.Unique = true + } + if fields := oq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, oauth.FieldID) + for i := range fields { + if fields[i] != oauth.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := oq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := oq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := oq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := oq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (oq *OAuthQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(oq.driver.Dialect()) + t1 := builder.Table(oauth.Table) + columns := oq.ctx.Fields + if len(columns) == 0 { + columns = oauth.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if oq.sql != nil { + selector = oq.sql + selector.Select(selector.Columns(columns...)...) + } + if oq.ctx.Unique != nil && *oq.ctx.Unique { + selector.Distinct() + } + for _, p := range oq.predicates { + p(selector) + } + for _, p := range oq.order { + p(selector) + } + if offset := oq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := oq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// OAuthGroupBy is the group-by builder for OAuth entities. +type OAuthGroupBy struct { + selector + build *OAuthQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (ogb *OAuthGroupBy) Aggregate(fns ...AggregateFunc) *OAuthGroupBy { + ogb.fns = append(ogb.fns, fns...) + return ogb +} + +// Scan applies the selector query and scans the result into the given value. +func (ogb *OAuthGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, ogb.build.ctx, "GroupBy") + if err := ogb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*OAuthQuery, *OAuthGroupBy](ctx, ogb.build, ogb, ogb.build.inters, v) +} + +func (ogb *OAuthGroupBy) sqlScan(ctx context.Context, root *OAuthQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(ogb.fns)) + for _, fn := range ogb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*ogb.flds)+len(ogb.fns)) + for _, f := range *ogb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*ogb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := ogb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// OAuthSelect is the builder for selecting fields of OAuth entities. +type OAuthSelect struct { + *OAuthQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (os *OAuthSelect) Aggregate(fns ...AggregateFunc) *OAuthSelect { + os.fns = append(os.fns, fns...) + return os +} + +// Scan applies the selector query and scans the result into the given value. +func (os *OAuthSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, os.ctx, "Select") + if err := os.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*OAuthQuery, *OAuthSelect](ctx, os.OAuthQuery, os, os.inters, v) +} + +func (os *OAuthSelect) sqlScan(ctx context.Context, root *OAuthQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(os.fns)) + for _, fn := range os.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*os.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := os.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/backend/internal/data/ent/oauth_update.go b/backend/internal/data/ent/oauth_update.go new file mode 100644 index 00000000..e38a8341 --- /dev/null +++ b/backend/internal/data/ent/oauth_update.go @@ -0,0 +1,426 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/google/uuid" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/oauth" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/user" +) + +// OAuthUpdate is the builder for updating OAuth entities. +type OAuthUpdate struct { + config + hooks []Hook + mutation *OAuthMutation +} + +// Where appends a list predicates to the OAuthUpdate builder. +func (ou *OAuthUpdate) Where(ps ...predicate.OAuth) *OAuthUpdate { + ou.mutation.Where(ps...) + return ou +} + +// SetUpdatedAt sets the "updated_at" field. +func (ou *OAuthUpdate) SetUpdatedAt(t time.Time) *OAuthUpdate { + ou.mutation.SetUpdatedAt(t) + return ou +} + +// SetProvider sets the "provider" field. +func (ou *OAuthUpdate) SetProvider(s string) *OAuthUpdate { + ou.mutation.SetProvider(s) + return ou +} + +// SetNillableProvider sets the "provider" field if the given value is not nil. +func (ou *OAuthUpdate) SetNillableProvider(s *string) *OAuthUpdate { + if s != nil { + ou.SetProvider(*s) + } + return ou +} + +// SetSub sets the "sub" field. +func (ou *OAuthUpdate) SetSub(s string) *OAuthUpdate { + ou.mutation.SetSub(s) + return ou +} + +// SetNillableSub sets the "sub" field if the given value is not nil. +func (ou *OAuthUpdate) SetNillableSub(s *string) *OAuthUpdate { + if s != nil { + ou.SetSub(*s) + } + return ou +} + +// SetUserID sets the "user" edge to the User entity by ID. +func (ou *OAuthUpdate) SetUserID(id uuid.UUID) *OAuthUpdate { + ou.mutation.SetUserID(id) + return ou +} + +// SetNillableUserID sets the "user" edge to the User entity by ID if the given value is not nil. +func (ou *OAuthUpdate) SetNillableUserID(id *uuid.UUID) *OAuthUpdate { + if id != nil { + ou = ou.SetUserID(*id) + } + return ou +} + +// SetUser sets the "user" edge to the User entity. +func (ou *OAuthUpdate) SetUser(u *User) *OAuthUpdate { + return ou.SetUserID(u.ID) +} + +// Mutation returns the OAuthMutation object of the builder. +func (ou *OAuthUpdate) Mutation() *OAuthMutation { + return ou.mutation +} + +// ClearUser clears the "user" edge to the User entity. +func (ou *OAuthUpdate) ClearUser() *OAuthUpdate { + ou.mutation.ClearUser() + return ou +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (ou *OAuthUpdate) Save(ctx context.Context) (int, error) { + ou.defaults() + return withHooks(ctx, ou.sqlSave, ou.mutation, ou.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (ou *OAuthUpdate) SaveX(ctx context.Context) int { + affected, err := ou.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (ou *OAuthUpdate) Exec(ctx context.Context) error { + _, err := ou.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ou *OAuthUpdate) ExecX(ctx context.Context) { + if err := ou.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (ou *OAuthUpdate) defaults() { + if _, ok := ou.mutation.UpdatedAt(); !ok { + v := oauth.UpdateDefaultUpdatedAt() + ou.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (ou *OAuthUpdate) check() error { + if v, ok := ou.mutation.Provider(); ok { + if err := oauth.ProviderValidator(v); err != nil { + return &ValidationError{Name: "provider", err: fmt.Errorf(`ent: validator failed for field "OAuth.provider": %w`, err)} + } + } + if v, ok := ou.mutation.Sub(); ok { + if err := oauth.SubValidator(v); err != nil { + return &ValidationError{Name: "sub", err: fmt.Errorf(`ent: validator failed for field "OAuth.sub": %w`, err)} + } + } + return nil +} + +func (ou *OAuthUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := ou.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(oauth.Table, oauth.Columns, sqlgraph.NewFieldSpec(oauth.FieldID, field.TypeUUID)) + if ps := ou.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := ou.mutation.UpdatedAt(); ok { + _spec.SetField(oauth.FieldUpdatedAt, field.TypeTime, value) + } + if value, ok := ou.mutation.Provider(); ok { + _spec.SetField(oauth.FieldProvider, field.TypeString, value) + } + if value, ok := ou.mutation.Sub(); ok { + _spec.SetField(oauth.FieldSub, field.TypeString, value) + } + if ou.mutation.UserCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: oauth.UserTable, + Columns: []string{oauth.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := ou.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: oauth.UserTable, + Columns: []string{oauth.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if n, err = sqlgraph.UpdateNodes(ctx, ou.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{oauth.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + ou.mutation.done = true + return n, nil +} + +// OAuthUpdateOne is the builder for updating a single OAuth entity. +type OAuthUpdateOne struct { + config + fields []string + hooks []Hook + mutation *OAuthMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (ouo *OAuthUpdateOne) SetUpdatedAt(t time.Time) *OAuthUpdateOne { + ouo.mutation.SetUpdatedAt(t) + return ouo +} + +// SetProvider sets the "provider" field. +func (ouo *OAuthUpdateOne) SetProvider(s string) *OAuthUpdateOne { + ouo.mutation.SetProvider(s) + return ouo +} + +// SetNillableProvider sets the "provider" field if the given value is not nil. +func (ouo *OAuthUpdateOne) SetNillableProvider(s *string) *OAuthUpdateOne { + if s != nil { + ouo.SetProvider(*s) + } + return ouo +} + +// SetSub sets the "sub" field. +func (ouo *OAuthUpdateOne) SetSub(s string) *OAuthUpdateOne { + ouo.mutation.SetSub(s) + return ouo +} + +// SetNillableSub sets the "sub" field if the given value is not nil. +func (ouo *OAuthUpdateOne) SetNillableSub(s *string) *OAuthUpdateOne { + if s != nil { + ouo.SetSub(*s) + } + return ouo +} + +// SetUserID sets the "user" edge to the User entity by ID. +func (ouo *OAuthUpdateOne) SetUserID(id uuid.UUID) *OAuthUpdateOne { + ouo.mutation.SetUserID(id) + return ouo +} + +// SetNillableUserID sets the "user" edge to the User entity by ID if the given value is not nil. +func (ouo *OAuthUpdateOne) SetNillableUserID(id *uuid.UUID) *OAuthUpdateOne { + if id != nil { + ouo = ouo.SetUserID(*id) + } + return ouo +} + +// SetUser sets the "user" edge to the User entity. +func (ouo *OAuthUpdateOne) SetUser(u *User) *OAuthUpdateOne { + return ouo.SetUserID(u.ID) +} + +// Mutation returns the OAuthMutation object of the builder. +func (ouo *OAuthUpdateOne) Mutation() *OAuthMutation { + return ouo.mutation +} + +// ClearUser clears the "user" edge to the User entity. +func (ouo *OAuthUpdateOne) ClearUser() *OAuthUpdateOne { + ouo.mutation.ClearUser() + return ouo +} + +// Where appends a list predicates to the OAuthUpdate builder. +func (ouo *OAuthUpdateOne) Where(ps ...predicate.OAuth) *OAuthUpdateOne { + ouo.mutation.Where(ps...) + return ouo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (ouo *OAuthUpdateOne) Select(field string, fields ...string) *OAuthUpdateOne { + ouo.fields = append([]string{field}, fields...) + return ouo +} + +// Save executes the query and returns the updated OAuth entity. +func (ouo *OAuthUpdateOne) Save(ctx context.Context) (*OAuth, error) { + ouo.defaults() + return withHooks(ctx, ouo.sqlSave, ouo.mutation, ouo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (ouo *OAuthUpdateOne) SaveX(ctx context.Context) *OAuth { + node, err := ouo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (ouo *OAuthUpdateOne) Exec(ctx context.Context) error { + _, err := ouo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ouo *OAuthUpdateOne) ExecX(ctx context.Context) { + if err := ouo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (ouo *OAuthUpdateOne) defaults() { + if _, ok := ouo.mutation.UpdatedAt(); !ok { + v := oauth.UpdateDefaultUpdatedAt() + ouo.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (ouo *OAuthUpdateOne) check() error { + if v, ok := ouo.mutation.Provider(); ok { + if err := oauth.ProviderValidator(v); err != nil { + return &ValidationError{Name: "provider", err: fmt.Errorf(`ent: validator failed for field "OAuth.provider": %w`, err)} + } + } + if v, ok := ouo.mutation.Sub(); ok { + if err := oauth.SubValidator(v); err != nil { + return &ValidationError{Name: "sub", err: fmt.Errorf(`ent: validator failed for field "OAuth.sub": %w`, err)} + } + } + return nil +} + +func (ouo *OAuthUpdateOne) sqlSave(ctx context.Context) (_node *OAuth, err error) { + if err := ouo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(oauth.Table, oauth.Columns, sqlgraph.NewFieldSpec(oauth.FieldID, field.TypeUUID)) + id, ok := ouo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "OAuth.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := ouo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, oauth.FieldID) + for _, f := range fields { + if !oauth.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != oauth.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := ouo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := ouo.mutation.UpdatedAt(); ok { + _spec.SetField(oauth.FieldUpdatedAt, field.TypeTime, value) + } + if value, ok := ouo.mutation.Provider(); ok { + _spec.SetField(oauth.FieldProvider, field.TypeString, value) + } + if value, ok := ouo.mutation.Sub(); ok { + _spec.SetField(oauth.FieldSub, field.TypeString, value) + } + if ouo.mutation.UserCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: oauth.UserTable, + Columns: []string{oauth.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := ouo.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: oauth.UserTable, + Columns: []string{oauth.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &OAuth{config: ouo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, ouo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{oauth.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + ouo.mutation.done = true + return _node, nil +} diff --git a/backend/internal/data/ent/predicate/predicate.go b/backend/internal/data/ent/predicate/predicate.go index bd36616e..f7d1e01f 100644 --- a/backend/internal/data/ent/predicate/predicate.go +++ b/backend/internal/data/ent/predicate/predicate.go @@ -42,5 +42,8 @@ type MaintenanceEntry func(*sql.Selector) // Notifier is the predicate function for notifier builders. type Notifier func(*sql.Selector) +// OAuth is the predicate function for oauth builders. +type OAuth func(*sql.Selector) + // User is the predicate function for user builders. type User func(*sql.Selector) diff --git a/backend/internal/data/ent/runtime.go b/backend/internal/data/ent/runtime.go index 12e507bd..5b570096 100644 --- a/backend/internal/data/ent/runtime.go +++ b/backend/internal/data/ent/runtime.go @@ -17,6 +17,7 @@ import ( "github.com/sysadminsmedia/homebox/backend/internal/data/ent/location" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/maintenanceentry" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/notifier" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/oauth" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/schema" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/user" ) @@ -544,6 +545,33 @@ func init() { notifierDescID := notifierMixinFields0[0].Descriptor() // notifier.DefaultID holds the default value on creation for the id field. notifier.DefaultID = notifierDescID.Default.(func() uuid.UUID) + oauthMixin := schema.OAuth{}.Mixin() + oauthMixinFields0 := oauthMixin[0].Fields() + _ = oauthMixinFields0 + oauthFields := schema.OAuth{}.Fields() + _ = oauthFields + // oauthDescCreatedAt is the schema descriptor for created_at field. + oauthDescCreatedAt := oauthMixinFields0[1].Descriptor() + // oauth.DefaultCreatedAt holds the default value on creation for the created_at field. + oauth.DefaultCreatedAt = oauthDescCreatedAt.Default.(func() time.Time) + // oauthDescUpdatedAt is the schema descriptor for updated_at field. + oauthDescUpdatedAt := oauthMixinFields0[2].Descriptor() + // oauth.DefaultUpdatedAt holds the default value on creation for the updated_at field. + oauth.DefaultUpdatedAt = oauthDescUpdatedAt.Default.(func() time.Time) + // oauth.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + oauth.UpdateDefaultUpdatedAt = oauthDescUpdatedAt.UpdateDefault.(func() time.Time) + // oauthDescProvider is the schema descriptor for provider field. + oauthDescProvider := oauthFields[0].Descriptor() + // oauth.ProviderValidator is a validator for the "provider" field. It is called by the builders before save. + oauth.ProviderValidator = oauthDescProvider.Validators[0].(func(string) error) + // oauthDescSub is the schema descriptor for sub field. + oauthDescSub := oauthFields[1].Descriptor() + // oauth.SubValidator is a validator for the "sub" field. It is called by the builders before save. + oauth.SubValidator = oauthDescSub.Validators[0].(func(string) error) + // oauthDescID is the schema descriptor for id field. + oauthDescID := oauthMixinFields0[0].Descriptor() + // oauth.DefaultID holds the default value on creation for the id field. + oauth.DefaultID = oauthDescID.Default.(func() uuid.UUID) userMixin := schema.User{}.Mixin() userMixinFields0 := userMixin[0].Fields() _ = userMixinFields0 diff --git a/backend/internal/data/ent/schema/oauth.go b/backend/internal/data/ent/schema/oauth.go new file mode 100644 index 00000000..94c679c9 --- /dev/null +++ b/backend/internal/data/ent/schema/oauth.go @@ -0,0 +1,42 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/schema/mixins" +) + +type OAuth struct { + ent.Schema +} + +func (OAuth) Mixin() []ent.Mixin { + return []ent.Mixin{ + mixins.BaseMixin{}, + } +} + +func (OAuth) Fields() []ent.Field { + return []ent.Field{ + field.String("provider"). + NotEmpty(), + field.String("sub"). + NotEmpty(), + } +} + +func (OAuth) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("user", User.Type). + Ref("oauth"). + Unique(), + } +} + +func (OAuth) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("provider", "sub"), + } +} diff --git a/backend/internal/data/ent/schema/user.go b/backend/internal/data/ent/schema/user.go index bd747aea..180c15c3 100644 --- a/backend/internal/data/ent/schema/user.go +++ b/backend/internal/data/ent/schema/user.go @@ -35,7 +35,8 @@ func (User) Fields() []ent.Field { field.String("password"). MaxLen(255). NotEmpty(). - Sensitive(), + Sensitive(). + Optional(), field.Bool("is_superuser"). Default(false), field.Bool("superuser"). @@ -59,6 +60,10 @@ func (User) Edges() []ent.Edge { Annotations(entsql.Annotation{ OnDelete: entsql.Cascade, }), + edge.To("oauth", OAuth.Type). + Annotations(entsql.Annotation{ + OnDelete: entsql.Cascade, + }), } } diff --git a/backend/internal/data/ent/tx.go b/backend/internal/data/ent/tx.go index f51f2ac7..01359180 100644 --- a/backend/internal/data/ent/tx.go +++ b/backend/internal/data/ent/tx.go @@ -36,6 +36,8 @@ type Tx struct { MaintenanceEntry *MaintenanceEntryClient // Notifier is the client for interacting with the Notifier builders. Notifier *NotifierClient + // OAuth is the client for interacting with the OAuth builders. + OAuth *OAuthClient // User is the client for interacting with the User builders. User *UserClient @@ -181,6 +183,7 @@ func (tx *Tx) init() { tx.Location = NewLocationClient(tx.config) tx.MaintenanceEntry = NewMaintenanceEntryClient(tx.config) tx.Notifier = NewNotifierClient(tx.config) + tx.OAuth = NewOAuthClient(tx.config) tx.User = NewUserClient(tx.config) } diff --git a/backend/internal/data/ent/user.go b/backend/internal/data/ent/user.go index b00f838b..c233f53e 100644 --- a/backend/internal/data/ent/user.go +++ b/backend/internal/data/ent/user.go @@ -52,9 +52,11 @@ type UserEdges struct { AuthTokens []*AuthTokens `json:"auth_tokens,omitempty"` // Notifiers holds the value of the notifiers edge. Notifiers []*Notifier `json:"notifiers,omitempty"` + // Oauth holds the value of the oauth edge. + Oauth []*OAuth `json:"oauth,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [3]bool + loadedTypes [4]bool } // GroupOrErr returns the Group value or an error if the edge @@ -88,6 +90,15 @@ func (e UserEdges) NotifiersOrErr() ([]*Notifier, error) { return nil, &NotLoadedError{edge: "notifiers"} } +// OauthOrErr returns the Oauth value or an error if the edge +// was not loaded in eager-loading. +func (e UserEdges) OauthOrErr() ([]*OAuth, error) { + if e.loadedTypes[3] { + return e.Oauth, nil + } + return nil, &NotLoadedError{edge: "oauth"} +} + // scanValues returns the types for scanning values from sql.Rows. func (*User) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) @@ -213,6 +224,11 @@ func (u *User) QueryNotifiers() *NotifierQuery { return NewUserClient(u.config).QueryNotifiers(u) } +// QueryOauth queries the "oauth" edge of the User entity. +func (u *User) QueryOauth() *OAuthQuery { + return NewUserClient(u.config).QueryOauth(u) +} + // Update returns a builder for updating this User. // Note that you need to call User.Unwrap() before calling this method if this User // was returned from a transaction, and the transaction was committed or rolled back. diff --git a/backend/internal/data/ent/user/user.go b/backend/internal/data/ent/user/user.go index 33b657bd..f6a553d3 100644 --- a/backend/internal/data/ent/user/user.go +++ b/backend/internal/data/ent/user/user.go @@ -40,6 +40,8 @@ const ( EdgeAuthTokens = "auth_tokens" // EdgeNotifiers holds the string denoting the notifiers edge name in mutations. EdgeNotifiers = "notifiers" + // EdgeOauth holds the string denoting the oauth edge name in mutations. + EdgeOauth = "oauth" // Table holds the table name of the user in the database. Table = "users" // GroupTable is the table that holds the group relation/edge. @@ -63,6 +65,13 @@ const ( NotifiersInverseTable = "notifiers" // NotifiersColumn is the table column denoting the notifiers relation/edge. NotifiersColumn = "user_id" + // OauthTable is the table that holds the oauth relation/edge. + OauthTable = "oauths" + // OauthInverseTable is the table name for the OAuth entity. + // It exists in this package in order to avoid circular dependency with the "oauth" package. + OauthInverseTable = "oauths" + // OauthColumn is the table column denoting the oauth relation/edge. + OauthColumn = "user_oauth" ) // Columns holds all SQL columns for user fields. @@ -234,6 +243,20 @@ func ByNotifiers(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { sqlgraph.OrderByNeighborTerms(s, newNotifiersStep(), append([]sql.OrderTerm{term}, terms...)...) } } + +// ByOauthCount orders the results by oauth count. +func ByOauthCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newOauthStep(), opts...) + } +} + +// ByOauth orders the results by oauth terms. +func ByOauth(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newOauthStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} func newGroupStep() *sqlgraph.Step { return sqlgraph.NewStep( sqlgraph.From(Table, FieldID), @@ -255,3 +278,10 @@ func newNotifiersStep() *sqlgraph.Step { sqlgraph.Edge(sqlgraph.O2M, false, NotifiersTable, NotifiersColumn), ) } +func newOauthStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(OauthInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, OauthTable, OauthColumn), + ) +} diff --git a/backend/internal/data/ent/user/where.go b/backend/internal/data/ent/user/where.go index f70676bc..a9204f21 100644 --- a/backend/internal/data/ent/user/where.go +++ b/backend/internal/data/ent/user/where.go @@ -361,6 +361,16 @@ func PasswordHasSuffix(v string) predicate.User { return predicate.User(sql.FieldHasSuffix(FieldPassword, v)) } +// PasswordIsNil applies the IsNil predicate on the "password" field. +func PasswordIsNil() predicate.User { + return predicate.User(sql.FieldIsNull(FieldPassword)) +} + +// PasswordNotNil applies the NotNil predicate on the "password" field. +func PasswordNotNil() predicate.User { + return predicate.User(sql.FieldNotNull(FieldPassword)) +} + // PasswordEqualFold applies the EqualFold predicate on the "password" field. func PasswordEqualFold(v string) predicate.User { return predicate.User(sql.FieldEqualFold(FieldPassword, v)) @@ -530,6 +540,29 @@ func HasNotifiersWith(preds ...predicate.Notifier) predicate.User { }) } +// HasOauth applies the HasEdge predicate on the "oauth" edge. +func HasOauth() predicate.User { + return predicate.User(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, OauthTable, OauthColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasOauthWith applies the HasEdge predicate on the "oauth" edge with a given conditions (other predicates). +func HasOauthWith(preds ...predicate.OAuth) predicate.User { + return predicate.User(func(s *sql.Selector) { + step := newOauthStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + // And groups predicates with the AND operator between them. func And(predicates ...predicate.User) predicate.User { return predicate.User(sql.AndPredicates(predicates...)) diff --git a/backend/internal/data/ent/user_create.go b/backend/internal/data/ent/user_create.go index 7367ddb7..5c657d9c 100644 --- a/backend/internal/data/ent/user_create.go +++ b/backend/internal/data/ent/user_create.go @@ -14,6 +14,7 @@ import ( "github.com/sysadminsmedia/homebox/backend/internal/data/ent/authtokens" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/group" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/notifier" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/oauth" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/user" ) @@ -70,6 +71,14 @@ func (uc *UserCreate) SetPassword(s string) *UserCreate { return uc } +// SetNillablePassword sets the "password" field if the given value is not nil. +func (uc *UserCreate) SetNillablePassword(s *string) *UserCreate { + if s != nil { + uc.SetPassword(*s) + } + return uc +} + // SetIsSuperuser sets the "is_superuser" field. func (uc *UserCreate) SetIsSuperuser(b bool) *UserCreate { uc.mutation.SetIsSuperuser(b) @@ -181,6 +190,21 @@ func (uc *UserCreate) AddNotifiers(n ...*Notifier) *UserCreate { return uc.AddNotifierIDs(ids...) } +// AddOauthIDs adds the "oauth" edge to the OAuth entity by IDs. +func (uc *UserCreate) AddOauthIDs(ids ...uuid.UUID) *UserCreate { + uc.mutation.AddOauthIDs(ids...) + return uc +} + +// AddOauth adds the "oauth" edges to the OAuth entity. +func (uc *UserCreate) AddOauth(o ...*OAuth) *UserCreate { + ids := make([]uuid.UUID, len(o)) + for i := range o { + ids[i] = o[i].ID + } + return uc.AddOauthIDs(ids...) +} + // Mutation returns the UserMutation object of the builder. func (uc *UserCreate) Mutation() *UserMutation { return uc.mutation @@ -266,9 +290,6 @@ func (uc *UserCreate) check() error { return &ValidationError{Name: "email", err: fmt.Errorf(`ent: validator failed for field "User.email": %w`, err)} } } - if _, ok := uc.mutation.Password(); !ok { - return &ValidationError{Name: "password", err: errors.New(`ent: missing required field "User.password"`)} - } if v, ok := uc.mutation.Password(); ok { if err := user.PasswordValidator(v); err != nil { return &ValidationError{Name: "password", err: fmt.Errorf(`ent: validator failed for field "User.password": %w`, err)} @@ -411,6 +432,22 @@ func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { } _spec.Edges = append(_spec.Edges, edge) } + if nodes := uc.mutation.OauthIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.OauthTable, + Columns: []string{user.OauthColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(oauth.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } return _node, _spec } diff --git a/backend/internal/data/ent/user_query.go b/backend/internal/data/ent/user_query.go index dce4037d..26212db3 100644 --- a/backend/internal/data/ent/user_query.go +++ b/backend/internal/data/ent/user_query.go @@ -15,6 +15,7 @@ import ( "github.com/sysadminsmedia/homebox/backend/internal/data/ent/authtokens" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/group" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/notifier" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/oauth" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/user" ) @@ -29,6 +30,7 @@ type UserQuery struct { withGroup *GroupQuery withAuthTokens *AuthTokensQuery withNotifiers *NotifierQuery + withOauth *OAuthQuery withFKs bool // intermediate query (i.e. traversal path). sql *sql.Selector @@ -132,6 +134,28 @@ func (uq *UserQuery) QueryNotifiers() *NotifierQuery { return query } +// QueryOauth chains the current query on the "oauth" edge. +func (uq *UserQuery) QueryOauth() *OAuthQuery { + query := (&OAuthClient{config: uq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := uq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := uq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, selector), + sqlgraph.To(oauth.Table, oauth.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, user.OauthTable, user.OauthColumn), + ) + fromU = sqlgraph.SetNeighbors(uq.driver.Dialect(), step) + return fromU, nil + } + return query +} + // First returns the first User entity from the query. // Returns a *NotFoundError when no User was found. func (uq *UserQuery) First(ctx context.Context) (*User, error) { @@ -327,6 +351,7 @@ func (uq *UserQuery) Clone() *UserQuery { withGroup: uq.withGroup.Clone(), withAuthTokens: uq.withAuthTokens.Clone(), withNotifiers: uq.withNotifiers.Clone(), + withOauth: uq.withOauth.Clone(), // clone intermediate query. sql: uq.sql.Clone(), path: uq.path, @@ -366,6 +391,17 @@ func (uq *UserQuery) WithNotifiers(opts ...func(*NotifierQuery)) *UserQuery { return uq } +// WithOauth tells the query-builder to eager-load the nodes that are connected to +// the "oauth" edge. The optional arguments are used to configure the query builder of the edge. +func (uq *UserQuery) WithOauth(opts ...func(*OAuthQuery)) *UserQuery { + query := (&OAuthClient{config: uq.config}).Query() + for _, opt := range opts { + opt(query) + } + uq.withOauth = query + return uq +} + // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // @@ -445,10 +481,11 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e nodes = []*User{} withFKs = uq.withFKs _spec = uq.querySpec() - loadedTypes = [3]bool{ + loadedTypes = [4]bool{ uq.withGroup != nil, uq.withAuthTokens != nil, uq.withNotifiers != nil, + uq.withOauth != nil, } ) if uq.withGroup != nil { @@ -495,6 +532,13 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e return nil, err } } + if query := uq.withOauth; query != nil { + if err := uq.loadOauth(ctx, query, nodes, + func(n *User) { n.Edges.Oauth = []*OAuth{} }, + func(n *User, e *OAuth) { n.Edges.Oauth = append(n.Edges.Oauth, e) }); err != nil { + return nil, err + } + } return nodes, nil } @@ -591,6 +635,37 @@ func (uq *UserQuery) loadNotifiers(ctx context.Context, query *NotifierQuery, no } return nil } +func (uq *UserQuery) loadOauth(ctx context.Context, query *OAuthQuery, nodes []*User, init func(*User), assign func(*User, *OAuth)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[uuid.UUID]*User) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + query.withFKs = true + query.Where(predicate.OAuth(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(user.OauthColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.user_oauth + if fk == nil { + return fmt.Errorf(`foreign-key "user_oauth" is nil for node %v`, n.ID) + } + node, ok := nodeids[*fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "user_oauth" returned %v for node %v`, *fk, n.ID) + } + assign(node, n) + } + return nil +} func (uq *UserQuery) sqlCount(ctx context.Context) (int, error) { _spec := uq.querySpec() diff --git a/backend/internal/data/ent/user_update.go b/backend/internal/data/ent/user_update.go index 3277713d..2a8c234a 100644 --- a/backend/internal/data/ent/user_update.go +++ b/backend/internal/data/ent/user_update.go @@ -15,6 +15,7 @@ import ( "github.com/sysadminsmedia/homebox/backend/internal/data/ent/authtokens" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/group" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/notifier" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/oauth" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/predicate" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/user" ) @@ -80,6 +81,12 @@ func (uu *UserUpdate) SetNillablePassword(s *string) *UserUpdate { return uu } +// ClearPassword clears the value of the "password" field. +func (uu *UserUpdate) ClearPassword() *UserUpdate { + uu.mutation.ClearPassword() + return uu +} + // SetIsSuperuser sets the "is_superuser" field. func (uu *UserUpdate) SetIsSuperuser(b bool) *UserUpdate { uu.mutation.SetIsSuperuser(b) @@ -183,6 +190,21 @@ func (uu *UserUpdate) AddNotifiers(n ...*Notifier) *UserUpdate { return uu.AddNotifierIDs(ids...) } +// AddOauthIDs adds the "oauth" edge to the OAuth entity by IDs. +func (uu *UserUpdate) AddOauthIDs(ids ...uuid.UUID) *UserUpdate { + uu.mutation.AddOauthIDs(ids...) + return uu +} + +// AddOauth adds the "oauth" edges to the OAuth entity. +func (uu *UserUpdate) AddOauth(o ...*OAuth) *UserUpdate { + ids := make([]uuid.UUID, len(o)) + for i := range o { + ids[i] = o[i].ID + } + return uu.AddOauthIDs(ids...) +} + // Mutation returns the UserMutation object of the builder. func (uu *UserUpdate) Mutation() *UserMutation { return uu.mutation @@ -236,6 +258,27 @@ func (uu *UserUpdate) RemoveNotifiers(n ...*Notifier) *UserUpdate { return uu.RemoveNotifierIDs(ids...) } +// ClearOauth clears all "oauth" edges to the OAuth entity. +func (uu *UserUpdate) ClearOauth() *UserUpdate { + uu.mutation.ClearOauth() + return uu +} + +// RemoveOauthIDs removes the "oauth" edge to OAuth entities by IDs. +func (uu *UserUpdate) RemoveOauthIDs(ids ...uuid.UUID) *UserUpdate { + uu.mutation.RemoveOauthIDs(ids...) + return uu +} + +// RemoveOauth removes "oauth" edges to OAuth entities. +func (uu *UserUpdate) RemoveOauth(o ...*OAuth) *UserUpdate { + ids := make([]uuid.UUID, len(o)) + for i := range o { + ids[i] = o[i].ID + } + return uu.RemoveOauthIDs(ids...) +} + // Save executes the query and returns the number of nodes affected by the update operation. func (uu *UserUpdate) Save(ctx context.Context) (int, error) { uu.defaults() @@ -324,6 +367,9 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := uu.mutation.Password(); ok { _spec.SetField(user.FieldPassword, field.TypeString, value) } + if uu.mutation.PasswordCleared() { + _spec.ClearField(user.FieldPassword, field.TypeString) + } if value, ok := uu.mutation.IsSuperuser(); ok { _spec.SetField(user.FieldIsSuperuser, field.TypeBool, value) } @@ -458,6 +504,51 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if uu.mutation.OauthCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.OauthTable, + Columns: []string{user.OauthColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(oauth.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.RemovedOauthIDs(); len(nodes) > 0 && !uu.mutation.OauthCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.OauthTable, + Columns: []string{user.OauthColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(oauth.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.OauthIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.OauthTable, + Columns: []string{user.OauthColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(oauth.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if n, err = sqlgraph.UpdateNodes(ctx, uu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{user.Label} @@ -526,6 +617,12 @@ func (uuo *UserUpdateOne) SetNillablePassword(s *string) *UserUpdateOne { return uuo } +// ClearPassword clears the value of the "password" field. +func (uuo *UserUpdateOne) ClearPassword() *UserUpdateOne { + uuo.mutation.ClearPassword() + return uuo +} + // SetIsSuperuser sets the "is_superuser" field. func (uuo *UserUpdateOne) SetIsSuperuser(b bool) *UserUpdateOne { uuo.mutation.SetIsSuperuser(b) @@ -629,6 +726,21 @@ func (uuo *UserUpdateOne) AddNotifiers(n ...*Notifier) *UserUpdateOne { return uuo.AddNotifierIDs(ids...) } +// AddOauthIDs adds the "oauth" edge to the OAuth entity by IDs. +func (uuo *UserUpdateOne) AddOauthIDs(ids ...uuid.UUID) *UserUpdateOne { + uuo.mutation.AddOauthIDs(ids...) + return uuo +} + +// AddOauth adds the "oauth" edges to the OAuth entity. +func (uuo *UserUpdateOne) AddOauth(o ...*OAuth) *UserUpdateOne { + ids := make([]uuid.UUID, len(o)) + for i := range o { + ids[i] = o[i].ID + } + return uuo.AddOauthIDs(ids...) +} + // Mutation returns the UserMutation object of the builder. func (uuo *UserUpdateOne) Mutation() *UserMutation { return uuo.mutation @@ -682,6 +794,27 @@ func (uuo *UserUpdateOne) RemoveNotifiers(n ...*Notifier) *UserUpdateOne { return uuo.RemoveNotifierIDs(ids...) } +// ClearOauth clears all "oauth" edges to the OAuth entity. +func (uuo *UserUpdateOne) ClearOauth() *UserUpdateOne { + uuo.mutation.ClearOauth() + return uuo +} + +// RemoveOauthIDs removes the "oauth" edge to OAuth entities by IDs. +func (uuo *UserUpdateOne) RemoveOauthIDs(ids ...uuid.UUID) *UserUpdateOne { + uuo.mutation.RemoveOauthIDs(ids...) + return uuo +} + +// RemoveOauth removes "oauth" edges to OAuth entities. +func (uuo *UserUpdateOne) RemoveOauth(o ...*OAuth) *UserUpdateOne { + ids := make([]uuid.UUID, len(o)) + for i := range o { + ids[i] = o[i].ID + } + return uuo.RemoveOauthIDs(ids...) +} + // Where appends a list predicates to the UserUpdate builder. func (uuo *UserUpdateOne) Where(ps ...predicate.User) *UserUpdateOne { uuo.mutation.Where(ps...) @@ -800,6 +933,9 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) if value, ok := uuo.mutation.Password(); ok { _spec.SetField(user.FieldPassword, field.TypeString, value) } + if uuo.mutation.PasswordCleared() { + _spec.ClearField(user.FieldPassword, field.TypeString) + } if value, ok := uuo.mutation.IsSuperuser(); ok { _spec.SetField(user.FieldIsSuperuser, field.TypeBool, value) } @@ -934,6 +1070,51 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if uuo.mutation.OauthCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.OauthTable, + Columns: []string{user.OauthColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(oauth.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.RemovedOauthIDs(); len(nodes) > 0 && !uuo.mutation.OauthCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.OauthTable, + Columns: []string{user.OauthColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(oauth.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.OauthIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.OauthTable, + Columns: []string{user.OauthColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(oauth.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _node = &User{config: uuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/backend/internal/data/migrations/migrations/20240811221935_oidc.sql b/backend/internal/data/migrations/migrations/20240811221935_oidc.sql new file mode 100644 index 00000000..f786fd2b --- /dev/null +++ b/backend/internal/data/migrations/migrations/20240811221935_oidc.sql @@ -0,0 +1,18 @@ +-- Disable the enforcement of foreign-keys constraints +PRAGMA foreign_keys = off; +-- Create "new_users" table +CREATE TABLE `new_users` (`id` uuid NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `name` text NOT NULL, `email` text NOT NULL, `password` text NULL, `is_superuser` bool NOT NULL DEFAULT (false), `superuser` bool NOT NULL DEFAULT (false), `role` text NOT NULL DEFAULT ('user'), `activated_on` datetime NULL, `group_users` uuid NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `users_groups_users` FOREIGN KEY (`group_users`) REFERENCES `groups` (`id`) ON DELETE CASCADE); +-- Copy rows from old table "users" to new temporary table "new_users" +INSERT INTO `new_users` (`id`, `created_at`, `updated_at`, `name`, `email`, `password`, `is_superuser`, `superuser`, `role`, `activated_on`, `group_users`) SELECT `id`, `created_at`, `updated_at`, `name`, `email`, `password`, `is_superuser`, `superuser`, `role`, `activated_on`, `group_users` FROM `users`; +-- Drop "users" table after copying rows +DROP TABLE `users`; +-- Rename temporary table "new_users" to "users" +ALTER TABLE `new_users` RENAME TO `users`; +-- Create index "users_email_key" to table: "users" +CREATE UNIQUE INDEX `users_email_key` ON `users` (`email`); +-- Create "oauths" table +CREATE TABLE `oauths` (`id` uuid NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `provider` text NOT NULL, `sub` text NOT NULL, `user_oauth` uuid NULL, PRIMARY KEY (`id`), CONSTRAINT `oauths_users_oauth` FOREIGN KEY (`user_oauth`) REFERENCES `users` (`id`) ON DELETE CASCADE); +-- Create index "oauth_provider_sub" to table: "oauths" +CREATE INDEX `oauth_provider_sub` ON `oauths` (`provider`, `sub`); +-- Enable back the enforcement of foreign-keys constraints +PRAGMA foreign_keys = on; diff --git a/backend/internal/data/migrations/migrations/atlas.sum b/backend/internal/data/migrations/migrations/atlas.sum index e8d99a61..d9f96ab9 100644 --- a/backend/internal/data/migrations/migrations/atlas.sum +++ b/backend/internal/data/migrations/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:sjJCTAqc9FG8BKBIzh5ZynYD/Ilz6vnLqM4XX83WQ4M= +h1:z8W8Bcqj4C40I5FbesiHKuqwSHZD1Cc52sS6TBdhvuE= 20220929052825_init.sql h1:ZlCqm1wzjDmofeAcSX3jE4h4VcdTNGpRg2eabztDy9Q= 20221001210956_group_invitations.sql h1:YQKJFtE39wFOcRNbZQ/d+ZlHwrcfcsZlcv/pLEYdpjw= 20221009173029_add_user_roles.sql h1:vWmzAfgEWQeGk0Vn70zfVPCcfEZth3E0JcvyKTjpYyU= @@ -13,3 +13,4 @@ h1:sjJCTAqc9FG8BKBIzh5ZynYD/Ilz6vnLqM4XX83WQ4M= 20230305065819_add_notifier_types.sql h1:r5xrgCKYQ2o9byBqYeAX1zdp94BLdaxf4vq9OmGHNl0= 20230305071524_add_group_id_to_notifiers.sql h1:xDShqbyClcFhvJbwclOHdczgXbdffkxXNWjV61hL/t4= 20231006213457_add_primary_attachment_flag.sql h1:J4tMSJQFa7vaj0jpnh8YKTssdyIjRyq6RXDXZIzDDu4= +20240811221935_oidc.sql h1:hsURA/DBBTlcTKEOjLENHBD/pdw8QBOUtZY0x+o9LLk= diff --git a/backend/internal/data/repo/repo_group.go b/backend/internal/data/repo/repo_group.go index d2e4abda..a427d22f 100644 --- a/backend/internal/data/repo/repo_group.go +++ b/backend/internal/data/repo/repo_group.go @@ -277,6 +277,10 @@ func (r *GroupRepository) GroupByID(ctx context.Context, id uuid.UUID) (Group, e return r.groupMapper.MapErr(r.db.Group.Get(ctx, id)) } +func (r *GroupRepository) GroupByName(ctx context.Context, name string) (Group, error) { + return r.groupMapper.MapErr(r.db.Group.Query().Where(group.Name(name)).Only(ctx)) +} + func (r *GroupRepository) InvitationGet(ctx context.Context, token []byte) (GroupInvitation, error) { return r.invitationMapper.MapErr(r.db.GroupInvitationToken.Query(). Where(groupinvitationtoken.Token(token)). diff --git a/backend/internal/data/repo/repo_oauth.go b/backend/internal/data/repo/repo_oauth.go new file mode 100644 index 00000000..8843d1b7 --- /dev/null +++ b/backend/internal/data/repo/repo_oauth.go @@ -0,0 +1,63 @@ +package repo + +import ( + "context" + "github.com/google/uuid" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent" + "github.com/sysadminsmedia/homebox/backend/internal/data/ent/oauth" + "time" +) + +type OAuthRepository struct { + db *ent.Client +} + +type ( + OAuthCreate struct { + Provider string `json:"provider"` + Subject string `json:"sub"` + UserId uuid.UUID `json:"userId"` + } + + OAuth struct { + OAuthCreate + CreatedAt time.Time `json:"createdAt"` + } +) + +func (r *OAuthRepository) GetUserFromToken(ctx context.Context, provider string, sub string) (UserOut, error) { + user, err := r.db.OAuth.Query(). + Where(oauth.Provider(provider)). + Where(oauth.Sub(sub)). + WithUser(). + QueryUser(). + WithGroup(). + Only(ctx) + if err != nil { + return UserOut{}, err + } + + return mapUserOut(user), nil +} + +func (r *OAuthRepository) Create(ctx context.Context, create OAuthCreate) (OAuth, error) { + dbOauth, err := r.db.OAuth.Create(). + SetProvider(create.Provider). + SetSub(create.Subject). + SetUserID(create.UserId). + Save(ctx) + if err != nil { + return OAuth{}, err + } + + return OAuth{ + OAuthCreate: OAuthCreate{ + Provider: dbOauth.Provider, + Subject: dbOauth.Sub, + UserId: create.UserId, + }, + CreatedAt: dbOauth.CreatedAt, + }, nil +} + +// TODO: delete connection, checking if password or other connections exists or delete user diff --git a/backend/internal/data/repo/repo_users.go b/backend/internal/data/repo/repo_users.go index 378bf316..494a5748 100644 --- a/backend/internal/data/repo/repo_users.go +++ b/backend/internal/data/repo/repo_users.go @@ -2,7 +2,6 @@ package repo import ( "context" - "github.com/google/uuid" "github.com/sysadminsmedia/homebox/backend/internal/data/ent" "github.com/sysadminsmedia/homebox/backend/internal/data/ent/user" @@ -85,14 +84,19 @@ func (r *UserRepository) Create(ctx context.Context, usr UserCreate) (UserOut, e role = user.RoleOwner } - entUser, err := r.db.User. + createQuery := r.db.User. Create(). SetName(usr.Name). SetEmail(usr.Email). - SetPassword(usr.Password). SetIsSuperuser(usr.IsSuperuser). SetGroupID(usr.GroupID). - SetRole(role). + SetRole(role) + if usr.Password != "" { + createQuery = createQuery. + SetPassword(usr.Password) + } + + entUser, err := createQuery. Save(ctx) if err != nil { return UserOut{}, err diff --git a/backend/internal/data/repo/repos_all.go b/backend/internal/data/repo/repos_all.go index 51c921fa..0a436c05 100644 --- a/backend/internal/data/repo/repos_all.go +++ b/backend/internal/data/repo/repos_all.go @@ -9,6 +9,7 @@ import ( // AllRepos is a container for all the repository interfaces type AllRepos struct { Users *UserRepository + OAuth *OAuthRepository AuthTokens *TokenRepository Groups *GroupRepository Locations *LocationRepository @@ -23,6 +24,7 @@ type AllRepos struct { func New(db *ent.Client, bus *eventbus.EventBus, root string) *AllRepos { return &AllRepos{ Users: &UserRepository{db}, + OAuth: &OAuthRepository{db}, AuthTokens: &TokenRepository{db}, Groups: NewGroupRepository(db), Locations: &LocationRepository{db, bus}, diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 7bd5000c..4ca48039 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -6,7 +6,7 @@ export default defineConfig({ description: "A simple home inventory management software", lastUpdated: true, sitemap: { - hostname: 'https://homebox.sysadminsmedia.com', + hostname: 'https://homebox.software', }, locales: { @@ -27,7 +27,8 @@ export default defineConfig({ }, // https://vitepress.dev/reference/default-theme-config nav: [ - { text: 'API', link: 'https://redocly.github.io/redoc/?url=https://raw.githubusercontent.com/sysadminsmedia/homebox/main/docs/docs/api/openapi-2.0.json' } + { text: 'API', link: 'https://redocly.github.io/redoc/?url=https://raw.githubusercontent.com/sysadminsmedia/homebox/main/docs/docs/api/openapi-2.0.json' }, + { text: 'Demo', link: 'https://demo.homebox.software' }, ], sidebar: { @@ -36,6 +37,8 @@ export default defineConfig({ text: 'Getting Started', items: [ { text: 'Quick Start', link: '/en/quick-start' }, + { text: 'Installation', link: '/en/installation' }, + { text: 'Configure Homebox', link: '/en/configure-homebox' }, { text: 'Tips and Tricks', link: '/en/tips-tricks' } ] }, @@ -56,8 +59,8 @@ export default defineConfig({ }, socialLinks: [ - { icon: 'discord', link: 'https://discord.gg/aY4DCkpNA9' }, - { icon: 'github', link: 'https://github.com/sysadminsmedia/homebox' }, + { icon: 'discord', link: 'https://discord.homebox.software' }, + { icon: 'github', link: 'https://git.homebox.software' }, { icon: 'mastodon', link: 'https://noc.social/@sysadminszone' }, ] } diff --git a/docs/en/configure-homebox.md b/docs/en/configure-homebox.md new file mode 100644 index 00000000..138193e8 --- /dev/null +++ b/docs/en/configure-homebox.md @@ -0,0 +1,59 @@ +# Configure Homebox + +## Env Variables & Configuration + +| Variable | Default | Description | +| ------------------------------------ | ---------------------- | ---------------------------------------------------------------------------------- | +| HBOX_MODE | `production` | application mode used for runtime behavior can be one of: `development`, `production` | +| HBOX_WEB_PORT | 7745 | port to run the web server on, if you're using docker do not change this | +| HBOX_WEB_HOST | | host to run the web server on, if you're using docker do not change this | +| HBOX_OPTIONS_ALLOW_REGISTRATION | true | allow users to register themselves | +| HBOX_OPTIONS_AUTO_INCREMENT_ASSET_ID | true | auto-increments the asset_id field for new items | +| HBOX_OPTIONS_CURRENCY_CONFIG | | json configuration file containing additional currencie | +| HBOX_WEB_MAX_UPLOAD_SIZE | 10 | maximum file upload size supported in MB | +| HBOX_WEB_READ_TIMEOUT | 10s | Read timeout of HTTP sever | +| HBOX_WEB_WRITE_TIMEOUT | 10s | Write timeout of HTTP server | +| HBOX_WEB_IDLE_TIMEOUT | 30s | Idle timeout of HTTP server | +| HBOX_STORAGE_DATA | /data/ | path to the data directory, do not change this if you're using docker | +| HBOX_STORAGE_SQLITE_URL | /data/homebox.db?_fk=1 | sqlite database url, if you're using docker do not change this | +| HBOX_LOG_LEVEL | `info` | log level to use, can be one of `trace`, `debug`, `info`, `warn`, `error`, `critical` | +| HBOX_LOG_FORMAT | `text` | log format to use, can be one of: `text`, `json` | +| HBOX_MAILER_HOST | | email host to use, if not set no email provider will be used | +| HBOX_MAILER_PORT | 587 | email port to use | +| HBOX_MAILER_USERNAME | | email user to use | +| HBOX_MAILER_PASSWORD | | email password to use | +| HBOX_MAILER_FROM | | email from address to use | +| HBOX_SWAGGER_HOST | 7745 | swagger host to use, if not set swagger will be disabled | +| HBOX_SWAGGER_SCHEMA | `http` | swagger schema to use, can be one of: `http`, `https` | + +::: tip "CLI Arguments" +If you're deploying without docker you can use command line arguments to configure the application. Run `homebox --help` for more information. + +```sh +Usage: api [options] [arguments] + +OPTIONS +--mode/$HBOX_MODE