From dd0995b241fff7c9f4c2df3b05d8bbf5ae0bbcf8 Mon Sep 17 00:00:00 2001 From: trev Date: Sat, 5 Apr 2025 14:27:36 -0400 Subject: [PATCH] WIP: stuff --- .dockerignore | 19 +- .github/workflows/{lint.yml => lint.yaml} | 0 .../workflows/{release.yml => release.yaml} | 0 .gitignore | 14 +- cli/cmd/ts-run/ts-run.go | 89 ++ cli/go.mod | 31 + cli/go.sum | 47 + cli/internal/apps/msg.go | 19 + cli/internal/apps/node.go | 118 +++ cli/internal/apps/proto.go | 269 ++++++ cli/internal/models/cbox.go | 82 ++ cli/internal/models/header.go | 33 + cli/internal/models/help.go | 84 ++ cli/internal/models/runner.go | 220 +++++ cli/internal/utils/pointers.go | 13 + cli/internal/utils/run.go | 55 ++ client/.gitignore | 2 + client/src/lib/services/user/v1/auth_pb.ts | 82 +- client/src/lib/services/user/v1/user_pb.ts | 73 +- client/static/openapi/openapi.yaml | 154 ++- ...compose.yml => docker-compose-example.yaml | 0 flake.nix | 16 + proto/user/v1/auth.proto | 18 +- proto/user/v1/user.proto | 15 +- server/.gitignore | 3 + server/bobgen.yaml | 10 + server/internal/handlers/user/v1/user.go | 32 + server/internal/models/bob_main.bob.go | 162 ++++ server/internal/models/bob_main_test.bob.go | 15 + .../models/factory/bobfactory_context.bob.go | 36 + .../models/factory/bobfactory_main.bob.go | 74 ++ .../models/factory/bobfactory_random.bob.go | 56 ++ .../factory/bobfactory_random_test.bob.go | 64 ++ server/internal/models/factory/files.bob.go | 466 +++++++++ server/internal/models/factory/items.bob.go | 608 ++++++++++++ server/internal/models/factory/users.bob.go | 610 ++++++++++++ server/internal/models/file.go | 12 - server/internal/models/files.bob.go | 658 +++++++++++++ server/internal/models/item.go | 33 - server/internal/models/items.bob.go | 734 +++++++++++++++ server/internal/models/passkey.go | 16 - server/internal/models/user.go | 36 - server/internal/models/users.bob.go | 887 ++++++++++++++++++ server/internal/services/user/v1/auth.pb.go | 277 +++--- server/internal/services/user/v1/user.pb.go | 226 +++-- .../user/v1/userv1connect/auth.connect.go | 77 +- .../user/v1/userv1connect/user.connect.go | 77 +- 47 files changed, 6148 insertions(+), 474 deletions(-) rename .github/workflows/{lint.yml => lint.yaml} (100%) rename .github/workflows/{release.yml => release.yaml} (100%) create mode 100644 cli/cmd/ts-run/ts-run.go create mode 100644 cli/go.mod create mode 100644 cli/go.sum create mode 100644 cli/internal/apps/msg.go create mode 100644 cli/internal/apps/node.go create mode 100644 cli/internal/apps/proto.go create mode 100644 cli/internal/models/cbox.go create mode 100644 cli/internal/models/header.go create mode 100644 cli/internal/models/help.go create mode 100644 cli/internal/models/runner.go create mode 100644 cli/internal/utils/pointers.go create mode 100644 cli/internal/utils/run.go create mode 100644 client/.gitignore rename docker-compose.yml => docker-compose-example.yaml (100%) create mode 100644 server/.gitignore create mode 100644 server/bobgen.yaml create mode 100644 server/internal/models/bob_main.bob.go create mode 100644 server/internal/models/bob_main_test.bob.go create mode 100644 server/internal/models/factory/bobfactory_context.bob.go create mode 100644 server/internal/models/factory/bobfactory_main.bob.go create mode 100644 server/internal/models/factory/bobfactory_random.bob.go create mode 100644 server/internal/models/factory/bobfactory_random_test.bob.go create mode 100644 server/internal/models/factory/files.bob.go create mode 100644 server/internal/models/factory/items.bob.go create mode 100644 server/internal/models/factory/users.bob.go delete mode 100644 server/internal/models/file.go create mode 100644 server/internal/models/files.bob.go delete mode 100644 server/internal/models/item.go create mode 100644 server/internal/models/items.bob.go delete mode 100644 server/internal/models/passkey.go delete mode 100644 server/internal/models/user.go create mode 100644 server/internal/models/users.bob.go diff --git a/.dockerignore b/.dockerignore index 1942cbd..6d2ece1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,13 +1,16 @@ -.direnv .env -build -result + +/docker-compose.* + +/.direnv/ +/build/ +/result/ # Client -/client/node_modules -/client/.svelte-kit +/client/node_modules/ +/client/.svelte-kit/ # Server -/server/client -/server/tmp -/server/internal/handlers/client/client \ No newline at end of file +/server/client/ +/server/tmp/ +/server/internal/handlers/client/client/ \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yaml similarity index 100% rename from .github/workflows/lint.yml rename to .github/workflows/lint.yaml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yaml similarity index 100% rename from .github/workflows/release.yml rename to .github/workflows/release.yaml diff --git a/.gitignore b/.gitignore index 1942cbd..d115bc5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,7 @@ -.direnv .env -build -result -# Client -/client/node_modules -/client/.svelte-kit +/docker-compose.* -# Server -/server/client -/server/tmp -/server/internal/handlers/client/client \ No newline at end of file +/.direnv/ +/build/ +/result/ \ No newline at end of file diff --git a/cli/cmd/ts-run/ts-run.go b/cli/cmd/ts-run/ts-run.go new file mode 100644 index 0000000..f7db522 --- /dev/null +++ b/cli/cmd/ts-run/ts-run.go @@ -0,0 +1,89 @@ +package main + +import ( + "fmt" + "log" + "os" + + "github.com/boyter/gocodewalker" +) + +type env struct { + DBType string + DBUser string + DBPass string + DBHost string + DBPort string + DBName string + + RootDir string + NodeDir string + ProtoDir string +} + +func main() { + // Get pwd + path, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + fmt.Printf("Current path: %s\n", path) + + findApps(path) + return + + // c := make(chan apps.Msg, 10) + + // // Create protobuf watcher + // proto, err := apps.NewProto(env.ProtoDir, env.RootDir, c) + // if err != nil { + // log.Fatal(err) + // } + + // // Create node watcher + // node := apps.NewNode(env.NodeDir, c) + // if err != nil { + // log.Fatal(err) + // } + + // apps := []*apps.App{ + // &proto.App, + // &node.App, + // } + + // // Start tea + // p := tea.NewProgram( + // models.NewRunner(c, apps), + // tea.WithAltScreen(), + // tea.WithMouseCellMotion(), + // ) + // if _, err := p.Run(); err != nil { + // fmt.Printf("Alas, there's been an error: %v", err) + // } + + // // Cancel watchers + // proto.Cancel() + // proto.Wait() + + // node.Cancel() + // node.Wait() + + // close(c) +} + +func findApps(path string) { + fileListQueue := make(chan *gocodewalker.File, 100) + fileWalker := gocodewalker.NewFileWalker(path, fileListQueue) + + errorHandler := func(e error) bool { + fmt.Println("ERR", e.Error()) + return true + } + fileWalker.SetErrorHandler(errorHandler) + + go fileWalker.Start() + + for f := range fileListQueue { + fmt.Printf("%s, %s\n", f.Filename, f.Location) + } +} diff --git a/cli/go.mod b/cli/go.mod new file mode 100644 index 0000000..afcbb5d --- /dev/null +++ b/cli/go.mod @@ -0,0 +1,31 @@ +module github.com/spotdemo4/trevstack/cli + +go 1.23.6 + +require ( + github.com/charmbracelet/bubbles v0.20.0 + github.com/charmbracelet/bubbletea v1.3.4 + github.com/charmbracelet/lipgloss v1.0.0 + github.com/fsnotify/fsnotify v1.8.0 + github.com/joho/godotenv v1.5.1 +) + +require ( + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/boyter/gocodewalker v1.4.0 // indirect + github.com/charmbracelet/x/ansi v0.8.0 // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.3.8 // indirect +) diff --git a/cli/go.sum b/cli/go.sum new file mode 100644 index 0000000..7b95fb0 --- /dev/null +++ b/cli/go.sum @@ -0,0 +1,47 @@ +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/boyter/gocodewalker v1.4.0 h1:fVmFeQxKpj5tlpjPcyTtJ96btgaHYd9yn6m+T/66et4= +github.com/boyter/gocodewalker v1.4.0/go.mod h1:hXG8xzR1uURS+99P5/3xh3uWHjaV2XfoMMmvPyhrCDg= +github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= +github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= +github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI= +github.com/charmbracelet/bubbletea v1.3.4/go.mod h1:dtcUCyCGEX3g9tosuYiut3MXgY/Jsv9nKVdibKKRRXo= +github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= +github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= +github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= +github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= diff --git a/cli/internal/apps/msg.go b/cli/internal/apps/msg.go new file mode 100644 index 0000000..ceb6258 --- /dev/null +++ b/cli/internal/apps/msg.go @@ -0,0 +1,19 @@ +package apps + +import "time" + +type Msg struct { + Text string + Time time.Time + Key *string + Loading *bool + Success *bool + + App *App +} + +type App struct { + Name string + Color string + Loading *bool +} diff --git a/cli/internal/apps/node.go b/cli/internal/apps/node.go new file mode 100644 index 0000000..2b51d50 --- /dev/null +++ b/cli/internal/apps/node.go @@ -0,0 +1,118 @@ +package apps + +import ( + "context" + "fmt" + "os" + "os/exec" + "sync" + "time" + + "github.com/spotdemo4/trevstack/cli/internal/utils" +) + +type Node struct { + App App + c chan Msg + ctx context.Context + wg *sync.WaitGroup + + dir string + + Cancel context.CancelFunc + Wait func() +} + +func NewNode(dir string, c chan Msg) *Node { + + // Create new context + ctx, cancel := context.WithCancel(context.Background()) + + // Create wait group + wg := sync.WaitGroup{} + + node := Node{ + App: App{ + Name: "node", + Color: "#fab387", + }, + c: c, + ctx: ctx, + wg: &wg, + + dir: dir, + + Cancel: cancel, + Wait: wg.Wait, + } + + // Start watching + go node.dev() + + return &node +} + +func (n *Node) msg(m Msg) { + m.Time = time.Now() + m.App = &n.App + n.c <- m +} + +func (n *Node) dev() { + n.wg.Add(1) + defer n.wg.Done() + + // Create cmd + cmd := exec.Command("npm", "run", "dev") + cmd.Dir = n.dir + + // Stop cmd on exit + n.wg.Add(1) + go func() { + defer n.wg.Done() + <-n.ctx.Done() + + if err := cmd.Process.Signal(os.Interrupt); err != nil { + cmd.Process.Kill() // If the process is not responding to the interrupt signal, kill it + } + }() + + // Start cmd + out, err := utils.Run(cmd) + if err != nil { + n.msg(Msg{ + Text: err.Error(), + Success: utils.BoolPointer(false), + }) + return + } + + // Watch for output + for line := range out { + switch line := line.(type) { + case utils.Stdout: + n.msg(Msg{ + Text: string(line), + }) + + case utils.Stderr: + n.msg(Msg{ + Text: string(line), + Success: utils.BoolPointer(false), + }) + + case utils.ExitCode: + if line == 0 { + n.msg(Msg{ + Text: "Node stopped", + Success: utils.BoolPointer(true), + }) + } else { + n.msg(Msg{ + Text: fmt.Sprintf("Node failed with exit code %d", out), + Success: utils.BoolPointer(false), + }) + } + } + } +} diff --git a/cli/internal/apps/proto.go b/cli/internal/apps/proto.go new file mode 100644 index 0000000..79e9fe4 --- /dev/null +++ b/cli/internal/apps/proto.go @@ -0,0 +1,269 @@ +package apps + +import ( + "context" + "fmt" + "io/fs" + "os/exec" + "path/filepath" + "slices" + "strings" + "sync" + "time" + + "github.com/fsnotify/fsnotify" + "github.com/spotdemo4/trevstack/cli/internal/utils" +) + +type Proto struct { + App App + c chan Msg + ctx context.Context + wg *sync.WaitGroup + watcher *fsnotify.Watcher + + dir string + rootDir string + + Cancel context.CancelFunc + Wait func() +} + +func NewProto(dir string, rootDir string, c chan Msg) (*Proto, error) { + + // Create new watcher + watcher, err := fsnotify.NewWatcher() + if err != nil { + return nil, err + } + + // Add directory to watcher + err = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + if slices.Contains(watcher.WatchList(), path) { + return nil + } + + err := watcher.Add(path) + if err != nil { + return err + } + } + + return nil + }) + if err != nil { + return nil, err + } + + // Create new context + ctx, cancel := context.WithCancel(context.Background()) + + // Create wait group + wg := sync.WaitGroup{} + + proto := Proto{ + App: App{ + Name: "proto", + Color: "#89dceb", + }, + c: c, + ctx: ctx, + wg: &wg, + watcher: watcher, + + dir: dir, + rootDir: rootDir, + + Cancel: cancel, + Wait: wg.Wait, + } + + // Start watching + go proto.watch() + + return &proto, nil +} + +func (p *Proto) msg(m Msg) { + m.Time = time.Now() + m.App = &p.App + p.c <- m +} + +func (p *Proto) watch() { + p.wg.Add(1) + defer p.wg.Done() + defer p.watcher.Close() + + // Create new rate limit map + rateLimit := make(map[string]time.Time) + + p.lint() + + p.msg(Msg{ + Text: "Watching for proto changes...", + }) + + for { + select { + case <-p.ctx.Done(): + return + + case event, ok := <-p.watcher.Events: + if !ok { + return + } + + // Rate limit + rl, ok := rateLimit[event.Name] + if ok && time.Since(rl) < 1*time.Second { + continue + } + rateLimit[event.Name] = time.Now() + + p.msg(Msg{ + Text: "File changed: " + strings.TrimPrefix(event.Name, p.dir), + }) + + ok, _ = p.lint() + if !ok { + continue + } + + p.generate() + + case err, ok := <-p.watcher.Errors: + if !ok { + return + } + + p.msg(Msg{ + Text: err.Error(), + Success: utils.BoolPointer(false), + }) + } + } +} + +func (p *Proto) lint() (bool, error) { + p.msg(Msg{ + Text: "Linting", + Loading: utils.BoolPointer(true), + Key: utils.StringPointer("lint"), + }) + + // Run buf lint + cmd := exec.Command("buf", "lint") + cmd.Dir = p.rootDir + out, err := utils.Run(cmd) + if err != nil { + p.msg(Msg{ + Text: err.Error(), + Success: utils.BoolPointer(false), + }) + return false, err + } + + // Watch for output + for line := range out { + switch line := line.(type) { + case utils.Stdout: + p.msg(Msg{ + Text: string(line), + }) + + case utils.Stderr: + p.msg(Msg{ + Text: string(line), + Success: utils.BoolPointer(false), + }) + + case utils.ExitCode: + if line == 0 { + p.msg(Msg{ + Text: "Buf lint successful", + Success: utils.BoolPointer(true), + Loading: utils.BoolPointer(false), + Key: utils.StringPointer("lint"), + }) + + return true, nil + } + + p.msg(Msg{ + Text: fmt.Sprintf("Buf lint failed with exit code %d", out), + Success: utils.BoolPointer(false), + Loading: utils.BoolPointer(false), + Key: utils.StringPointer("lint"), + }) + + return false, fmt.Errorf("buf lint failed with exit code %d", line) + } + } + + return false, fmt.Errorf("buf lint failed") +} + +func (p *Proto) generate() error { + p.msg(Msg{ + Text: "Generating proto files", + Loading: utils.BoolPointer(true), + Key: utils.StringPointer("generate"), + }) + + // Run buf gen + cmd := exec.Command("buf", "generate") + cmd.Dir = p.rootDir + out, err := utils.Run(cmd) + if err != nil { + p.msg(Msg{ + Text: err.Error(), + Success: utils.BoolPointer(false), + }) + return err + } + + // Watch for output + for line := range out { + switch line := line.(type) { + case utils.Stdout: + p.msg(Msg{ + Text: string(line), + }) + + case utils.Stderr: + p.msg(Msg{ + Text: string(line), + Success: utils.BoolPointer(false), + }) + + case utils.ExitCode: + if line == 0 { + p.msg(Msg{ + Text: "Buf generate successful", + Success: utils.BoolPointer(true), + Loading: utils.BoolPointer(false), + Key: utils.StringPointer("generate"), + }) + + return nil + } + + p.msg(Msg{ + Text: fmt.Sprintf("Buf generate failed with exit code %d", out), + Success: utils.BoolPointer(false), + Loading: utils.BoolPointer(false), + Key: utils.StringPointer("generate"), + }) + + return fmt.Errorf("generate failed with exit code %d", line) + } + } + + return fmt.Errorf("generate failed") +} diff --git a/cli/internal/models/cbox.go b/cli/internal/models/cbox.go new file mode 100644 index 0000000..77c667a --- /dev/null +++ b/cli/internal/models/cbox.go @@ -0,0 +1,82 @@ +package models + +import ( + "time" + + "github.com/charmbracelet/bubbles/viewport" + "github.com/charmbracelet/lipgloss" +) + +type Cbox struct { + style lipgloss.Style + Viewport *viewport.Model + maxPrefixLen int +} + +func NewCbox(maxPrefixLen int) *Cbox { + s := lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("#45475a")). + BorderTop(true). + BorderBottom(true). + Margin(1, 0, 0) + + return &Cbox{ + style: s, + Viewport: nil, + maxPrefixLen: maxPrefixLen, + } +} + +func (c *Cbox) Gen(text string, width int, height int, mtop int, mbottom int) string { + if c.Viewport == nil { + vp := viewport.New(width, height-(mtop+mbottom)) + c.Viewport = &vp + c.Viewport.YPosition = mtop + c.Viewport.Style = c.style + } else { + c.Viewport.Width = width + c.Viewport.Height = height - (mtop + mbottom) + c.Viewport.YPosition = mtop + } + + atBottom := c.Viewport.AtBottom() + + // Need to add extra lines because of https://github.com/charmbracelet/bubbles/pull/731 + c.Viewport.SetContent(text + "\n\n\n") + + if atBottom { + c.Viewport.GotoBottom() + } + + return c.Viewport.View() +} + +func (c *Cbox) GenItem(ti time.Time, prefix string, text string, color string, width int) string { + t := lipgloss.NewStyle(). + Padding(0, 1, 0, 1). + Foreground(lipgloss.Color("#a6adc8")). + Render(ti.Format(time.Kitchen)) + + p := lipgloss.NewStyle(). + Padding(0, 1, 0, 0). + Width(c.maxPrefixLen). + Foreground(lipgloss.Color(color)). + BorderStyle(lipgloss.NormalBorder()). + BorderRight(true). + BorderForeground(lipgloss.Color(color)) + + m := lipgloss.NewStyle(). + Padding(0, 1, 0, 1). + Foreground(lipgloss.Color("#cdd6f4")). + Width(width - lipgloss.Width(t) - lipgloss.Width(p.Render(prefix))). + Render(text) + + p = p.Height(lipgloss.Height(m)) + + combine := lipgloss.JoinHorizontal(lipgloss.Top, t, p.Render(prefix), m) + + return lipgloss.NewStyle(). + Width(width). + Render(combine) +} diff --git a/cli/internal/models/header.go b/cli/internal/models/header.go new file mode 100644 index 0000000..19a33bc --- /dev/null +++ b/cli/internal/models/header.go @@ -0,0 +1,33 @@ +package models + +import "github.com/charmbracelet/lipgloss" + +type Header struct { + style lipgloss.Style +} + +func NewHeader() *Header { + s := lipgloss.NewStyle(). + AlignHorizontal(lipgloss.Center). + AlignVertical(lipgloss.Bottom). + MarginTop(1) + + return &Header{ + style: s, + } +} + +func (h *Header) Gen(width int, items ...string) string { + s := h.style.Width(width) + + pp := lipgloss.JoinHorizontal(lipgloss.Center, items...) + + return s.Render(pp) +} + +func (h *Header) GenItem(text string) string { + return lipgloss.NewStyle(). + Foreground(lipgloss.Color("#cdd6f4")). + Margin(0, 1). + Render(text) +} diff --git a/cli/internal/models/help.go b/cli/internal/models/help.go new file mode 100644 index 0000000..042cbc5 --- /dev/null +++ b/cli/internal/models/help.go @@ -0,0 +1,84 @@ +package models + +import ( + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/lipgloss" +) + +// keyMap defines a set of keybindings. To work for help it must satisfy +// key.Map. It could also very easily be a map[string]key.Binding. +type keyMap struct { + Up key.Binding + Down key.Binding + Left key.Binding + Right key.Binding + Help key.Binding + Quit key.Binding +} + +// ShortHelp returns keybindings to be shown in the mini help view. It's part +// of the key.Map interface. +func (k keyMap) ShortHelp() []key.Binding { + return []key.Binding{k.Help, k.Quit} +} + +// FullHelp returns keybindings for the expanded help view. It's part of the +// key.Map interface. +func (k keyMap) FullHelp() [][]key.Binding { + return [][]key.Binding{ + {k.Up, k.Down, k.Left, k.Right}, // first column + {k.Help, k.Quit}, // second column + } +} + +type Help struct { + keys keyMap + style lipgloss.Style + help help.Model +} + +func NewHelp() *Help { + keys := keyMap{ + Up: key.NewBinding( + key.WithKeys("up", "k"), + key.WithHelp("↑/k", "move up"), + ), + Down: key.NewBinding( + key.WithKeys("down", "j"), + key.WithHelp("↓/j", "move down"), + ), + Left: key.NewBinding( + key.WithKeys("left", "h"), + key.WithHelp("←/h", "move left"), + ), + Right: key.NewBinding( + key.WithKeys("right", "l"), + key.WithHelp("→/l", "move right"), + ), + Help: key.NewBinding( + key.WithKeys("?"), + key.WithHelp("?", "toggle help"), + ), + Quit: key.NewBinding( + key.WithKeys("q", "esc", "ctrl+c"), + key.WithHelp("q", "quit"), + ), + } + + return &Help{ + keys: keys, + style: lipgloss.NewStyle().Padding(1, 2), + help: help.New(), + } +} + +func (h *Help) Gen(width int) string { + h.help.Width = width + render := h.help.View(h.keys) + return h.style.Render(render) +} + +func (h *Help) Toggle() { + h.help.ShowAll = !h.help.ShowAll +} diff --git a/cli/internal/models/runner.go b/cli/internal/models/runner.go new file mode 100644 index 0000000..cad0932 --- /dev/null +++ b/cli/internal/models/runner.go @@ -0,0 +1,220 @@ +package models + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/spinner" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/spotdemo4/trevstack/cli/internal/apps" + "github.com/spotdemo4/trevstack/cli/internal/utils" +) + +type runner struct { + width *int + height *int + + prefix lipgloss.Style + checkmark string + xmark string + + header *Header + cbox *Cbox + help *Help + spinner spinner.Model + + msgChan chan apps.Msg + msgs []apps.Msg + apps []*apps.App +} + +func NewRunner(msgChan chan apps.Msg, applications []*apps.App) *runner { + + prefix := lipgloss.NewStyle(). + Padding(0, 1, 0, 1). + Margin(0, 1, 0, 1). + Background(lipgloss.Color("#89dceb")). + Foreground(lipgloss.Color("#11111b")) + + checkmark := lipgloss.NewStyle(). + Foreground(lipgloss.Color("#a6e3a1")). + Bold(true). + Render("✓") + + xmark := lipgloss.NewStyle(). + Foreground(lipgloss.Color("#f38ba8")). + Bold(true). + Render("✕") + + mpl := 0 + for _, app := range applications { + if len(app.Name) > mpl { + mpl = len(app.Name) + } + } + + return &runner{ + width: nil, + height: nil, + + prefix: prefix, + checkmark: checkmark, + xmark: xmark, + + header: NewHeader(), + cbox: NewCbox(mpl + 1), + help: NewHelp(), + spinner: spinner.New(spinner.WithSpinner(spinner.MiniDot), spinner.WithStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("#a6adc8")))), + + msgChan: msgChan, + msgs: []apps.Msg{}, + apps: applications, + } +} + +func (m runner) Init() tea.Cmd { + return tea.Batch( + m.spinner.Tick, + func() tea.Msg { + return apps.Msg(<-m.msgChan) + }, + ) +} + +func (m runner) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + var cmds []tea.Cmd + + switch msg := msg.(type) { + + case apps.Msg: + // Remove old message with the same key + if msg.Key != nil && msg.Loading != nil && !*msg.Loading { + for i, prev := range m.msgs { + if prev.Key != nil && prev.Loading != nil && *prev.Key == *msg.Key && *prev.Loading { + m.msgs = append(m.msgs[:i], m.msgs[i+1:]...) + break + } + } + } + + // Set current state + if msg.Loading != nil { + if *msg.Loading { + msg.App.Loading = nil + } else { + msg.App.Loading = msg.Success + } + } + + // Append new message + m.msgs = append(m.msgs, msg) + + return m, func() tea.Msg { + return apps.Msg(<-m.msgChan) + } + + case spinner.TickMsg: + m.spinner, cmd = m.spinner.Update(msg) + return m, cmd + + case tea.WindowSizeMsg: + m.width = utils.IntPointer(msg.Width) + m.height = utils.IntPointer(msg.Height) + + case tea.KeyMsg: + switch { + case key.Matches(msg, m.help.keys.Help): + m.help.Toggle() + + case key.Matches(msg, m.help.keys.Quit): + return m, tea.Quit + } + + case tea.MouseMsg: + switch msg.Button { + case tea.MouseButtonWheelDown: + m.cbox.Viewport.LineDown(1) + + case tea.MouseButtonWheelUp: + m.cbox.Viewport.LineUp(1) + } + } + + return m, tea.Batch(cmds...) +} + +func (m runner) term() (rows []string) { + if m.width == nil { + return rows + } + + for _, msg := range m.msgs { + item := []string{} + + if msg.Loading != nil && *msg.Loading { + item = append(item, m.spinner.View()) + } + + if msg.Success != nil { + if *msg.Success { + item = append(item, m.checkmark) + } else { + item = append(item, m.xmark) + } + } + + item = append(item, msg.Text) + itemStr := strings.Join(item, " ") + + // Render the row + rows = append(rows, m.cbox.GenItem(msg.Time, msg.App.Name, itemStr, msg.App.Color, *m.width)) + } + + return rows +} + +func (m runner) head() (items []string) { + for _, app := range m.apps { + item := []string{} + + if app.Loading == nil { + item = append(item, m.spinner.View()) + } else if *app.Loading { + item = append(item, m.checkmark) + } else { + item = append(item, m.xmark) + } + + item = append(item, app.Name) + items = append(items, m.header.GenItem(strings.Join(item, " "))) + } + + return items +} + +func (m runner) View() string { + if m.width == nil || m.height == nil { + return fmt.Sprintf("\n %s Loading...", m.spinner.View()) + } + + // Generate the UI + header := m.header.Gen(*m.width, m.head()...) + footer := m.help.Gen(*m.width) + main := m.cbox.Gen( + strings.Join(m.term(), "\n"), + *m.width, + *m.height, + lipgloss.Height(header), + lipgloss.Height(footer), + ) + + s := header + s += main + s += footer + + // Send the UI for rendering + return s +} diff --git a/cli/internal/utils/pointers.go b/cli/internal/utils/pointers.go new file mode 100644 index 0000000..e047ed8 --- /dev/null +++ b/cli/internal/utils/pointers.go @@ -0,0 +1,13 @@ +package utils + +func BoolPointer(b bool) *bool { + return &b +} + +func StringPointer(s string) *string { + return &s +} + +func IntPointer(i int) *int { + return &i +} diff --git a/cli/internal/utils/run.go b/cli/internal/utils/run.go new file mode 100644 index 0000000..32ed9b3 --- /dev/null +++ b/cli/internal/utils/run.go @@ -0,0 +1,55 @@ +package utils + +import ( + "bufio" + "os/exec" +) + +type Stdout string +type Stderr string +type ExitCode int + +func Run(cmd *exec.Cmd) (chan interface{}, error) { + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + stderr, err := cmd.StderrPipe() + if err != nil { + return nil, err + } + if err := cmd.Start(); err != nil { + return nil, err + } + + c := make(chan interface{}, 10) + + go func() { + scan := bufio.NewScanner(stdout) + for scan.Scan() { + c <- Stdout(scan.Text()) + } + }() + go func() { + scan := bufio.NewScanner(stderr) + for scan.Scan() { + c <- Stderr(scan.Text()) + } + }() + + go func() { + defer close(c) + + if err := cmd.Wait(); err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + c <- ExitCode(exitError.ExitCode()) + } else { + c <- ExitCode(1) + } + } else { + c <- ExitCode(0) + } + }() + + return c, nil +} diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..58b61af --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,2 @@ +/node_modules/ +/.svelte-kit/ \ No newline at end of file diff --git a/client/src/lib/services/user/v1/auth_pb.ts b/client/src/lib/services/user/v1/auth_pb.ts index 5951b25..be31bc4 100644 --- a/client/src/lib/services/user/v1/auth_pb.ts +++ b/client/src/lib/services/user/v1/auth_pb.ts @@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file user/v1/auth.proto. */ export const file_user_v1_auth: GenFile = /*@__PURE__*/ - fileDesc("ChJ1c2VyL3YxL2F1dGgucHJvdG8SB3VzZXIudjEiMgoMTG9naW5SZXF1ZXN0EhAKCHVzZXJuYW1lGAEgASgJEhAKCHBhc3N3b3JkGAIgASgJIh4KDUxvZ2luUmVzcG9uc2USDQoFdG9rZW4YASABKAkiTQoNU2lnblVwUmVxdWVzdBIQCgh1c2VybmFtZRgBIAEoCRIQCghwYXNzd29yZBgCIAEoCRIYChBjb25maXJtX3Bhc3N3b3JkGAMgASgJIhAKDlNpZ25VcFJlc3BvbnNlIg8KDUxvZ291dFJlcXVlc3QiEAoOTG9nb3V0UmVzcG9uc2UiKAoUR2V0UGFzc2tleUlEc1JlcXVlc3QSEAoIdXNlcm5hbWUYASABKAkiLAoVR2V0UGFzc2tleUlEc1Jlc3BvbnNlEhMKC3Bhc3NrZXlfaWRzGAEgAygJIkcKE1Bhc3NrZXlMb2dpblJlcXVlc3QSCgoCaWQYASABKAkSEQoJc2lnbmF0dXJlGAIgASgMEhEKCWFsZ29yaXRobRgDIAEoBSIlChRQYXNza2V5TG9naW5SZXNwb25zZRINCgV0b2tlbhgBIAEoCTLiAgoLQXV0aFNlcnZpY2USOAoFTG9naW4SFS51c2VyLnYxLkxvZ2luUmVxdWVzdBoWLnVzZXIudjEuTG9naW5SZXNwb25zZSIAEjsKBlNpZ25VcBIWLnVzZXIudjEuU2lnblVwUmVxdWVzdBoXLnVzZXIudjEuU2lnblVwUmVzcG9uc2UiABI7CgZMb2dvdXQSFi51c2VyLnYxLkxvZ291dFJlcXVlc3QaFy51c2VyLnYxLkxvZ291dFJlc3BvbnNlIgASUAoNR2V0UGFzc2tleUlEcxIdLnVzZXIudjEuR2V0UGFzc2tleUlEc1JlcXVlc3QaHi51c2VyLnYxLkdldFBhc3NrZXlJRHNSZXNwb25zZSIAEk0KDFBhc3NrZXlMb2dpbhIcLnVzZXIudjEuUGFzc2tleUxvZ2luUmVxdWVzdBodLnVzZXIudjEuUGFzc2tleUxvZ2luUmVzcG9uc2UiAEKdAQoLY29tLnVzZXIudjFCCUF1dGhQcm90b1ABWkZnaXRodWIuY29tL3Nwb3RkZW1vNC90cmV2c3RhY2svc2VydmVyL2ludGVybmFsL3NlcnZpY2VzL3VzZXIvdjE7dXNlcnYxogIDVVhYqgIHVXNlci5WMcoCB1VzZXJcVjHiAhNVc2VyXFYxXEdQQk1ldGFkYXRh6gIIVXNlcjo6VjFiBnByb3RvMw"); + fileDesc("ChJ1c2VyL3YxL2F1dGgucHJvdG8SB3VzZXIudjEiMgoMTG9naW5SZXF1ZXN0EhAKCHVzZXJuYW1lGAEgASgJEhAKCHBhc3N3b3JkGAIgASgJIh4KDUxvZ2luUmVzcG9uc2USDQoFdG9rZW4YASABKAkiTQoNU2lnblVwUmVxdWVzdBIQCgh1c2VybmFtZRgBIAEoCRIQCghwYXNzd29yZBgCIAEoCRIYChBjb25maXJtX3Bhc3N3b3JkGAMgASgJIhAKDlNpZ25VcFJlc3BvbnNlIg8KDUxvZ291dFJlcXVlc3QiEAoOTG9nb3V0UmVzcG9uc2UiKAoUR2V0UGFzc2tleUlEc1JlcXVlc3QSEAoIdXNlcm5hbWUYASABKAkiLAoVR2V0UGFzc2tleUlEc1Jlc3BvbnNlEhMKC3Bhc3NrZXlfaWRzGAEgAygJIhoKGEJlZ2luUGFzc2tleUxvZ2luUmVxdWVzdCIbChlCZWdpblBhc3NrZXlMb2dpblJlc3BvbnNlIhsKGUZpbmlzaFBhc3NrZXlMb2dpblJlcXVlc3QiHAoaRmluaXNoUGFzc2tleUxvZ2luUmVzcG9uc2Uy0gMKC0F1dGhTZXJ2aWNlEjgKBUxvZ2luEhUudXNlci52MS5Mb2dpblJlcXVlc3QaFi51c2VyLnYxLkxvZ2luUmVzcG9uc2UiABI7CgZTaWduVXASFi51c2VyLnYxLlNpZ25VcFJlcXVlc3QaFy51c2VyLnYxLlNpZ25VcFJlc3BvbnNlIgASOwoGTG9nb3V0EhYudXNlci52MS5Mb2dvdXRSZXF1ZXN0GhcudXNlci52MS5Mb2dvdXRSZXNwb25zZSIAElAKDUdldFBhc3NrZXlJRHMSHS51c2VyLnYxLkdldFBhc3NrZXlJRHNSZXF1ZXN0Gh4udXNlci52MS5HZXRQYXNza2V5SURzUmVzcG9uc2UiABJcChFCZWdpblBhc3NrZXlMb2dpbhIhLnVzZXIudjEuQmVnaW5QYXNza2V5TG9naW5SZXF1ZXN0GiIudXNlci52MS5CZWdpblBhc3NrZXlMb2dpblJlc3BvbnNlIgASXwoSRmluaXNoUGFzc2tleUxvZ2luEiIudXNlci52MS5GaW5pc2hQYXNza2V5TG9naW5SZXF1ZXN0GiMudXNlci52MS5GaW5pc2hQYXNza2V5TG9naW5SZXNwb25zZSIAQp0BCgtjb20udXNlci52MUIJQXV0aFByb3RvUAFaRmdpdGh1Yi5jb20vc3BvdGRlbW80L3RyZXZzdGFjay9zZXJ2ZXIvaW50ZXJuYWwvc2VydmljZXMvdXNlci92MTt1c2VydjGiAgNVWFiqAgdVc2VyLlYxygIHVXNlclxWMeICE1VzZXJcVjFcR1BCTWV0YWRhdGHqAghVc2VyOjpWMWIGcHJvdG8z"); /** * @generated from message user.v1.LoginRequest @@ -152,49 +152,57 @@ export const GetPasskeyIDsResponseSchema: GenMessage = /* messageDesc(file_user_v1_auth, 7); /** - * @generated from message user.v1.PasskeyLoginRequest + * @generated from message user.v1.BeginPasskeyLoginRequest */ -export type PasskeyLoginRequest = Message<"user.v1.PasskeyLoginRequest"> & { - /** - * @generated from field: string id = 1; - */ - id: string; - - /** - * @generated from field: bytes signature = 2; - */ - signature: Uint8Array; - - /** - * @generated from field: int32 algorithm = 3; - */ - algorithm: number; +export type BeginPasskeyLoginRequest = Message<"user.v1.BeginPasskeyLoginRequest"> & { }; /** - * Describes the message user.v1.PasskeyLoginRequest. - * Use `create(PasskeyLoginRequestSchema)` to create a new message. + * Describes the message user.v1.BeginPasskeyLoginRequest. + * Use `create(BeginPasskeyLoginRequestSchema)` to create a new message. */ -export const PasskeyLoginRequestSchema: GenMessage = /*@__PURE__*/ +export const BeginPasskeyLoginRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_user_v1_auth, 8); /** - * @generated from message user.v1.PasskeyLoginResponse + * @generated from message user.v1.BeginPasskeyLoginResponse */ -export type PasskeyLoginResponse = Message<"user.v1.PasskeyLoginResponse"> & { - /** - * @generated from field: string token = 1; - */ - token: string; +export type BeginPasskeyLoginResponse = Message<"user.v1.BeginPasskeyLoginResponse"> & { }; /** - * Describes the message user.v1.PasskeyLoginResponse. - * Use `create(PasskeyLoginResponseSchema)` to create a new message. + * Describes the message user.v1.BeginPasskeyLoginResponse. + * Use `create(BeginPasskeyLoginResponseSchema)` to create a new message. */ -export const PasskeyLoginResponseSchema: GenMessage = /*@__PURE__*/ +export const BeginPasskeyLoginResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_user_v1_auth, 9); +/** + * @generated from message user.v1.FinishPasskeyLoginRequest + */ +export type FinishPasskeyLoginRequest = Message<"user.v1.FinishPasskeyLoginRequest"> & { +}; + +/** + * Describes the message user.v1.FinishPasskeyLoginRequest. + * Use `create(FinishPasskeyLoginRequestSchema)` to create a new message. + */ +export const FinishPasskeyLoginRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_user_v1_auth, 10); + +/** + * @generated from message user.v1.FinishPasskeyLoginResponse + */ +export type FinishPasskeyLoginResponse = Message<"user.v1.FinishPasskeyLoginResponse"> & { +}; + +/** + * Describes the message user.v1.FinishPasskeyLoginResponse. + * Use `create(FinishPasskeyLoginResponseSchema)` to create a new message. + */ +export const FinishPasskeyLoginResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_user_v1_auth, 11); + /** * @generated from service user.v1.AuthService */ @@ -232,12 +240,20 @@ export const AuthService: GenService<{ output: typeof GetPasskeyIDsResponseSchema; }, /** - * @generated from rpc user.v1.AuthService.PasskeyLogin + * @generated from rpc user.v1.AuthService.BeginPasskeyLogin */ - passkeyLogin: { + beginPasskeyLogin: { methodKind: "unary"; - input: typeof PasskeyLoginRequestSchema; - output: typeof PasskeyLoginResponseSchema; + input: typeof BeginPasskeyLoginRequestSchema; + output: typeof BeginPasskeyLoginResponseSchema; + }, + /** + * @generated from rpc user.v1.AuthService.FinishPasskeyLogin + */ + finishPasskeyLogin: { + methodKind: "unary"; + input: typeof FinishPasskeyLoginRequestSchema; + output: typeof FinishPasskeyLoginResponseSchema; }, }> = /*@__PURE__*/ serviceDesc(file_user_v1_auth, 0); diff --git a/client/src/lib/services/user/v1/user_pb.ts b/client/src/lib/services/user/v1/user_pb.ts index 3d2fc39..956c7ae 100644 --- a/client/src/lib/services/user/v1/user_pb.ts +++ b/client/src/lib/services/user/v1/user_pb.ts @@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file user/v1/user.proto. */ export const file_user_v1_user: GenFile = /*@__PURE__*/ - fileDesc("ChJ1c2VyL3YxL3VzZXIucHJvdG8SB3VzZXIudjEiVgoEVXNlchIKCgJpZBgBIAEoDRIQCgh1c2VybmFtZRgCIAEoCRIcCg9wcm9maWxlX3BpY3R1cmUYAyABKAlIAIgBAUISChBfcHJvZmlsZV9waWN0dXJlIhAKDkdldFVzZXJSZXF1ZXN0Ii4KD0dldFVzZXJSZXNwb25zZRIbCgR1c2VyGAEgASgLMg0udXNlci52MS5Vc2VyIl0KFVVwZGF0ZVBhc3N3b3JkUmVxdWVzdBIUCgxvbGRfcGFzc3dvcmQYASABKAkSFAoMbmV3X3Bhc3N3b3JkGAIgASgJEhgKEGNvbmZpcm1fcGFzc3dvcmQYAyABKAkiNQoWVXBkYXRlUGFzc3dvcmRSZXNwb25zZRIbCgR1c2VyGAEgASgLMg0udXNlci52MS5Vc2VyIj4KEEdldEFQSUtleVJlcXVlc3QSEAoIcGFzc3dvcmQYASABKAkSGAoQY29uZmlybV9wYXNzd29yZBgCIAEoCSIgChFHZXRBUElLZXlSZXNwb25zZRILCgNrZXkYASABKAkiPgobVXBkYXRlUHJvZmlsZVBpY3R1cmVSZXF1ZXN0EhEKCWZpbGVfbmFtZRgBIAEoCRIMCgRkYXRhGAIgASgMIjsKHFVwZGF0ZVByb2ZpbGVQaWN0dXJlUmVzcG9uc2USGwoEdXNlchgBIAEoCzINLnVzZXIudjEuVXNlciI2ChRDcmVhdGVQYXNza2V5UmVxdWVzdBIKCgJpZBgBIAEoCRISCgpwdWJsaWNfa2V5GAIgASgMIhcKFUNyZWF0ZVBhc3NrZXlSZXNwb25zZTKhAwoLVXNlclNlcnZpY2USPgoHR2V0VXNlchIXLnVzZXIudjEuR2V0VXNlclJlcXVlc3QaGC51c2VyLnYxLkdldFVzZXJSZXNwb25zZSIAElMKDlVwZGF0ZVBhc3N3b3JkEh4udXNlci52MS5VcGRhdGVQYXNzd29yZFJlcXVlc3QaHy51c2VyLnYxLlVwZGF0ZVBhc3N3b3JkUmVzcG9uc2UiABJECglHZXRBUElLZXkSGS51c2VyLnYxLkdldEFQSUtleVJlcXVlc3QaGi51c2VyLnYxLkdldEFQSUtleVJlc3BvbnNlIgASZQoUVXBkYXRlUHJvZmlsZVBpY3R1cmUSJC51c2VyLnYxLlVwZGF0ZVByb2ZpbGVQaWN0dXJlUmVxdWVzdBolLnVzZXIudjEuVXBkYXRlUHJvZmlsZVBpY3R1cmVSZXNwb25zZSIAElAKDUNyZWF0ZVBhc3NrZXkSHS51c2VyLnYxLkNyZWF0ZVBhc3NrZXlSZXF1ZXN0Gh4udXNlci52MS5DcmVhdGVQYXNza2V5UmVzcG9uc2UiAEKdAQoLY29tLnVzZXIudjFCCVVzZXJQcm90b1ABWkZnaXRodWIuY29tL3Nwb3RkZW1vNC90cmV2c3RhY2svc2VydmVyL2ludGVybmFsL3NlcnZpY2VzL3VzZXIvdjE7dXNlcnYxogIDVVhYqgIHVXNlci5WMcoCB1VzZXJcVjHiAhNVc2VyXFYxXEdQQk1ldGFkYXRh6gIIVXNlcjo6VjFiBnByb3RvMw"); + fileDesc("ChJ1c2VyL3YxL3VzZXIucHJvdG8SB3VzZXIudjEiVgoEVXNlchIKCgJpZBgBIAEoDRIQCgh1c2VybmFtZRgCIAEoCRIcCg9wcm9maWxlX3BpY3R1cmUYAyABKAlIAIgBAUISChBfcHJvZmlsZV9waWN0dXJlIhAKDkdldFVzZXJSZXF1ZXN0Ii4KD0dldFVzZXJSZXNwb25zZRIbCgR1c2VyGAEgASgLMg0udXNlci52MS5Vc2VyIl0KFVVwZGF0ZVBhc3N3b3JkUmVxdWVzdBIUCgxvbGRfcGFzc3dvcmQYASABKAkSFAoMbmV3X3Bhc3N3b3JkGAIgASgJEhgKEGNvbmZpcm1fcGFzc3dvcmQYAyABKAkiNQoWVXBkYXRlUGFzc3dvcmRSZXNwb25zZRIbCgR1c2VyGAEgASgLMg0udXNlci52MS5Vc2VyIj4KEEdldEFQSUtleVJlcXVlc3QSEAoIcGFzc3dvcmQYASABKAkSGAoQY29uZmlybV9wYXNzd29yZBgCIAEoCSIgChFHZXRBUElLZXlSZXNwb25zZRILCgNrZXkYASABKAkiPgobVXBkYXRlUHJvZmlsZVBpY3R1cmVSZXF1ZXN0EhEKCWZpbGVfbmFtZRgBIAEoCRIMCgRkYXRhGAIgASgMIjsKHFVwZGF0ZVByb2ZpbGVQaWN0dXJlUmVzcG9uc2USGwoEdXNlchgBIAEoCzINLnVzZXIudjEuVXNlciIhCh9CZWdpblBhc3NrZXlSZWdpc3RyYXRpb25SZXF1ZXN0IiIKIEJlZ2luUGFzc2tleVJlZ2lzdHJhdGlvblJlc3BvbnNlIiIKIEZpbmlzaFBhc3NrZXlSZWdpc3RyYXRpb25SZXF1ZXN0IiMKIUZpbmlzaFBhc3NrZXlSZWdpc3RyYXRpb25SZXNwb25zZTK4BAoLVXNlclNlcnZpY2USPgoHR2V0VXNlchIXLnVzZXIudjEuR2V0VXNlclJlcXVlc3QaGC51c2VyLnYxLkdldFVzZXJSZXNwb25zZSIAElMKDlVwZGF0ZVBhc3N3b3JkEh4udXNlci52MS5VcGRhdGVQYXNzd29yZFJlcXVlc3QaHy51c2VyLnYxLlVwZGF0ZVBhc3N3b3JkUmVzcG9uc2UiABJECglHZXRBUElLZXkSGS51c2VyLnYxLkdldEFQSUtleVJlcXVlc3QaGi51c2VyLnYxLkdldEFQSUtleVJlc3BvbnNlIgASZQoUVXBkYXRlUHJvZmlsZVBpY3R1cmUSJC51c2VyLnYxLlVwZGF0ZVByb2ZpbGVQaWN0dXJlUmVxdWVzdBolLnVzZXIudjEuVXBkYXRlUHJvZmlsZVBpY3R1cmVSZXNwb25zZSIAEnEKGEJlZ2luUGFzc2tleVJlZ2lzdHJhdGlvbhIoLnVzZXIudjEuQmVnaW5QYXNza2V5UmVnaXN0cmF0aW9uUmVxdWVzdBopLnVzZXIudjEuQmVnaW5QYXNza2V5UmVnaXN0cmF0aW9uUmVzcG9uc2UiABJ0ChlGaW5pc2hQYXNza2V5UmVnaXN0cmF0aW9uEikudXNlci52MS5GaW5pc2hQYXNza2V5UmVnaXN0cmF0aW9uUmVxdWVzdBoqLnVzZXIudjEuRmluaXNoUGFzc2tleVJlZ2lzdHJhdGlvblJlc3BvbnNlIgBCnQEKC2NvbS51c2VyLnYxQglVc2VyUHJvdG9QAVpGZ2l0aHViLmNvbS9zcG90ZGVtbzQvdHJldnN0YWNrL3NlcnZlci9pbnRlcm5hbC9zZXJ2aWNlcy91c2VyL3YxO3VzZXJ2MaICA1VYWKoCB1VzZXIuVjHKAgdVc2VyXFYx4gITVXNlclxWMVxHUEJNZXRhZGF0YeoCCFVzZXI6OlYxYgZwcm90bzM"); /** * @generated from message user.v1.User @@ -192,40 +192,57 @@ export const UpdateProfilePictureResponseSchema: GenMessage & { - /** - * @generated from field: string id = 1; - */ - id: string; - - /** - * @generated from field: bytes public_key = 2; - */ - publicKey: Uint8Array; +export type BeginPasskeyRegistrationRequest = Message<"user.v1.BeginPasskeyRegistrationRequest"> & { }; /** - * Describes the message user.v1.CreatePasskeyRequest. - * Use `create(CreatePasskeyRequestSchema)` to create a new message. + * Describes the message user.v1.BeginPasskeyRegistrationRequest. + * Use `create(BeginPasskeyRegistrationRequestSchema)` to create a new message. */ -export const CreatePasskeyRequestSchema: GenMessage = /*@__PURE__*/ +export const BeginPasskeyRegistrationRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_user_v1_user, 9); /** - * @generated from message user.v1.CreatePasskeyResponse + * @generated from message user.v1.BeginPasskeyRegistrationResponse */ -export type CreatePasskeyResponse = Message<"user.v1.CreatePasskeyResponse"> & { +export type BeginPasskeyRegistrationResponse = Message<"user.v1.BeginPasskeyRegistrationResponse"> & { }; /** - * Describes the message user.v1.CreatePasskeyResponse. - * Use `create(CreatePasskeyResponseSchema)` to create a new message. + * Describes the message user.v1.BeginPasskeyRegistrationResponse. + * Use `create(BeginPasskeyRegistrationResponseSchema)` to create a new message. */ -export const CreatePasskeyResponseSchema: GenMessage = /*@__PURE__*/ +export const BeginPasskeyRegistrationResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_user_v1_user, 10); +/** + * @generated from message user.v1.FinishPasskeyRegistrationRequest + */ +export type FinishPasskeyRegistrationRequest = Message<"user.v1.FinishPasskeyRegistrationRequest"> & { +}; + +/** + * Describes the message user.v1.FinishPasskeyRegistrationRequest. + * Use `create(FinishPasskeyRegistrationRequestSchema)` to create a new message. + */ +export const FinishPasskeyRegistrationRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_user_v1_user, 11); + +/** + * @generated from message user.v1.FinishPasskeyRegistrationResponse + */ +export type FinishPasskeyRegistrationResponse = Message<"user.v1.FinishPasskeyRegistrationResponse"> & { +}; + +/** + * Describes the message user.v1.FinishPasskeyRegistrationResponse. + * Use `create(FinishPasskeyRegistrationResponseSchema)` to create a new message. + */ +export const FinishPasskeyRegistrationResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_user_v1_user, 12); + /** * @generated from service user.v1.UserService */ @@ -263,12 +280,20 @@ export const UserService: GenService<{ output: typeof UpdateProfilePictureResponseSchema; }, /** - * @generated from rpc user.v1.UserService.CreatePasskey + * @generated from rpc user.v1.UserService.BeginPasskeyRegistration */ - createPasskey: { + beginPasskeyRegistration: { methodKind: "unary"; - input: typeof CreatePasskeyRequestSchema; - output: typeof CreatePasskeyResponseSchema; + input: typeof BeginPasskeyRegistrationRequestSchema; + output: typeof BeginPasskeyRegistrationResponseSchema; + }, + /** + * @generated from rpc user.v1.UserService.FinishPasskeyRegistration + */ + finishPasskeyRegistration: { + methodKind: "unary"; + input: typeof FinishPasskeyRegistrationRequestSchema; + output: typeof FinishPasskeyRegistrationResponseSchema; }, }> = /*@__PURE__*/ serviceDesc(file_user_v1_user, 0); diff --git a/client/static/openapi/openapi.yaml b/client/static/openapi/openapi.yaml index e2a2a46..16b06f3 100644 --- a/client/static/openapi/openapi.yaml +++ b/client/static/openapi/openapi.yaml @@ -292,6 +292,22 @@ components: additionalProperties: true additionalProperties: true description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + user.v1.BeginPasskeyLoginRequest: + type: object + title: BeginPasskeyLoginRequest + additionalProperties: false + user.v1.BeginPasskeyLoginResponse: + type: object + title: BeginPasskeyLoginResponse + additionalProperties: false + user.v1.FinishPasskeyLoginRequest: + type: object + title: FinishPasskeyLoginRequest + additionalProperties: false + user.v1.FinishPasskeyLoginResponse: + type: object + title: FinishPasskeyLoginResponse + additionalProperties: false user.v1.GetPasskeyIDsRequest: type: object properties: @@ -337,30 +353,6 @@ components: type: object title: LogoutResponse additionalProperties: false - user.v1.PasskeyLoginRequest: - type: object - properties: - id: - type: string - title: id - signature: - type: string - title: signature - format: byte - algorithm: - type: integer - title: algorithm - format: int32 - title: PasskeyLoginRequest - additionalProperties: false - user.v1.PasskeyLoginResponse: - type: object - properties: - token: - type: string - title: token - title: PasskeyLoginResponse - additionalProperties: false user.v1.SignUpRequest: type: object properties: @@ -379,21 +371,21 @@ components: type: object title: SignUpResponse additionalProperties: false - user.v1.CreatePasskeyRequest: + user.v1.BeginPasskeyRegistrationRequest: type: object - properties: - id: - type: string - title: id - publicKey: - type: string - title: public_key - format: byte - title: CreatePasskeyRequest + title: BeginPasskeyRegistrationRequest additionalProperties: false - user.v1.CreatePasskeyResponse: + user.v1.BeginPasskeyRegistrationResponse: type: object - title: CreatePasskeyResponse + title: BeginPasskeyRegistrationResponse + additionalProperties: false + user.v1.FinishPasskeyRegistrationRequest: + type: object + title: FinishPasskeyRegistrationRequest + additionalProperties: false + user.v1.FinishPasskeyRegistrationResponse: + type: object + title: FinishPasskeyRegistrationResponse additionalProperties: false user.v1.GetAPIKeyRequest: type: object @@ -801,12 +793,12 @@ paths: application/json: schema: $ref: '#/components/schemas/user.v1.GetPasskeyIDsResponse' - /user.v1.AuthService/PasskeyLogin: + /user.v1.AuthService/BeginPasskeyLogin: post: tags: - user.v1.AuthService - summary: PasskeyLogin - operationId: user.v1.AuthService.PasskeyLogin + summary: BeginPasskeyLogin + operationId: user.v1.AuthService.BeginPasskeyLogin parameters: - name: Connect-Protocol-Version in: header @@ -821,7 +813,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/user.v1.PasskeyLoginRequest' + $ref: '#/components/schemas/user.v1.BeginPasskeyLoginRequest' required: true responses: default: @@ -835,7 +827,42 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/user.v1.PasskeyLoginResponse' + $ref: '#/components/schemas/user.v1.BeginPasskeyLoginResponse' + /user.v1.AuthService/FinishPasskeyLogin: + post: + tags: + - user.v1.AuthService + summary: FinishPasskeyLogin + operationId: user.v1.AuthService.FinishPasskeyLogin + parameters: + - name: Connect-Protocol-Version + in: header + required: true + schema: + $ref: '#/components/schemas/connect-protocol-version' + - name: Connect-Timeout-Ms + in: header + schema: + $ref: '#/components/schemas/connect-timeout-header' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/user.v1.FinishPasskeyLoginRequest' + required: true + responses: + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/user.v1.FinishPasskeyLoginResponse' /user.v1.UserService/GetUser: post: tags: @@ -976,12 +1003,12 @@ paths: application/json: schema: $ref: '#/components/schemas/user.v1.UpdateProfilePictureResponse' - /user.v1.UserService/CreatePasskey: + /user.v1.UserService/BeginPasskeyRegistration: post: tags: - user.v1.UserService - summary: CreatePasskey - operationId: user.v1.UserService.CreatePasskey + summary: BeginPasskeyRegistration + operationId: user.v1.UserService.BeginPasskeyRegistration parameters: - name: Connect-Protocol-Version in: header @@ -996,7 +1023,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/user.v1.CreatePasskeyRequest' + $ref: '#/components/schemas/user.v1.BeginPasskeyRegistrationRequest' required: true responses: default: @@ -1010,7 +1037,42 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/user.v1.CreatePasskeyResponse' + $ref: '#/components/schemas/user.v1.BeginPasskeyRegistrationResponse' + /user.v1.UserService/FinishPasskeyRegistration: + post: + tags: + - user.v1.UserService + summary: FinishPasskeyRegistration + operationId: user.v1.UserService.FinishPasskeyRegistration + parameters: + - name: Connect-Protocol-Version + in: header + required: true + schema: + $ref: '#/components/schemas/connect-protocol-version' + - name: Connect-Timeout-Ms + in: header + schema: + $ref: '#/components/schemas/connect-timeout-header' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/user.v1.FinishPasskeyRegistrationRequest' + required: true + responses: + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/user.v1.FinishPasskeyRegistrationResponse' tags: - name: item.v1.ItemService - name: user.v1.AuthService diff --git a/docker-compose.yml b/docker-compose-example.yaml similarity index 100% rename from docker-compose.yml rename to docker-compose-example.yaml diff --git a/flake.nix b/flake.nix index 9577ddf..0212e8e 100644 --- a/flake.nix +++ b/flake.nix @@ -34,6 +34,21 @@ nativeCheckInputs = with pkgs; [ less ]; }; + bobgen = pkgs.buildGoModule { + name = "bobgen"; + src = pkgs.fetchFromGitHub { + owner = "stephenafamo"; + repo = "bob"; + rev = "v0.31.0"; + sha256 = "sha256-APAckQ+EDAu459NTPXUISLIrcAcX3aQ5B/jrMUEW0EY="; + }; + vendorHash = "sha256-3blGiSxlKpWH8k0acAXXks8nCdnoWmXLmzPStJmmGcM="; + subPackages = [ + "gen/bobgen-sqlite" + "gen/bobgen-psql" + ]; + }; + client = pkgs.buildNpmPackage { pname = "${pname}-client"; inherit version; @@ -72,6 +87,7 @@ gopls air revive + bobgen # Protobuf middleware buf diff --git a/proto/user/v1/auth.proto b/proto/user/v1/auth.proto index 126aabd..bdd024d 100644 --- a/proto/user/v1/auth.proto +++ b/proto/user/v1/auth.proto @@ -7,7 +7,8 @@ service AuthService { rpc SignUp (SignUpRequest) returns (SignUpResponse) {} rpc Logout (LogoutRequest) returns (LogoutResponse) {} rpc GetPasskeyIDs (GetPasskeyIDsRequest) returns (GetPasskeyIDsResponse) {} - rpc PasskeyLogin (PasskeyLoginRequest) returns (PasskeyLoginResponse) {} + rpc BeginPasskeyLogin (BeginPasskeyLoginRequest) returns (BeginPasskeyLoginResponse) {} + rpc FinishPasskeyLogin (FinishPasskeyLoginRequest) returns (FinishPasskeyLoginResponse) {} } message LoginRequest { @@ -20,7 +21,7 @@ message LoginResponse { message SignUpRequest { string username = 1; - string password = 2; + string password = 2; string confirm_password = 3; } message SignUpResponse {} @@ -35,11 +36,8 @@ message GetPasskeyIDsResponse { repeated string passkey_ids = 1; } -message PasskeyLoginRequest { - string id = 1; - bytes signature = 2; - int32 algorithm = 3; -} -message PasskeyLoginResponse { - string token = 1; -} \ No newline at end of file +message BeginPasskeyLoginRequest {} +message BeginPasskeyLoginResponse {} + +message FinishPasskeyLoginRequest {} +message FinishPasskeyLoginResponse {} \ No newline at end of file diff --git a/proto/user/v1/user.proto b/proto/user/v1/user.proto index c5ea3f0..85ec6aa 100644 --- a/proto/user/v1/user.proto +++ b/proto/user/v1/user.proto @@ -13,7 +13,8 @@ service UserService { rpc UpdatePassword (UpdatePasswordRequest) returns (UpdatePasswordResponse) {} rpc GetAPIKey (GetAPIKeyRequest) returns (GetAPIKeyResponse) {} rpc UpdateProfilePicture (UpdateProfilePictureRequest) returns (UpdateProfilePictureResponse) {} - rpc CreatePasskey (CreatePasskeyRequest) returns (CreatePasskeyResponse) {} + rpc BeginPasskeyRegistration (BeginPasskeyRegistrationRequest) returns (BeginPasskeyRegistrationResponse) {} + rpc FinishPasskeyRegistration (FinishPasskeyRegistrationRequest) returns (FinishPasskeyRegistrationResponse) {} } message GetUserRequest {} @@ -34,7 +35,7 @@ message GetAPIKeyRequest { string password = 1; string confirm_password = 2; } -message GetAPIKeyResponse { +message GetAPIKeyResponse { string key = 1; } @@ -46,8 +47,8 @@ message UpdateProfilePictureResponse { User user = 1; } -message CreatePasskeyRequest { - string id = 1; - bytes public_key = 2; -} -message CreatePasskeyResponse {} \ No newline at end of file +message BeginPasskeyRegistrationRequest {} +message BeginPasskeyRegistrationResponse {} + +message FinishPasskeyRegistrationRequest {} +message FinishPasskeyRegistrationResponse {} \ No newline at end of file diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..8f63a55 --- /dev/null +++ b/server/.gitignore @@ -0,0 +1,3 @@ +/client/ +/tmp/ +/internal/handlers/client/client/ \ No newline at end of file diff --git a/server/bobgen.yaml b/server/bobgen.yaml new file mode 100644 index 0000000..2fe4ae2 --- /dev/null +++ b/server/bobgen.yaml @@ -0,0 +1,10 @@ +wipe: true + +sqlite: + output: internal/models + +psql: + output: internal/models + +mysql: + output: internal/models \ No newline at end of file diff --git a/server/internal/handlers/user/v1/user.go b/server/internal/handlers/user/v1/user.go index 5426236..a3418cc 100644 --- a/server/internal/handlers/user/v1/user.go +++ b/server/internal/handlers/user/v1/user.go @@ -169,6 +169,38 @@ func (h *Handler) UpdateProfilePicture(ctx context.Context, req *connect.Request return res, nil } +func (h *Handler) BeginPasskeyRegistration(ctx context.Context, req *connect.Request[userv1.BeginPasskeyRegistrationRequest]) (*connect.Response[userv1.BeginPasskeyRegistrationResponse], error) { + // Get user ID from context + userid, ok := interceptors.GetUserContext(ctx) + if !ok { + return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("user not authenticated")) + } + + // Get user + user := models.User{} + if err := h.db.First(&user, "id = ?", userid).Error; err != nil { + return nil, connect.NewError(connect.CodeInternal, err) + } + + return connect.NewResponse(&userv1.BeginPasskeyRegistrationResponse{}), nil +} + +func (h *Handler) FinishPasskeyRegistration(ctx context.Context, req *connect.Request[userv1.FinishPasskeyRegistrationRequest]) (*connect.Response[userv1.FinishPasskeyRegistrationResponse], error) { + // Get user ID from context + userid, ok := interceptors.GetUserContext(ctx) + if !ok { + return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("user not authenticated")) + } + + // Get user + user := models.User{} + if err := h.db.First(&user, "id = ?", userid).Error; err != nil { + return nil, connect.NewError(connect.CodeInternal, err) + } + + return connect.NewResponse(&userv1.FinishPasskeyRegistrationResponse{}), nil +} + // func BeginRegistration(ctx context.Context) error { // userid, ok := interceptors.GetUserContext(ctx) // if !ok { diff --git a/server/internal/models/bob_main.bob.go b/server/internal/models/bob_main.bob.go new file mode 100644 index 0000000..153ea59 --- /dev/null +++ b/server/internal/models/bob_main.bob.go @@ -0,0 +1,162 @@ +// Code generated by BobGen sqlite (devel). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "hash/maphash" + "strings" + + "github.com/stephenafamo/bob" + "github.com/stephenafamo/bob/clause" + "github.com/stephenafamo/bob/dialect/sqlite" + "github.com/stephenafamo/bob/dialect/sqlite/dialect" + sqliteDriver "modernc.org/sqlite" +) + +var TableNames = struct { + Files string + Items string + Users string +}{ + Files: "files", + Items: "items", + Users: "users", +} + +var ColumnNames = struct { + Files fileColumnNames + Items itemColumnNames + Users userColumnNames +}{ + Files: fileColumnNames{ + ID: "id", + Name: "name", + Data: "data", + UserID: "user_id", + }, + Items: itemColumnNames{ + ID: "id", + Name: "name", + Description: "description", + Price: "price", + Quantity: "quantity", + Added: "added", + UserID: "user_id", + }, + Users: userColumnNames{ + ID: "id", + Username: "username", + Password: "password", + ProfilePictureID: "profile_picture_id", + Challenge: "challenge", + }, +} + +var ( + SelectWhere = Where[*dialect.SelectQuery]() + InsertWhere = Where[*dialect.InsertQuery]() + UpdateWhere = Where[*dialect.UpdateQuery]() + DeleteWhere = Where[*dialect.DeleteQuery]() +) + +func Where[Q sqlite.Filterable]() struct { + Files fileWhere[Q] + Items itemWhere[Q] + Users userWhere[Q] +} { + return struct { + Files fileWhere[Q] + Items itemWhere[Q] + Users userWhere[Q] + }{ + Files: buildFileWhere[Q](FileColumns), + Items: buildItemWhere[Q](ItemColumns), + Users: buildUserWhere[Q](UserColumns), + } +} + +var ( + SelectJoins = getJoins[*dialect.SelectQuery] + UpdateJoins = getJoins[*dialect.UpdateQuery] +) + +type joinSet[Q interface{ aliasedAs(string) Q }] struct { + InnerJoin Q + LeftJoin Q + RightJoin Q +} + +func (j joinSet[Q]) AliasedAs(alias string) joinSet[Q] { + return joinSet[Q]{ + InnerJoin: j.InnerJoin.aliasedAs(alias), + LeftJoin: j.LeftJoin.aliasedAs(alias), + RightJoin: j.RightJoin.aliasedAs(alias), + } +} + +type joins[Q dialect.Joinable] struct { + Files joinSet[fileJoins[Q]] + Items joinSet[itemJoins[Q]] + Users joinSet[userJoins[Q]] +} + +func buildJoinSet[Q interface{ aliasedAs(string) Q }, C any, F func(C, string) Q](c C, f F) joinSet[Q] { + return joinSet[Q]{ + InnerJoin: f(c, clause.InnerJoin), + LeftJoin: f(c, clause.LeftJoin), + RightJoin: f(c, clause.RightJoin), + } +} + +func getJoins[Q dialect.Joinable]() joins[Q] { + return joins[Q]{ + Files: buildJoinSet[fileJoins[Q]](FileColumns, buildFileJoins), + Items: buildJoinSet[itemJoins[Q]](ItemColumns, buildItemJoins), + Users: buildJoinSet[userJoins[Q]](UserColumns, buildUserJoins), + } +} + +type modAs[Q any, C interface{ AliasedAs(string) C }] struct { + c C + f func(C) bob.Mod[Q] +} + +func (m modAs[Q, C]) Apply(q Q) { + m.f(m.c).Apply(q) +} + +func (m modAs[Q, C]) AliasedAs(alias string) bob.Mod[Q] { + m.c = m.c.AliasedAs(alias) + return m +} + +func randInt() int64 { + out := int64(new(maphash.Hash).Sum64()) + + if out < 0 { + return -out % 10000 + } + + return out % 10000 +} + +// ErrUniqueConstraint captures all unique constraint errors by explicitly leaving `s` empty. +var ErrUniqueConstraint = &UniqueConstraintError{s: ""} + +type UniqueConstraintError struct { + // s is a string uniquely identifying the constraint in the raw error message returned from the database. + s string +} + +func (e *UniqueConstraintError) Error() string { + return e.s +} + +func (e *UniqueConstraintError) Is(target error) bool { + err, ok := target.(*sqliteDriver.Error) + if !ok { + return false + } + return err.Code() == 2067 && strings.Contains(err.Error(), e.s) +} diff --git a/server/internal/models/bob_main_test.bob.go b/server/internal/models/bob_main_test.bob.go new file mode 100644 index 0000000..dde600b --- /dev/null +++ b/server/internal/models/bob_main_test.bob.go @@ -0,0 +1,15 @@ +// Code generated by BobGen sqlite (devel). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import "github.com/stephenafamo/bob" + +// Make sure the type File runs hooks after queries +var _ bob.HookableType = &File{} + +// Make sure the type Item runs hooks after queries +var _ bob.HookableType = &Item{} + +// Make sure the type User runs hooks after queries +var _ bob.HookableType = &User{} diff --git a/server/internal/models/factory/bobfactory_context.bob.go b/server/internal/models/factory/bobfactory_context.bob.go new file mode 100644 index 0000000..3bf4375 --- /dev/null +++ b/server/internal/models/factory/bobfactory_context.bob.go @@ -0,0 +1,36 @@ +// Code generated by BobGen sqlite (devel). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + + models "github.com/spotdemo4/trevstack/server/internal/models" +) + +type contextKey string + +var ( + fileCtx = newContextual[*models.File]("file") + itemCtx = newContextual[*models.Item]("item") + userCtx = newContextual[*models.User]("user") +) + +// Contextual is a convienience wrapper around context.WithValue and context.Value +type contextual[V any] struct { + key contextKey +} + +func newContextual[V any](key string) contextual[V] { + return contextual[V]{key: contextKey(key)} +} + +func (k contextual[V]) WithValue(ctx context.Context, val V) context.Context { + return context.WithValue(ctx, k.key, val) +} + +func (k contextual[V]) Value(ctx context.Context) (V, bool) { + v, ok := ctx.Value(k.key).(V) + return v, ok +} diff --git a/server/internal/models/factory/bobfactory_main.bob.go b/server/internal/models/factory/bobfactory_main.bob.go new file mode 100644 index 0000000..6b9e78b --- /dev/null +++ b/server/internal/models/factory/bobfactory_main.bob.go @@ -0,0 +1,74 @@ +// Code generated by BobGen sqlite (devel). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +type Factory struct { + baseFileMods FileModSlice + baseItemMods ItemModSlice + baseUserMods UserModSlice +} + +func New() *Factory { + return &Factory{} +} + +func (f *Factory) NewFile(mods ...FileMod) *FileTemplate { + o := &FileTemplate{f: f} + + if f != nil { + f.baseFileMods.Apply(o) + } + + FileModSlice(mods).Apply(o) + + return o +} + +func (f *Factory) NewItem(mods ...ItemMod) *ItemTemplate { + o := &ItemTemplate{f: f} + + if f != nil { + f.baseItemMods.Apply(o) + } + + ItemModSlice(mods).Apply(o) + + return o +} + +func (f *Factory) NewUser(mods ...UserMod) *UserTemplate { + o := &UserTemplate{f: f} + + if f != nil { + f.baseUserMods.Apply(o) + } + + UserModSlice(mods).Apply(o) + + return o +} + +func (f *Factory) ClearBaseFileMods() { + f.baseFileMods = nil +} + +func (f *Factory) AddBaseFileMod(mods ...FileMod) { + f.baseFileMods = append(f.baseFileMods, mods...) +} + +func (f *Factory) ClearBaseItemMods() { + f.baseItemMods = nil +} + +func (f *Factory) AddBaseItemMod(mods ...ItemMod) { + f.baseItemMods = append(f.baseItemMods, mods...) +} + +func (f *Factory) ClearBaseUserMods() { + f.baseUserMods = nil +} + +func (f *Factory) AddBaseUserMod(mods ...UserMod) { + f.baseUserMods = append(f.baseUserMods, mods...) +} diff --git a/server/internal/models/factory/bobfactory_random.bob.go b/server/internal/models/factory/bobfactory_random.bob.go new file mode 100644 index 0000000..f2e0ab4 --- /dev/null +++ b/server/internal/models/factory/bobfactory_random.bob.go @@ -0,0 +1,56 @@ +// Code generated by BobGen sqlite (devel). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "strings" + "time" + + "github.com/jaswdr/faker/v2" +) + +var defaultFaker = faker.New() + +func random___byte(f *faker.Faker) []byte { + if f == nil { + f = &defaultFaker + } + + return []byte(random_string(f)) +} + +func random_float32(f *faker.Faker) float32 { + if f == nil { + f = &defaultFaker + } + + return f.Float32(10, -1_000_000, 1_000_000) +} + +func random_int32(f *faker.Faker) int32 { + if f == nil { + f = &defaultFaker + } + + return f.Int32() +} + +func random_string(f *faker.Faker) string { + if f == nil { + f = &defaultFaker + } + + return strings.Join(f.Lorem().Words(f.IntBetween(1, 5)), " ") +} + +func random_time_Time(f *faker.Faker) time.Time { + if f == nil { + f = &defaultFaker + } + + year := time.Hour * 24 * 365 + min := time.Now().Add(-year) + max := time.Now().Add(year) + return f.Time().TimeBetween(min, max) +} diff --git a/server/internal/models/factory/bobfactory_random_test.bob.go b/server/internal/models/factory/bobfactory_random_test.bob.go new file mode 100644 index 0000000..3957f15 --- /dev/null +++ b/server/internal/models/factory/bobfactory_random_test.bob.go @@ -0,0 +1,64 @@ +// Code generated by BobGen sqlite (devel). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "bytes" + "testing" +) + +func TestRandom_int32(t *testing.T) { + t.Parallel() + + val1 := random_int32(nil) + val2 := random_int32(nil) + + if val1 == val2 { + t.Fatalf("random_int32() returned the same value twice: %v", val1) + } +} + +func TestRandom_string(t *testing.T) { + t.Parallel() + + val1 := random_string(nil) + val2 := random_string(nil) + + if val1 == val2 { + t.Fatalf("random_string() returned the same value twice: %v", val1) + } +} + +func TestRandom___byte(t *testing.T) { + t.Parallel() + + val1 := random___byte(nil) + val2 := random___byte(nil) + + if bytes.Equal(val1, val2) { + t.Fatalf("random___byte() returned the same value twice: %v", val1) + } +} + +func TestRandom_float32(t *testing.T) { + t.Parallel() + + val1 := random_float32(nil) + val2 := random_float32(nil) + + if val1 == val2 { + t.Fatalf("random_float32() returned the same value twice: %v", val1) + } +} + +func TestRandom_time_Time(t *testing.T) { + t.Parallel() + + val1 := random_time_Time(nil) + val2 := random_time_Time(nil) + + if val1.Equal(val2) { + t.Fatalf("random_time_Time() returned the same value twice: %v", val1) + } +} diff --git a/server/internal/models/factory/files.bob.go b/server/internal/models/factory/files.bob.go new file mode 100644 index 0000000..54cfef8 --- /dev/null +++ b/server/internal/models/factory/files.bob.go @@ -0,0 +1,466 @@ +// Code generated by BobGen sqlite (devel). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + "testing" + + "github.com/aarondl/opt/null" + "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" + "github.com/jaswdr/faker/v2" + models "github.com/spotdemo4/trevstack/server/internal/models" + "github.com/stephenafamo/bob" +) + +type FileMod interface { + Apply(*FileTemplate) +} + +type FileModFunc func(*FileTemplate) + +func (f FileModFunc) Apply(n *FileTemplate) { + f(n) +} + +type FileModSlice []FileMod + +func (mods FileModSlice) Apply(n *FileTemplate) { + for _, f := range mods { + f.Apply(n) + } +} + +// FileTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type FileTemplate struct { + ID func() int32 + Name func() null.Val[string] + Data func() null.Val[[]byte] + UserID func() null.Val[int32] + + r fileR + f *Factory +} + +type fileR struct { + User *fileRUserR +} + +type fileRUserR struct { + o *UserTemplate +} + +// Apply mods to the FileTemplate +func (o *FileTemplate) Apply(mods ...FileMod) { + for _, mod := range mods { + mod.Apply(o) + } +} + +// toModel returns an *models.File +// this does nothing with the relationship templates +func (o FileTemplate) toModel() *models.File { + m := &models.File{} + + if o.ID != nil { + m.ID = o.ID() + } + if o.Name != nil { + m.Name = o.Name() + } + if o.Data != nil { + m.Data = o.Data() + } + if o.UserID != nil { + m.UserID = o.UserID() + } + + return m +} + +// toModels returns an models.FileSlice +// this does nothing with the relationship templates +func (o FileTemplate) toModels(number int) models.FileSlice { + m := make(models.FileSlice, number) + + for i := range m { + m[i] = o.toModel() + } + + return m +} + +// setModelRels creates and sets the relationships on *models.File +// according to the relationships in the template. Nothing is inserted into the db +func (t FileTemplate) setModelRels(o *models.File) { + if t.r.User != nil { + rel := t.r.User.o.toModel() + rel.R.Files = append(rel.R.Files, o) + o.UserID = null.From(rel.ID) + o.R.User = rel + } +} + +// BuildSetter returns an *models.FileSetter +// this does nothing with the relationship templates +func (o FileTemplate) BuildSetter() *models.FileSetter { + m := &models.FileSetter{} + + if o.ID != nil { + m.ID = omit.From(o.ID()) + } + if o.Name != nil { + m.Name = omitnull.FromNull(o.Name()) + } + if o.Data != nil { + m.Data = omitnull.FromNull(o.Data()) + } + if o.UserID != nil { + m.UserID = omitnull.FromNull(o.UserID()) + } + + return m +} + +// BuildManySetter returns an []*models.FileSetter +// this does nothing with the relationship templates +func (o FileTemplate) BuildManySetter(number int) []*models.FileSetter { + m := make([]*models.FileSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.File +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use FileTemplate.Create +func (o FileTemplate) Build() *models.File { + m := o.toModel() + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.FileSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use FileTemplate.CreateMany +func (o FileTemplate) BuildMany(number int) models.FileSlice { + m := make(models.FileSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableFile(m *models.FileSetter) { +} + +// insertOptRels creates and inserts any optional the relationships on *models.File +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *FileTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.File) (context.Context, error) { + var err error + + if o.r.User != nil { + var rel0 *models.User + ctx, rel0, err = o.r.User.o.create(ctx, exec) + if err != nil { + return ctx, err + } + err = m.AttachUser(ctx, exec, rel0) + if err != nil { + return ctx, err + } + } + + return ctx, err +} + +// Create builds a file and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *FileTemplate) Create(ctx context.Context, exec bob.Executor) (*models.File, error) { + _, m, err := o.create(ctx, exec) + return m, err +} + +// MustCreate builds a file and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *FileTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.File { + _, m, err := o.create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a file and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs +func (o *FileTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.File { + tb.Helper() + _, m, err := o.create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// create builds a file and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// this returns a context that includes the newly inserted model +func (o *FileTemplate) create(ctx context.Context, exec bob.Executor) (context.Context, *models.File, error) { + var err error + opt := o.BuildSetter() + ensureCreatableFile(opt) + + m, err := models.Files.Insert(opt).One(ctx, exec) + if err != nil { + return ctx, nil, err + } + ctx = fileCtx.WithValue(ctx, m) + + ctx, err = o.insertOptRels(ctx, exec, m) + return ctx, m, err +} + +// CreateMany builds multiple files and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o FileTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.FileSlice, error) { + _, m, err := o.createMany(ctx, exec, number) + return m, err +} + +// MustCreateMany builds multiple files and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o FileTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.FileSlice { + _, m, err := o.createMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple files and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs +func (o FileTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.FileSlice { + tb.Helper() + _, m, err := o.createMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// createMany builds multiple files and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// this returns a context that includes the newly inserted models +func (o FileTemplate) createMany(ctx context.Context, exec bob.Executor, number int) (context.Context, models.FileSlice, error) { + var err error + m := make(models.FileSlice, number) + + for i := range m { + ctx, m[i], err = o.create(ctx, exec) + if err != nil { + return ctx, nil, err + } + } + + return ctx, m, nil +} + +// File has methods that act as mods for the FileTemplate +var FileMods fileMods + +type fileMods struct{} + +func (m fileMods) RandomizeAllColumns(f *faker.Faker) FileMod { + return FileModSlice{ + FileMods.RandomID(f), + FileMods.RandomName(f), + FileMods.RandomData(f), + FileMods.RandomUserID(f), + } +} + +// Set the model columns to this value +func (m fileMods) ID(val int32) FileMod { + return FileModFunc(func(o *FileTemplate) { + o.ID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m fileMods) IDFunc(f func() int32) FileMod { + return FileModFunc(func(o *FileTemplate) { + o.ID = f + }) +} + +// Clear any values for the column +func (m fileMods) UnsetID() FileMod { + return FileModFunc(func(o *FileTemplate) { + o.ID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m fileMods) RandomID(f *faker.Faker) FileMod { + return FileModFunc(func(o *FileTemplate) { + o.ID = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m fileMods) Name(val null.Val[string]) FileMod { + return FileModFunc(func(o *FileTemplate) { + o.Name = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m fileMods) NameFunc(f func() null.Val[string]) FileMod { + return FileModFunc(func(o *FileTemplate) { + o.Name = f + }) +} + +// Clear any values for the column +func (m fileMods) UnsetName() FileMod { + return FileModFunc(func(o *FileTemplate) { + o.Name = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m fileMods) RandomName(f *faker.Faker) FileMod { + return FileModFunc(func(o *FileTemplate) { + o.Name = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + if f.Bool() { + return null.FromPtr[string](nil) + } + + return null.From(random_string(f)) + } + }) +} + +// Set the model columns to this value +func (m fileMods) Data(val null.Val[[]byte]) FileMod { + return FileModFunc(func(o *FileTemplate) { + o.Data = func() null.Val[[]byte] { return val } + }) +} + +// Set the Column from the function +func (m fileMods) DataFunc(f func() null.Val[[]byte]) FileMod { + return FileModFunc(func(o *FileTemplate) { + o.Data = f + }) +} + +// Clear any values for the column +func (m fileMods) UnsetData() FileMod { + return FileModFunc(func(o *FileTemplate) { + o.Data = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m fileMods) RandomData(f *faker.Faker) FileMod { + return FileModFunc(func(o *FileTemplate) { + o.Data = func() null.Val[[]byte] { + if f == nil { + f = &defaultFaker + } + + if f.Bool() { + return null.FromPtr[[]byte](nil) + } + + return null.From(random___byte(f)) + } + }) +} + +// Set the model columns to this value +func (m fileMods) UserID(val null.Val[int32]) FileMod { + return FileModFunc(func(o *FileTemplate) { + o.UserID = func() null.Val[int32] { return val } + }) +} + +// Set the Column from the function +func (m fileMods) UserIDFunc(f func() null.Val[int32]) FileMod { + return FileModFunc(func(o *FileTemplate) { + o.UserID = f + }) +} + +// Clear any values for the column +func (m fileMods) UnsetUserID() FileMod { + return FileModFunc(func(o *FileTemplate) { + o.UserID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m fileMods) RandomUserID(f *faker.Faker) FileMod { + return FileModFunc(func(o *FileTemplate) { + o.UserID = func() null.Val[int32] { + if f == nil { + f = &defaultFaker + } + + if f.Bool() { + return null.FromPtr[int32](nil) + } + + return null.From(random_int32(f)) + } + }) +} + +func (m fileMods) WithUser(rel *UserTemplate) FileMod { + return FileModFunc(func(o *FileTemplate) { + o.r.User = &fileRUserR{ + o: rel, + } + }) +} + +func (m fileMods) WithNewUser(mods ...UserMod) FileMod { + return FileModFunc(func(o *FileTemplate) { + related := o.f.NewUser(mods...) + + m.WithUser(related).Apply(o) + }) +} + +func (m fileMods) WithoutUser() FileMod { + return FileModFunc(func(o *FileTemplate) { + o.r.User = nil + }) +} diff --git a/server/internal/models/factory/items.bob.go b/server/internal/models/factory/items.bob.go new file mode 100644 index 0000000..3f7b961 --- /dev/null +++ b/server/internal/models/factory/items.bob.go @@ -0,0 +1,608 @@ +// Code generated by BobGen sqlite (devel). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + "testing" + "time" + + "github.com/aarondl/opt/null" + "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" + "github.com/jaswdr/faker/v2" + models "github.com/spotdemo4/trevstack/server/internal/models" + "github.com/stephenafamo/bob" +) + +type ItemMod interface { + Apply(*ItemTemplate) +} + +type ItemModFunc func(*ItemTemplate) + +func (f ItemModFunc) Apply(n *ItemTemplate) { + f(n) +} + +type ItemModSlice []ItemMod + +func (mods ItemModSlice) Apply(n *ItemTemplate) { + for _, f := range mods { + f.Apply(n) + } +} + +// ItemTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type ItemTemplate struct { + ID func() int32 + Name func() null.Val[string] + Description func() null.Val[string] + Price func() null.Val[float32] + Quantity func() null.Val[int32] + Added func() null.Val[time.Time] + UserID func() null.Val[int32] + + r itemR + f *Factory +} + +type itemR struct { + User *itemRUserR +} + +type itemRUserR struct { + o *UserTemplate +} + +// Apply mods to the ItemTemplate +func (o *ItemTemplate) Apply(mods ...ItemMod) { + for _, mod := range mods { + mod.Apply(o) + } +} + +// toModel returns an *models.Item +// this does nothing with the relationship templates +func (o ItemTemplate) toModel() *models.Item { + m := &models.Item{} + + if o.ID != nil { + m.ID = o.ID() + } + if o.Name != nil { + m.Name = o.Name() + } + if o.Description != nil { + m.Description = o.Description() + } + if o.Price != nil { + m.Price = o.Price() + } + if o.Quantity != nil { + m.Quantity = o.Quantity() + } + if o.Added != nil { + m.Added = o.Added() + } + if o.UserID != nil { + m.UserID = o.UserID() + } + + return m +} + +// toModels returns an models.ItemSlice +// this does nothing with the relationship templates +func (o ItemTemplate) toModels(number int) models.ItemSlice { + m := make(models.ItemSlice, number) + + for i := range m { + m[i] = o.toModel() + } + + return m +} + +// setModelRels creates and sets the relationships on *models.Item +// according to the relationships in the template. Nothing is inserted into the db +func (t ItemTemplate) setModelRels(o *models.Item) { + if t.r.User != nil { + rel := t.r.User.o.toModel() + rel.R.Items = append(rel.R.Items, o) + o.UserID = null.From(rel.ID) + o.R.User = rel + } +} + +// BuildSetter returns an *models.ItemSetter +// this does nothing with the relationship templates +func (o ItemTemplate) BuildSetter() *models.ItemSetter { + m := &models.ItemSetter{} + + if o.ID != nil { + m.ID = omit.From(o.ID()) + } + if o.Name != nil { + m.Name = omitnull.FromNull(o.Name()) + } + if o.Description != nil { + m.Description = omitnull.FromNull(o.Description()) + } + if o.Price != nil { + m.Price = omitnull.FromNull(o.Price()) + } + if o.Quantity != nil { + m.Quantity = omitnull.FromNull(o.Quantity()) + } + if o.Added != nil { + m.Added = omitnull.FromNull(o.Added()) + } + if o.UserID != nil { + m.UserID = omitnull.FromNull(o.UserID()) + } + + return m +} + +// BuildManySetter returns an []*models.ItemSetter +// this does nothing with the relationship templates +func (o ItemTemplate) BuildManySetter(number int) []*models.ItemSetter { + m := make([]*models.ItemSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.Item +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use ItemTemplate.Create +func (o ItemTemplate) Build() *models.Item { + m := o.toModel() + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.ItemSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use ItemTemplate.CreateMany +func (o ItemTemplate) BuildMany(number int) models.ItemSlice { + m := make(models.ItemSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableItem(m *models.ItemSetter) { +} + +// insertOptRels creates and inserts any optional the relationships on *models.Item +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *ItemTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.Item) (context.Context, error) { + var err error + + if o.r.User != nil { + var rel0 *models.User + ctx, rel0, err = o.r.User.o.create(ctx, exec) + if err != nil { + return ctx, err + } + err = m.AttachUser(ctx, exec, rel0) + if err != nil { + return ctx, err + } + } + + return ctx, err +} + +// Create builds a item and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *ItemTemplate) Create(ctx context.Context, exec bob.Executor) (*models.Item, error) { + _, m, err := o.create(ctx, exec) + return m, err +} + +// MustCreate builds a item and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *ItemTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.Item { + _, m, err := o.create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a item and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs +func (o *ItemTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.Item { + tb.Helper() + _, m, err := o.create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// create builds a item and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// this returns a context that includes the newly inserted model +func (o *ItemTemplate) create(ctx context.Context, exec bob.Executor) (context.Context, *models.Item, error) { + var err error + opt := o.BuildSetter() + ensureCreatableItem(opt) + + m, err := models.Items.Insert(opt).One(ctx, exec) + if err != nil { + return ctx, nil, err + } + ctx = itemCtx.WithValue(ctx, m) + + ctx, err = o.insertOptRels(ctx, exec, m) + return ctx, m, err +} + +// CreateMany builds multiple items and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o ItemTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.ItemSlice, error) { + _, m, err := o.createMany(ctx, exec, number) + return m, err +} + +// MustCreateMany builds multiple items and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o ItemTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.ItemSlice { + _, m, err := o.createMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple items and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs +func (o ItemTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.ItemSlice { + tb.Helper() + _, m, err := o.createMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// createMany builds multiple items and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// this returns a context that includes the newly inserted models +func (o ItemTemplate) createMany(ctx context.Context, exec bob.Executor, number int) (context.Context, models.ItemSlice, error) { + var err error + m := make(models.ItemSlice, number) + + for i := range m { + ctx, m[i], err = o.create(ctx, exec) + if err != nil { + return ctx, nil, err + } + } + + return ctx, m, nil +} + +// Item has methods that act as mods for the ItemTemplate +var ItemMods itemMods + +type itemMods struct{} + +func (m itemMods) RandomizeAllColumns(f *faker.Faker) ItemMod { + return ItemModSlice{ + ItemMods.RandomID(f), + ItemMods.RandomName(f), + ItemMods.RandomDescription(f), + ItemMods.RandomPrice(f), + ItemMods.RandomQuantity(f), + ItemMods.RandomAdded(f), + ItemMods.RandomUserID(f), + } +} + +// Set the model columns to this value +func (m itemMods) ID(val int32) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.ID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m itemMods) IDFunc(f func() int32) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.ID = f + }) +} + +// Clear any values for the column +func (m itemMods) UnsetID() ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.ID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m itemMods) RandomID(f *faker.Faker) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.ID = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m itemMods) Name(val null.Val[string]) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Name = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m itemMods) NameFunc(f func() null.Val[string]) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Name = f + }) +} + +// Clear any values for the column +func (m itemMods) UnsetName() ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Name = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m itemMods) RandomName(f *faker.Faker) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Name = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + if f.Bool() { + return null.FromPtr[string](nil) + } + + return null.From(random_string(f)) + } + }) +} + +// Set the model columns to this value +func (m itemMods) Description(val null.Val[string]) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Description = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m itemMods) DescriptionFunc(f func() null.Val[string]) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Description = f + }) +} + +// Clear any values for the column +func (m itemMods) UnsetDescription() ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Description = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m itemMods) RandomDescription(f *faker.Faker) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Description = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + if f.Bool() { + return null.FromPtr[string](nil) + } + + return null.From(random_string(f)) + } + }) +} + +// Set the model columns to this value +func (m itemMods) Price(val null.Val[float32]) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Price = func() null.Val[float32] { return val } + }) +} + +// Set the Column from the function +func (m itemMods) PriceFunc(f func() null.Val[float32]) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Price = f + }) +} + +// Clear any values for the column +func (m itemMods) UnsetPrice() ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Price = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m itemMods) RandomPrice(f *faker.Faker) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Price = func() null.Val[float32] { + if f == nil { + f = &defaultFaker + } + + if f.Bool() { + return null.FromPtr[float32](nil) + } + + return null.From(random_float32(f)) + } + }) +} + +// Set the model columns to this value +func (m itemMods) Quantity(val null.Val[int32]) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Quantity = func() null.Val[int32] { return val } + }) +} + +// Set the Column from the function +func (m itemMods) QuantityFunc(f func() null.Val[int32]) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Quantity = f + }) +} + +// Clear any values for the column +func (m itemMods) UnsetQuantity() ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Quantity = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m itemMods) RandomQuantity(f *faker.Faker) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Quantity = func() null.Val[int32] { + if f == nil { + f = &defaultFaker + } + + if f.Bool() { + return null.FromPtr[int32](nil) + } + + return null.From(random_int32(f)) + } + }) +} + +// Set the model columns to this value +func (m itemMods) Added(val null.Val[time.Time]) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Added = func() null.Val[time.Time] { return val } + }) +} + +// Set the Column from the function +func (m itemMods) AddedFunc(f func() null.Val[time.Time]) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Added = f + }) +} + +// Clear any values for the column +func (m itemMods) UnsetAdded() ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Added = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m itemMods) RandomAdded(f *faker.Faker) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.Added = func() null.Val[time.Time] { + if f == nil { + f = &defaultFaker + } + + if f.Bool() { + return null.FromPtr[time.Time](nil) + } + + return null.From(random_time_Time(f)) + } + }) +} + +// Set the model columns to this value +func (m itemMods) UserID(val null.Val[int32]) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.UserID = func() null.Val[int32] { return val } + }) +} + +// Set the Column from the function +func (m itemMods) UserIDFunc(f func() null.Val[int32]) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.UserID = f + }) +} + +// Clear any values for the column +func (m itemMods) UnsetUserID() ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.UserID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m itemMods) RandomUserID(f *faker.Faker) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.UserID = func() null.Val[int32] { + if f == nil { + f = &defaultFaker + } + + if f.Bool() { + return null.FromPtr[int32](nil) + } + + return null.From(random_int32(f)) + } + }) +} + +func (m itemMods) WithUser(rel *UserTemplate) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.r.User = &itemRUserR{ + o: rel, + } + }) +} + +func (m itemMods) WithNewUser(mods ...UserMod) ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + related := o.f.NewUser(mods...) + + m.WithUser(related).Apply(o) + }) +} + +func (m itemMods) WithoutUser() ItemMod { + return ItemModFunc(func(o *ItemTemplate) { + o.r.User = nil + }) +} diff --git a/server/internal/models/factory/users.bob.go b/server/internal/models/factory/users.bob.go new file mode 100644 index 0000000..64d9b64 --- /dev/null +++ b/server/internal/models/factory/users.bob.go @@ -0,0 +1,610 @@ +// Code generated by BobGen sqlite (devel). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + "testing" + + "github.com/aarondl/opt/null" + "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" + "github.com/jaswdr/faker/v2" + models "github.com/spotdemo4/trevstack/server/internal/models" + "github.com/stephenafamo/bob" +) + +type UserMod interface { + Apply(*UserTemplate) +} + +type UserModFunc func(*UserTemplate) + +func (f UserModFunc) Apply(n *UserTemplate) { + f(n) +} + +type UserModSlice []UserMod + +func (mods UserModSlice) Apply(n *UserTemplate) { + for _, f := range mods { + f.Apply(n) + } +} + +// UserTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type UserTemplate struct { + ID func() int32 + Username func() null.Val[string] + Password func() null.Val[string] + ProfilePictureID func() null.Val[int32] + Challenge func() null.Val[string] + + r userR + f *Factory +} + +type userR struct { + Files []*userRFilesR + Items []*userRItemsR +} + +type userRFilesR struct { + number int + o *FileTemplate +} +type userRItemsR struct { + number int + o *ItemTemplate +} + +// Apply mods to the UserTemplate +func (o *UserTemplate) Apply(mods ...UserMod) { + for _, mod := range mods { + mod.Apply(o) + } +} + +// toModel returns an *models.User +// this does nothing with the relationship templates +func (o UserTemplate) toModel() *models.User { + m := &models.User{} + + if o.ID != nil { + m.ID = o.ID() + } + if o.Username != nil { + m.Username = o.Username() + } + if o.Password != nil { + m.Password = o.Password() + } + if o.ProfilePictureID != nil { + m.ProfilePictureID = o.ProfilePictureID() + } + if o.Challenge != nil { + m.Challenge = o.Challenge() + } + + return m +} + +// toModels returns an models.UserSlice +// this does nothing with the relationship templates +func (o UserTemplate) toModels(number int) models.UserSlice { + m := make(models.UserSlice, number) + + for i := range m { + m[i] = o.toModel() + } + + return m +} + +// setModelRels creates and sets the relationships on *models.User +// according to the relationships in the template. Nothing is inserted into the db +func (t UserTemplate) setModelRels(o *models.User) { + if t.r.Files != nil { + rel := models.FileSlice{} + for _, r := range t.r.Files { + related := r.o.toModels(r.number) + for _, rel := range related { + rel.UserID = null.From(o.ID) + rel.R.User = o + } + rel = append(rel, related...) + } + o.R.Files = rel + } + + if t.r.Items != nil { + rel := models.ItemSlice{} + for _, r := range t.r.Items { + related := r.o.toModels(r.number) + for _, rel := range related { + rel.UserID = null.From(o.ID) + rel.R.User = o + } + rel = append(rel, related...) + } + o.R.Items = rel + } +} + +// BuildSetter returns an *models.UserSetter +// this does nothing with the relationship templates +func (o UserTemplate) BuildSetter() *models.UserSetter { + m := &models.UserSetter{} + + if o.ID != nil { + m.ID = omit.From(o.ID()) + } + if o.Username != nil { + m.Username = omitnull.FromNull(o.Username()) + } + if o.Password != nil { + m.Password = omitnull.FromNull(o.Password()) + } + if o.ProfilePictureID != nil { + m.ProfilePictureID = omitnull.FromNull(o.ProfilePictureID()) + } + if o.Challenge != nil { + m.Challenge = omitnull.FromNull(o.Challenge()) + } + + return m +} + +// BuildManySetter returns an []*models.UserSetter +// this does nothing with the relationship templates +func (o UserTemplate) BuildManySetter(number int) []*models.UserSetter { + m := make([]*models.UserSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.User +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use UserTemplate.Create +func (o UserTemplate) Build() *models.User { + m := o.toModel() + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.UserSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use UserTemplate.CreateMany +func (o UserTemplate) BuildMany(number int) models.UserSlice { + m := make(models.UserSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableUser(m *models.UserSetter) { +} + +// insertOptRels creates and inserts any optional the relationships on *models.User +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *UserTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.User) (context.Context, error) { + var err error + + if o.r.Files != nil { + for _, r := range o.r.Files { + var rel0 models.FileSlice + ctx, rel0, err = r.o.createMany(ctx, exec, r.number) + if err != nil { + return ctx, err + } + + err = m.AttachFiles(ctx, exec, rel0...) + if err != nil { + return ctx, err + } + } + } + + if o.r.Items != nil { + for _, r := range o.r.Items { + var rel1 models.ItemSlice + ctx, rel1, err = r.o.createMany(ctx, exec, r.number) + if err != nil { + return ctx, err + } + + err = m.AttachItems(ctx, exec, rel1...) + if err != nil { + return ctx, err + } + } + } + + return ctx, err +} + +// Create builds a user and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *UserTemplate) Create(ctx context.Context, exec bob.Executor) (*models.User, error) { + _, m, err := o.create(ctx, exec) + return m, err +} + +// MustCreate builds a user and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *UserTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.User { + _, m, err := o.create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a user and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs +func (o *UserTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.User { + tb.Helper() + _, m, err := o.create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// create builds a user and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// this returns a context that includes the newly inserted model +func (o *UserTemplate) create(ctx context.Context, exec bob.Executor) (context.Context, *models.User, error) { + var err error + opt := o.BuildSetter() + ensureCreatableUser(opt) + + m, err := models.Users.Insert(opt).One(ctx, exec) + if err != nil { + return ctx, nil, err + } + ctx = userCtx.WithValue(ctx, m) + + ctx, err = o.insertOptRels(ctx, exec, m) + return ctx, m, err +} + +// CreateMany builds multiple users and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o UserTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.UserSlice, error) { + _, m, err := o.createMany(ctx, exec, number) + return m, err +} + +// MustCreateMany builds multiple users and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o UserTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.UserSlice { + _, m, err := o.createMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple users and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs +func (o UserTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.UserSlice { + tb.Helper() + _, m, err := o.createMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// createMany builds multiple users and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// this returns a context that includes the newly inserted models +func (o UserTemplate) createMany(ctx context.Context, exec bob.Executor, number int) (context.Context, models.UserSlice, error) { + var err error + m := make(models.UserSlice, number) + + for i := range m { + ctx, m[i], err = o.create(ctx, exec) + if err != nil { + return ctx, nil, err + } + } + + return ctx, m, nil +} + +// User has methods that act as mods for the UserTemplate +var UserMods userMods + +type userMods struct{} + +func (m userMods) RandomizeAllColumns(f *faker.Faker) UserMod { + return UserModSlice{ + UserMods.RandomID(f), + UserMods.RandomUsername(f), + UserMods.RandomPassword(f), + UserMods.RandomProfilePictureID(f), + UserMods.RandomChallenge(f), + } +} + +// Set the model columns to this value +func (m userMods) ID(val int32) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.ID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m userMods) IDFunc(f func() int32) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.ID = f + }) +} + +// Clear any values for the column +func (m userMods) UnsetID() UserMod { + return UserModFunc(func(o *UserTemplate) { + o.ID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m userMods) RandomID(f *faker.Faker) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.ID = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m userMods) Username(val null.Val[string]) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.Username = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m userMods) UsernameFunc(f func() null.Val[string]) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.Username = f + }) +} + +// Clear any values for the column +func (m userMods) UnsetUsername() UserMod { + return UserModFunc(func(o *UserTemplate) { + o.Username = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m userMods) RandomUsername(f *faker.Faker) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.Username = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + if f.Bool() { + return null.FromPtr[string](nil) + } + + return null.From(random_string(f)) + } + }) +} + +// Set the model columns to this value +func (m userMods) Password(val null.Val[string]) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.Password = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m userMods) PasswordFunc(f func() null.Val[string]) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.Password = f + }) +} + +// Clear any values for the column +func (m userMods) UnsetPassword() UserMod { + return UserModFunc(func(o *UserTemplate) { + o.Password = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m userMods) RandomPassword(f *faker.Faker) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.Password = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + if f.Bool() { + return null.FromPtr[string](nil) + } + + return null.From(random_string(f)) + } + }) +} + +// Set the model columns to this value +func (m userMods) ProfilePictureID(val null.Val[int32]) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.ProfilePictureID = func() null.Val[int32] { return val } + }) +} + +// Set the Column from the function +func (m userMods) ProfilePictureIDFunc(f func() null.Val[int32]) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.ProfilePictureID = f + }) +} + +// Clear any values for the column +func (m userMods) UnsetProfilePictureID() UserMod { + return UserModFunc(func(o *UserTemplate) { + o.ProfilePictureID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m userMods) RandomProfilePictureID(f *faker.Faker) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.ProfilePictureID = func() null.Val[int32] { + if f == nil { + f = &defaultFaker + } + + if f.Bool() { + return null.FromPtr[int32](nil) + } + + return null.From(random_int32(f)) + } + }) +} + +// Set the model columns to this value +func (m userMods) Challenge(val null.Val[string]) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.Challenge = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m userMods) ChallengeFunc(f func() null.Val[string]) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.Challenge = f + }) +} + +// Clear any values for the column +func (m userMods) UnsetChallenge() UserMod { + return UserModFunc(func(o *UserTemplate) { + o.Challenge = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m userMods) RandomChallenge(f *faker.Faker) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.Challenge = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + if f.Bool() { + return null.FromPtr[string](nil) + } + + return null.From(random_string(f)) + } + }) +} + +func (m userMods) WithFiles(number int, related *FileTemplate) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.r.Files = []*userRFilesR{{ + number: number, + o: related, + }} + }) +} + +func (m userMods) WithNewFiles(number int, mods ...FileMod) UserMod { + return UserModFunc(func(o *UserTemplate) { + related := o.f.NewFile(mods...) + m.WithFiles(number, related).Apply(o) + }) +} + +func (m userMods) AddFiles(number int, related *FileTemplate) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.r.Files = append(o.r.Files, &userRFilesR{ + number: number, + o: related, + }) + }) +} + +func (m userMods) AddNewFiles(number int, mods ...FileMod) UserMod { + return UserModFunc(func(o *UserTemplate) { + related := o.f.NewFile(mods...) + m.AddFiles(number, related).Apply(o) + }) +} + +func (m userMods) WithoutFiles() UserMod { + return UserModFunc(func(o *UserTemplate) { + o.r.Files = nil + }) +} + +func (m userMods) WithItems(number int, related *ItemTemplate) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.r.Items = []*userRItemsR{{ + number: number, + o: related, + }} + }) +} + +func (m userMods) WithNewItems(number int, mods ...ItemMod) UserMod { + return UserModFunc(func(o *UserTemplate) { + related := o.f.NewItem(mods...) + m.WithItems(number, related).Apply(o) + }) +} + +func (m userMods) AddItems(number int, related *ItemTemplate) UserMod { + return UserModFunc(func(o *UserTemplate) { + o.r.Items = append(o.r.Items, &userRItemsR{ + number: number, + o: related, + }) + }) +} + +func (m userMods) AddNewItems(number int, mods ...ItemMod) UserMod { + return UserModFunc(func(o *UserTemplate) { + related := o.f.NewItem(mods...) + m.AddItems(number, related).Apply(o) + }) +} + +func (m userMods) WithoutItems() UserMod { + return UserModFunc(func(o *UserTemplate) { + o.r.Items = nil + }) +} diff --git a/server/internal/models/file.go b/server/internal/models/file.go deleted file mode 100644 index 06b48e0..0000000 --- a/server/internal/models/file.go +++ /dev/null @@ -1,12 +0,0 @@ -package models - -type File struct { - ID uint32 `gorm:"primaryKey"` - - Name string - Data []byte - - // User - UserID uint - User User -} diff --git a/server/internal/models/files.bob.go b/server/internal/models/files.bob.go new file mode 100644 index 0000000..a987b61 --- /dev/null +++ b/server/internal/models/files.bob.go @@ -0,0 +1,658 @@ +// Code generated by BobGen sqlite (devel). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "errors" + "fmt" + "io" + + "github.com/aarondl/opt/null" + "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" + "github.com/stephenafamo/bob" + "github.com/stephenafamo/bob/dialect/sqlite" + "github.com/stephenafamo/bob/dialect/sqlite/dialect" + "github.com/stephenafamo/bob/dialect/sqlite/dm" + "github.com/stephenafamo/bob/dialect/sqlite/sm" + "github.com/stephenafamo/bob/dialect/sqlite/um" + "github.com/stephenafamo/bob/expr" + "github.com/stephenafamo/bob/mods" + "github.com/stephenafamo/bob/orm" +) + +// File is an object representing the database table. +type File struct { + ID int32 `db:"id,pk" ` + Name null.Val[string] `db:"name" ` + Data null.Val[[]byte] `db:"data" ` + UserID null.Val[int32] `db:"user_id" ` + + R fileR `db:"-" ` +} + +// FileSlice is an alias for a slice of pointers to File. +// This should almost always be used instead of []*File. +type FileSlice []*File + +// Files contains methods to work with the files table +var Files = sqlite.NewTablex[*File, FileSlice, *FileSetter]("", "files") + +// FilesQuery is a query on the files table +type FilesQuery = *sqlite.ViewQuery[*File, FileSlice] + +// fileR is where relationships are stored. +type fileR struct { + User *User // fk_files_0 +} + +type fileColumnNames struct { + ID string + Name string + Data string + UserID string +} + +var FileColumns = buildFileColumns("files") + +type fileColumns struct { + tableAlias string + ID sqlite.Expression + Name sqlite.Expression + Data sqlite.Expression + UserID sqlite.Expression +} + +func (c fileColumns) Alias() string { + return c.tableAlias +} + +func (fileColumns) AliasedAs(alias string) fileColumns { + return buildFileColumns(alias) +} + +func buildFileColumns(alias string) fileColumns { + return fileColumns{ + tableAlias: alias, + ID: sqlite.Quote(alias, "id"), + Name: sqlite.Quote(alias, "name"), + Data: sqlite.Quote(alias, "data"), + UserID: sqlite.Quote(alias, "user_id"), + } +} + +type fileWhere[Q sqlite.Filterable] struct { + ID sqlite.WhereMod[Q, int32] + Name sqlite.WhereNullMod[Q, string] + Data sqlite.WhereNullMod[Q, []byte] + UserID sqlite.WhereNullMod[Q, int32] +} + +func (fileWhere[Q]) AliasedAs(alias string) fileWhere[Q] { + return buildFileWhere[Q](buildFileColumns(alias)) +} + +func buildFileWhere[Q sqlite.Filterable](cols fileColumns) fileWhere[Q] { + return fileWhere[Q]{ + ID: sqlite.Where[Q, int32](cols.ID), + Name: sqlite.WhereNull[Q, string](cols.Name), + Data: sqlite.WhereNull[Q, []byte](cols.Data), + UserID: sqlite.WhereNull[Q, int32](cols.UserID), + } +} + +var FileErrors = &fileErrors{ + ErrUniquePkMainFiles: &UniqueConstraintError{s: "pk_main_files"}, +} + +type fileErrors struct { + ErrUniquePkMainFiles *UniqueConstraintError +} + +// FileSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type FileSetter struct { + ID omit.Val[int32] `db:"id,pk" ` + Name omitnull.Val[string] `db:"name" ` + Data omitnull.Val[[]byte] `db:"data" ` + UserID omitnull.Val[int32] `db:"user_id" ` +} + +func (s FileSetter) SetColumns() []string { + vals := make([]string, 0, 4) + if !s.ID.IsUnset() { + vals = append(vals, "id") + } + + if !s.Name.IsUnset() { + vals = append(vals, "name") + } + + if !s.Data.IsUnset() { + vals = append(vals, "data") + } + + if !s.UserID.IsUnset() { + vals = append(vals, "user_id") + } + + return vals +} + +func (s FileSetter) Overwrite(t *File) { + if !s.ID.IsUnset() { + t.ID, _ = s.ID.Get() + } + if !s.Name.IsUnset() { + t.Name, _ = s.Name.GetNull() + } + if !s.Data.IsUnset() { + t.Data, _ = s.Data.GetNull() + } + if !s.UserID.IsUnset() { + t.UserID, _ = s.UserID.GetNull() + } +} + +func (s *FileSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return Files.BeforeInsertHooks.RunHooks(ctx, exec, s) + }) + + if len(q.Table.Columns) == 0 { + q.Table.Columns = s.SetColumns() + } + + q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + vals := make([]bob.Expression, 0, 4) + if !s.ID.IsUnset() { + vals = append(vals, sqlite.Arg(s.ID)) + } + + if !s.Name.IsUnset() { + vals = append(vals, sqlite.Arg(s.Name)) + } + + if !s.Data.IsUnset() { + vals = append(vals, sqlite.Arg(s.Data)) + } + + if !s.UserID.IsUnset() { + vals = append(vals, sqlite.Arg(s.UserID)) + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s FileSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s FileSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 4) + + if !s.ID.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + sqlite.Quote(append(prefix, "id")...), + sqlite.Arg(s.ID), + }}) + } + + if !s.Name.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + sqlite.Quote(append(prefix, "name")...), + sqlite.Arg(s.Name), + }}) + } + + if !s.Data.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + sqlite.Quote(append(prefix, "data")...), + sqlite.Arg(s.Data), + }}) + } + + if !s.UserID.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + sqlite.Quote(append(prefix, "user_id")...), + sqlite.Arg(s.UserID), + }}) + } + + return exprs +} + +// FindFile retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindFile(ctx context.Context, exec bob.Executor, IDPK int32, cols ...string) (*File, error) { + if len(cols) == 0 { + return Files.Query( + SelectWhere.Files.ID.EQ(IDPK), + ).One(ctx, exec) + } + + return Files.Query( + SelectWhere.Files.ID.EQ(IDPK), + sm.Columns(Files.Columns().Only(cols...)), + ).One(ctx, exec) +} + +// FileExists checks the presence of a single record by primary key +func FileExists(ctx context.Context, exec bob.Executor, IDPK int32) (bool, error) { + return Files.Query( + SelectWhere.Files.ID.EQ(IDPK), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after File is retrieved from the database +func (o *File) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = Files.AfterSelectHooks.RunHooks(ctx, exec, FileSlice{o}) + case bob.QueryTypeInsert: + ctx, err = Files.AfterInsertHooks.RunHooks(ctx, exec, FileSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = Files.AfterUpdateHooks.RunHooks(ctx, exec, FileSlice{o}) + case bob.QueryTypeDelete: + ctx, err = Files.AfterDeleteHooks.RunHooks(ctx, exec, FileSlice{o}) + } + + return err +} + +// PrimaryKeyVals returns the primary key values of the File +func (o *File) PrimaryKeyVals() bob.Expression { + return sqlite.Arg(o.ID) +} + +func (o *File) pkEQ() dialect.Expression { + return sqlite.Quote("files", "id").EQ(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + return o.PrimaryKeyVals().WriteSQL(ctx, w, d, start) + })) +} + +// Update uses an executor to update the File +func (o *File) Update(ctx context.Context, exec bob.Executor, s *FileSetter) error { + v, err := Files.Update(s.UpdateMod(), um.Where(o.pkEQ())).One(ctx, exec) + if err != nil { + return err + } + + o.R = v.R + *o = *v + + return nil +} + +// Delete deletes a single File record with an executor +func (o *File) Delete(ctx context.Context, exec bob.Executor) error { + _, err := Files.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the File using the executor +func (o *File) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := Files.Query( + SelectWhere.Files.ID.EQ(o.ID), + ).One(ctx, exec) + if err != nil { + return err + } + o2.R = o.R + *o = *o2 + + return nil +} + +// AfterQueryHook is called after FileSlice is retrieved from the database +func (o FileSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = Files.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = Files.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = Files.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = Files.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o FileSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return sqlite.Raw("NULL") + } + + return sqlite.Quote("files", "id").In(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + pkPairs := make([]bob.Expression, len(o)) + for i, row := range o { + pkPairs[i] = row.PrimaryKeyVals() + } + return bob.ExpressSlice(ctx, w, d, start, pkPairs, "", ", ", "") + })) +} + +// copyMatchingRows finds models in the given slice that have the same primary key +// then it first copies the existing relationships from the old model to the new model +// and then replaces the old model in the slice with the new model +func (o FileSlice) copyMatchingRows(from ...*File) { + for i, old := range o { + for _, new := range from { + if new.ID != old.ID { + continue + } + new.R = old.R + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o FileSlice) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return bob.ModFunc[*dialect.UpdateQuery](func(q *dialect.UpdateQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return Files.BeforeUpdateHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *File: + o.copyMatchingRows(retrieved) + case []*File: + o.copyMatchingRows(retrieved...) + case FileSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a File or a slice of File + // then run the AfterUpdateHooks on the slice + _, err = Files.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o FileSlice) DeleteMod() bob.Mod[*dialect.DeleteQuery] { + return bob.ModFunc[*dialect.DeleteQuery](func(q *dialect.DeleteQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return Files.BeforeDeleteHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *File: + o.copyMatchingRows(retrieved) + case []*File: + o.copyMatchingRows(retrieved...) + case FileSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a File or a slice of File + // then run the AfterDeleteHooks on the slice + _, err = Files.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o FileSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals FileSetter) error { + if len(o) == 0 { + return nil + } + + _, err := Files.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o FileSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := Files.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o FileSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := Files.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +type fileJoins[Q dialect.Joinable] struct { + typ string + User func(context.Context) modAs[Q, userColumns] +} + +func (j fileJoins[Q]) aliasedAs(alias string) fileJoins[Q] { + return buildFileJoins[Q](buildFileColumns(alias), j.typ) +} + +func buildFileJoins[Q dialect.Joinable](cols fileColumns, typ string) fileJoins[Q] { + return fileJoins[Q]{ + typ: typ, + User: filesJoinUser[Q](cols, typ), + } +} + +func filesJoinUser[Q dialect.Joinable](from fileColumns, typ string) func(context.Context) modAs[Q, userColumns] { + return func(ctx context.Context) modAs[Q, userColumns] { + return modAs[Q, userColumns]{ + c: UserColumns, + f: func(to userColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, Users.Name().As(to.Alias())).On( + to.ID.EQ(from.UserID), + )) + } + + return mods + }, + } + } +} + +// User starts a query for related objects on users +func (o *File) User(mods ...bob.Mod[*dialect.SelectQuery]) UsersQuery { + return Users.Query(append(mods, + sm.Where(UserColumns.ID.EQ(sqlite.Arg(o.UserID))), + )...) +} + +func (os FileSlice) User(mods ...bob.Mod[*dialect.SelectQuery]) UsersQuery { + PKArgs := make([]bob.Expression, len(os)) + for i, o := range os { + PKArgs[i] = sqlite.ArgGroup(o.UserID) + } + + return Users.Query(append(mods, + sm.Where(sqlite.Group(UserColumns.ID).In(PKArgs...)), + )...) +} + +func (o *File) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "User": + rel, ok := retrieved.(*User) + if !ok { + return fmt.Errorf("file cannot load %T as %q", retrieved, name) + } + + o.R.User = rel + + if rel != nil { + rel.R.Files = FileSlice{o} + } + return nil + default: + return fmt.Errorf("file has no relationship %q", name) + } +} + +func PreloadFileUser(opts ...sqlite.PreloadOption) sqlite.Preloader { + return sqlite.Preload[*User, UserSlice](orm.Relationship{ + Name: "User", + Sides: []orm.RelSide{ + { + From: TableNames.Files, + To: TableNames.Users, + FromColumns: []string{ + ColumnNames.Files.UserID, + }, + ToColumns: []string{ + ColumnNames.Users.ID, + }, + }, + }, + }, Users.Columns().Names(), opts...) +} + +func ThenLoadFileUser(queryMods ...bob.Mod[*dialect.SelectQuery]) sqlite.Loader { + return sqlite.Loader(func(ctx context.Context, exec bob.Executor, retrieved any) error { + loader, isLoader := retrieved.(interface { + LoadFileUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + }) + if !isLoader { + return fmt.Errorf("object %T cannot load FileUser", retrieved) + } + + err := loader.LoadFileUser(ctx, exec, queryMods...) + + // Don't cause an issue due to missing relationships + if errors.Is(err, sql.ErrNoRows) { + return nil + } + + return err + }) +} + +// LoadFileUser loads the file's User into the .R struct +func (o *File) LoadFileUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.User = nil + + related, err := o.User(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.Files = FileSlice{o} + + o.R.User = related + return nil +} + +// LoadFileUser loads the file's User into the .R struct +func (os FileSlice) LoadFileUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + users, err := os.User(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + for _, rel := range users { + if o.UserID.GetOrZero() != rel.ID { + continue + } + + rel.R.Files = append(rel.R.Files, o) + + o.R.User = rel + break + } + } + + return nil +} + +func attachFileUser0(ctx context.Context, exec bob.Executor, count int, file0 *File, user1 *User) (*File, error) { + setter := &FileSetter{ + UserID: omitnull.From(user1.ID), + } + + err := file0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachFileUser0: %w", err) + } + + return file0, nil +} + +func (file0 *File) InsertUser(ctx context.Context, exec bob.Executor, related *UserSetter) error { + user1, err := Users.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachFileUser0(ctx, exec, 1, file0, user1) + if err != nil { + return err + } + + file0.R.User = user1 + + user1.R.Files = append(user1.R.Files, file0) + + return nil +} + +func (file0 *File) AttachUser(ctx context.Context, exec bob.Executor, user1 *User) error { + var err error + + _, err = attachFileUser0(ctx, exec, 1, file0, user1) + if err != nil { + return err + } + + file0.R.User = user1 + + user1.R.Files = append(user1.R.Files, file0) + + return nil +} diff --git a/server/internal/models/item.go b/server/internal/models/item.go deleted file mode 100644 index f25a907..0000000 --- a/server/internal/models/item.go +++ /dev/null @@ -1,33 +0,0 @@ -package models - -import ( - "time" - - itemv1 "github.com/spotdemo4/trevstack/server/internal/services/item/v1" - "google.golang.org/protobuf/types/known/timestamppb" -) - -type Item struct { - ID uint32 `gorm:"primaryKey"` - - Name string - Description string - Price float32 - Quantity int - Added time.Time - - // User - UserID uint - User User -} - -func (i Item) ToConnectV1() *itemv1.Item { - return &itemv1.Item{ - Id: &i.ID, - Name: i.Name, - Description: i.Description, - Price: i.Price, - Quantity: uint32(i.Quantity), - Added: timestamppb.New(i.Added), - } -} diff --git a/server/internal/models/items.bob.go b/server/internal/models/items.bob.go new file mode 100644 index 0000000..f5ba27f --- /dev/null +++ b/server/internal/models/items.bob.go @@ -0,0 +1,734 @@ +// Code generated by BobGen sqlite (devel). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "errors" + "fmt" + "io" + "time" + + "github.com/aarondl/opt/null" + "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" + "github.com/stephenafamo/bob" + "github.com/stephenafamo/bob/dialect/sqlite" + "github.com/stephenafamo/bob/dialect/sqlite/dialect" + "github.com/stephenafamo/bob/dialect/sqlite/dm" + "github.com/stephenafamo/bob/dialect/sqlite/sm" + "github.com/stephenafamo/bob/dialect/sqlite/um" + "github.com/stephenafamo/bob/expr" + "github.com/stephenafamo/bob/mods" + "github.com/stephenafamo/bob/orm" +) + +// Item is an object representing the database table. +type Item struct { + ID int32 `db:"id,pk" ` + Name null.Val[string] `db:"name" ` + Description null.Val[string] `db:"description" ` + Price null.Val[float32] `db:"price" ` + Quantity null.Val[int32] `db:"quantity" ` + Added null.Val[time.Time] `db:"added" ` + UserID null.Val[int32] `db:"user_id" ` + + R itemR `db:"-" ` +} + +// ItemSlice is an alias for a slice of pointers to Item. +// This should almost always be used instead of []*Item. +type ItemSlice []*Item + +// Items contains methods to work with the items table +var Items = sqlite.NewTablex[*Item, ItemSlice, *ItemSetter]("", "items") + +// ItemsQuery is a query on the items table +type ItemsQuery = *sqlite.ViewQuery[*Item, ItemSlice] + +// itemR is where relationships are stored. +type itemR struct { + User *User // fk_items_0 +} + +type itemColumnNames struct { + ID string + Name string + Description string + Price string + Quantity string + Added string + UserID string +} + +var ItemColumns = buildItemColumns("items") + +type itemColumns struct { + tableAlias string + ID sqlite.Expression + Name sqlite.Expression + Description sqlite.Expression + Price sqlite.Expression + Quantity sqlite.Expression + Added sqlite.Expression + UserID sqlite.Expression +} + +func (c itemColumns) Alias() string { + return c.tableAlias +} + +func (itemColumns) AliasedAs(alias string) itemColumns { + return buildItemColumns(alias) +} + +func buildItemColumns(alias string) itemColumns { + return itemColumns{ + tableAlias: alias, + ID: sqlite.Quote(alias, "id"), + Name: sqlite.Quote(alias, "name"), + Description: sqlite.Quote(alias, "description"), + Price: sqlite.Quote(alias, "price"), + Quantity: sqlite.Quote(alias, "quantity"), + Added: sqlite.Quote(alias, "added"), + UserID: sqlite.Quote(alias, "user_id"), + } +} + +type itemWhere[Q sqlite.Filterable] struct { + ID sqlite.WhereMod[Q, int32] + Name sqlite.WhereNullMod[Q, string] + Description sqlite.WhereNullMod[Q, string] + Price sqlite.WhereNullMod[Q, float32] + Quantity sqlite.WhereNullMod[Q, int32] + Added sqlite.WhereNullMod[Q, time.Time] + UserID sqlite.WhereNullMod[Q, int32] +} + +func (itemWhere[Q]) AliasedAs(alias string) itemWhere[Q] { + return buildItemWhere[Q](buildItemColumns(alias)) +} + +func buildItemWhere[Q sqlite.Filterable](cols itemColumns) itemWhere[Q] { + return itemWhere[Q]{ + ID: sqlite.Where[Q, int32](cols.ID), + Name: sqlite.WhereNull[Q, string](cols.Name), + Description: sqlite.WhereNull[Q, string](cols.Description), + Price: sqlite.WhereNull[Q, float32](cols.Price), + Quantity: sqlite.WhereNull[Q, int32](cols.Quantity), + Added: sqlite.WhereNull[Q, time.Time](cols.Added), + UserID: sqlite.WhereNull[Q, int32](cols.UserID), + } +} + +var ItemErrors = &itemErrors{ + ErrUniquePkMainItems: &UniqueConstraintError{s: "pk_main_items"}, +} + +type itemErrors struct { + ErrUniquePkMainItems *UniqueConstraintError +} + +// ItemSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type ItemSetter struct { + ID omit.Val[int32] `db:"id,pk" ` + Name omitnull.Val[string] `db:"name" ` + Description omitnull.Val[string] `db:"description" ` + Price omitnull.Val[float32] `db:"price" ` + Quantity omitnull.Val[int32] `db:"quantity" ` + Added omitnull.Val[time.Time] `db:"added" ` + UserID omitnull.Val[int32] `db:"user_id" ` +} + +func (s ItemSetter) SetColumns() []string { + vals := make([]string, 0, 7) + if !s.ID.IsUnset() { + vals = append(vals, "id") + } + + if !s.Name.IsUnset() { + vals = append(vals, "name") + } + + if !s.Description.IsUnset() { + vals = append(vals, "description") + } + + if !s.Price.IsUnset() { + vals = append(vals, "price") + } + + if !s.Quantity.IsUnset() { + vals = append(vals, "quantity") + } + + if !s.Added.IsUnset() { + vals = append(vals, "added") + } + + if !s.UserID.IsUnset() { + vals = append(vals, "user_id") + } + + return vals +} + +func (s ItemSetter) Overwrite(t *Item) { + if !s.ID.IsUnset() { + t.ID, _ = s.ID.Get() + } + if !s.Name.IsUnset() { + t.Name, _ = s.Name.GetNull() + } + if !s.Description.IsUnset() { + t.Description, _ = s.Description.GetNull() + } + if !s.Price.IsUnset() { + t.Price, _ = s.Price.GetNull() + } + if !s.Quantity.IsUnset() { + t.Quantity, _ = s.Quantity.GetNull() + } + if !s.Added.IsUnset() { + t.Added, _ = s.Added.GetNull() + } + if !s.UserID.IsUnset() { + t.UserID, _ = s.UserID.GetNull() + } +} + +func (s *ItemSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return Items.BeforeInsertHooks.RunHooks(ctx, exec, s) + }) + + if len(q.Table.Columns) == 0 { + q.Table.Columns = s.SetColumns() + } + + q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + vals := make([]bob.Expression, 0, 7) + if !s.ID.IsUnset() { + vals = append(vals, sqlite.Arg(s.ID)) + } + + if !s.Name.IsUnset() { + vals = append(vals, sqlite.Arg(s.Name)) + } + + if !s.Description.IsUnset() { + vals = append(vals, sqlite.Arg(s.Description)) + } + + if !s.Price.IsUnset() { + vals = append(vals, sqlite.Arg(s.Price)) + } + + if !s.Quantity.IsUnset() { + vals = append(vals, sqlite.Arg(s.Quantity)) + } + + if !s.Added.IsUnset() { + vals = append(vals, sqlite.Arg(s.Added)) + } + + if !s.UserID.IsUnset() { + vals = append(vals, sqlite.Arg(s.UserID)) + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s ItemSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s ItemSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 7) + + if !s.ID.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + sqlite.Quote(append(prefix, "id")...), + sqlite.Arg(s.ID), + }}) + } + + if !s.Name.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + sqlite.Quote(append(prefix, "name")...), + sqlite.Arg(s.Name), + }}) + } + + if !s.Description.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + sqlite.Quote(append(prefix, "description")...), + sqlite.Arg(s.Description), + }}) + } + + if !s.Price.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + sqlite.Quote(append(prefix, "price")...), + sqlite.Arg(s.Price), + }}) + } + + if !s.Quantity.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + sqlite.Quote(append(prefix, "quantity")...), + sqlite.Arg(s.Quantity), + }}) + } + + if !s.Added.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + sqlite.Quote(append(prefix, "added")...), + sqlite.Arg(s.Added), + }}) + } + + if !s.UserID.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + sqlite.Quote(append(prefix, "user_id")...), + sqlite.Arg(s.UserID), + }}) + } + + return exprs +} + +// FindItem retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindItem(ctx context.Context, exec bob.Executor, IDPK int32, cols ...string) (*Item, error) { + if len(cols) == 0 { + return Items.Query( + SelectWhere.Items.ID.EQ(IDPK), + ).One(ctx, exec) + } + + return Items.Query( + SelectWhere.Items.ID.EQ(IDPK), + sm.Columns(Items.Columns().Only(cols...)), + ).One(ctx, exec) +} + +// ItemExists checks the presence of a single record by primary key +func ItemExists(ctx context.Context, exec bob.Executor, IDPK int32) (bool, error) { + return Items.Query( + SelectWhere.Items.ID.EQ(IDPK), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after Item is retrieved from the database +func (o *Item) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = Items.AfterSelectHooks.RunHooks(ctx, exec, ItemSlice{o}) + case bob.QueryTypeInsert: + ctx, err = Items.AfterInsertHooks.RunHooks(ctx, exec, ItemSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = Items.AfterUpdateHooks.RunHooks(ctx, exec, ItemSlice{o}) + case bob.QueryTypeDelete: + ctx, err = Items.AfterDeleteHooks.RunHooks(ctx, exec, ItemSlice{o}) + } + + return err +} + +// PrimaryKeyVals returns the primary key values of the Item +func (o *Item) PrimaryKeyVals() bob.Expression { + return sqlite.Arg(o.ID) +} + +func (o *Item) pkEQ() dialect.Expression { + return sqlite.Quote("items", "id").EQ(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + return o.PrimaryKeyVals().WriteSQL(ctx, w, d, start) + })) +} + +// Update uses an executor to update the Item +func (o *Item) Update(ctx context.Context, exec bob.Executor, s *ItemSetter) error { + v, err := Items.Update(s.UpdateMod(), um.Where(o.pkEQ())).One(ctx, exec) + if err != nil { + return err + } + + o.R = v.R + *o = *v + + return nil +} + +// Delete deletes a single Item record with an executor +func (o *Item) Delete(ctx context.Context, exec bob.Executor) error { + _, err := Items.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the Item using the executor +func (o *Item) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := Items.Query( + SelectWhere.Items.ID.EQ(o.ID), + ).One(ctx, exec) + if err != nil { + return err + } + o2.R = o.R + *o = *o2 + + return nil +} + +// AfterQueryHook is called after ItemSlice is retrieved from the database +func (o ItemSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = Items.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = Items.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = Items.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = Items.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o ItemSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return sqlite.Raw("NULL") + } + + return sqlite.Quote("items", "id").In(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + pkPairs := make([]bob.Expression, len(o)) + for i, row := range o { + pkPairs[i] = row.PrimaryKeyVals() + } + return bob.ExpressSlice(ctx, w, d, start, pkPairs, "", ", ", "") + })) +} + +// copyMatchingRows finds models in the given slice that have the same primary key +// then it first copies the existing relationships from the old model to the new model +// and then replaces the old model in the slice with the new model +func (o ItemSlice) copyMatchingRows(from ...*Item) { + for i, old := range o { + for _, new := range from { + if new.ID != old.ID { + continue + } + new.R = old.R + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o ItemSlice) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return bob.ModFunc[*dialect.UpdateQuery](func(q *dialect.UpdateQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return Items.BeforeUpdateHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *Item: + o.copyMatchingRows(retrieved) + case []*Item: + o.copyMatchingRows(retrieved...) + case ItemSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a Item or a slice of Item + // then run the AfterUpdateHooks on the slice + _, err = Items.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o ItemSlice) DeleteMod() bob.Mod[*dialect.DeleteQuery] { + return bob.ModFunc[*dialect.DeleteQuery](func(q *dialect.DeleteQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return Items.BeforeDeleteHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *Item: + o.copyMatchingRows(retrieved) + case []*Item: + o.copyMatchingRows(retrieved...) + case ItemSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a Item or a slice of Item + // then run the AfterDeleteHooks on the slice + _, err = Items.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o ItemSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals ItemSetter) error { + if len(o) == 0 { + return nil + } + + _, err := Items.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o ItemSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := Items.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o ItemSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := Items.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +type itemJoins[Q dialect.Joinable] struct { + typ string + User func(context.Context) modAs[Q, userColumns] +} + +func (j itemJoins[Q]) aliasedAs(alias string) itemJoins[Q] { + return buildItemJoins[Q](buildItemColumns(alias), j.typ) +} + +func buildItemJoins[Q dialect.Joinable](cols itemColumns, typ string) itemJoins[Q] { + return itemJoins[Q]{ + typ: typ, + User: itemsJoinUser[Q](cols, typ), + } +} + +func itemsJoinUser[Q dialect.Joinable](from itemColumns, typ string) func(context.Context) modAs[Q, userColumns] { + return func(ctx context.Context) modAs[Q, userColumns] { + return modAs[Q, userColumns]{ + c: UserColumns, + f: func(to userColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, Users.Name().As(to.Alias())).On( + to.ID.EQ(from.UserID), + )) + } + + return mods + }, + } + } +} + +// User starts a query for related objects on users +func (o *Item) User(mods ...bob.Mod[*dialect.SelectQuery]) UsersQuery { + return Users.Query(append(mods, + sm.Where(UserColumns.ID.EQ(sqlite.Arg(o.UserID))), + )...) +} + +func (os ItemSlice) User(mods ...bob.Mod[*dialect.SelectQuery]) UsersQuery { + PKArgs := make([]bob.Expression, len(os)) + for i, o := range os { + PKArgs[i] = sqlite.ArgGroup(o.UserID) + } + + return Users.Query(append(mods, + sm.Where(sqlite.Group(UserColumns.ID).In(PKArgs...)), + )...) +} + +func (o *Item) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "User": + rel, ok := retrieved.(*User) + if !ok { + return fmt.Errorf("item cannot load %T as %q", retrieved, name) + } + + o.R.User = rel + + if rel != nil { + rel.R.Items = ItemSlice{o} + } + return nil + default: + return fmt.Errorf("item has no relationship %q", name) + } +} + +func PreloadItemUser(opts ...sqlite.PreloadOption) sqlite.Preloader { + return sqlite.Preload[*User, UserSlice](orm.Relationship{ + Name: "User", + Sides: []orm.RelSide{ + { + From: TableNames.Items, + To: TableNames.Users, + FromColumns: []string{ + ColumnNames.Items.UserID, + }, + ToColumns: []string{ + ColumnNames.Users.ID, + }, + }, + }, + }, Users.Columns().Names(), opts...) +} + +func ThenLoadItemUser(queryMods ...bob.Mod[*dialect.SelectQuery]) sqlite.Loader { + return sqlite.Loader(func(ctx context.Context, exec bob.Executor, retrieved any) error { + loader, isLoader := retrieved.(interface { + LoadItemUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + }) + if !isLoader { + return fmt.Errorf("object %T cannot load ItemUser", retrieved) + } + + err := loader.LoadItemUser(ctx, exec, queryMods...) + + // Don't cause an issue due to missing relationships + if errors.Is(err, sql.ErrNoRows) { + return nil + } + + return err + }) +} + +// LoadItemUser loads the item's User into the .R struct +func (o *Item) LoadItemUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.User = nil + + related, err := o.User(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.Items = ItemSlice{o} + + o.R.User = related + return nil +} + +// LoadItemUser loads the item's User into the .R struct +func (os ItemSlice) LoadItemUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + users, err := os.User(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + for _, rel := range users { + if o.UserID.GetOrZero() != rel.ID { + continue + } + + rel.R.Items = append(rel.R.Items, o) + + o.R.User = rel + break + } + } + + return nil +} + +func attachItemUser0(ctx context.Context, exec bob.Executor, count int, item0 *Item, user1 *User) (*Item, error) { + setter := &ItemSetter{ + UserID: omitnull.From(user1.ID), + } + + err := item0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachItemUser0: %w", err) + } + + return item0, nil +} + +func (item0 *Item) InsertUser(ctx context.Context, exec bob.Executor, related *UserSetter) error { + user1, err := Users.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachItemUser0(ctx, exec, 1, item0, user1) + if err != nil { + return err + } + + item0.R.User = user1 + + user1.R.Items = append(user1.R.Items, item0) + + return nil +} + +func (item0 *Item) AttachUser(ctx context.Context, exec bob.Executor, user1 *User) error { + var err error + + _, err = attachItemUser0(ctx, exec, 1, item0, user1) + if err != nil { + return err + } + + item0.R.User = user1 + + user1.R.Items = append(user1.R.Items, item0) + + return nil +} diff --git a/server/internal/models/passkey.go b/server/internal/models/passkey.go deleted file mode 100644 index d85f72e..0000000 --- a/server/internal/models/passkey.go +++ /dev/null @@ -1,16 +0,0 @@ -package models - -import "time" - -type Passkey struct { - ID string `gorm:"primaryKey"` - - PublicKey string - Algorithm int - CreatedAt time.Time - LastUsed time.Time - - // User - UserID uint - User User -} diff --git a/server/internal/models/user.go b/server/internal/models/user.go deleted file mode 100644 index b9d28ed..0000000 --- a/server/internal/models/user.go +++ /dev/null @@ -1,36 +0,0 @@ -package models - -import ( - "fmt" - - userv1 "github.com/spotdemo4/trevstack/server/internal/services/user/v1" -) - -type User struct { - ID uint32 `gorm:"primaryKey"` - - Username string - Password string - Challenge *string - - // Passkeys - Passkeys []Passkey - - // Profile picture - ProfilePictureID *uint - ProfilePicture *File -} - -func (u User) ToConnectV1() *userv1.User { - var ppid *string - if u.ProfilePicture != nil { - id := fmt.Sprintf("/file/%d", u.ProfilePicture.ID) - ppid = &id - } - - return &userv1.User{ - Id: u.ID, - Username: u.Username, - ProfilePicture: ppid, - } -} diff --git a/server/internal/models/users.bob.go b/server/internal/models/users.bob.go new file mode 100644 index 0000000..d415dd3 --- /dev/null +++ b/server/internal/models/users.bob.go @@ -0,0 +1,887 @@ +// Code generated by BobGen sqlite (devel). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "errors" + "fmt" + "io" + + "github.com/aarondl/opt/null" + "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" + "github.com/stephenafamo/bob" + "github.com/stephenafamo/bob/dialect/sqlite" + "github.com/stephenafamo/bob/dialect/sqlite/dialect" + "github.com/stephenafamo/bob/dialect/sqlite/dm" + "github.com/stephenafamo/bob/dialect/sqlite/sm" + "github.com/stephenafamo/bob/dialect/sqlite/um" + "github.com/stephenafamo/bob/expr" + "github.com/stephenafamo/bob/mods" +) + +// User is an object representing the database table. +type User struct { + ID int32 `db:"id,pk" ` + Username null.Val[string] `db:"username" ` + Password null.Val[string] `db:"password" ` + ProfilePictureID null.Val[int32] `db:"profile_picture_id" ` + Challenge null.Val[string] `db:"challenge" ` + + R userR `db:"-" ` +} + +// UserSlice is an alias for a slice of pointers to User. +// This should almost always be used instead of []*User. +type UserSlice []*User + +// Users contains methods to work with the users table +var Users = sqlite.NewTablex[*User, UserSlice, *UserSetter]("", "users") + +// UsersQuery is a query on the users table +type UsersQuery = *sqlite.ViewQuery[*User, UserSlice] + +// userR is where relationships are stored. +type userR struct { + Files FileSlice // fk_files_0 + Items ItemSlice // fk_items_0 +} + +type userColumnNames struct { + ID string + Username string + Password string + ProfilePictureID string + Challenge string +} + +var UserColumns = buildUserColumns("users") + +type userColumns struct { + tableAlias string + ID sqlite.Expression + Username sqlite.Expression + Password sqlite.Expression + ProfilePictureID sqlite.Expression + Challenge sqlite.Expression +} + +func (c userColumns) Alias() string { + return c.tableAlias +} + +func (userColumns) AliasedAs(alias string) userColumns { + return buildUserColumns(alias) +} + +func buildUserColumns(alias string) userColumns { + return userColumns{ + tableAlias: alias, + ID: sqlite.Quote(alias, "id"), + Username: sqlite.Quote(alias, "username"), + Password: sqlite.Quote(alias, "password"), + ProfilePictureID: sqlite.Quote(alias, "profile_picture_id"), + Challenge: sqlite.Quote(alias, "challenge"), + } +} + +type userWhere[Q sqlite.Filterable] struct { + ID sqlite.WhereMod[Q, int32] + Username sqlite.WhereNullMod[Q, string] + Password sqlite.WhereNullMod[Q, string] + ProfilePictureID sqlite.WhereNullMod[Q, int32] + Challenge sqlite.WhereNullMod[Q, string] +} + +func (userWhere[Q]) AliasedAs(alias string) userWhere[Q] { + return buildUserWhere[Q](buildUserColumns(alias)) +} + +func buildUserWhere[Q sqlite.Filterable](cols userColumns) userWhere[Q] { + return userWhere[Q]{ + ID: sqlite.Where[Q, int32](cols.ID), + Username: sqlite.WhereNull[Q, string](cols.Username), + Password: sqlite.WhereNull[Q, string](cols.Password), + ProfilePictureID: sqlite.WhereNull[Q, int32](cols.ProfilePictureID), + Challenge: sqlite.WhereNull[Q, string](cols.Challenge), + } +} + +var UserErrors = &userErrors{ + ErrUniquePkMainUsers: &UniqueConstraintError{s: "pk_main_users"}, +} + +type userErrors struct { + ErrUniquePkMainUsers *UniqueConstraintError +} + +// UserSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type UserSetter struct { + ID omit.Val[int32] `db:"id,pk" ` + Username omitnull.Val[string] `db:"username" ` + Password omitnull.Val[string] `db:"password" ` + ProfilePictureID omitnull.Val[int32] `db:"profile_picture_id" ` + Challenge omitnull.Val[string] `db:"challenge" ` +} + +func (s UserSetter) SetColumns() []string { + vals := make([]string, 0, 5) + if !s.ID.IsUnset() { + vals = append(vals, "id") + } + + if !s.Username.IsUnset() { + vals = append(vals, "username") + } + + if !s.Password.IsUnset() { + vals = append(vals, "password") + } + + if !s.ProfilePictureID.IsUnset() { + vals = append(vals, "profile_picture_id") + } + + if !s.Challenge.IsUnset() { + vals = append(vals, "challenge") + } + + return vals +} + +func (s UserSetter) Overwrite(t *User) { + if !s.ID.IsUnset() { + t.ID, _ = s.ID.Get() + } + if !s.Username.IsUnset() { + t.Username, _ = s.Username.GetNull() + } + if !s.Password.IsUnset() { + t.Password, _ = s.Password.GetNull() + } + if !s.ProfilePictureID.IsUnset() { + t.ProfilePictureID, _ = s.ProfilePictureID.GetNull() + } + if !s.Challenge.IsUnset() { + t.Challenge, _ = s.Challenge.GetNull() + } +} + +func (s *UserSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return Users.BeforeInsertHooks.RunHooks(ctx, exec, s) + }) + + if len(q.Table.Columns) == 0 { + q.Table.Columns = s.SetColumns() + } + + q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + vals := make([]bob.Expression, 0, 5) + if !s.ID.IsUnset() { + vals = append(vals, sqlite.Arg(s.ID)) + } + + if !s.Username.IsUnset() { + vals = append(vals, sqlite.Arg(s.Username)) + } + + if !s.Password.IsUnset() { + vals = append(vals, sqlite.Arg(s.Password)) + } + + if !s.ProfilePictureID.IsUnset() { + vals = append(vals, sqlite.Arg(s.ProfilePictureID)) + } + + if !s.Challenge.IsUnset() { + vals = append(vals, sqlite.Arg(s.Challenge)) + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s UserSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s UserSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 5) + + if !s.ID.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + sqlite.Quote(append(prefix, "id")...), + sqlite.Arg(s.ID), + }}) + } + + if !s.Username.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + sqlite.Quote(append(prefix, "username")...), + sqlite.Arg(s.Username), + }}) + } + + if !s.Password.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + sqlite.Quote(append(prefix, "password")...), + sqlite.Arg(s.Password), + }}) + } + + if !s.ProfilePictureID.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + sqlite.Quote(append(prefix, "profile_picture_id")...), + sqlite.Arg(s.ProfilePictureID), + }}) + } + + if !s.Challenge.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + sqlite.Quote(append(prefix, "challenge")...), + sqlite.Arg(s.Challenge), + }}) + } + + return exprs +} + +// FindUser retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindUser(ctx context.Context, exec bob.Executor, IDPK int32, cols ...string) (*User, error) { + if len(cols) == 0 { + return Users.Query( + SelectWhere.Users.ID.EQ(IDPK), + ).One(ctx, exec) + } + + return Users.Query( + SelectWhere.Users.ID.EQ(IDPK), + sm.Columns(Users.Columns().Only(cols...)), + ).One(ctx, exec) +} + +// UserExists checks the presence of a single record by primary key +func UserExists(ctx context.Context, exec bob.Executor, IDPK int32) (bool, error) { + return Users.Query( + SelectWhere.Users.ID.EQ(IDPK), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after User is retrieved from the database +func (o *User) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = Users.AfterSelectHooks.RunHooks(ctx, exec, UserSlice{o}) + case bob.QueryTypeInsert: + ctx, err = Users.AfterInsertHooks.RunHooks(ctx, exec, UserSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = Users.AfterUpdateHooks.RunHooks(ctx, exec, UserSlice{o}) + case bob.QueryTypeDelete: + ctx, err = Users.AfterDeleteHooks.RunHooks(ctx, exec, UserSlice{o}) + } + + return err +} + +// PrimaryKeyVals returns the primary key values of the User +func (o *User) PrimaryKeyVals() bob.Expression { + return sqlite.Arg(o.ID) +} + +func (o *User) pkEQ() dialect.Expression { + return sqlite.Quote("users", "id").EQ(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + return o.PrimaryKeyVals().WriteSQL(ctx, w, d, start) + })) +} + +// Update uses an executor to update the User +func (o *User) Update(ctx context.Context, exec bob.Executor, s *UserSetter) error { + v, err := Users.Update(s.UpdateMod(), um.Where(o.pkEQ())).One(ctx, exec) + if err != nil { + return err + } + + o.R = v.R + *o = *v + + return nil +} + +// Delete deletes a single User record with an executor +func (o *User) Delete(ctx context.Context, exec bob.Executor) error { + _, err := Users.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the User using the executor +func (o *User) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := Users.Query( + SelectWhere.Users.ID.EQ(o.ID), + ).One(ctx, exec) + if err != nil { + return err + } + o2.R = o.R + *o = *o2 + + return nil +} + +// AfterQueryHook is called after UserSlice is retrieved from the database +func (o UserSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = Users.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = Users.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = Users.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = Users.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o UserSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return sqlite.Raw("NULL") + } + + return sqlite.Quote("users", "id").In(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + pkPairs := make([]bob.Expression, len(o)) + for i, row := range o { + pkPairs[i] = row.PrimaryKeyVals() + } + return bob.ExpressSlice(ctx, w, d, start, pkPairs, "", ", ", "") + })) +} + +// copyMatchingRows finds models in the given slice that have the same primary key +// then it first copies the existing relationships from the old model to the new model +// and then replaces the old model in the slice with the new model +func (o UserSlice) copyMatchingRows(from ...*User) { + for i, old := range o { + for _, new := range from { + if new.ID != old.ID { + continue + } + new.R = old.R + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o UserSlice) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return bob.ModFunc[*dialect.UpdateQuery](func(q *dialect.UpdateQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return Users.BeforeUpdateHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *User: + o.copyMatchingRows(retrieved) + case []*User: + o.copyMatchingRows(retrieved...) + case UserSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a User or a slice of User + // then run the AfterUpdateHooks on the slice + _, err = Users.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o UserSlice) DeleteMod() bob.Mod[*dialect.DeleteQuery] { + return bob.ModFunc[*dialect.DeleteQuery](func(q *dialect.DeleteQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return Users.BeforeDeleteHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *User: + o.copyMatchingRows(retrieved) + case []*User: + o.copyMatchingRows(retrieved...) + case UserSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a User or a slice of User + // then run the AfterDeleteHooks on the slice + _, err = Users.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o UserSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals UserSetter) error { + if len(o) == 0 { + return nil + } + + _, err := Users.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o UserSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := Users.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o UserSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := Users.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +type userJoins[Q dialect.Joinable] struct { + typ string + Files func(context.Context) modAs[Q, fileColumns] + Items func(context.Context) modAs[Q, itemColumns] +} + +func (j userJoins[Q]) aliasedAs(alias string) userJoins[Q] { + return buildUserJoins[Q](buildUserColumns(alias), j.typ) +} + +func buildUserJoins[Q dialect.Joinable](cols userColumns, typ string) userJoins[Q] { + return userJoins[Q]{ + typ: typ, + Files: usersJoinFiles[Q](cols, typ), + Items: usersJoinItems[Q](cols, typ), + } +} + +func usersJoinFiles[Q dialect.Joinable](from userColumns, typ string) func(context.Context) modAs[Q, fileColumns] { + return func(ctx context.Context) modAs[Q, fileColumns] { + return modAs[Q, fileColumns]{ + c: FileColumns, + f: func(to fileColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, Files.Name().As(to.Alias())).On( + to.UserID.EQ(from.ID), + )) + } + + return mods + }, + } + } +} + +func usersJoinItems[Q dialect.Joinable](from userColumns, typ string) func(context.Context) modAs[Q, itemColumns] { + return func(ctx context.Context) modAs[Q, itemColumns] { + return modAs[Q, itemColumns]{ + c: ItemColumns, + f: func(to itemColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, Items.Name().As(to.Alias())).On( + to.UserID.EQ(from.ID), + )) + } + + return mods + }, + } + } +} + +// Files starts a query for related objects on files +func (o *User) Files(mods ...bob.Mod[*dialect.SelectQuery]) FilesQuery { + return Files.Query(append(mods, + sm.Where(FileColumns.UserID.EQ(sqlite.Arg(o.ID))), + )...) +} + +func (os UserSlice) Files(mods ...bob.Mod[*dialect.SelectQuery]) FilesQuery { + PKArgs := make([]bob.Expression, len(os)) + for i, o := range os { + PKArgs[i] = sqlite.ArgGroup(o.ID) + } + + return Files.Query(append(mods, + sm.Where(sqlite.Group(FileColumns.UserID).In(PKArgs...)), + )...) +} + +// Items starts a query for related objects on items +func (o *User) Items(mods ...bob.Mod[*dialect.SelectQuery]) ItemsQuery { + return Items.Query(append(mods, + sm.Where(ItemColumns.UserID.EQ(sqlite.Arg(o.ID))), + )...) +} + +func (os UserSlice) Items(mods ...bob.Mod[*dialect.SelectQuery]) ItemsQuery { + PKArgs := make([]bob.Expression, len(os)) + for i, o := range os { + PKArgs[i] = sqlite.ArgGroup(o.ID) + } + + return Items.Query(append(mods, + sm.Where(sqlite.Group(ItemColumns.UserID).In(PKArgs...)), + )...) +} + +func (o *User) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "Files": + rels, ok := retrieved.(FileSlice) + if !ok { + return fmt.Errorf("user cannot load %T as %q", retrieved, name) + } + + o.R.Files = rels + + for _, rel := range rels { + if rel != nil { + rel.R.User = o + } + } + return nil + case "Items": + rels, ok := retrieved.(ItemSlice) + if !ok { + return fmt.Errorf("user cannot load %T as %q", retrieved, name) + } + + o.R.Items = rels + + for _, rel := range rels { + if rel != nil { + rel.R.User = o + } + } + return nil + default: + return fmt.Errorf("user has no relationship %q", name) + } +} + +func ThenLoadUserFiles(queryMods ...bob.Mod[*dialect.SelectQuery]) sqlite.Loader { + return sqlite.Loader(func(ctx context.Context, exec bob.Executor, retrieved any) error { + loader, isLoader := retrieved.(interface { + LoadUserFiles(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + }) + if !isLoader { + return fmt.Errorf("object %T cannot load UserFiles", retrieved) + } + + err := loader.LoadUserFiles(ctx, exec, queryMods...) + + // Don't cause an issue due to missing relationships + if errors.Is(err, sql.ErrNoRows) { + return nil + } + + return err + }) +} + +// LoadUserFiles loads the user's Files into the .R struct +func (o *User) LoadUserFiles(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Files = nil + + related, err := o.Files(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.User = o + } + + o.R.Files = related + return nil +} + +// LoadUserFiles loads the user's Files into the .R struct +func (os UserSlice) LoadUserFiles(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + files, err := os.Files(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + o.R.Files = nil + } + + for _, o := range os { + for _, rel := range files { + if o.ID != rel.UserID.GetOrZero() { + continue + } + + rel.R.User = o + + o.R.Files = append(o.R.Files, rel) + } + } + + return nil +} + +func ThenLoadUserItems(queryMods ...bob.Mod[*dialect.SelectQuery]) sqlite.Loader { + return sqlite.Loader(func(ctx context.Context, exec bob.Executor, retrieved any) error { + loader, isLoader := retrieved.(interface { + LoadUserItems(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + }) + if !isLoader { + return fmt.Errorf("object %T cannot load UserItems", retrieved) + } + + err := loader.LoadUserItems(ctx, exec, queryMods...) + + // Don't cause an issue due to missing relationships + if errors.Is(err, sql.ErrNoRows) { + return nil + } + + return err + }) +} + +// LoadUserItems loads the user's Items into the .R struct +func (o *User) LoadUserItems(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Items = nil + + related, err := o.Items(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.User = o + } + + o.R.Items = related + return nil +} + +// LoadUserItems loads the user's Items into the .R struct +func (os UserSlice) LoadUserItems(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + items, err := os.Items(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + o.R.Items = nil + } + + for _, o := range os { + for _, rel := range items { + if o.ID != rel.UserID.GetOrZero() { + continue + } + + rel.R.User = o + + o.R.Items = append(o.R.Items, rel) + } + } + + return nil +} + +func insertUserFiles0(ctx context.Context, exec bob.Executor, files1 []*FileSetter, user0 *User) (FileSlice, error) { + for i := range files1 { + files1[i].UserID = omitnull.From(user0.ID) + } + + ret, err := Files.Insert(bob.ToMods(files1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertUserFiles0: %w", err) + } + + return ret, nil +} + +func attachUserFiles0(ctx context.Context, exec bob.Executor, count int, files1 FileSlice, user0 *User) (FileSlice, error) { + setter := &FileSetter{ + UserID: omitnull.From(user0.ID), + } + + err := files1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachUserFiles0: %w", err) + } + + return files1, nil +} + +func (user0 *User) InsertFiles(ctx context.Context, exec bob.Executor, related ...*FileSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + files1, err := insertUserFiles0(ctx, exec, related, user0) + if err != nil { + return err + } + + user0.R.Files = append(user0.R.Files, files1...) + + for _, rel := range files1 { + rel.R.User = user0 + } + return nil +} + +func (user0 *User) AttachFiles(ctx context.Context, exec bob.Executor, related ...*File) error { + if len(related) == 0 { + return nil + } + + var err error + files1 := FileSlice(related) + + _, err = attachUserFiles0(ctx, exec, len(related), files1, user0) + if err != nil { + return err + } + + user0.R.Files = append(user0.R.Files, files1...) + + for _, rel := range related { + rel.R.User = user0 + } + + return nil +} + +func insertUserItems0(ctx context.Context, exec bob.Executor, items1 []*ItemSetter, user0 *User) (ItemSlice, error) { + for i := range items1 { + items1[i].UserID = omitnull.From(user0.ID) + } + + ret, err := Items.Insert(bob.ToMods(items1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertUserItems0: %w", err) + } + + return ret, nil +} + +func attachUserItems0(ctx context.Context, exec bob.Executor, count int, items1 ItemSlice, user0 *User) (ItemSlice, error) { + setter := &ItemSetter{ + UserID: omitnull.From(user0.ID), + } + + err := items1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachUserItems0: %w", err) + } + + return items1, nil +} + +func (user0 *User) InsertItems(ctx context.Context, exec bob.Executor, related ...*ItemSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + items1, err := insertUserItems0(ctx, exec, related, user0) + if err != nil { + return err + } + + user0.R.Items = append(user0.R.Items, items1...) + + for _, rel := range items1 { + rel.R.User = user0 + } + return nil +} + +func (user0 *User) AttachItems(ctx context.Context, exec bob.Executor, related ...*Item) error { + if len(related) == 0 { + return nil + } + + var err error + items1 := ItemSlice(related) + + _, err = attachUserItems0(ctx, exec, len(related), items1, user0) + if err != nil { + return err + } + + user0.R.Items = append(user0.R.Items, items1...) + + for _, rel := range related { + rel.R.User = user0 + } + + return nil +} diff --git a/server/internal/services/user/v1/auth.pb.go b/server/internal/services/user/v1/auth.pb.go index 27d39fb..8ce9fec 100644 --- a/server/internal/services/user/v1/auth.pb.go +++ b/server/internal/services/user/v1/auth.pb.go @@ -373,29 +373,26 @@ func (x *GetPasskeyIDsResponse) GetPasskeyIds() []string { return nil } -type PasskeyLoginRequest struct { +type BeginPasskeyLoginRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` - Algorithm int32 `protobuf:"varint,3,opt,name=algorithm,proto3" json:"algorithm,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *PasskeyLoginRequest) Reset() { - *x = PasskeyLoginRequest{} +func (x *BeginPasskeyLoginRequest) Reset() { + *x = BeginPasskeyLoginRequest{} mi := &file_user_v1_auth_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *PasskeyLoginRequest) String() string { +func (x *BeginPasskeyLoginRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*PasskeyLoginRequest) ProtoMessage() {} +func (*BeginPasskeyLoginRequest) ProtoMessage() {} -func (x *PasskeyLoginRequest) ProtoReflect() protoreflect.Message { +func (x *BeginPasskeyLoginRequest) ProtoReflect() protoreflect.Message { mi := &file_user_v1_auth_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -407,53 +404,31 @@ func (x *PasskeyLoginRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use PasskeyLoginRequest.ProtoReflect.Descriptor instead. -func (*PasskeyLoginRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use BeginPasskeyLoginRequest.ProtoReflect.Descriptor instead. +func (*BeginPasskeyLoginRequest) Descriptor() ([]byte, []int) { return file_user_v1_auth_proto_rawDescGZIP(), []int{8} } -func (x *PasskeyLoginRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *PasskeyLoginRequest) GetSignature() []byte { - if x != nil { - return x.Signature - } - return nil -} - -func (x *PasskeyLoginRequest) GetAlgorithm() int32 { - if x != nil { - return x.Algorithm - } - return 0 -} - -type PasskeyLoginResponse struct { +type BeginPasskeyLoginResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *PasskeyLoginResponse) Reset() { - *x = PasskeyLoginResponse{} +func (x *BeginPasskeyLoginResponse) Reset() { + *x = BeginPasskeyLoginResponse{} mi := &file_user_v1_auth_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *PasskeyLoginResponse) String() string { +func (x *BeginPasskeyLoginResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*PasskeyLoginResponse) ProtoMessage() {} +func (*BeginPasskeyLoginResponse) ProtoMessage() {} -func (x *PasskeyLoginResponse) ProtoReflect() protoreflect.Message { +func (x *BeginPasskeyLoginResponse) ProtoReflect() protoreflect.Message { mi := &file_user_v1_auth_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -465,16 +440,81 @@ func (x *PasskeyLoginResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use PasskeyLoginResponse.ProtoReflect.Descriptor instead. -func (*PasskeyLoginResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use BeginPasskeyLoginResponse.ProtoReflect.Descriptor instead. +func (*BeginPasskeyLoginResponse) Descriptor() ([]byte, []int) { return file_user_v1_auth_proto_rawDescGZIP(), []int{9} } -func (x *PasskeyLoginResponse) GetToken() string { +type FinishPasskeyLoginRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FinishPasskeyLoginRequest) Reset() { + *x = FinishPasskeyLoginRequest{} + mi := &file_user_v1_auth_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FinishPasskeyLoginRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FinishPasskeyLoginRequest) ProtoMessage() {} + +func (x *FinishPasskeyLoginRequest) ProtoReflect() protoreflect.Message { + mi := &file_user_v1_auth_proto_msgTypes[10] if x != nil { - return x.Token + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return "" + return mi.MessageOf(x) +} + +// Deprecated: Use FinishPasskeyLoginRequest.ProtoReflect.Descriptor instead. +func (*FinishPasskeyLoginRequest) Descriptor() ([]byte, []int) { + return file_user_v1_auth_proto_rawDescGZIP(), []int{10} +} + +type FinishPasskeyLoginResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FinishPasskeyLoginResponse) Reset() { + *x = FinishPasskeyLoginResponse{} + mi := &file_user_v1_auth_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FinishPasskeyLoginResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FinishPasskeyLoginResponse) ProtoMessage() {} + +func (x *FinishPasskeyLoginResponse) ProtoReflect() protoreflect.Message { + mi := &file_user_v1_auth_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FinishPasskeyLoginResponse.ProtoReflect.Descriptor instead. +func (*FinishPasskeyLoginResponse) Descriptor() ([]byte, []int) { + return file_user_v1_auth_proto_rawDescGZIP(), []int{11} } var File_user_v1_auth_proto protoreflect.FileDescriptor @@ -506,49 +546,54 @@ var file_user_v1_auth_proto_rawDesc = string([]byte{ 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, - 0x49, 0x64, 0x73, 0x22, 0x61, 0x0a, 0x13, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x4c, 0x6f, - 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, - 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, - 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x6c, 0x67, 0x6f, - 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x61, 0x6c, 0x67, - 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x22, 0x2c, 0x0a, 0x14, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, - 0x79, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0xe2, 0x02, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x38, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x15, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, - 0x0a, 0x06, 0x53, 0x69, 0x67, 0x6e, 0x55, 0x70, 0x12, 0x16, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x17, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x55, - 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06, 0x4c, - 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x50, - 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x49, 0x44, 0x73, 0x12, 0x1d, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x49, 0x44, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x49, 0x44, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0c, 0x50, 0x61, - 0x73, 0x73, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x67, 0x69, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x9d, 0x01, 0x0a, 0x0b, 0x63, 0x6f, - 0x6d, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x42, 0x09, 0x41, 0x75, 0x74, 0x68, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x73, 0x70, 0x6f, 0x74, 0x64, 0x65, 0x6d, 0x6f, 0x34, 0x2f, 0x74, 0x72, 0x65, - 0x76, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, - 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x76, 0x31, 0xa2, 0x02, - 0x03, 0x55, 0x58, 0x58, 0xaa, 0x02, 0x07, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x56, 0x31, 0xca, 0x02, - 0x07, 0x55, 0x73, 0x65, 0x72, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x13, 0x55, 0x73, 0x65, 0x72, 0x5c, - 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, - 0x08, 0x55, 0x73, 0x65, 0x72, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x49, 0x64, 0x73, 0x22, 0x1a, 0x0a, 0x18, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, + 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0x1b, 0x0a, 0x19, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x0a, 0x19, + 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x67, + 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x1c, 0x0a, 0x1a, 0x46, 0x69, 0x6e, + 0x69, 0x73, 0x68, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xd2, 0x03, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x38, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x12, 0x15, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x3b, 0x0a, 0x06, 0x53, 0x69, 0x67, 0x6e, 0x55, 0x70, 0x12, 0x16, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, + 0x67, 0x6e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, + 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x17, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x0d, 0x47, + 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x49, 0x44, 0x73, 0x12, 0x1d, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, + 0x79, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, + 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5c, 0x0a, + 0x11, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x67, + 0x69, 0x6e, 0x12, 0x21, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x65, 0x67, + 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x42, 0x65, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5f, 0x0a, 0x12, 0x46, + 0x69, 0x6e, 0x69, 0x73, 0x68, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x12, 0x22, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x69, + 0x73, 0x68, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x67, + 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x9d, 0x01, 0x0a, + 0x0b, 0x63, 0x6f, 0x6d, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x42, 0x09, 0x41, 0x75, + 0x74, 0x68, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x70, 0x6f, 0x74, 0x64, 0x65, 0x6d, 0x6f, 0x34, 0x2f, + 0x74, 0x72, 0x65, 0x76, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x76, + 0x31, 0xa2, 0x02, 0x03, 0x55, 0x58, 0x58, 0xaa, 0x02, 0x07, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x56, + 0x31, 0xca, 0x02, 0x07, 0x55, 0x73, 0x65, 0x72, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x13, 0x55, 0x73, + 0x65, 0x72, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0xea, 0x02, 0x08, 0x55, 0x73, 0x65, 0x72, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -563,35 +608,39 @@ func file_user_v1_auth_proto_rawDescGZIP() []byte { return file_user_v1_auth_proto_rawDescData } -var file_user_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_user_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_user_v1_auth_proto_goTypes = []any{ - (*LoginRequest)(nil), // 0: user.v1.LoginRequest - (*LoginResponse)(nil), // 1: user.v1.LoginResponse - (*SignUpRequest)(nil), // 2: user.v1.SignUpRequest - (*SignUpResponse)(nil), // 3: user.v1.SignUpResponse - (*LogoutRequest)(nil), // 4: user.v1.LogoutRequest - (*LogoutResponse)(nil), // 5: user.v1.LogoutResponse - (*GetPasskeyIDsRequest)(nil), // 6: user.v1.GetPasskeyIDsRequest - (*GetPasskeyIDsResponse)(nil), // 7: user.v1.GetPasskeyIDsResponse - (*PasskeyLoginRequest)(nil), // 8: user.v1.PasskeyLoginRequest - (*PasskeyLoginResponse)(nil), // 9: user.v1.PasskeyLoginResponse + (*LoginRequest)(nil), // 0: user.v1.LoginRequest + (*LoginResponse)(nil), // 1: user.v1.LoginResponse + (*SignUpRequest)(nil), // 2: user.v1.SignUpRequest + (*SignUpResponse)(nil), // 3: user.v1.SignUpResponse + (*LogoutRequest)(nil), // 4: user.v1.LogoutRequest + (*LogoutResponse)(nil), // 5: user.v1.LogoutResponse + (*GetPasskeyIDsRequest)(nil), // 6: user.v1.GetPasskeyIDsRequest + (*GetPasskeyIDsResponse)(nil), // 7: user.v1.GetPasskeyIDsResponse + (*BeginPasskeyLoginRequest)(nil), // 8: user.v1.BeginPasskeyLoginRequest + (*BeginPasskeyLoginResponse)(nil), // 9: user.v1.BeginPasskeyLoginResponse + (*FinishPasskeyLoginRequest)(nil), // 10: user.v1.FinishPasskeyLoginRequest + (*FinishPasskeyLoginResponse)(nil), // 11: user.v1.FinishPasskeyLoginResponse } var file_user_v1_auth_proto_depIdxs = []int32{ - 0, // 0: user.v1.AuthService.Login:input_type -> user.v1.LoginRequest - 2, // 1: user.v1.AuthService.SignUp:input_type -> user.v1.SignUpRequest - 4, // 2: user.v1.AuthService.Logout:input_type -> user.v1.LogoutRequest - 6, // 3: user.v1.AuthService.GetPasskeyIDs:input_type -> user.v1.GetPasskeyIDsRequest - 8, // 4: user.v1.AuthService.PasskeyLogin:input_type -> user.v1.PasskeyLoginRequest - 1, // 5: user.v1.AuthService.Login:output_type -> user.v1.LoginResponse - 3, // 6: user.v1.AuthService.SignUp:output_type -> user.v1.SignUpResponse - 5, // 7: user.v1.AuthService.Logout:output_type -> user.v1.LogoutResponse - 7, // 8: user.v1.AuthService.GetPasskeyIDs:output_type -> user.v1.GetPasskeyIDsResponse - 9, // 9: user.v1.AuthService.PasskeyLogin:output_type -> user.v1.PasskeyLoginResponse - 5, // [5:10] is the sub-list for method output_type - 0, // [0:5] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 0, // 0: user.v1.AuthService.Login:input_type -> user.v1.LoginRequest + 2, // 1: user.v1.AuthService.SignUp:input_type -> user.v1.SignUpRequest + 4, // 2: user.v1.AuthService.Logout:input_type -> user.v1.LogoutRequest + 6, // 3: user.v1.AuthService.GetPasskeyIDs:input_type -> user.v1.GetPasskeyIDsRequest + 8, // 4: user.v1.AuthService.BeginPasskeyLogin:input_type -> user.v1.BeginPasskeyLoginRequest + 10, // 5: user.v1.AuthService.FinishPasskeyLogin:input_type -> user.v1.FinishPasskeyLoginRequest + 1, // 6: user.v1.AuthService.Login:output_type -> user.v1.LoginResponse + 3, // 7: user.v1.AuthService.SignUp:output_type -> user.v1.SignUpResponse + 5, // 8: user.v1.AuthService.Logout:output_type -> user.v1.LogoutResponse + 7, // 9: user.v1.AuthService.GetPasskeyIDs:output_type -> user.v1.GetPasskeyIDsResponse + 9, // 10: user.v1.AuthService.BeginPasskeyLogin:output_type -> user.v1.BeginPasskeyLoginResponse + 11, // 11: user.v1.AuthService.FinishPasskeyLogin:output_type -> user.v1.FinishPasskeyLoginResponse + 6, // [6:12] is the sub-list for method output_type + 0, // [0:6] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name } func init() { file_user_v1_auth_proto_init() } @@ -605,7 +654,7 @@ func file_user_v1_auth_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_user_v1_auth_proto_rawDesc), len(file_user_v1_auth_proto_rawDesc)), NumEnums: 0, - NumMessages: 10, + NumMessages: 12, NumExtensions: 0, NumServices: 1, }, diff --git a/server/internal/services/user/v1/user.pb.go b/server/internal/services/user/v1/user.pb.go index d9b09ca..e46824b 100644 --- a/server/internal/services/user/v1/user.pb.go +++ b/server/internal/services/user/v1/user.pb.go @@ -457,28 +457,26 @@ func (x *UpdateProfilePictureResponse) GetUser() *User { return nil } -type CreatePasskeyRequest struct { +type BeginPasskeyRegistrationRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - PublicKey []byte `protobuf:"bytes,2,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *CreatePasskeyRequest) Reset() { - *x = CreatePasskeyRequest{} +func (x *BeginPasskeyRegistrationRequest) Reset() { + *x = BeginPasskeyRegistrationRequest{} mi := &file_user_v1_user_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CreatePasskeyRequest) String() string { +func (x *BeginPasskeyRegistrationRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreatePasskeyRequest) ProtoMessage() {} +func (*BeginPasskeyRegistrationRequest) ProtoMessage() {} -func (x *CreatePasskeyRequest) ProtoReflect() protoreflect.Message { +func (x *BeginPasskeyRegistrationRequest) ProtoReflect() protoreflect.Message { mi := &file_user_v1_user_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -490,45 +488,31 @@ func (x *CreatePasskeyRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreatePasskeyRequest.ProtoReflect.Descriptor instead. -func (*CreatePasskeyRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use BeginPasskeyRegistrationRequest.ProtoReflect.Descriptor instead. +func (*BeginPasskeyRegistrationRequest) Descriptor() ([]byte, []int) { return file_user_v1_user_proto_rawDescGZIP(), []int{9} } -func (x *CreatePasskeyRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *CreatePasskeyRequest) GetPublicKey() []byte { - if x != nil { - return x.PublicKey - } - return nil -} - -type CreatePasskeyResponse struct { +type BeginPasskeyRegistrationResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *CreatePasskeyResponse) Reset() { - *x = CreatePasskeyResponse{} +func (x *BeginPasskeyRegistrationResponse) Reset() { + *x = BeginPasskeyRegistrationResponse{} mi := &file_user_v1_user_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CreatePasskeyResponse) String() string { +func (x *BeginPasskeyRegistrationResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreatePasskeyResponse) ProtoMessage() {} +func (*BeginPasskeyRegistrationResponse) ProtoMessage() {} -func (x *CreatePasskeyResponse) ProtoReflect() protoreflect.Message { +func (x *BeginPasskeyRegistrationResponse) ProtoReflect() protoreflect.Message { mi := &file_user_v1_user_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -540,11 +524,83 @@ func (x *CreatePasskeyResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreatePasskeyResponse.ProtoReflect.Descriptor instead. -func (*CreatePasskeyResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use BeginPasskeyRegistrationResponse.ProtoReflect.Descriptor instead. +func (*BeginPasskeyRegistrationResponse) Descriptor() ([]byte, []int) { return file_user_v1_user_proto_rawDescGZIP(), []int{10} } +type FinishPasskeyRegistrationRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FinishPasskeyRegistrationRequest) Reset() { + *x = FinishPasskeyRegistrationRequest{} + mi := &file_user_v1_user_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FinishPasskeyRegistrationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FinishPasskeyRegistrationRequest) ProtoMessage() {} + +func (x *FinishPasskeyRegistrationRequest) ProtoReflect() protoreflect.Message { + mi := &file_user_v1_user_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FinishPasskeyRegistrationRequest.ProtoReflect.Descriptor instead. +func (*FinishPasskeyRegistrationRequest) Descriptor() ([]byte, []int) { + return file_user_v1_user_proto_rawDescGZIP(), []int{11} +} + +type FinishPasskeyRegistrationResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FinishPasskeyRegistrationResponse) Reset() { + *x = FinishPasskeyRegistrationResponse{} + mi := &file_user_v1_user_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FinishPasskeyRegistrationResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FinishPasskeyRegistrationResponse) ProtoMessage() {} + +func (x *FinishPasskeyRegistrationResponse) ProtoReflect() protoreflect.Message { + mi := &file_user_v1_user_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FinishPasskeyRegistrationResponse.ProtoReflect.Descriptor instead. +func (*FinishPasskeyRegistrationResponse) Descriptor() ([]byte, []int) { + return file_user_v1_user_proto_rawDescGZIP(), []int{12} +} + var File_user_v1_user_proto protoreflect.FileDescriptor var file_user_v1_user_proto_rawDesc = string([]byte{ @@ -591,13 +647,16 @@ var file_user_v1_user_proto_rawDesc = string([]byte{ 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, - 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x45, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, - 0x17, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xa1, 0x03, 0x0a, 0x0b, 0x55, 0x73, 0x65, + 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x21, 0x0a, 0x1f, 0x42, 0x65, 0x67, 0x69, + 0x6e, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x22, 0x0a, 0x20, 0x42, + 0x65, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x22, 0x0a, 0x20, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x22, 0x23, 0x0a, 0x21, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x50, 0x61, 0x73, + 0x73, 0x6b, 0x65, 0x79, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xb8, 0x04, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x17, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x75, @@ -618,23 +677,32 @@ var file_user_v1_user_proto_rawDesc = string([]byte{ 0x69, 0x6c, 0x65, 0x50, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x0d, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x12, 0x1d, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, - 0x6b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x6b, - 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x9d, 0x01, 0x0a, - 0x0b, 0x63, 0x6f, 0x6d, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x42, 0x09, 0x55, 0x73, - 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x70, 0x6f, 0x74, 0x64, 0x65, 0x6d, 0x6f, 0x34, 0x2f, - 0x74, 0x72, 0x65, 0x76, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x76, - 0x31, 0xa2, 0x02, 0x03, 0x55, 0x58, 0x58, 0xaa, 0x02, 0x07, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x56, - 0x31, 0xca, 0x02, 0x07, 0x55, 0x73, 0x65, 0x72, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x13, 0x55, 0x73, - 0x65, 0x72, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0xea, 0x02, 0x08, 0x55, 0x73, 0x65, 0x72, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x71, 0x0a, 0x18, 0x42, 0x65, + 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x29, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x65, 0x67, 0x69, 0x6e, + 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, 0x0a, + 0x19, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x52, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x50, 0x61, 0x73, 0x73, 0x6b, + 0x65, 0x79, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x50, 0x61, 0x73, 0x73, 0x6b, 0x65, 0x79, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x42, 0x9d, 0x01, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x42, 0x09, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x70, 0x6f, + 0x74, 0x64, 0x65, 0x6d, 0x6f, 0x34, 0x2f, 0x74, 0x72, 0x65, 0x76, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, + 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x55, 0x58, 0x58, 0xaa, 0x02, + 0x07, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x07, 0x55, 0x73, 0x65, 0x72, 0x5c, + 0x56, 0x31, 0xe2, 0x02, 0x13, 0x55, 0x73, 0x65, 0x72, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x08, 0x55, 0x73, 0x65, 0x72, 0x3a, + 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -649,19 +717,21 @@ func file_user_v1_user_proto_rawDescGZIP() []byte { return file_user_v1_user_proto_rawDescData } -var file_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 13) var file_user_v1_user_proto_goTypes = []any{ - (*User)(nil), // 0: user.v1.User - (*GetUserRequest)(nil), // 1: user.v1.GetUserRequest - (*GetUserResponse)(nil), // 2: user.v1.GetUserResponse - (*UpdatePasswordRequest)(nil), // 3: user.v1.UpdatePasswordRequest - (*UpdatePasswordResponse)(nil), // 4: user.v1.UpdatePasswordResponse - (*GetAPIKeyRequest)(nil), // 5: user.v1.GetAPIKeyRequest - (*GetAPIKeyResponse)(nil), // 6: user.v1.GetAPIKeyResponse - (*UpdateProfilePictureRequest)(nil), // 7: user.v1.UpdateProfilePictureRequest - (*UpdateProfilePictureResponse)(nil), // 8: user.v1.UpdateProfilePictureResponse - (*CreatePasskeyRequest)(nil), // 9: user.v1.CreatePasskeyRequest - (*CreatePasskeyResponse)(nil), // 10: user.v1.CreatePasskeyResponse + (*User)(nil), // 0: user.v1.User + (*GetUserRequest)(nil), // 1: user.v1.GetUserRequest + (*GetUserResponse)(nil), // 2: user.v1.GetUserResponse + (*UpdatePasswordRequest)(nil), // 3: user.v1.UpdatePasswordRequest + (*UpdatePasswordResponse)(nil), // 4: user.v1.UpdatePasswordResponse + (*GetAPIKeyRequest)(nil), // 5: user.v1.GetAPIKeyRequest + (*GetAPIKeyResponse)(nil), // 6: user.v1.GetAPIKeyResponse + (*UpdateProfilePictureRequest)(nil), // 7: user.v1.UpdateProfilePictureRequest + (*UpdateProfilePictureResponse)(nil), // 8: user.v1.UpdateProfilePictureResponse + (*BeginPasskeyRegistrationRequest)(nil), // 9: user.v1.BeginPasskeyRegistrationRequest + (*BeginPasskeyRegistrationResponse)(nil), // 10: user.v1.BeginPasskeyRegistrationResponse + (*FinishPasskeyRegistrationRequest)(nil), // 11: user.v1.FinishPasskeyRegistrationRequest + (*FinishPasskeyRegistrationResponse)(nil), // 12: user.v1.FinishPasskeyRegistrationResponse } var file_user_v1_user_proto_depIdxs = []int32{ 0, // 0: user.v1.GetUserResponse.user:type_name -> user.v1.User @@ -671,14 +741,16 @@ var file_user_v1_user_proto_depIdxs = []int32{ 3, // 4: user.v1.UserService.UpdatePassword:input_type -> user.v1.UpdatePasswordRequest 5, // 5: user.v1.UserService.GetAPIKey:input_type -> user.v1.GetAPIKeyRequest 7, // 6: user.v1.UserService.UpdateProfilePicture:input_type -> user.v1.UpdateProfilePictureRequest - 9, // 7: user.v1.UserService.CreatePasskey:input_type -> user.v1.CreatePasskeyRequest - 2, // 8: user.v1.UserService.GetUser:output_type -> user.v1.GetUserResponse - 4, // 9: user.v1.UserService.UpdatePassword:output_type -> user.v1.UpdatePasswordResponse - 6, // 10: user.v1.UserService.GetAPIKey:output_type -> user.v1.GetAPIKeyResponse - 8, // 11: user.v1.UserService.UpdateProfilePicture:output_type -> user.v1.UpdateProfilePictureResponse - 10, // 12: user.v1.UserService.CreatePasskey:output_type -> user.v1.CreatePasskeyResponse - 8, // [8:13] is the sub-list for method output_type - 3, // [3:8] is the sub-list for method input_type + 9, // 7: user.v1.UserService.BeginPasskeyRegistration:input_type -> user.v1.BeginPasskeyRegistrationRequest + 11, // 8: user.v1.UserService.FinishPasskeyRegistration:input_type -> user.v1.FinishPasskeyRegistrationRequest + 2, // 9: user.v1.UserService.GetUser:output_type -> user.v1.GetUserResponse + 4, // 10: user.v1.UserService.UpdatePassword:output_type -> user.v1.UpdatePasswordResponse + 6, // 11: user.v1.UserService.GetAPIKey:output_type -> user.v1.GetAPIKeyResponse + 8, // 12: user.v1.UserService.UpdateProfilePicture:output_type -> user.v1.UpdateProfilePictureResponse + 10, // 13: user.v1.UserService.BeginPasskeyRegistration:output_type -> user.v1.BeginPasskeyRegistrationResponse + 12, // 14: user.v1.UserService.FinishPasskeyRegistration:output_type -> user.v1.FinishPasskeyRegistrationResponse + 9, // [9:15] is the sub-list for method output_type + 3, // [3:9] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name @@ -696,7 +768,7 @@ func file_user_v1_user_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_user_v1_user_proto_rawDesc), len(file_user_v1_user_proto_rawDesc)), NumEnums: 0, - NumMessages: 11, + NumMessages: 13, NumExtensions: 0, NumServices: 1, }, diff --git a/server/internal/services/user/v1/userv1connect/auth.connect.go b/server/internal/services/user/v1/userv1connect/auth.connect.go index a63593c..db25f9b 100644 --- a/server/internal/services/user/v1/userv1connect/auth.connect.go +++ b/server/internal/services/user/v1/userv1connect/auth.connect.go @@ -42,9 +42,12 @@ const ( // AuthServiceGetPasskeyIDsProcedure is the fully-qualified name of the AuthService's GetPasskeyIDs // RPC. AuthServiceGetPasskeyIDsProcedure = "/user.v1.AuthService/GetPasskeyIDs" - // AuthServicePasskeyLoginProcedure is the fully-qualified name of the AuthService's PasskeyLogin - // RPC. - AuthServicePasskeyLoginProcedure = "/user.v1.AuthService/PasskeyLogin" + // AuthServiceBeginPasskeyLoginProcedure is the fully-qualified name of the AuthService's + // BeginPasskeyLogin RPC. + AuthServiceBeginPasskeyLoginProcedure = "/user.v1.AuthService/BeginPasskeyLogin" + // AuthServiceFinishPasskeyLoginProcedure is the fully-qualified name of the AuthService's + // FinishPasskeyLogin RPC. + AuthServiceFinishPasskeyLoginProcedure = "/user.v1.AuthService/FinishPasskeyLogin" ) // AuthServiceClient is a client for the user.v1.AuthService service. @@ -53,7 +56,8 @@ type AuthServiceClient interface { SignUp(context.Context, *connect.Request[v1.SignUpRequest]) (*connect.Response[v1.SignUpResponse], error) Logout(context.Context, *connect.Request[v1.LogoutRequest]) (*connect.Response[v1.LogoutResponse], error) GetPasskeyIDs(context.Context, *connect.Request[v1.GetPasskeyIDsRequest]) (*connect.Response[v1.GetPasskeyIDsResponse], error) - PasskeyLogin(context.Context, *connect.Request[v1.PasskeyLoginRequest]) (*connect.Response[v1.PasskeyLoginResponse], error) + BeginPasskeyLogin(context.Context, *connect.Request[v1.BeginPasskeyLoginRequest]) (*connect.Response[v1.BeginPasskeyLoginResponse], error) + FinishPasskeyLogin(context.Context, *connect.Request[v1.FinishPasskeyLoginRequest]) (*connect.Response[v1.FinishPasskeyLoginResponse], error) } // NewAuthServiceClient constructs a client for the user.v1.AuthService service. By default, it uses @@ -91,10 +95,16 @@ func NewAuthServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. connect.WithSchema(authServiceMethods.ByName("GetPasskeyIDs")), connect.WithClientOptions(opts...), ), - passkeyLogin: connect.NewClient[v1.PasskeyLoginRequest, v1.PasskeyLoginResponse]( + beginPasskeyLogin: connect.NewClient[v1.BeginPasskeyLoginRequest, v1.BeginPasskeyLoginResponse]( httpClient, - baseURL+AuthServicePasskeyLoginProcedure, - connect.WithSchema(authServiceMethods.ByName("PasskeyLogin")), + baseURL+AuthServiceBeginPasskeyLoginProcedure, + connect.WithSchema(authServiceMethods.ByName("BeginPasskeyLogin")), + connect.WithClientOptions(opts...), + ), + finishPasskeyLogin: connect.NewClient[v1.FinishPasskeyLoginRequest, v1.FinishPasskeyLoginResponse]( + httpClient, + baseURL+AuthServiceFinishPasskeyLoginProcedure, + connect.WithSchema(authServiceMethods.ByName("FinishPasskeyLogin")), connect.WithClientOptions(opts...), ), } @@ -102,11 +112,12 @@ func NewAuthServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. // authServiceClient implements AuthServiceClient. type authServiceClient struct { - login *connect.Client[v1.LoginRequest, v1.LoginResponse] - signUp *connect.Client[v1.SignUpRequest, v1.SignUpResponse] - logout *connect.Client[v1.LogoutRequest, v1.LogoutResponse] - getPasskeyIDs *connect.Client[v1.GetPasskeyIDsRequest, v1.GetPasskeyIDsResponse] - passkeyLogin *connect.Client[v1.PasskeyLoginRequest, v1.PasskeyLoginResponse] + login *connect.Client[v1.LoginRequest, v1.LoginResponse] + signUp *connect.Client[v1.SignUpRequest, v1.SignUpResponse] + logout *connect.Client[v1.LogoutRequest, v1.LogoutResponse] + getPasskeyIDs *connect.Client[v1.GetPasskeyIDsRequest, v1.GetPasskeyIDsResponse] + beginPasskeyLogin *connect.Client[v1.BeginPasskeyLoginRequest, v1.BeginPasskeyLoginResponse] + finishPasskeyLogin *connect.Client[v1.FinishPasskeyLoginRequest, v1.FinishPasskeyLoginResponse] } // Login calls user.v1.AuthService.Login. @@ -129,9 +140,14 @@ func (c *authServiceClient) GetPasskeyIDs(ctx context.Context, req *connect.Requ return c.getPasskeyIDs.CallUnary(ctx, req) } -// PasskeyLogin calls user.v1.AuthService.PasskeyLogin. -func (c *authServiceClient) PasskeyLogin(ctx context.Context, req *connect.Request[v1.PasskeyLoginRequest]) (*connect.Response[v1.PasskeyLoginResponse], error) { - return c.passkeyLogin.CallUnary(ctx, req) +// BeginPasskeyLogin calls user.v1.AuthService.BeginPasskeyLogin. +func (c *authServiceClient) BeginPasskeyLogin(ctx context.Context, req *connect.Request[v1.BeginPasskeyLoginRequest]) (*connect.Response[v1.BeginPasskeyLoginResponse], error) { + return c.beginPasskeyLogin.CallUnary(ctx, req) +} + +// FinishPasskeyLogin calls user.v1.AuthService.FinishPasskeyLogin. +func (c *authServiceClient) FinishPasskeyLogin(ctx context.Context, req *connect.Request[v1.FinishPasskeyLoginRequest]) (*connect.Response[v1.FinishPasskeyLoginResponse], error) { + return c.finishPasskeyLogin.CallUnary(ctx, req) } // AuthServiceHandler is an implementation of the user.v1.AuthService service. @@ -140,7 +156,8 @@ type AuthServiceHandler interface { SignUp(context.Context, *connect.Request[v1.SignUpRequest]) (*connect.Response[v1.SignUpResponse], error) Logout(context.Context, *connect.Request[v1.LogoutRequest]) (*connect.Response[v1.LogoutResponse], error) GetPasskeyIDs(context.Context, *connect.Request[v1.GetPasskeyIDsRequest]) (*connect.Response[v1.GetPasskeyIDsResponse], error) - PasskeyLogin(context.Context, *connect.Request[v1.PasskeyLoginRequest]) (*connect.Response[v1.PasskeyLoginResponse], error) + BeginPasskeyLogin(context.Context, *connect.Request[v1.BeginPasskeyLoginRequest]) (*connect.Response[v1.BeginPasskeyLoginResponse], error) + FinishPasskeyLogin(context.Context, *connect.Request[v1.FinishPasskeyLoginRequest]) (*connect.Response[v1.FinishPasskeyLoginResponse], error) } // NewAuthServiceHandler builds an HTTP handler from the service implementation. It returns the path @@ -174,10 +191,16 @@ func NewAuthServiceHandler(svc AuthServiceHandler, opts ...connect.HandlerOption connect.WithSchema(authServiceMethods.ByName("GetPasskeyIDs")), connect.WithHandlerOptions(opts...), ) - authServicePasskeyLoginHandler := connect.NewUnaryHandler( - AuthServicePasskeyLoginProcedure, - svc.PasskeyLogin, - connect.WithSchema(authServiceMethods.ByName("PasskeyLogin")), + authServiceBeginPasskeyLoginHandler := connect.NewUnaryHandler( + AuthServiceBeginPasskeyLoginProcedure, + svc.BeginPasskeyLogin, + connect.WithSchema(authServiceMethods.ByName("BeginPasskeyLogin")), + connect.WithHandlerOptions(opts...), + ) + authServiceFinishPasskeyLoginHandler := connect.NewUnaryHandler( + AuthServiceFinishPasskeyLoginProcedure, + svc.FinishPasskeyLogin, + connect.WithSchema(authServiceMethods.ByName("FinishPasskeyLogin")), connect.WithHandlerOptions(opts...), ) return "/user.v1.AuthService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -190,8 +213,10 @@ func NewAuthServiceHandler(svc AuthServiceHandler, opts ...connect.HandlerOption authServiceLogoutHandler.ServeHTTP(w, r) case AuthServiceGetPasskeyIDsProcedure: authServiceGetPasskeyIDsHandler.ServeHTTP(w, r) - case AuthServicePasskeyLoginProcedure: - authServicePasskeyLoginHandler.ServeHTTP(w, r) + case AuthServiceBeginPasskeyLoginProcedure: + authServiceBeginPasskeyLoginHandler.ServeHTTP(w, r) + case AuthServiceFinishPasskeyLoginProcedure: + authServiceFinishPasskeyLoginHandler.ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -217,6 +242,10 @@ func (UnimplementedAuthServiceHandler) GetPasskeyIDs(context.Context, *connect.R return nil, connect.NewError(connect.CodeUnimplemented, errors.New("user.v1.AuthService.GetPasskeyIDs is not implemented")) } -func (UnimplementedAuthServiceHandler) PasskeyLogin(context.Context, *connect.Request[v1.PasskeyLoginRequest]) (*connect.Response[v1.PasskeyLoginResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("user.v1.AuthService.PasskeyLogin is not implemented")) +func (UnimplementedAuthServiceHandler) BeginPasskeyLogin(context.Context, *connect.Request[v1.BeginPasskeyLoginRequest]) (*connect.Response[v1.BeginPasskeyLoginResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("user.v1.AuthService.BeginPasskeyLogin is not implemented")) +} + +func (UnimplementedAuthServiceHandler) FinishPasskeyLogin(context.Context, *connect.Request[v1.FinishPasskeyLoginRequest]) (*connect.Response[v1.FinishPasskeyLoginResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("user.v1.AuthService.FinishPasskeyLogin is not implemented")) } diff --git a/server/internal/services/user/v1/userv1connect/user.connect.go b/server/internal/services/user/v1/userv1connect/user.connect.go index ded5cfe..6eea8c3 100644 --- a/server/internal/services/user/v1/userv1connect/user.connect.go +++ b/server/internal/services/user/v1/userv1connect/user.connect.go @@ -43,9 +43,12 @@ const ( // UserServiceUpdateProfilePictureProcedure is the fully-qualified name of the UserService's // UpdateProfilePicture RPC. UserServiceUpdateProfilePictureProcedure = "/user.v1.UserService/UpdateProfilePicture" - // UserServiceCreatePasskeyProcedure is the fully-qualified name of the UserService's CreatePasskey - // RPC. - UserServiceCreatePasskeyProcedure = "/user.v1.UserService/CreatePasskey" + // UserServiceBeginPasskeyRegistrationProcedure is the fully-qualified name of the UserService's + // BeginPasskeyRegistration RPC. + UserServiceBeginPasskeyRegistrationProcedure = "/user.v1.UserService/BeginPasskeyRegistration" + // UserServiceFinishPasskeyRegistrationProcedure is the fully-qualified name of the UserService's + // FinishPasskeyRegistration RPC. + UserServiceFinishPasskeyRegistrationProcedure = "/user.v1.UserService/FinishPasskeyRegistration" ) // UserServiceClient is a client for the user.v1.UserService service. @@ -54,7 +57,8 @@ type UserServiceClient interface { UpdatePassword(context.Context, *connect.Request[v1.UpdatePasswordRequest]) (*connect.Response[v1.UpdatePasswordResponse], error) GetAPIKey(context.Context, *connect.Request[v1.GetAPIKeyRequest]) (*connect.Response[v1.GetAPIKeyResponse], error) UpdateProfilePicture(context.Context, *connect.Request[v1.UpdateProfilePictureRequest]) (*connect.Response[v1.UpdateProfilePictureResponse], error) - CreatePasskey(context.Context, *connect.Request[v1.CreatePasskeyRequest]) (*connect.Response[v1.CreatePasskeyResponse], error) + BeginPasskeyRegistration(context.Context, *connect.Request[v1.BeginPasskeyRegistrationRequest]) (*connect.Response[v1.BeginPasskeyRegistrationResponse], error) + FinishPasskeyRegistration(context.Context, *connect.Request[v1.FinishPasskeyRegistrationRequest]) (*connect.Response[v1.FinishPasskeyRegistrationResponse], error) } // NewUserServiceClient constructs a client for the user.v1.UserService service. By default, it uses @@ -92,10 +96,16 @@ func NewUserServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. connect.WithSchema(userServiceMethods.ByName("UpdateProfilePicture")), connect.WithClientOptions(opts...), ), - createPasskey: connect.NewClient[v1.CreatePasskeyRequest, v1.CreatePasskeyResponse]( + beginPasskeyRegistration: connect.NewClient[v1.BeginPasskeyRegistrationRequest, v1.BeginPasskeyRegistrationResponse]( httpClient, - baseURL+UserServiceCreatePasskeyProcedure, - connect.WithSchema(userServiceMethods.ByName("CreatePasskey")), + baseURL+UserServiceBeginPasskeyRegistrationProcedure, + connect.WithSchema(userServiceMethods.ByName("BeginPasskeyRegistration")), + connect.WithClientOptions(opts...), + ), + finishPasskeyRegistration: connect.NewClient[v1.FinishPasskeyRegistrationRequest, v1.FinishPasskeyRegistrationResponse]( + httpClient, + baseURL+UserServiceFinishPasskeyRegistrationProcedure, + connect.WithSchema(userServiceMethods.ByName("FinishPasskeyRegistration")), connect.WithClientOptions(opts...), ), } @@ -103,11 +113,12 @@ func NewUserServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. // userServiceClient implements UserServiceClient. type userServiceClient struct { - getUser *connect.Client[v1.GetUserRequest, v1.GetUserResponse] - updatePassword *connect.Client[v1.UpdatePasswordRequest, v1.UpdatePasswordResponse] - getAPIKey *connect.Client[v1.GetAPIKeyRequest, v1.GetAPIKeyResponse] - updateProfilePicture *connect.Client[v1.UpdateProfilePictureRequest, v1.UpdateProfilePictureResponse] - createPasskey *connect.Client[v1.CreatePasskeyRequest, v1.CreatePasskeyResponse] + getUser *connect.Client[v1.GetUserRequest, v1.GetUserResponse] + updatePassword *connect.Client[v1.UpdatePasswordRequest, v1.UpdatePasswordResponse] + getAPIKey *connect.Client[v1.GetAPIKeyRequest, v1.GetAPIKeyResponse] + updateProfilePicture *connect.Client[v1.UpdateProfilePictureRequest, v1.UpdateProfilePictureResponse] + beginPasskeyRegistration *connect.Client[v1.BeginPasskeyRegistrationRequest, v1.BeginPasskeyRegistrationResponse] + finishPasskeyRegistration *connect.Client[v1.FinishPasskeyRegistrationRequest, v1.FinishPasskeyRegistrationResponse] } // GetUser calls user.v1.UserService.GetUser. @@ -130,9 +141,14 @@ func (c *userServiceClient) UpdateProfilePicture(ctx context.Context, req *conne return c.updateProfilePicture.CallUnary(ctx, req) } -// CreatePasskey calls user.v1.UserService.CreatePasskey. -func (c *userServiceClient) CreatePasskey(ctx context.Context, req *connect.Request[v1.CreatePasskeyRequest]) (*connect.Response[v1.CreatePasskeyResponse], error) { - return c.createPasskey.CallUnary(ctx, req) +// BeginPasskeyRegistration calls user.v1.UserService.BeginPasskeyRegistration. +func (c *userServiceClient) BeginPasskeyRegistration(ctx context.Context, req *connect.Request[v1.BeginPasskeyRegistrationRequest]) (*connect.Response[v1.BeginPasskeyRegistrationResponse], error) { + return c.beginPasskeyRegistration.CallUnary(ctx, req) +} + +// FinishPasskeyRegistration calls user.v1.UserService.FinishPasskeyRegistration. +func (c *userServiceClient) FinishPasskeyRegistration(ctx context.Context, req *connect.Request[v1.FinishPasskeyRegistrationRequest]) (*connect.Response[v1.FinishPasskeyRegistrationResponse], error) { + return c.finishPasskeyRegistration.CallUnary(ctx, req) } // UserServiceHandler is an implementation of the user.v1.UserService service. @@ -141,7 +157,8 @@ type UserServiceHandler interface { UpdatePassword(context.Context, *connect.Request[v1.UpdatePasswordRequest]) (*connect.Response[v1.UpdatePasswordResponse], error) GetAPIKey(context.Context, *connect.Request[v1.GetAPIKeyRequest]) (*connect.Response[v1.GetAPIKeyResponse], error) UpdateProfilePicture(context.Context, *connect.Request[v1.UpdateProfilePictureRequest]) (*connect.Response[v1.UpdateProfilePictureResponse], error) - CreatePasskey(context.Context, *connect.Request[v1.CreatePasskeyRequest]) (*connect.Response[v1.CreatePasskeyResponse], error) + BeginPasskeyRegistration(context.Context, *connect.Request[v1.BeginPasskeyRegistrationRequest]) (*connect.Response[v1.BeginPasskeyRegistrationResponse], error) + FinishPasskeyRegistration(context.Context, *connect.Request[v1.FinishPasskeyRegistrationRequest]) (*connect.Response[v1.FinishPasskeyRegistrationResponse], error) } // NewUserServiceHandler builds an HTTP handler from the service implementation. It returns the path @@ -175,10 +192,16 @@ func NewUserServiceHandler(svc UserServiceHandler, opts ...connect.HandlerOption connect.WithSchema(userServiceMethods.ByName("UpdateProfilePicture")), connect.WithHandlerOptions(opts...), ) - userServiceCreatePasskeyHandler := connect.NewUnaryHandler( - UserServiceCreatePasskeyProcedure, - svc.CreatePasskey, - connect.WithSchema(userServiceMethods.ByName("CreatePasskey")), + userServiceBeginPasskeyRegistrationHandler := connect.NewUnaryHandler( + UserServiceBeginPasskeyRegistrationProcedure, + svc.BeginPasskeyRegistration, + connect.WithSchema(userServiceMethods.ByName("BeginPasskeyRegistration")), + connect.WithHandlerOptions(opts...), + ) + userServiceFinishPasskeyRegistrationHandler := connect.NewUnaryHandler( + UserServiceFinishPasskeyRegistrationProcedure, + svc.FinishPasskeyRegistration, + connect.WithSchema(userServiceMethods.ByName("FinishPasskeyRegistration")), connect.WithHandlerOptions(opts...), ) return "/user.v1.UserService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -191,8 +214,10 @@ func NewUserServiceHandler(svc UserServiceHandler, opts ...connect.HandlerOption userServiceGetAPIKeyHandler.ServeHTTP(w, r) case UserServiceUpdateProfilePictureProcedure: userServiceUpdateProfilePictureHandler.ServeHTTP(w, r) - case UserServiceCreatePasskeyProcedure: - userServiceCreatePasskeyHandler.ServeHTTP(w, r) + case UserServiceBeginPasskeyRegistrationProcedure: + userServiceBeginPasskeyRegistrationHandler.ServeHTTP(w, r) + case UserServiceFinishPasskeyRegistrationProcedure: + userServiceFinishPasskeyRegistrationHandler.ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -218,6 +243,10 @@ func (UnimplementedUserServiceHandler) UpdateProfilePicture(context.Context, *co return nil, connect.NewError(connect.CodeUnimplemented, errors.New("user.v1.UserService.UpdateProfilePicture is not implemented")) } -func (UnimplementedUserServiceHandler) CreatePasskey(context.Context, *connect.Request[v1.CreatePasskeyRequest]) (*connect.Response[v1.CreatePasskeyResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("user.v1.UserService.CreatePasskey is not implemented")) +func (UnimplementedUserServiceHandler) BeginPasskeyRegistration(context.Context, *connect.Request[v1.BeginPasskeyRegistrationRequest]) (*connect.Response[v1.BeginPasskeyRegistrationResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("user.v1.UserService.BeginPasskeyRegistration is not implemented")) +} + +func (UnimplementedUserServiceHandler) FinishPasskeyRegistration(context.Context, *connect.Request[v1.FinishPasskeyRegistrationRequest]) (*connect.Response[v1.FinishPasskeyRegistrationResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("user.v1.UserService.FinishPasskeyRegistration is not implemented")) }