diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..caa6457 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,38 @@ +name: Pull Request + +on: + pull_request: + +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - + name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: "1.21.1" + - + name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - + name: Run GoReleaser + uses: goreleaser/goreleaser-action@v5 + with: + distribution: goreleaser + version: latest + args: release --clean --snapshot + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..1aa89fc --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,48 @@ +name: Publish + +on: + push: + branches: + - main + tags: + - v* + +permissions: + contents: write + packages: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - + name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: "1.21.1" + + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - + + name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - + name: Run GoReleaser + uses: goreleaser/goreleaser-action@v5 + with: + distribution: goreleaser + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index cde0123..3341418 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ dist/ +tmp/ +feed.db diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 6627bd8..a1f7cf2 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -22,7 +22,6 @@ builds: - linux - windows - darwin - archives: - format: tar.gz @@ -48,8 +47,8 @@ dockers: - --platform=linux/amd64 - --label=org.opencontainers.image.title={{ .ProjectName }} - --label=org.opencontainers.image.description={{ .ProjectName }} - - --label=org.opencontainers.image.url=https://github.com/caarlos0/{{ .ProjectName }} - - --label=org.opencontainers.image.source=https://github.com/caarlos0/{{ .ProjectName }} + - --label=org.opencontainers.image.url=https://github.com/snorremd/{{ .ProjectName }} + - --label=org.opencontainers.image.source=https://github.com/snorremd/{{ .ProjectName }} - --label=org.opencontainers.image.version={{ .Version }} - --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }} - --label=org.opencontainers.image.revision={{ .FullCommit }} @@ -62,8 +61,8 @@ dockers: - --platform=linux/arm64/v8 - --label=org.opencontainers.image.title={{ .ProjectName }} - --label=org.opencontainers.image.description={{ .ProjectName }} - - --label=org.opencontainers.image.url=https://github.com/caarlos0/{{ .ProjectName }} - - --label=org.opencontainers.image.source=https://github.com/caarlos0/{{ .ProjectName }} + - --label=org.opencontainers.image.url=https://github.com/snorremd/{{ .ProjectName }} + - --label=org.opencontainers.image.source=https://github.com/snorremd/{{ .ProjectName }} - --label=org.opencontainers.image.version={{ .Version }} - --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }} - --label=org.opencontainers.image.revision={{ .FullCommit }} @@ -75,7 +74,7 @@ docker_manifests: image_templates: - ghcr.io/snorremd/{{ .ProjectName }}:{{ .Version }}-amd64 - ghcr.io/snorremd/{{ .ProjectName }}:{{ .Version }}-arm64v8 -- name_template: ghcr.io/caarlos0/{{ .ProjectName }}:latest +- name_template: ghcr.io/snorremd/{{ .ProjectName }}:latest image_templates: - ghcr.io/snorremd/{{ .ProjectName }}:{{ .Version }}-amd64 - ghcr.io/snorremd/{{ .ProjectName }}:{{ .Version }}-arm64v8 diff --git a/Dockerfile b/Dockerfile index 22dfb04..a117660 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,6 @@ FROM scratch + ENTRYPOINT ["/norsky"] CMD ["serve"] +EXPOSE 3000 COPY norsky / \ No newline at end of file diff --git a/LICENSE b/LICENSE index 4ca20b3..e69de29 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 Snorre Magnus Davøen - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index f092264..c3b2062 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,42 @@ docker run -d --name norsky -p 8080:8080 -v /path/to/db:/db ghcr.io/snorreio/nor ``` -## Development +## Usage + +The feed server is a standalone application that you can run on your machine. +It is also available as a Docker image. + +### Commands + +``` +serve Serve the norsky feed +migrate Run database migrations +tidy Tidy up the database +subscribe Log all norwegian posts to the command line +publish Publish feeds on Bluesky +unpublish Unpublish feeds from Bluesky +help, h Shows a list of commands or help for one command +``` -The application has been developed using go 1.21.1. -You should at least have go 1.18+ installed to build the application. +Example: +``` +# Command line +norsky serve --hostname yourdomain.tld --port 8080 --database /path/to/db/feed.db + +# Or docker run +docker run -d \ + --env=NORSKY_HOSTNAME="yourdomain.tld" \ + --env NORSKY_DATABASE="/db/feed.db" \ + --name norsky \ + -p 3000:3000 \ + -v /path/to/db:/db \ + ghrc.io/snorreio/norsky:latest +``` + +## Development + +The application has been developed using go 1.21.1 which is the required version to build the application as the `go.mod` file has been initialized with this version. To get started clone the repository and run go run on the main file to list the available commands. @@ -40,9 +71,6 @@ To get started clone the repository and run go run on the main file to list the go run main.go --help ``` -The application uses the [cobra](https://github.com/spf13/cobra) library to manage commands and flags. -[cobra-cli](https://github.com/spf13/cobra-cli) should be used to generate new commands. - ### Structure The application follows the standard way to structure a Go project and contains the following packages: @@ -50,29 +78,22 @@ The application follows the standard way to structure a Go project and contains - `main` - The main package that contains the entrypoint for the application. - `cmd` - The command package that contains the different commands for the application using cobra. - `server` - The server package that contains setup code for initializing a Fiber server. -- `database` - The database package that contains the database connection, models, migrations, and queries. +- `db` - The database package that contains the database connection, models, migrations, and queries. - `firehose` - The firehose package that contains the firehose client and the firehose subscription. +- `feeds` - The feeds package that contains the feeds and functions that generate the feed responses. +- `models` - The models package that contains the models for the application. +- `dist` - Where goreleaser puts the release artifacts if you build the application using goreleaser locally. + + +## Contributing + +Pull requests are welcome. +For major changes, please open an issue first to discuss what you would like to change. +If you find a bug or have a feature request, please open an issue. +I am also open to suggestions for improvements to the codebase as I'm not a golang expert. + +If you want to provide feedback in private, you can send me an email at [contact@snorre.io](mailto:contact@snorre.io). -``` -. -├── cmd -│ ├── gc.go -│ ├── root.go -│ ├── serve.go -│ └── subscribe.go -├── database -│ └── database.go -├── firehose -│ └── firehose.go -├── server -│ └── server.go -├── go.mod -├── go.sum -├── LICENSE -├── main.go -├── norsky -├── README.md -``` ## Release diff --git a/cmd/gc.go b/cmd/gc.go deleted file mode 100644 index 338fa03..0000000 --- a/cmd/gc.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright © 2023 NAME HERE -*/ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -// gcCmd represents the gc command -var gcCmd = &cobra.Command{ - Use: "gc", - Short: "Garbage collect the SQLite database", - Long: `Delete all posts that are older than 30 days from the SQLite database. - -Can be run as a cron job to keep the database size down.`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("gc called") - }, -} - -func init() { - rootCmd.AddCommand(gcCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // gcCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // gcCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") -} diff --git a/cmd/migrate.go b/cmd/migrate.go new file mode 100644 index 0000000..bc6990b --- /dev/null +++ b/cmd/migrate.go @@ -0,0 +1,34 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "fmt" + "norsky/db" + + "github.com/urfave/cli/v2" +) + +func migrateCmd() *cli.Command { + return &cli.Command{ + Name: "migrate", + Usage: "Run database migrations", + Description: `Runs database migrations on the configured database. Will create the database if it does not exist.`, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "database", + Aliases: []string{"d"}, + Value: "feed.db", + Usage: "SQLite database file location", + EnvVars: []string{"NORSKY_DATABASE"}, + }, + }, + Action: func(ctx *cli.Context) error { + database := ctx.String("database") + fmt.Println("Database configured: ", database) + err := db.Migrate(database) + return err + }, + } +} diff --git a/cmd/publish.go b/cmd/publish.go new file mode 100644 index 0000000..b784413 --- /dev/null +++ b/cmd/publish.go @@ -0,0 +1,135 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "errors" + "fmt" + "norsky/feeds" + "os" + "time" + + "github.com/bluesky-social/indigo/api/bsky" + "github.com/cqroot/prompt" + "github.com/cqroot/prompt/input" + "github.com/labstack/gommon/log" + "github.com/urfave/cli/v2" + + "github.com/strideynet/bsky-furry-feed/bluesky" +) + +// publishCmd represents the publish command +func publishCmd() *cli.Command { + return &cli.Command{ + Name: "publish", + Usage: "Publish feeds on Bluesky", + Description: `Publishes feeds on Bluesky.: + +A Bluesky user account is required to publish feeds on Bluesky. +Registers the feed with your preferred name, description, etc.`, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "hostname", + Aliases: []string{"n"}, + Usage: "The hostname where the server is running", + EnvVars: []string{"NORSKY_HOSTNAME"}, + }, + }, + Action: func(ctx *cli.Context) error { + // This command was made possible thanks to the appreciated work by the Bluesky Furry Feed team + + // Hostname of the Feed Generator + hostname := ctx.String("hostname") + + if hostname == "" { + return errors.New("please specify a hostname in the config") + } + + handle, err := prompt.New().Ask("Handle:").Input("myname.bsky.social") + if err != nil { + return err + } + + password, err := prompt.New().Ask("Password:").Input("", input.WithEchoMode(input.EchoNone)) + if err != nil { + return err + } + + client, err := bluesky.ClientFromCredentials(ctx.Context, bluesky.DefaultPDSHost, &bluesky.Credentials{ + Identifier: handle, + Password: password, + }) + if err != nil { + return fmt.Errorf("could not create client with provided credentials: %w", err) + } + + // Get the feed avatar from file + f, err := os.Open("./server/assets/avatar.png") + if err != nil { + return fmt.Errorf("could not open avatar file: %w", err) + } + + blob, err := client.UploadBlob(ctx.Context, f) + if err != nil { + return fmt.Errorf("could not upload avatar blob: %w", err) + } + + for _, feed := range feeds.Feeds { + log.Infof("Publishing feed %s", feed.DisplayName) + err := client.PutRecord(ctx.Context, "app.bsky.feed.generator", feed.Id, &bsky.FeedGenerator{ + Avatar: blob, + Did: fmt.Sprintf("did:web:%s", hostname), + CreatedAt: bluesky.FormatTime(time.Now().UTC()), + DisplayName: feed.DisplayName, + Description: &feed.Description, + }) + + if err != nil { + return fmt.Errorf("could not publish feed: %w", err) + } + } + + fmt.Println("Publishing feed...", f) + + return nil + + }, + } +} + +func unpublishCmd() *cli.Command { + return &cli.Command{ + Name: "unpublish", + Usage: "Unpublish feeds from Bluesky", + Description: `Unpublishes all feeds associated with account publisher on Bluesky.: + +A Bluesky user account is required to unpublish feeds on Bluesky.`, + + Action: func(ctx *cli.Context) error { + handle, err := prompt.New().Ask("Handle:").Input("myname.bsky.social") + if err != nil { + return err + } + + password, err := prompt.New().Ask("Password:").Input("", input.WithEchoMode(input.EchoNone)) + if err != nil { + return err + } + + client, err := bluesky.ClientFromCredentials(ctx.Context, bluesky.DefaultPDSHost, &bluesky.Credentials{ + Identifier: handle, + Password: password, + }) + + if err != nil { + return err + } + + fmt.Println("Unpublishing feeds...") + + return client.PurgeFeeds(ctx.Context) + + }, + } +} diff --git a/cmd/root.go b/cmd/root.go index e836a9a..3c8bafb 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,44 +4,37 @@ Copyright © 2023 NAME HERE package cmd import ( - "os" - - "github.com/spf13/cobra" + "github.com/urfave/cli/v2" ) -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "norsky", - Short: "A Bluesky feed for Norwegian Bluesky posts", - Long: `A Bluesky feed that only shows posts written in -Norwegian bokmål, nynorsk and sami. - -Norsky works by subscribing to the Bluesky firehose and filtering -down the result to posts written in Norwegian bokmål, nynorsk and sami. -The result is written to an SQLite database and can be accessed via an -HTTP API as specified in the Bluesky API specification.`, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, -} - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - err := rootCmd.Execute() - if err != nil { - os.Exit(1) +func RootApp() *cli.App { + return &cli.App{ + Name: "norsky", + Usage: "A Bluesky feed for Norwegian Bluesky posts", + Description: `A Bluesky feed that only shows posts written in + Norwegian bokmål, nynorsk and sami. + + Norsky works by subscribing to the Bluesky firehose and filtering + down the result to posts written in Norwegian bokmål, nynorsk and sami. + The result is written to an SQLite database and can be accessed via an + HTTP API as specified in the Bluesky API specification. + + Flags can generally be set via environment variables, e.g.: + + --database => NORSKY_DATABASE=feed.db + --port => NORSKY_PORT=8080 + `, + Commands: []*cli.Command{ + serveCmd(), + migrateCmd(), + tidyCmd(), + subscribeCmd(), + publishCmd(), + unpublishCmd(), + }, + Action: func(ctx *cli.Context) error { + // Show help if no command is specified + return ctx.App.Run([]string{"", "help"}) + }, } } - -func init() { - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - - // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.norsky.yaml)") - - // Cobra also supports local flags, which will only run - // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") -} diff --git a/cmd/serve.go b/cmd/serve.go index d0dd8ef..ebeb761 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -4,93 +4,134 @@ Copyright © 2023 NAME HERE package cmd import ( + "errors" "fmt" - "log" + "norsky/db" + "norsky/firehose" + "norsky/server" "os" "os/signal" "sync" "time" - "github.com/spf13/cobra" - - "norsky/firehose" - "norsky/server" + log "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" ) -// serveCmd represents the serve command -var serveCmd = &cobra.Command{ - Use: "serve", - Short: "Serve the norsky feed", - Long: `Starts the norsky feed HTTP server and firehose subscriber. - -Launches the HTTP server on the specified or default port and subscribes to the -Bluesky firehose. All posts written in Norwegian bokmål, nynorsk and sami are -written to the SQLite database and can be accessed via the HTTP API as specified -in the Bluesky API specification.`, - Run: func(cmd *cobra.Command, args []string) { - - fmt.Println("Starting norsky...") - - // Channel for subscribing to bluesky posts - postChan := make(chan interface{}) - - // Setup the server and firehose - app := server.Server() - fh := firehose.New(postChan) - - // Graceful shutdown - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - var wg sync.WaitGroup - - go func() { - <-c - fmt.Println("Gracefully shutting down...") - app.ShutdownWithTimeout(60 * time.Second) - defer wg.Add(-2) // Decrement the waitgroup counter by 2 after shutdown of server and firehose - fh.Shutdown() - }() - - go func() { - fmt.Println("Subscribing to firehose...") - if err := fh.Subscribe(); err != nil { - log.Panic(err) +func serveCmd() *cli.Command { + + return &cli.Command{ + Name: "serve", + Usage: "Serve the norsky feed", + Description: `Starts the norsky feed HTTP server and firehose subscriber. + + Launches the HTTP server on the specified or default port and subscribes to the + Bluesky firehose. All posts written in Norwegian bokmål, nynorsk and sami are + written to the SQLite database and can be accessed via the HTTP API as specified + in the Bluesky API specification.`, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "database", + Aliases: []string{"d"}, + Value: "feed.db", + Usage: "SQLite database file location", + EnvVars: []string{"NORSKY_DATABASE"}, + }, + &cli.StringFlag{ + Name: "hostname", + Aliases: []string{"n"}, + Usage: "The hostname where the server is running", + EnvVars: []string{"NORSKY_HOSTNAME"}, + }, + &cli.StringFlag{ + Name: "host", + Aliases: []string{"o"}, + Usage: "Host of the server", + EnvVars: []string{"NORSKY_HOST"}, + Value: "0.0.0.0", + }, + &cli.IntFlag{ + Name: "port", + Aliases: []string{"p"}, + Usage: "Port of the server", + EnvVars: []string{"NORSKY_PORT"}, + Value: 3000, + }, + }, + Action: func(ctx *cli.Context) error { + + log.Info("Starting Norsky feed generator") + + database := ctx.String("database") + hostname := ctx.String("hostname") + host := ctx.String("host") + port := ctx.Int("port") + + // Check if any of the required flags are missing + if hostname == "" { + return errors.New("missing required flag: --hostname") } - }() - go func() { - fmt.Println("Starting server...") - if err := app.Listen(":3000"); err != nil { - log.Panic(err) - } - }() + err := db.Migrate(database) - go func() { - // Subscribe to the post channel and log the posts - for post := range postChan { - fmt.Println(post) + if err != nil { + return fmt.Errorf("failed to run database migrations: %w", err) } - }() - - // Wait for both the server and firehose to shutdown - wg.Add(2) - wg.Wait() - - fmt.Println("Done!") - - }, -} - -func init() { - rootCmd.AddCommand(serveCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // serveCmd.PersistentFlags().String("foo", "", "A help for foo") - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // serveCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + // Channel for subscribing to bluesky posts + postChan := make(chan interface{}) + + // Setup the server and firehose + app := server.Server(&server.ServerConfig{ + Hostname: hostname, + Reader: db.NewReader(database), + }) + fh := firehose.New(postChan, ctx.Context) + dbwriter := db.NewWriter(database, postChan) + + // Graceful shutdown via wait group + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + var wg sync.WaitGroup + + go func() { + <-c + fmt.Println("Gracefully shutting down...") + app.ShutdownWithTimeout(60 * time.Second) + defer wg.Add(-3) // Decrement the waitgroup counter by 2 after shutdown of server and firehose + fh.Shutdown() + }() + + go func() { + fmt.Println("Subscribing to firehose...") + if err := fh.Subscribe(); err != nil { + // Use signal to shutdown all the goroutines + log.Error(err) + c <- os.Interrupt + } + }() + + go func() { + fmt.Println("Starting server...") + if err := app.Listen(fmt.Sprintf("%s:%d", host, port)); err != nil { + log.Error(err) + c <- os.Interrupt + } + }() + + go func() { + fmt.Println("Starting database writer...") + dbwriter.Subscribe() + }() + + // Wait for both the server and firehose to shutdown + wg.Add(3) + wg.Wait() + + log.Info("Norsky feed generator stopped") + + return nil + + }, + } } diff --git a/cmd/subscribe.go b/cmd/subscribe.go index 4ebe27d..39f211f 100644 --- a/cmd/subscribe.go +++ b/cmd/subscribe.go @@ -4,38 +4,94 @@ Copyright © 2023 NAME HERE package cmd import ( + "context" + "encoding/json" "fmt" + "norsky/firehose" + "norsky/models" + "os" + "os/signal" + "sync" - "github.com/spf13/cobra" + log "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" ) // logCmd represents the log command -var logCmd = &cobra.Command{ - Use: "subscribe", - Short: "Log all norwegian posts to the command line", - Long: `Subscribe to the Bluesky firehose and log all posts written in +func subscribeCmd() *cli.Command { + return &cli.Command{ + Name: "subscribe", + Usage: "Log all norwegian posts to the command line", + Description: `Subscribe to the Bluesky firehose and log all posts written in Norwegian bokmål, nynorsk and sami to the command line. Can be used if you want to collect all posts written in Norwegian by passing the output to a file or another application. Returns each post as a JSON object on a single line. Use a tool like jq to process -the output.`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("log called") - }, -} +the output. + +Prints all other log messages to stderr.`, + Action: func(ctx *cli.Context) error { + // Get the context for this process to pass to firehose + context := context.Background() + + // Disable logging to stdout + log.SetOutput(os.Stderr) + + // Channel for subscribing to bluesky posts + postChan := make(chan interface{}) + + // Setup the server and firehose + fh := firehose.New(postChan, context) -func init() { - rootCmd.AddCommand(logCmd) + // Graceful shutdown + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + var wg sync.WaitGroup - // Here you will define your flags and configuration settings. + go func() { + <-c + defer wg.Add(-1) // Decrement the waitgroup counter by 2 after shutdown of server and firehose + fh.Shutdown() + }() + + go func() { + fmt.Println("Subscribing to firehose...") + if err := fh.Subscribe(); err != nil { + log.Panic(err) + } + }() + + go func() { + // Subscribe to the post channel and log the posts + for message := range postChan { + switch message := message.(type) { + case models.CreatePostEvent: + printStdout(&message.Post) + case models.UpdatePostEvent: + printStdout(&message.Post) + case models.DeletePostEvent: + printStdout(&message.Post) + } + } + }() + + // Wait for both the server and firehose to shutdown + wg.Add(1) + wg.Wait() + + return nil + }, + } +} - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // logCmd.PersistentFlags().String("foo", "", "A help for foo") +func printStdout(post *models.Post) { + // Print as single JSON string on a single line - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // logCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + // Convert Post to JSON string + postJson, err := json.Marshal(post) + if err == nil { + fmt.Println(string(postJson)) + } } diff --git a/cmd/tidy.go b/cmd/tidy.go new file mode 100644 index 0000000..12b7c72 --- /dev/null +++ b/cmd/tidy.go @@ -0,0 +1,37 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "fmt" + "norsky/db" + + "github.com/urfave/cli/v2" +) + +func tidyCmd() *cli.Command { + return &cli.Command{ + Name: "tidy", + Usage: "Tidy up the database", + Description: `Tidy up the database by removing posts that are old. + + Remove posts that are older than 90 days from the database. + This is to keep the database size down and to keep the feed fresh.`, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "database", + Aliases: []string{"d"}, + Value: "feed.db", + Usage: "SQLite database file location", + EnvVars: []string{"NORSKY_DATABASE"}, + }, + }, + Action: func(ctx *cli.Context) error { + database := ctx.String("database") + fmt.Println("Database configured: ", database) + err := db.Tidy(database) + return err + }, + } +} diff --git a/database/migrations/20230923175802_create_posts_table.down.sql b/database/migrations/20230923175802_create_posts_table.down.sql deleted file mode 100644 index e69de29..0000000 diff --git a/database/migrations/20230923175802_create_posts_table.up.sql b/database/migrations/20230923175802_create_posts_table.up.sql deleted file mode 100644 index f89dc50..0000000 --- a/database/migrations/20230923175802_create_posts_table.up.sql +++ /dev/null @@ -1,10 +0,0 @@ -/* Create a Posts table for the Bluesky feed post data: - * - cid: string - * - created_at: timestamp - */ - -CREATE TABLE posts ( - uri TEXT PRIMARY KEY, - created_at TIMESTAMP NOT NULL -); - diff --git a/database/models.go b/database/models.go deleted file mode 100644 index c1dc06e..0000000 --- a/database/models.go +++ /dev/null @@ -1,23 +0,0 @@ -package database - -// define struct Post - -type Post struct { - Uri string - CreatedAt string -} - -// CreateEvent fired when a new post is created -type CreatePostEvent struct { - Post Post -} - -// UpdateEvent fired when a post is updated -type UpdatePostEvent struct { - Post Post -} - -// DeleteEvent fired when a post is deleted -type DeletePostEvent struct { - Post Post -} diff --git a/db/connection.go b/db/connection.go new file mode 100644 index 0000000..09f6623 --- /dev/null +++ b/db/connection.go @@ -0,0 +1,10 @@ +package db + +import ( + "database/sql" + "fmt" +) + +func connection(database string) (*sql.DB, error) { + return sql.Open("sqlite", fmt.Sprintf("%s?_pragma=foreign_keys(1)", database)) +} diff --git a/database/migrations.go b/db/migrations.go similarity index 57% rename from database/migrations.go rename to db/migrations.go index 7068445..a53a1d8 100644 --- a/database/migrations.go +++ b/db/migrations.go @@ -1,9 +1,11 @@ -package database +package db import ( "embed" + "fmt" "github.com/golang-migrate/migrate/v4" + _ "github.com/golang-migrate/migrate/v4/database/sqlite" "github.com/golang-migrate/migrate/v4/source/iofs" ) @@ -11,24 +13,25 @@ import ( var fs embed.FS // Migrate runs the SQLite database migrations using golang-migrate -func Migrate() error { +func Migrate(dbPath string) error { + fmt.Println("Running migrations...") // Create a new source instance using the embedded migrations - d, err := iofs.New(fs, "testdata/migrations") + d, err := iofs.New(fs, "migrations") if err != nil { return err } // Create a new migrate instance using the iofs source instance and our SQLite database - m, err := migrate.NewWithSourceInstance("iofs", d, "sqlite://feed.db") + m, err := migrate.NewWithSourceInstance("iofs", d, "sqlite://"+dbPath) if err != nil { + fmt.Println("Error creating migrate instance", err) return err } // Run the migrations - if err := m.Up(); err != nil { + if err := m.Up(); err != nil && err != migrate.ErrNoChange { return err } return nil - } diff --git a/db/migrations/20230923175802_create_posts_table.down.sql b/db/migrations/20230923175802_create_posts_table.down.sql new file mode 100644 index 0000000..c1e08b9 --- /dev/null +++ b/db/migrations/20230923175802_create_posts_table.down.sql @@ -0,0 +1,4 @@ +-- Drop posts table if it exists: + +DROP TABLE IF EXISTS post_languages; +DROP TABLE IF EXISTS posts; \ No newline at end of file diff --git a/db/migrations/20230923175802_create_posts_table.up.sql b/db/migrations/20230923175802_create_posts_table.up.sql new file mode 100644 index 0000000..42397e6 --- /dev/null +++ b/db/migrations/20230923175802_create_posts_table.up.sql @@ -0,0 +1,25 @@ +/* Create a Posts table for the Bluesky feed post data: + * - cid: string + * - created_at: timestamp + */ + +CREATE TABLE posts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, -- We need an id to implement deterministic cursor based pagination + uri TEXT, -- The URI of the post + created_at INTEGER NOT NULL -- The time the post was created as a Unix timestamp +); + +-- Table to hold the languages that a post is written in + +-- Path: db/migrations/20230923175803_create_post_languages_table.up.sql +/* Create a PostLanguages table for the Bluesky feed post data: + * - cid: string + * - language: string + */ + +CREATE TABLE post_languages ( + post_id INTEGER NOT NULL, + language TEXT NOT NULL, + PRIMARY KEY (post_id, language), + FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/db/reader.go b/db/reader.go new file mode 100644 index 0000000..79cfcd5 --- /dev/null +++ b/db/reader.go @@ -0,0 +1,69 @@ +package db + +import ( + "database/sql" + "norsky/models" + "strings" + + sqlbuilder "github.com/huandu/go-sqlbuilder" +) + +type Reader struct { + db *sql.DB +} + +func NewReader(database string) *Reader { + // Open in read-only mode + db, err := sql.Open("sqlite", database+"?mode=ro") + if err != nil { + panic("failed to connect database") + } + return &Reader{ + db: db, + } +} + +func (reader *Reader) GetFeed(lang string, limit int, postId int64) ([]models.Post, error) { + + // Return next limit posts after cursor, ordered by created_at and uri + + sb := sqlbuilder.NewSelectBuilder() + sb.Select("id", "uri", "created_at", "group_concat(language)").From("posts") + if postId != 0 { + sb.Where( + sb.LessThan("id", postId), + ) + } + if lang != "" { + sb.Where(sb.Equal("language", lang)) + } + sb.Join("post_languages", "posts.id = post_languages.post_id") + sb.GroupBy("posts.id") + sb.Limit(limit).OrderBy("id").Desc() + + sql, args := sb.BuildWithFlavor(sqlbuilder.Flavor(sqlbuilder.SQLite)) + + rows, err := reader.db.Query(sql, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + // Scan rows, collapse languages into single post + var posts []models.Post + + for rows.Next() { + var post models.Post + var langs string + // Scan row and + if err := rows.Scan(&post.Id, &post.Uri, &post.CreatedAt, &langs); err != nil { + return nil, err + } + + // Split languages into a slice and add to the post model + post.Languages = strings.Split(langs, ",") + posts = append(posts, post) + } + + return posts, nil +} diff --git a/db/tidy.go b/db/tidy.go new file mode 100644 index 0000000..0102b40 --- /dev/null +++ b/db/tidy.go @@ -0,0 +1,37 @@ +package db + +import ( + "database/sql" + "time" + + sb "github.com/huandu/go-sqlbuilder" + log "github.com/sirupsen/logrus" +) + +// Tidy removes posts that are older than 90 days from the database +func Tidy(database string) error { + db, err := connection(database) + if err != nil { + return err + } + + return tidy(db) +} + +func tidy(db *sql.DB) error { + ninetyDaysAgo := time.Now().Add(90 * 24 * time.Hour).Unix() + deletePosts := sb.NewDeleteBuilder() + sql, args := deletePosts.DeleteFrom("posts").Where(deletePosts.LessEqualThan("created_at", ninetyDaysAgo)).Build() + + log.WithFields(log.Fields{ + "sql": sql, + "args": args, + }).Info("Tidying database") + + // _, err := db.Exec(sql, args...) + // if err != nil { + // return err + // } + + return nil +} diff --git a/db/writer.go b/db/writer.go new file mode 100644 index 0000000..76aa05b --- /dev/null +++ b/db/writer.go @@ -0,0 +1,100 @@ +package db + +import ( + "norsky/models" + "time" + + "database/sql" + + sqlbuilder "github.com/huandu/go-sqlbuilder" + log "github.com/sirupsen/logrus" +) + +type Writer struct { + db *sql.DB + postChan chan interface{} + tidyChan *time.Ticker +} + +func NewWriter(database string, postChan chan interface{}) *Writer { + db, err := connection(database) + if err != nil { + panic("failed to connect database") + } + return &Writer{ + db: db, + postChan: postChan, + // Create new tidy channel that is pinged every 5 minutes + tidyChan: time.NewTicker(5 * time.Minute), + } +} + +func (writer *Writer) Subscribe() { + for { + select { + case <-writer.tidyChan.C: + log.Info("Tidying database") + if err := tidy(writer.db); err != nil { + log.Error("Error tidying database", err) + } + + case post := <-writer.postChan: + log.WithField("post", post).Info("Received post") + switch post := post.(type) { + case models.CreatePostEvent: + createPost(writer.db, post.Post) + case models.DeletePostEvent: + deletePost(writer.db, post.Post) + default: + log.Info("Unknown post type") + } + } + + } +} + +func createPost(db *sql.DB, post models.Post) error { + // Post insert query + insertPost := sqlbuilder.NewInsertBuilder() + sql, args := insertPost.InsertInto("posts").Cols("uri", "created_at").Values(post.Uri, post.CreatedAt).Build() + + // Spread args + res, err := db.Exec(sql, args...) + if err != nil { + log.Error("Error inserting post", err) + return err + } + + // Get inserted id + id, err := res.LastInsertId() + if err != nil { + log.Error("Error getting inserted id", err) + return err + } + + // Post languages insert query + insertLangs := sqlbuilder.NewInsertBuilder() + insertLangs.InsertInto("post_languages").Cols("post_id", "language") + for _, lang := range post.Languages { + insertLangs.Values(id, lang) + } + sql, args = insertLangs.Build() + + _, err = db.Exec(sql, args...) + + if err != nil { + log.Error("Error inserting languages", err) + return err + } + + return nil +} + +func deletePost(db *sql.DB, post models.Post) error { + log.Info("Deleting post") + _, err := db.Exec("DELETE FROM posts WHERE uri = ?", post.Uri) + if err != nil { + return err + } + return nil +} diff --git a/feeds/feeds.go b/feeds/feeds.go new file mode 100644 index 0000000..582d6d7 --- /dev/null +++ b/feeds/feeds.go @@ -0,0 +1,101 @@ +package feeds + +import ( + "norsky/db" + "norsky/models" + "strconv" + + log "github.com/sirupsen/logrus" +) + +type Algorithm func(reader *db.Reader, cursor string, limit int) (*models.FeedResponse, error) + +func bokmaal(reader *db.Reader, cursor string, limit int) (*models.FeedResponse, error) { + return genericAlgo(reader, cursor, limit, "nb") +} + +func nynorsk(reader *db.Reader, cursor string, limit int) (*models.FeedResponse, error) { + return genericAlgo(reader, cursor, limit, "nn") +} + +func samisk(reader *db.Reader, cursor string, limit int) (*models.FeedResponse, error) { + return genericAlgo(reader, cursor, limit, "smi") +} + +func all(reader *db.Reader, cursor string, limit int) (*models.FeedResponse, error) { + return genericAlgo(reader, cursor, limit, "") +} + +// Reuse genericAlgo for all algorithms +func genericAlgo(reader *db.Reader, cursor string, limit int, lang string) (*models.FeedResponse, error) { + postId := safeParseCursor(cursor) + + posts, err := reader.GetFeed(lang, limit+1, postId) + if err != nil { + log.Error("Error getting feed", err) + return nil, err + } + + if posts == nil { + posts = []models.Post{} + } + + var nextCursor *string + nextCursor = nil + + if len(posts) > limit { + posts = posts[:len(posts)-1] + + parsed := strconv.FormatInt(posts[len(posts)-1].Id, 10) + nextCursor = &parsed + } + return &models.FeedResponse{ + Feed: posts, + Cursor: nextCursor, + }, nil +} + +// safeParseCursor parses the cursor string and returns the unix time and post id +// If the cursor is invalid, it returns 0, 0, nil +func safeParseCursor(cursor string) int64 { + id, err := strconv.ParseInt(cursor, 10, 64) + if err != nil { + return 0 + } + return id +} + +type Feed struct { + Id string + DisplayName string + Description string + Algorithm Algorithm +} + +// List of available feeds +var Feeds = map[string]Feed{ + "bokmaal": { + Id: "bokmaal", + DisplayName: "Bokmål", + Description: "A feed of Bluesky posts written in Norwegian bokmål", + Algorithm: bokmaal, + }, + "nynorsk": { + Id: "nynorsk", + DisplayName: "Nynorsk", + Description: "A feed of Bluesky posts written in Norwegian nynorsk", + Algorithm: nynorsk, + }, + "sami": { + Id: "sami", + DisplayName: "Sami", + Description: "A feed of Bluesky posts written in Sami", + Algorithm: samisk, + }, + "all": { + Id: "all", + DisplayName: "Norwegian languages", + Description: "A feed of Bluesky posts written in Norwegian bokmål, nynorsk and sami", + Algorithm: all, + }, +} diff --git a/firehose/firehose.go b/firehose/firehose.go index 3f7c01b..912a63c 100644 --- a/firehose/firehose.go +++ b/firehose/firehose.go @@ -5,8 +5,9 @@ import ( "context" "encoding/json" "fmt" - "norsky/database" + "norsky/models" "strings" + "time" "github.com/bluesky-social/indigo/api/atproto" appbsky "github.com/bluesky-social/indigo/api/bsky" @@ -28,14 +29,17 @@ type Firehose struct { scheduler *sequential.Scheduler // The scheduler to use for the firehose // A channel to write feed posts to postChan chan interface{} + // The context for this process + context context.Context } -func New(postChan chan interface{}) *Firehose { +func New(postChan chan interface{}, context context.Context) *Firehose { dialer := websocket.DefaultDialer firehose := &Firehose{ address: "wss://bsky.social/xrpc/com.atproto.sync.subscribeRepos", dialer: dialer, postChan: postChan, + context: context, } return firehose @@ -50,7 +54,7 @@ func (firehose *Firehose) Subscribe() error { } firehose.conn = conn - firehose.scheduler = sequential.NewScheduler(conn.RemoteAddr().String(), eventProcessor(firehose.postChan).EventHandler) + firehose.scheduler = sequential.NewScheduler(conn.RemoteAddr().String(), eventProcessor(firehose.postChan, firehose.context).EventHandler) events.HandleRepoStream(context.Background(), conn, firehose.scheduler) return nil @@ -62,28 +66,26 @@ func (firehose *Firehose) Shutdown() { fmt.Println("Firehose shutdown") } -func eventProcessor(postChan chan interface{}) *events.RepoStreamCallbacks { +func eventProcessor(postChan chan interface{}, context context.Context) *events.RepoStreamCallbacks { streamCallbacks := &events.RepoStreamCallbacks{ RepoCommit: func(evt *atproto.SyncSubscribeRepos_Commit) error { - rr, err := repo.ReadRepoFromCar(context.TODO(), bytes.NewReader(evt.Blocks)) + rr, err := repo.ReadRepoFromCar(context, bytes.NewReader(evt.Blocks)) if err != nil { - fmt.Printf("Error reading repo from car: %s\n", err) return err } // Get operations by type for _, op := range evt.Ops { if strings.Split(op.Path, "/")[0] != "app.bsky.feed.post" { - continue + continue // Skip if not a post, e.g. like, follow, etc. } uri := fmt.Sprintf("at://%s/%s", evt.Repo, op.Path) - event_type := repomgr.EventKind(op.Action) + switch event_type { case repomgr.EvtKindCreateRecord, repomgr.EvtKindUpdateRecord: - _, rec, err := rr.GetRecord(context.TODO(), op.Path) + _, rec, err := rr.GetRecord(context, op.Path) if err != nil { - fmt.Printf("Error getting record %s: %s\n", op.Path, err) continue } @@ -91,25 +93,30 @@ func eventProcessor(postChan chan interface{}) *events.RepoStreamCallbacks { Val: rec, } - var post = appbsky.FeedPost{} - - marshaller, err := decoder.MarshalJSON() + jsonRecord, err := decoder.MarshalJSON() // The LexiconTypeDecoder will decode the record into a JSON representation if err != nil { - fmt.Println(err) + continue } - err = json.Unmarshal(marshaller, &post) + var post = appbsky.FeedPost{} // Unmarshal JSON formatted record into a FeedPost + err = json.Unmarshal(jsonRecord, &post) if err != nil { - fmt.Println(err) + continue } - if lo.Contains(post.Langs, "nb") { - postChan <- database.CreatePostEvent{ - Post: database.Post{ - Uri: uri, - CreatedAt: post.CreatedAt, - }, + // Contains any of the languages in the post that are one of the following: nb, nn, smi + if lo.Some(post.Langs, []string{"no", "nb", "nn", "smi"}) { + createdAt, err := time.Parse(time.RFC3339, post.CreatedAt) + if err == nil { + postChan <- models.CreatePostEvent{ + Post: models.Post{ + Uri: uri, + CreatedAt: createdAt.Unix(), + Text: post.Text, + Languages: post.Langs, + }, + } } } diff --git a/go.mod b/go.mod index 45dfdf3..cbc7ccf 100644 --- a/go.mod +++ b/go.mod @@ -3,23 +3,36 @@ module norsky go 1.21.1 require ( - github.com/bluesky-social/indigo v0.0.0-20230921034325-a6fcf91cff69 + github.com/bluesky-social/indigo v0.0.0-20230919180850-251fff6498dc + github.com/cqroot/prompt v0.9.1 github.com/gofiber/fiber/v2 v2.49.2 + github.com/golang-migrate/migrate/v4 v4.16.2 github.com/gorilla/websocket v1.5.0 + github.com/huandu/go-sqlbuilder v1.22.0 + github.com/labstack/gommon v0.4.0 github.com/samber/lo v1.38.1 - github.com/spf13/cobra v1.7.0 - golang.org/x/crypto/x509roots/fallback v0.0.0-20230920181032-a1aeb9b34eb6 + github.com/sirupsen/logrus v1.9.3 + github.com/strideynet/bsky-furry-feed v0.0.37 + github.com/urfave/cli/v2 v2.25.1 + golang.org/x/crypto/x509roots/fallback v0.0.0-20230928175846-ec07f4e35b9e ) require ( github.com/andybalholm/brotli v1.0.5 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect + github.com/charmbracelet/bubbles v0.16.1 // indirect + github.com/charmbracelet/bubbletea v0.24.2 // indirect + github.com/charmbracelet/lipgloss v0.7.1 // indirect + github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/cqroot/multichoose v0.1.0 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-migrate/migrate/v4 v4.16.2 // indirect + github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.3.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -27,7 +40,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/huandu/xstrings v1.3.2 // indirect github.com/ipfs/bbloom v0.0.4 // indirect github.com/ipfs/go-block-format v0.1.2 // indirect github.com/ipfs/go-blockservice v0.5.0 // indirect @@ -53,18 +66,21 @@ require ( github.com/jbenet/goprocess v0.1.4 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/json-iterator/go v1.1.12 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/compress v1.16.7 // indirect - github.com/klauspost/cpuid/v2 v2.2.3 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/minio/sha256-simd v1.0.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mr-tron/base58 v1.2.0 // indirect + github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.1 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-multibase v0.2.0 // indirect @@ -72,45 +88,48 @@ require ( github.com/multiformats/go-multihash v0.2.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect - github.com/philhofer/fwd v1.1.2 // indirect github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.40.0 // indirect + github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/tinylib/msgp v1.1.8 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.49.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/tcplisten v1.0.0 // indirect github.com/whyrusleeping/cbor-gen v0.0.0-20230818171029-f91ae536ca25 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect go.opentelemetry.io/otel v1.16.0 // indirect go.opentelemetry.io/otel/metric v1.16.0 // indirect go.opentelemetry.io/otel/trace v1.16.0 // indirect - go.uber.org/atomic v1.10.0 // indirect + go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/crypto v0.12.0 // indirect - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect - golang.org/x/mod v0.10.0 // indirect + golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.12.0 // indirect + golang.org/x/term v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.9.1 // indirect + golang.org/x/tools v0.11.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect gorm.io/gorm v1.25.1 // indirect lukechampine.com/blake3 v1.1.7 // indirect lukechampine.com/uint128 v1.2.0 // indirect - modernc.org/cc/v3 v3.40.0 // indirect - modernc.org/ccgo/v3 v3.16.13 // indirect - modernc.org/libc v1.24.1 // indirect + modernc.org/cc/v3 v3.36.3 // indirect + modernc.org/ccgo/v3 v3.16.9 // indirect + modernc.org/libc v1.17.1 // indirect modernc.org/mathutil v1.5.0 // indirect - modernc.org/memory v1.6.0 // indirect + modernc.org/memory v1.2.1 // indirect modernc.org/opt v0.1.3 // indirect - modernc.org/sqlite v1.25.0 // indirect + modernc.org/sqlite v1.18.1 // indirect modernc.org/strutil v1.1.3 // indirect - modernc.org/token v1.0.1 // indirect + modernc.org/token v1.0.0 // indirect ) diff --git a/go.sum b/go.sum index 4371f27..7bd8f91 100644 --- a/go.sum +++ b/go.sum @@ -3,17 +3,34 @@ github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 h1:iW0a5 github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5/go.mod h1:Y2QMoi1vgtOIfc+6DhrMOGkLoGzqSV2rKp4Sm+opsyA= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +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/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bluesky-social/indigo v0.0.0-20230921034325-a6fcf91cff69 h1:Q3ahjpatcGPI7GZSzfBM2iRwMX+ZWOyNXmpeW9Hp2iI= -github.com/bluesky-social/indigo v0.0.0-20230921034325-a6fcf91cff69/go.mod h1:xeZ7rqlwFUpv5iuzYwOXDo4PgNzYPl6J/DytBvQgVuE= +github.com/bluesky-social/indigo v0.0.0-20230919180850-251fff6498dc h1:zgMSIxrsR7ZASv3uge+N2wY6EV8gojrP9G7hAorz6d0= +github.com/bluesky-social/indigo v0.0.0-20230919180850-251fff6498dc/go.mod h1:xeZ7rqlwFUpv5iuzYwOXDo4PgNzYPl6J/DytBvQgVuE= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= +github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= +github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY= +github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg= +github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= +github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cqroot/multichoose v0.1.0 h1:525pYO4VrdVH17JJdNAV2bacygF0jJ+BfM0HM6XqjDg= +github.com/cqroot/multichoose v0.1.0/go.mod h1:BJzIGqbQZNADPDuA3IzhmTMpRc2F3fZKysMRYP+Ydw8= +github.com/cqroot/prompt v0.9.1 h1:to4w0LSX6psO6ymitbyA8a+8I5xYksyVjUHRbdhyiLY= +github.com/cqroot/prompt v0.9.1/go.mod h1:RigW6I6C5W8YX0bRkNf8mtVxnt5Fmb/dqYe0Z3Fb/jc= github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -21,6 +38,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= @@ -40,6 +58,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -48,15 +68,16 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -78,10 +99,14 @@ github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUD github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= +github.com/huandu/go-sqlbuilder v1.22.0 h1:69SpvXvhAoeb7y5uERUCB0/Ck09DwQ6ccYovejm1zHA= +github.com/huandu/go-sqlbuilder v1.22.0/go.mod h1:nUVmMitjOmn/zacMLXT0d3Yd3RHoO2K+vy906JzqxMI= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= @@ -177,8 +202,6 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= @@ -189,8 +212,8 @@ github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGC github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= -github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8= github.com/koron/go-ssdp v0.0.3/go.mod h1:b2MxI6yh02pKrsyNoQUsk4+YNikaGhe4894J+Q5lDvA= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -218,6 +241,8 @@ github.com/lestrrat-go/jwx/v2 v2.0.12 h1:3d589+5w/b9b7S3DneICPW16AqTyYXB7VRjgluS github.com/lestrrat-go/jwx/v2 v2.0.12/go.mod h1:Mq4KN1mM7bp+5z/W5HS8aCNs5RKZ911G/0y2qUjAQuQ= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= @@ -237,17 +262,23 @@ github.com/libp2p/go-nat v0.1.0 h1:MfVsH6DLcpa04Xr+p8hmVRG4juse0s3J8HyNWYHffXg= github.com/libp2p/go-nat v0.1.0/go.mod h1:X7teVkwRHNInVNWQiO/tAiAVRwSr5zoRz4YSTC3uRBM= github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= +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-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-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.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= -github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= @@ -257,16 +288,19 @@ github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+ github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +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/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= +github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= @@ -302,8 +336,6 @@ github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 h1:1/WtZae0yGtPq+TI6+Tv1WTxkukpXeMlviSxvL7SRgk= github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9/go.mod h1:x3N5drFsm2uilKKuuYo6LdyD8vZAW55sH/9w+pbo1sw= -github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= -github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -316,25 +348,28 @@ github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.40.0 h1:Afz7EVRqGg2Mqqf4JuF9vdvp1pi220m55Pi9T2JnO4Q= -github.com/prometheus/common v0.40.0/go.mod h1:L65ZJPSmfn/UBWLQIHV7dBrKFidB/wPlF1y5TlSt9OE= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= @@ -343,10 +378,6 @@ github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hg github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -354,13 +385,16 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= -github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= +github.com/strideynet/bsky-furry-feed v0.0.37 h1:JksjxBTN8DIvJ8TZ7fbCB9sJ2DikHUMLy4I/jogZBlw= +github.com/strideynet/bsky-furry-feed v0.0.37/go.mod h1:gbIgQmwrl0o3iX5WZmE+tvVvvu34sxNUrPUueuueCxA= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.25.1 h1:zw8dSP7ghX0Gmm8vugrs6q9Ku0wzweqPyshy+syu9Gw= +github.com/urfave/cli/v2 v2.25.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.49.0 h1:9FdvCpmxB74LH4dPb7IJ1cOSsluR07XG3I1txXWwJpE= github.com/valyala/fasthttp v1.49.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= @@ -378,10 +412,11 @@ github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8= github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6 h1:yJ9/LwIGIk/c0CdoavpC9RNSGSruIspSZtxG3Nnldic= github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6/go.mod h1:39U9RRVr4CKbXpXYopWn+FSH5s+vWu6+RmguSPWAq5s= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= gitlab.com/yawning/secp256k1-voi v0.0.0-20230815035612-a7264edccf80 h1:+Hti+G65Kc88hK0GFQ6NzzncsOmoqxmlXaxM1+FPPqM= gitlab.com/yawning/secp256k1-voi v0.0.0-20230815035612-a7264edccf80/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= @@ -394,8 +429,8 @@ go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZE go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= @@ -415,31 +450,25 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto/x509roots/fallback v0.0.0-20230920181032-a1aeb9b34eb6 h1:8HfE56/VB0pU3pfY1wKU4uu5t7NPvegA3hqPppU9BHA= -golang.org/x/crypto/x509roots/fallback v0.0.0-20230920181032-a1aeb9b34eb6/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/crypto/x509roots/fallback v0.0.0-20230928175846-ec07f4e35b9e h1:Ny9TROlcGymSrZC9S27oT6zbbd+3XYqnAeh7SNJjSd0= +golang.org/x/crypto/x509roots/fallback v0.0.0-20230928175846-ec07f4e35b9e/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= +golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= +golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -447,37 +476,35 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= @@ -490,14 +517,11 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= -golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= -golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= +golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -506,8 +530,8 @@ golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3j golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -529,23 +553,39 @@ gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= -modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= -modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= -modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= -modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM= -modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak= +modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.3 h1:uISP3F66UlixxWEcKuIWERa4TwrZENHSL8tWxZz8bHg= +modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/ccgo/v3 v3.16.9 h1:AXquSwg7GuMk11pIdw7fmO1Y/ybgazVkMhsZWCV0mHM= +modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= +modernc.org/libc v1.17.1 h1:Q8/Cpi36V/QBfuQaFVeisEBs3WqoGAJprZzmf7TfEYI= +modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o= -modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.1 h1:dkRh86wgmq/bJu2cAS2oqBCz/KsMZU7TUM4CibQ7eBs= +modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA= -modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU= +modernc.org/sqlite v1.18.1 h1:ko32eKt3jf7eqIkCgPAeHMBXw3riNSLhl2f3loEF7o8= +modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= -modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/tcl v1.13.1 h1:npxzTwFTZYM8ghWicVIX1cRWzj7Nd8i6AqqX2p+IYao= +modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= +modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.5.1 h1:RTNHdsrOpeoSeOF4FbzTo8gBYByaJ5xT7NgZ9ZqRiJM= +modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= diff --git a/main.go b/main.go index e9ed666..5d07070 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,17 @@ package main import ( - _ "embed" + "fmt" "norsky/cmd" + "os" - _ "golang.org/x/crypto/x509roots/fallback" // We need this to make TLS work in scratch containers + _ "golang.org/x/crypto/x509roots/fallback" ) func main() { - cmd.Execute() + app := cmd.RootApp() + if err := app.Run(os.Args); err != nil { + fmt.Println(err) + os.Exit(1) + } } diff --git a/models/models.go b/models/models.go new file mode 100644 index 0000000..76ab7e9 --- /dev/null +++ b/models/models.go @@ -0,0 +1,30 @@ +package models + +// Post model with key fields from the post +type Post struct { + Id int64 `json:"id"` + Uri string `json:"post"` + CreatedAt int64 `json:"createdAt"` // Unix timestamp + Text string `json:"text"` // We don't want to store the text in the database + Languages []string `json:"languages"` +} + +// CreateEvent fired when a new post is created +type CreatePostEvent struct { + Post Post +} + +// UpdateEvent fired when a post is updated +type UpdatePostEvent struct { + Post Post +} + +// DeleteEvent fired when a post is deleted +type DeletePostEvent struct { + Post Post +} + +type FeedResponse struct { + Feed []Post `json:"feed"` + Cursor *string `json:"cursor"` +} diff --git a/server/assets/avatar.png b/server/assets/avatar.png new file mode 100644 index 0000000..13eaf90 Binary files /dev/null and b/server/assets/avatar.png differ diff --git a/server/assets/favicon.ico b/server/assets/favicon.ico new file mode 100644 index 0000000..f40a8a7 Binary files /dev/null and b/server/assets/favicon.ico differ diff --git a/server/assets/favicon.png b/server/assets/favicon.png new file mode 100644 index 0000000..f7f3569 Binary files /dev/null and b/server/assets/favicon.png differ diff --git a/server/server.go b/server/server.go index 13c7184..2aa881b 100644 --- a/server/server.go +++ b/server/server.go @@ -1,19 +1,138 @@ package server import ( + _ "embed" + "fmt" + "norsky/db" + "norsky/feeds" + "strconv" + "time" + + "github.com/bluesky-social/indigo/api/bsky" + "github.com/bluesky-social/indigo/atproto/syntax" "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cache" + "github.com/gofiber/fiber/v2/middleware/favicon" + log "github.com/sirupsen/logrus" ) +//go:embed assets/favicon.ico +var faviconFile []byte + +type ServerConfig struct { + + // The hostname to use for the server + Hostname string + + // The reader to use for reading posts + Reader *db.Reader +} + // Returns a fiber.App instance to be used as an HTTP server for the norsky feed -func Server() *fiber.App { +func Server(config *ServerConfig) *fiber.App { app := fiber.New() - app.Use(cache.New()) + // app.Use(cache.New()) + + // Middleware to track the latency of each request + app.Use(func(c *fiber.Ctx) error { + // start timer + start := time.Now() + + // next routes + err := c.Next() + + // stop timer + stop := time.Now() + + // Diff + log.WithFields(log.Fields{ + "method": c.Method(), + "route": c.Route().Path, + "latency": stop.Sub(start), + }).Info("Request") + return err + }) + + app.Use(favicon.New(favicon.Config{ + Data: faviconFile, + })) app.Get("/", func(c *fiber.Ctx) error { - return c.SendString("Hello, World!") + return c.SendString("This is the Norsky feed generator for listing Norwegian posts on Bluesky.") + }) + + // Well known + app.Get("/.well-known/did.json", func(c *fiber.Ctx) error { + // Return the DID document, using regular map[string]interface{} for now + + return c.JSON(map[string]interface{}{ + "@context": []string{"https://www.w3.org/ns/did/v1"}, + "id": "did:web:" + config.Hostname, + "service": []map[string]interface{}{ + { + "id": "#norsky", + "type": "BskyFeedGenerator", + "serviceEndpoint": "https://" + config.Hostname, + }, + }, + }) + }) + + // Endpoint to describe the feed + app.Get("/xrpc/app.bsky.feed.describeFeedGenerator", func(c *fiber.Ctx) error { + + // Map over algorithms.Algorithms keys and return a bsky.FeedDescribeFeedGenerator_Output + generatorFeeds := []*bsky.FeedDescribeFeedGenerator_Feed{} + for algorithm := range feeds.Feeds { + generatorFeeds = append(generatorFeeds, &bsky.FeedDescribeFeedGenerator_Feed{ + Uri: "did:web:" + config.Hostname + "/app.bsky.feed.generator/" + algorithm, + }) + } + + // Return the list of feeds + return c.JSON(bsky.FeedDescribeFeedGenerator_Output{ + Did: "did:web:" + config.Hostname, + Feeds: generatorFeeds, + }) + }) + + app.Get("/xrpc/app.bsky.feed.getFeedSkeleton", func(c *fiber.Ctx) error { + // Get the feed query parameters and parse the limit + feed := c.Query("feed", "at://did:web:"+config.Hostname+"/app.bsky.feed.generator/all") + cursor := c.Query("cursor", "") + limit, err := strconv.ParseInt(c.Query("limit", "20"), 0, 32) + if err != nil || limit < 1 || limit > 100 { + limit = 20 + } + + // Parse the feed URI + uri, err := syntax.ParseATURI(feed) + if err != nil { + fmt.Println("Error parsing URI", err) + return c.Status(400).SendString("Invalid feed URI") + } + + feedName := uri.RecordKey().String() + + log.WithFields(log.Fields{ + "feed": feedName, + "cursor": cursor, + "limit": limit, + }).Info("Generate feed skeleton with parameters") + + if feed, ok := feeds.Feeds[feedName]; ok { + // Call the algorithm + posts, err := feed.Algorithm(config.Reader, cursor, int(limit)) + if err != nil { + fmt.Println("Error calling algorithm", err) + return c.Status(500).SendString("Error calling algorithm") + } + return c.JSON(posts) + } + + return c.Status(400).SendString("Invalid feed") + }) return app