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 @@

HomeBox

- 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 (default: development) +--web-port/$HBOX_WEB_PORT (default: 7745) +--web-host/$HBOX_WEB_HOST +--web-max-upload-size/$HBOX_WEB_MAX_UPLOAD_SIZE (default: 10) +--storage-data/$HBOX_STORAGE_DATA (default: ./.data) +--storage-sqlite-url/$HBOX_STORAGE_SQLITE_URL (default: ./.data/homebox.db?_fk=1) +--log-level/$HBOX_LOG_LEVEL (default: info) +--log-format/$HBOX_LOG_FORMAT (default: text) +--mailer-host/$HBOX_MAILER_HOST +--mailer-port/$HBOX_MAILER_PORT +--mailer-username/$HBOX_MAILER_USERNAME +--mailer-password/$HBOX_MAILER_PASSWORD +--mailer-from/$HBOX_MAILER_FROM +--swagger-host/$HBOX_SWAGGER_HOST (default: localhost:7745) +--swagger-scheme/$HBOX_SWAGGER_SCHEME (default: http) +--demo/$HBOX_DEMO +--debug-enabled/$HBOX_DEBUG_ENABLED (default: false) +--debug-port/$HBOX_DEBUG_PORT (default: 4000) +--options-allow-registration/$HBOX_OPTIONS_ALLOW_REGISTRATION (default: true) +--options-auto-increment-asset-id/$HBOX_OPTIONS_AUTO_INCREMENT_ASSET_ID (default: true) +--options-currency-config/$HBOX_OPTIONS_CURRENCY_CONFIG +--help/-h display this help message +``` +::: diff --git a/docs/en/images/home-screen.png b/docs/en/images/home-screen.png new file mode 100644 index 00000000..b85edd1d Binary files /dev/null and b/docs/en/images/home-screen.png differ diff --git a/docs/en/index.md b/docs/en/index.md index a629290f..c52f9c32 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -15,6 +15,9 @@ hero: - theme: alt text: Tips and Tricks link: /en/tips-tricks + - theme: alt + text: Try It Out + link: https://demo.homebox.software features: - title: Add/Update/Delete Items @@ -28,9 +31,11 @@ features: - title: Custom labeling and locations details: Use custom labels and locations to organize items - title: Multi-Tenant Support - details: All users are in a group, and can only see what's in the group. Invite family memebers or share an instance with friends. + details: All users are in a group, and can only see what's in the group. Invite family members or share an instance with friends. --- +![HomeBox Home Screen Screenshot](images/home-screen.png) + Homebox is the inventory and organization system built for the Home User! With a focus on simplicity and ease of use, Homebox is the perfect solution for your home inventory, organization, and management needs. While developing this project, I've tried to keep the following principles in mind: - _Simple_ - Homebox is designed to be simple and easy to use. No complicated setup or configuration required. Use either a single docker container, or deploy yourself by compiling the binary for your platform of choice. diff --git a/docs/en/installation.md b/docs/en/installation.md new file mode 100644 index 00000000..29239197 --- /dev/null +++ b/docs/en/installation.md @@ -0,0 +1,99 @@ +# Installation + +There are two main ways to run the application. + +1. As a [Docker](https://www.docker.com/) container. +2. Using the correct executable for your platform by downloading it from the [Releases](https://github.com/sysadminsmedia/homebox/releases). + + +## Docker + +The following instructions assume Docker is already installed on your system. See [(Docker's official installation guide)](https://docs.docker.com/engine/install/) + +The official image is `ghcr.io/sysadminsmedia/homebox:latest`. For each image there are two tags, respectively the regular tag and $TAG-rootless, which uses a non-root image. + +### Docker Run + +Great for testing out the application, but not recommended for stable use. Checkout the docker-compose below for the recommended deployment. + + +```sh +# If using the rootless image, ensure data +# folder has correct permissions +$ mkdir -p /path/to/data/folder +$ chown 65532:65532 -R /path/to/data/folder +# --------------------------------------- +# Run the image +$ docker run -d \ + --name homebox \ + --restart unless-stopped \ + --publish 3100:7745 \ + --env TZ=Europe/Bucharest \ + --volume /path/to/data/folder/:/data \ + ghcr.io/sysadminsmedia/homebox:latest +# ghcr.io/sysadminsmedia/homebox:latest-rootless +``` + +### Docker Compose + +1. Create a `docker-compose.yml` file. + +```yaml +services: + homebox: + image: ghcr.io/sysadminsmedia/homebox:latest +# image: ghcr.io/sysadminsmedia/homebox:latest-rootless + container_name: homebox + restart: always + environment: + - HBOX_LOG_LEVEL=info + - HBOX_LOG_FORMAT=text + - HBOX_WEB_MAX_UPLOAD_SIZE=10 + volumes: + - homebox-data:/data/ + ports: + - 3100:7745 + +volumes: + homebox-data: + driver: local +``` + +::: info +If you use the `rootless` image, and instead of using named volumes you would prefer using a hostMount directly (e.g., `volumes: [ /path/to/data/folder:/data ]`) you need to `chown` the chosen directory in advance to the `65532` user (as shown in the Docker example above). +::: + +::: warning +If you have previously set up docker compose with the `HBOX_WEB_READ_TIMEOUT`, `HBOX_WEB_WRITE_TIMEOUT`, or `HBOX_IDLE_TIMEOUT` options, and you were previously using the hay-kot image, please note that you will have to add an `s` for seconds or `m` for minutes to the end of the integers. A dependency update removed the defaultation to seconds and it now requires an explicit duration time. +::: + +2. While in the same folder as docker-compose.yml, start the container by running: + +```bash +docker compose up --detach +``` + +3. Navigate to `http://server.local.ip.address:3100/` to access the web interface. (replace with the right IP address). + +You can learn more about Docker by [reading the official Docker documentation.](https://docs.docker.com/) + +## Windows + +1. Download the appropriate release for your CPU architecture from the [releases page on GitHub](https://github.com/sysadminsmedia/homebox/releases). +2. Extract the archive. +3. Run `homebox.exe`. This will start the server on port 7745. +4. You can test it by accessing http://localhost:7745. + +## Linux + +1. Download the appropriate release for your CPU architecture from the [releases page on GitHub](https://github.com/sysadminsmedia/homebox/releases). +2. Extract the archive. +3. Run the `homebox` executable. +4. The web interface will be accessible on port 7745 by default. Access the page by navigating to `http://server.local.ip.address:7745/` (replace with the right ip address) + +## macOS + +1. Download the appropriate release for your CPU architecture from the [releases page on GitHub](https://github.com/sysadminsmedia/homebox/releases). (Use `homebox_Darwin_x86_64.tar.gz` for Intel-based macs and `homebox_Darwin_arm64.tar.gz` for Apple Silicon) +2. Extract the archive. +3. Run the `homebox` executable. +4. The web interface will be accessible on port 7745 by default. Access the page by navigating to `http://local.ip.address:7745/` (replace with the right ip address) \ No newline at end of file diff --git a/docs/en/quick-start.md b/docs/en/quick-start.md index 791edf20..0048251f 100644 --- a/docs/en/quick-start.md +++ b/docs/en/quick-start.md @@ -1,113 +1,12 @@ # Quick Start -## Docker Run +1. Install Homebox either by using [the latest Docker image](https://ghcr.io/sysadminsmedia/homebox:latest), or by downloading the correct executable for your Operating System from the [Releases](https://github.com/sysadminsmedia/homebox/releases). (See [Installation](./installation) for more details) -Great for testing out the application, but not recommended for stable use. Checkout the docker-compose for the recommended deployment. +2. Browse to `http://SERVER_IP:3100` (if Using Docker) or `http://SERVER_IP:7745` (if installed locally) to access the included web User Interface. -For each image there are two tags, respectively the regular tag and $TAG-rootless, which uses a non-root image. +3. Register your first user. -```sh -# If using the rootless image, ensure data -# folder has correct permissions -$ mkdir -p /path/to/data/folder -$ chown 65532:65532 -R /path/to/data/folder -# --------------------------------------- -# Run the image -$ docker run -d \ - --name homebox \ - --restart unless-stopped \ - --publish 3100:7745 \ - --env TZ=Europe/Bucharest \ - --volume /path/to/data/folder/:/data \ - ghcr.io/sysadminsmedia/homebox:latest -# ghcr.io/sysadminsmedia/homebox:latest-rootless +4. Login with the user you just created and start adding your locations and items! -``` - -## Docker-Compose - -```yaml -services: - homebox: - image: ghcr.io/sysadminsmedia/homebox:latest -# image: ghcr.io/sysadminsmedia/homebox:latest-rootless - container_name: homebox - restart: always - environment: - - HBOX_LOG_LEVEL=info - - HBOX_LOG_FORMAT=text - - HBOX_WEB_MAX_UPLOAD_SIZE=10 - volumes: - - homebox-data:/data/ - ports: - - 3100:7745 - -volumes: - homebox-data: - driver: local -``` -::: info -If you use the `rootless` image, and instead of using named volumes you would prefer using a hostMount directly (e.g., `volumes: [ /path/to/data/folder:/data ]`) you need to `chown` the chosen directory in advance to the `65532` user (as shown in the Docker example above). -::: - -::: warning -If you have previously set up docker compose with the `HBOX_WEB_READ_TIMEOUT`, `HBOX_WEB_WRITE_TIMEOUT`, or `HBOX_IDLE_TIMEOUT` options, and you were previously using the hay-kot image, please note that you will have to add an `s` for seconds or `m` for minutes to the end of the integers. A dependency update removed the defaultation to seconds and it now requires an explicit duration time. -::: - -## 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 (default: development) ---web-port/$HBOX_WEB_PORT (default: 7745) ---web-host/$HBOX_WEB_HOST ---web-max-upload-size/$HBOX_WEB_MAX_UPLOAD_SIZE (default: 10) ---storage-data/$HBOX_STORAGE_DATA (default: ./.data) ---storage-sqlite-url/$HBOX_STORAGE_SQLITE_URL (default: ./.data/homebox.db?_fk=1) ---log-level/$HBOX_LOG_LEVEL (default: info) ---log-format/$HBOX_LOG_FORMAT (default: text) ---mailer-host/$HBOX_MAILER_HOST ---mailer-port/$HBOX_MAILER_PORT ---mailer-username/$HBOX_MAILER_USERNAME ---mailer-password/$HBOX_MAILER_PASSWORD ---mailer-from/$HBOX_MAILER_FROM ---swagger-host/$HBOX_SWAGGER_HOST (default: localhost:7745) ---swagger-scheme/$HBOX_SWAGGER_SCHEME (default: http) ---demo/$HBOX_DEMO ---debug-enabled/$HBOX_DEBUG_ENABLED (default: false) ---debug-port/$HBOX_DEBUG_PORT (default: 4000) ---options-allow-registration/$HBOX_OPTIONS_ALLOW_REGISTRATION (default: true) ---options-auto-increment-asset-id/$HBOX_OPTIONS_AUTO_INCREMENT_ASSET_ID (default: true) ---options-currency-config/$HBOX_OPTIONS_CURRENCY_CONFIG ---help/-h display this help message -``` -::: +> [!TIP] +> If you want other users to see your items and locations, they will need to sign up using your invite link, otherwise they will only see their own items. Go to the **Profile** section in the left navigation bar and under **User Profile**, click **Generate Invite Link**. \ No newline at end of file diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index c567952c..e018b4b1 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -11,6 +11,7 @@ module.exports = { "@nuxtjs/eslint-config-typescript", "plugin:vue/vue3-recommended", "plugin:prettier/recommended", + "plugin:tailwindcss/recommended", ], parserOptions: { ecmaVersion: "latest", diff --git a/frontend/components/App/Header.vue b/frontend/components/App/Header.vue index b42da7f6..c3703730 100644 --- a/frontend/components/App/Header.vue +++ b/frontend/components/App/Header.vue @@ -68,23 +68,23 @@ -
+
- + -

+

HomeB - + x

-
+
-
+