From bd1612e182982153383832fd2b3d861ed6358765 Mon Sep 17 00:00:00 2001 From: Joe Constant Date: Fri, 24 Nov 2023 07:44:20 +0000 Subject: [PATCH 01/12] Update .codesandbox/tasks.json and .devcontainer/devcontainer.json Add 'branch' and 'resume' keys to .codesandbox/tasks.json, change 'dockerfile' path in .devcontainer/devcontainer.json --- .codesandbox/tasks.json | 4 +++- .devcontainer/devcontainer.json | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.codesandbox/tasks.json b/.codesandbox/tasks.json index 41bfb3f..c9e1f24 100644 --- a/.codesandbox/tasks.json +++ b/.codesandbox/tasks.json @@ -12,7 +12,9 @@ "port": 8080 }, "restartOn": { - "files": ["*"] + "files": ["*"], + "branch": true, + "resume": true } } } diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4116484..36a329b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,7 @@ { "name": "Devcontainer", "build": { - "dockerfile": "./Dockerfile" + // Path is relative to the devcontainer.json file. + "dockerfile": "Dockerfile" } -} \ No newline at end of file +} From 1c12f4fb2318e4409a1a37b7b5bf0cc10a3e7f62 Mon Sep 17 00:00:00 2001 From: Joe Constant Date: Sun, 26 Nov 2023 06:59:11 +0000 Subject: [PATCH 02/12] Heavily refactor code --- cmd/federation.go | 232 +----------------- internal/activitypub/post.go | 13 + internal/activitypub/user.go | 47 ++++ internal/db/db.go | 37 +++ .../db/migrations}/1_create_users.sql | 0 internal/http/routes.go | 79 ++++++ internal/http/server.go | 65 +++++ internal/lemmy/client.go | 7 + 8 files changed, 252 insertions(+), 228 deletions(-) create mode 100644 internal/db/db.go rename {cmd/db/migration => internal/db/migrations}/1_create_users.sql (100%) create mode 100644 internal/http/routes.go create mode 100644 internal/http/server.go diff --git a/cmd/federation.go b/cmd/federation.go index 5d4e7a7..90afb13 100644 --- a/cmd/federation.go +++ b/cmd/federation.go @@ -1,237 +1,13 @@ package main import ( - "context" - "crypto/rand" - "crypto/rsa" - "database/sql" - "embed" - "encoding/json" - "flag" - "fmt" - "log" - "net/http" "os" - "os/signal" - "time" - - "participating-online/sublinks-federation/internal/activitypub" - "participating-online/sublinks-federation/internal/lemmy" - - "github.com/gorilla/mux" - _ "github.com/libsql/libsql-client-go/libsql" - "github.com/pressly/goose/v3" + "participating-online/sublinks-federation/internal/db" + "participating-online/sublinks-federation/internal/http" ) -//go:embed db/migration/*.sql -var embedMigrations embed.FS - -func saveUser(user *activitypub.User, privateKey string) { - dbUrl, _ := os.LookupEnv("DB_URL") - db, err := getDb(dbUrl) - if err != nil { - log.Fatal(err) - } - defer db.Close() - res, err := db.Exec(fmt.Sprintf("INSERT INTO users (name, public_key, private_key) VALUES ('%s', '%s', '%s', '%s');", user.Name, user.Publickey.Keyid, user.Publickey.PublicKeyPem, privateKey)) - if err != nil { - log.Fatal(err) - } - fmt.Print(res) -} - -func getDb(dbUrl string) (*sql.DB, error) { - db, err := sql.Open("libsql", dbUrl) - return db, err -} - -func getLemmyClient(ctx context.Context) *lemmy.Client { - user, _ := os.LookupEnv("LEMMY_USER") - pass, _ := os.LookupEnv("LEMMY_PASSWORD") - return lemmy.NewClient("https://demo.sublinks.org", user, pass) -} - -func getUser(name string) (*activitypub.User, error) { - dbUrl, _ := os.LookupEnv("DB_URL") - db, err := getDb(dbUrl) - if err != nil { - return nil, err - } - defer db.Close() - rows, err := db.Query(fmt.Sprintf("select * from users where name='%s';", name)) - if err != nil { - return nil, err - } - var user activitypub.User - if !rows.Next() { - privatekey, publickey := GenerateKeyPair() - user := activitypub.NewUser(name, privatekey, publickey) - saveUser(&user, activitypub.GetPrivateKeyString(privatekey)) - return &user, nil - } - var id int - var public_key, private_key string - err = rows.Scan(&id, &name, &public_key, &private_key) - if err != nil { - fmt.Print(err) - privatekey, publickey := GenerateKeyPair() - user := activitypub.NewUser(name, privatekey, publickey) - saveUser(&user, activitypub.GetPrivateKeyString(privatekey)) - return &user, nil - } - user = activitypub.NewExistingUser(name, private_key, public_key) - return &user, nil -} - -func runMigrations() { - dbUrl, _ := os.LookupEnv("DB_URL") - db, err := getDb(dbUrl) - if err != nil { - log.Fatal(err) - } - defer db.Close() - goose.SetBaseFS(embedMigrations) - - if err := goose.SetDialect("sqlite"); err != nil { - panic(err) - } - - if err := goose.Up(db, "db/migration"); err != nil { - panic(err) - } -} - func main() { - runMigrations() - - var wait time.Duration - flag.DurationVar(&wait, "graceful-timeout", time.Second*15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m") - flag.Parse() - - r := mux.NewRouter() - r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Location", "/post/1") - w.WriteHeader(http.StatusFound) // 302 - }) - r.HandleFunc("/users/{user}", GetUserInfoHandler).Methods("GET") - r.HandleFunc("/users/{user}/inbox", GetInboxHandler).Methods("GET") - r.HandleFunc("/users/{user}/inbox", PostInboxHandler).Methods("POST") - r.HandleFunc("/users/{user}/outbox", GetOutboxHandler).Methods("GET") - r.HandleFunc("/users/{user}/outbox", PostOutboxHandler).Methods("POST") - r.HandleFunc("/post/{postId}", GetPostHandler).Methods("GET") - - srv := &http.Server{ - Addr: "0.0.0.0:8080", - // Good practice to set timeouts to avoid Slowloris attacks. - WriteTimeout: time.Second * 15, - ReadTimeout: time.Second * 15, - IdleTimeout: time.Second * 60, - Handler: r, // Pass our instance of gorilla/mux in. - } - - // Run our server in a goroutine so that it doesn't block. - go func() { - if err := srv.ListenAndServe(); err != nil { - log.Println(err) - } - }() - - c := make(chan os.Signal, 1) - // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) - // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught. - signal.Notify(c, os.Interrupt) - - // Block until we receive our signal. - <-c - - // Create a deadline to wait for. - ctx, cancel := context.WithTimeout(context.Background(), wait) - defer cancel() - // Doesn't block if no connections, but will otherwise wait - // until the timeout deadline. - srv.Shutdown(ctx) - // Optionally, you could run srv.Shutdown in a goroutine and block on - // <-ctx.Done() if your application should wait for other services - // to finalize based on context cancellation. - log.Println("shutting down") + db.RunMigrations() + http.RunServer() os.Exit(0) } - -func convertToApub(p *lemmy.Response) activitypub.Post { - return activitypub.NewPost( - p.PostView.Post.ApId, - fmt.Sprintf("https://demo.sublinks.org/u/%s", p.PostView.Creator.Name), - p.CommunityView.Community.ActorId, - p.PostView.Post.Name, - p.PostView.Post.Body, - p.PostView.Post.Nsfw, - p.PostView.Post.Published, - ) -} - -func GetPostHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - ctx := context.Background() - c := getLemmyClient(ctx) - post, err := c.GetPost(ctx, vars["postId"]) - if err != nil { - log.Println("Error reading post", err) - return - } - postLd := convertToApub(post) - w.WriteHeader(http.StatusOK) - w.Header().Add("content-type", "application/activity+json") - content, _ := json.MarshalIndent(postLd, "", " ") - w.Write(content) -} - -func GetUserInfoHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - if vars["user"] != "lazyguru" { - w.WriteHeader(http.StatusNotFound) - fmt.Fprint(w, "User not found") - return - } - - user, err := getUser(vars["user"]) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "Error: %s", err) - return - } - w.WriteHeader(http.StatusOK) - w.Header().Add("content-type", "application/activity+json") - content, _ := json.MarshalIndent(user, "", " ") - w.Write(content) -} - -func GenerateKeyPair() (*rsa.PrivateKey, *rsa.PublicKey) { - // generate key - privatekey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - fmt.Printf("Cannot generate RSA key\n") - os.Exit(1) - } - publickey := &privatekey.PublicKey - return privatekey, publickey -} - -func GetInboxHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Header().Add("content-type", "application/activity+json") -} - -func PostInboxHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Header().Add("content-type", "application/activity+json") -} - -func GetOutboxHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Header().Add("content-type", "application/activity+json") -} - -func PostOutboxHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Header().Add("content-type", "application/activity+json") -} diff --git a/internal/activitypub/post.go b/internal/activitypub/post.go index 61301bb..fe5c8f7 100644 --- a/internal/activitypub/post.go +++ b/internal/activitypub/post.go @@ -2,6 +2,7 @@ package activitypub import ( "fmt" + "participating-online/sublinks-federation/internal/lemmy" "time" ) @@ -68,3 +69,15 @@ func NewPost(postUrl string, fromUser string, communityUrl string, postTitle str } return post } + +func ConvertPostToApub(p *lemmy.Response) Post { + return NewPost( + p.PostView.Post.ApId, + fmt.Sprintf("https://demo.sublinks.org/u/%s", p.PostView.Creator.Name), + p.CommunityView.Community.ActorId, + p.PostView.Post.Name, + p.PostView.Post.Body, + p.PostView.Post.Nsfw, + p.PostView.Post.Published, + ) +} diff --git a/internal/activitypub/user.go b/internal/activitypub/user.go index d136b13..f83ca6e 100644 --- a/internal/activitypub/user.go +++ b/internal/activitypub/user.go @@ -1,8 +1,10 @@ package activitypub import ( + "crypto/rand" "crypto/rsa" "crypto/x509" + "database/sql" "encoding/pem" "fmt" "log" @@ -98,3 +100,48 @@ func NewUser(name string, privatekey *rsa.PrivateKey, publickey *rsa.PublicKey) user.Endpoints.SharedInbox = fmt.Sprintf("https://%s/inbox", Hostname) return user } + +func (user *User) SaveUser(db *sql.DB, privateKey string) { + res, err := db.Exec(fmt.Sprintf("INSERT INTO users (name, public_key, private_key) VALUES ('%s', '%s', '%s', '%s');", user.Name, user.Publickey.Keyid, user.Publickey.PublicKeyPem, privateKey)) + if err != nil { + log.Fatal(err) + } + fmt.Print(res) +} + +func GetUser(db *sql.DB, name string) (*User, error) { + rows, err := db.Query(fmt.Sprintf("select * from users where name='%s';", name)) + if err != nil { + return nil, err + } + var user User + if !rows.Next() { + privatekey, publickey := GenerateKeyPair() + user := NewUser(name, privatekey, publickey) + user.SaveUser(db, GetPrivateKeyString(privatekey)) + return &user, nil + } + var id int + var public_key, private_key string + err = rows.Scan(&id, &name, &public_key, &private_key) + if err != nil { + fmt.Print(err) + privatekey, publickey := GenerateKeyPair() + user := NewUser(name, privatekey, publickey) + user.SaveUser(db, GetPrivateKeyString(privatekey)) + return &user, nil + } + user = NewExistingUser(name, private_key, public_key) + return &user, nil +} + +func GenerateKeyPair() (*rsa.PrivateKey, *rsa.PublicKey) { + // generate key + privatekey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + fmt.Printf("Cannot generate RSA key\n") + os.Exit(1) + } + publickey := &privatekey.PublicKey + return privatekey, publickey +} diff --git a/internal/db/db.go b/internal/db/db.go new file mode 100644 index 0000000..0b0efa5 --- /dev/null +++ b/internal/db/db.go @@ -0,0 +1,37 @@ +package db + +import ( + "database/sql" + "embed" + "log" + "os" + + _ "github.com/libsql/libsql-client-go/libsql" + "github.com/pressly/goose/v3" +) + +//go:embed migrations/*.sql +var embedMigrations embed.FS + +func GetDb(dbUrl string) (*sql.DB, error) { + db, err := sql.Open("libsql", dbUrl) + return db, err +} + +func RunMigrations() { + dbUrl, _ := os.LookupEnv("DB_URL") + db, err := GetDb(dbUrl) + if err != nil { + log.Fatal(err) + } + defer db.Close() + goose.SetBaseFS(embedMigrations) + + if err := goose.SetDialect("sqlite"); err != nil { + panic(err) + } + + if err := goose.Up(db, "migrations"); err != nil { + panic(err) + } +} diff --git a/cmd/db/migration/1_create_users.sql b/internal/db/migrations/1_create_users.sql similarity index 100% rename from cmd/db/migration/1_create_users.sql rename to internal/db/migrations/1_create_users.sql diff --git a/internal/http/routes.go b/internal/http/routes.go new file mode 100644 index 0000000..1d662e2 --- /dev/null +++ b/internal/http/routes.go @@ -0,0 +1,79 @@ +package http + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "participating-online/sublinks-federation/internal/activitypub" + "participating-online/sublinks-federation/internal/db" + "participating-online/sublinks-federation/internal/lemmy" + + "github.com/gorilla/mux" +) + +func GetPostHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + ctx := context.Background() + c := lemmy.GetLemmyClient(ctx) + post, err := c.GetPost(ctx, vars["postId"]) + if err != nil { + log.Println("Error reading post", err) + return + } + postLd := activitypub.ConvertPostToApub(post) + w.WriteHeader(http.StatusOK) + w.Header().Add("content-type", "application/activity+json") + content, _ := json.MarshalIndent(postLd, "", " ") + w.Write(content) +} + +func GetUserInfoHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + if vars["user"] != "lazyguru" { + w.WriteHeader(http.StatusNotFound) + fmt.Fprint(w, "User not found") + return + } + + dbUrl, _ := os.LookupEnv("DB_URL") + db, err := db.GetDb(dbUrl) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error: %s", err) + return + } + defer db.Close() + user, err := activitypub.GetUser(db, vars["user"]) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error: %s", err) + return + } + w.WriteHeader(http.StatusOK) + w.Header().Add("content-type", "application/activity+json") + content, _ := json.MarshalIndent(user, "", " ") + w.Write(content) +} + +func GetInboxHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Header().Add("content-type", "application/activity+json") +} + +func PostInboxHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Header().Add("content-type", "application/activity+json") +} + +func GetOutboxHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Header().Add("content-type", "application/activity+json") +} + +func PostOutboxHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Header().Add("content-type", "application/activity+json") +} diff --git a/internal/http/server.go b/internal/http/server.go new file mode 100644 index 0000000..de9ded5 --- /dev/null +++ b/internal/http/server.go @@ -0,0 +1,65 @@ +package http + +import ( + "context" + "flag" + "github.com/gorilla/mux" + "log" + "net/http" + "os" + "os/signal" + "time" +) + +func RunServer() { + var wait time.Duration + flag.DurationVar(&wait, "graceful-timeout", time.Second*15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m") + flag.Parse() + + r := mux.NewRouter() + r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Location", "/post/1") + w.WriteHeader(http.StatusFound) // 302 + }) + r.HandleFunc("/users/{user}", GetUserInfoHandler).Methods("GET") + r.HandleFunc("/users/{user}/inbox", GetInboxHandler).Methods("GET") + r.HandleFunc("/users/{user}/inbox", PostInboxHandler).Methods("POST") + r.HandleFunc("/users/{user}/outbox", GetOutboxHandler).Methods("GET") + r.HandleFunc("/users/{user}/outbox", PostOutboxHandler).Methods("POST") + r.HandleFunc("/post/{postId}", GetPostHandler).Methods("GET") + + srv := &http.Server{ + Addr: "0.0.0.0:8080", + // Good practice to set timeouts to avoid Slowloris attacks. + WriteTimeout: time.Second * 15, + ReadTimeout: time.Second * 15, + IdleTimeout: time.Second * 60, + Handler: r, // Pass our instance of gorilla/mux in. + } + + // Run our server in a goroutine so that it doesn't block. + go func() { + if err := srv.ListenAndServe(); err != nil { + log.Println(err) + } + }() + + c := make(chan os.Signal, 1) + // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) + // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught. + signal.Notify(c, os.Interrupt) + + // Block until we receive our signal. + <-c + + // Create a deadline to wait for. + ctx, cancel := context.WithTimeout(context.Background(), wait) + defer cancel() + // Doesn't block if no connections, but will otherwise wait + // until the timeout deadline. + srv.Shutdown(ctx) + // Optionally, you could run srv.Shutdown in a goroutine and block on + // <-ctx.Done() if your application should wait for other services + // to finalize based on context cancellation. + log.Println("shutting down") +} diff --git a/internal/lemmy/client.go b/internal/lemmy/client.go index 702564b..0e7d8ce 100644 --- a/internal/lemmy/client.go +++ b/internal/lemmy/client.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "net/http" + "os" "time" ) @@ -28,6 +29,12 @@ func NewClient(url string, user string, password string) *Client { } } +func GetLemmyClient(ctx context.Context) *Client { + user, _ := os.LookupEnv("LEMMY_USER") + pass, _ := os.LookupEnv("LEMMY_PASSWORD") + return NewClient("https://demo.sublinks.org", user, pass) +} + func (c *Client) GetPost(ctx context.Context, id string) (*Response, error) { req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v3/post?id=%s", c.BaseURL, id), nil) if err != nil { From a34187c6c973aefeba7fc9f795d6c3ac2e7b9388 Mon Sep 17 00:00:00 2001 From: Joe Constant Date: Sun, 26 Nov 2023 08:50:06 +0000 Subject: [PATCH 03/12] Remove DB code --- cmd/federation.go | 2 - internal/activitypub/user.go | 120 ++++------------------ internal/db/db.go | 37 ------- internal/db/migrations/1_create_users.sql | 11 -- internal/http/routes.go | 28 ++--- 5 files changed, 29 insertions(+), 169 deletions(-) delete mode 100644 internal/db/db.go delete mode 100644 internal/db/migrations/1_create_users.sql diff --git a/cmd/federation.go b/cmd/federation.go index 90afb13..236d342 100644 --- a/cmd/federation.go +++ b/cmd/federation.go @@ -2,12 +2,10 @@ package main import ( "os" - "participating-online/sublinks-federation/internal/db" "participating-online/sublinks-federation/internal/http" ) func main() { - db.RunMigrations() http.RunServer() os.Exit(0) } diff --git a/internal/activitypub/user.go b/internal/activitypub/user.go index f83ca6e..0cb2638 100644 --- a/internal/activitypub/user.go +++ b/internal/activitypub/user.go @@ -1,14 +1,9 @@ package activitypub import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "database/sql" - "encoding/pem" "fmt" - "log" "os" + "participating-online/sublinks-federation/internal/lemmy" "time" ) @@ -27,121 +22,48 @@ type Endpoints struct { } type User struct { - Context string `json:"@context"` + Context Context `json:"@context"` Id string `json:"id"` PreferredUsername string `json:"preferredUsername"` Inbox string `json:"inbox"` Outbox string `json:"outbox"` Type string `json:"type"` - Name string `json:"name"` + Summary string `json:"summary"` + MatrixUserId string `json:"matrixUserId"` + Image []Link `json:"image"` + Icon []Link `json:"icon"` + Source Source `json:"source"` Publickey PublicKey `json:"publicKey"` - privatekey string Published time.Time `json:"published"` - Updated time.Time `json:"updated"` Endpoints Endpoints `json:"endpoints"` } -func GetPrivateKeyString(privatekey *rsa.PrivateKey) string { - privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privatekey) - if err != nil { - log.Fatalf("error when dumping privatekey: %s \n", err) - } - return string(pem.EncodeToMemory(&pem.Block{ - Type: "PRIVATE KEY", - Bytes: privateKeyBytes, - })) -} - -func NewExistingUser(name string, privatekey string, publickey string) User { +func NewUser(name string, matrixUserId string, bio string, publickey string) User { user := User{} - user.Context = "https://www.w3.org/ns/activitystreams" - user.PreferredUsername = name - user.Id = fmt.Sprintf("https://%s/users/%s", Hostname, name) - user.Inbox = fmt.Sprintf("https://%s/users/%s/inbox", Hostname, name) - user.Outbox = fmt.Sprintf("https://%s/users/%s/outbox", Hostname, name) - user.Type = "Person" - user.Name = name - user.privatekey = privatekey - pubKeyId := fmt.Sprintf("https://%s/users/%s#main-key", Hostname, name) - owner := fmt.Sprintf("https://%s/users/%s", Hostname, name) - user.Publickey = PublicKey{Keyid: pubKeyId, PublicKeyPem: publickey, Owner: owner} - user.Published = time.Now() - user.Updated = time.Now() - user.Endpoints.SharedInbox = fmt.Sprintf("https://%s/inbox", Hostname) - return user -} - -func NewUser(name string, privatekey *rsa.PrivateKey, publickey *rsa.PublicKey) User { - user := User{} - user.Context = "https://www.w3.org/ns/activitystreams" + user.Context = GetContext() user.Id = fmt.Sprintf("https://%s/users/%s", Hostname, name) user.PreferredUsername = name user.Inbox = fmt.Sprintf("https://%s/users/%s/inbox", Hostname, name) user.Outbox = fmt.Sprintf("https://%s/users/%s/outbox", Hostname, name) user.Type = "Person" - user.Name = name - user.privatekey = GetPrivateKeyString(privatekey) - publicKeyBytes, err := x509.MarshalPKIXPublicKey(publickey) - if err != nil { - fmt.Printf("error when dumping publickey: %s \n", err) - return User{} - } + user.Summary = bio + user.MatrixUserId = matrixUserId owner := fmt.Sprintf("https://%s/users/%s", Hostname, name) user.Publickey = PublicKey{ - Keyid: fmt.Sprintf("https://%s/users/%s#main-key", Hostname, name), - Owner: owner, - PublicKeyPem: string(pem.EncodeToMemory(&pem.Block{ - Type: "PUBLIC KEY", - Bytes: publicKeyBytes, - })), + Keyid: fmt.Sprintf("https://%s/users/%s#main-key", Hostname, name), + Owner: owner, + PublicKeyPem: publickey, } user.Published = time.Now() - user.Updated = time.Now() user.Endpoints.SharedInbox = fmt.Sprintf("https://%s/inbox", Hostname) return user } -func (user *User) SaveUser(db *sql.DB, privateKey string) { - res, err := db.Exec(fmt.Sprintf("INSERT INTO users (name, public_key, private_key) VALUES ('%s', '%s', '%s', '%s');", user.Name, user.Publickey.Keyid, user.Publickey.PublicKeyPem, privateKey)) - if err != nil { - log.Fatal(err) - } - fmt.Print(res) -} - -func GetUser(db *sql.DB, name string) (*User, error) { - rows, err := db.Query(fmt.Sprintf("select * from users where name='%s';", name)) - if err != nil { - return nil, err - } - var user User - if !rows.Next() { - privatekey, publickey := GenerateKeyPair() - user := NewUser(name, privatekey, publickey) - user.SaveUser(db, GetPrivateKeyString(privatekey)) - return &user, nil - } - var id int - var public_key, private_key string - err = rows.Scan(&id, &name, &public_key, &private_key) - if err != nil { - fmt.Print(err) - privatekey, publickey := GenerateKeyPair() - user := NewUser(name, privatekey, publickey) - user.SaveUser(db, GetPrivateKeyString(privatekey)) - return &user, nil - } - user = NewExistingUser(name, private_key, public_key) - return &user, nil -} - -func GenerateKeyPair() (*rsa.PrivateKey, *rsa.PublicKey) { - // generate key - privatekey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - fmt.Printf("Cannot generate RSA key\n") - os.Exit(1) - } - publickey := &privatekey.PublicKey - return privatekey, publickey +func ConvertUserToApub(u *lemmy.UserResponse) User { + return NewUser( + u.PersonView.Person.Name, + u.PersonView.Person.MatrixUserId, + u.PersonView.Person.Bio, + "", + ) } diff --git a/internal/db/db.go b/internal/db/db.go deleted file mode 100644 index 0b0efa5..0000000 --- a/internal/db/db.go +++ /dev/null @@ -1,37 +0,0 @@ -package db - -import ( - "database/sql" - "embed" - "log" - "os" - - _ "github.com/libsql/libsql-client-go/libsql" - "github.com/pressly/goose/v3" -) - -//go:embed migrations/*.sql -var embedMigrations embed.FS - -func GetDb(dbUrl string) (*sql.DB, error) { - db, err := sql.Open("libsql", dbUrl) - return db, err -} - -func RunMigrations() { - dbUrl, _ := os.LookupEnv("DB_URL") - db, err := GetDb(dbUrl) - if err != nil { - log.Fatal(err) - } - defer db.Close() - goose.SetBaseFS(embedMigrations) - - if err := goose.SetDialect("sqlite"); err != nil { - panic(err) - } - - if err := goose.Up(db, "migrations"); err != nil { - panic(err) - } -} diff --git a/internal/db/migrations/1_create_users.sql b/internal/db/migrations/1_create_users.sql deleted file mode 100644 index 0031be5..0000000 --- a/internal/db/migrations/1_create_users.sql +++ /dev/null @@ -1,11 +0,0 @@ --- +goose Up -CREATE TABLE users ( - id INTEGER PRIMARY KEY, - name CHAR(50) NOT NULL, - public_key TEXT NOT NULL, - private_key TEXT NOT NULL, - unique (name) -); - --- +goose Down -DROP TABLE users; \ No newline at end of file diff --git a/internal/http/routes.go b/internal/http/routes.go index 1d662e2..77e9070 100644 --- a/internal/http/routes.go +++ b/internal/http/routes.go @@ -6,9 +6,7 @@ import ( "fmt" "log" "net/http" - "os" "participating-online/sublinks-federation/internal/activitypub" - "participating-online/sublinks-federation/internal/db" "participating-online/sublinks-federation/internal/lemmy" "github.com/gorilla/mux" @@ -32,29 +30,19 @@ func GetPostHandler(w http.ResponseWriter, r *http.Request) { func GetUserInfoHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - if vars["user"] != "lazyguru" { - w.WriteHeader(http.StatusNotFound) - fmt.Fprint(w, "User not found") - return - } - - dbUrl, _ := os.LookupEnv("DB_URL") - db, err := db.GetDb(dbUrl) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "Error: %s", err) - return - } - defer db.Close() - user, err := activitypub.GetUser(db, vars["user"]) + ctx := context.Background() + c := lemmy.GetLemmyClient(ctx) + log.Println(fmt.Sprintf("Looking up user %s", vars["user"])) + user, err := c.GetUser(ctx, vars["user"]) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "Error: %s", err) + log.Println("Error reading user", err) return } + + userLd := activitypub.ConvertUserToApub(user) w.WriteHeader(http.StatusOK) w.Header().Add("content-type", "application/activity+json") - content, _ := json.MarshalIndent(user, "", " ") + content, _ := json.MarshalIndent(userLd, "", " ") w.Write(content) } From 8ef39c76b591765404b69b9aeaec8e8cbf95daec Mon Sep 17 00:00:00 2001 From: Joe Constant Date: Sun, 26 Nov 2023 08:50:59 +0000 Subject: [PATCH 04/12] Additional refactor to support other object types --- internal/activitypub/common.go | 11 +++ internal/activitypub/post.go | 12 +-- internal/http/server.go | 4 - internal/lemmy/client.go | 146 ++++----------------------------- internal/lemmy/comment.go | 3 + internal/lemmy/community.go | 45 ++++++++++ internal/lemmy/post.go | 66 +++++++++++++++ internal/lemmy/user.go | 54 ++++++++++++ 8 files changed, 196 insertions(+), 145 deletions(-) create mode 100644 internal/activitypub/common.go create mode 100644 internal/lemmy/comment.go create mode 100644 internal/lemmy/community.go create mode 100644 internal/lemmy/post.go create mode 100644 internal/lemmy/user.go diff --git a/internal/activitypub/common.go b/internal/activitypub/common.go new file mode 100644 index 0000000..ec4d486 --- /dev/null +++ b/internal/activitypub/common.go @@ -0,0 +1,11 @@ +package activitypub + +type Source struct { + Content string `json:"content"` + MediaType string `json:"mediaType"` +} + +type Link struct { + Type string `json:"type"` //"Link" | "Image" + Href string `json:"href"` //"https://enterprise.lemmy.ml/pictrs/image/eOtYb9iEiB.png" +} diff --git a/internal/activitypub/post.go b/internal/activitypub/post.go index fe5c8f7..2d7f57e 100644 --- a/internal/activitypub/post.go +++ b/internal/activitypub/post.go @@ -6,16 +6,6 @@ import ( "time" ) -type Source struct { - Content string `json:"content"` - MediaType string `json:"mediaType"` -} - -type Link struct { - Type string `json:"type"` //"Link" | "Image" - Href string `json:"href"` //"https://enterprise.lemmy.ml/pictrs/image/eOtYb9iEiB.png" -} - type Language struct { Identifier string `json:"identifier"` // "fr", Name string `json:"name"` // "Français" @@ -70,7 +60,7 @@ func NewPost(postUrl string, fromUser string, communityUrl string, postTitle str return post } -func ConvertPostToApub(p *lemmy.Response) Post { +func ConvertPostToApub(p *lemmy.PostResponse) Post { return NewPost( p.PostView.Post.ApId, fmt.Sprintf("https://demo.sublinks.org/u/%s", p.PostView.Creator.Name), diff --git a/internal/http/server.go b/internal/http/server.go index de9ded5..cf9a0ad 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -17,10 +17,6 @@ func RunServer() { flag.Parse() r := mux.NewRouter() - r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Location", "/post/1") - w.WriteHeader(http.StatusFound) // 302 - }) r.HandleFunc("/users/{user}", GetUserInfoHandler).Methods("GET") r.HandleFunc("/users/{user}/inbox", GetInboxHandler).Methods("GET") r.HandleFunc("/users/{user}/inbox", PostInboxHandler).Methods("POST") diff --git a/internal/lemmy/client.go b/internal/lemmy/client.go index 0e7d8ce..2478b72 100644 --- a/internal/lemmy/client.go +++ b/internal/lemmy/client.go @@ -35,14 +35,28 @@ func GetLemmyClient(ctx context.Context) *Client { return NewClient("https://demo.sublinks.org", user, pass) } -func (c *Client) GetPost(ctx context.Context, id string) (*Response, error) { +func (c *Client) GetPost(ctx context.Context, id string) (*PostResponse, error) { req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v3/post?id=%s", c.BaseURL, id), nil) if err != nil { return nil, err } req = req.WithContext(ctx) - res := Response{} + res := PostResponse{} + if _, err := c.sendRequest(req, &res); err != nil { + return nil, errors.New(err.Message) + } + return &res, nil +} + +func (c *Client) GetUser(ctx context.Context, id string) (*UserResponse, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v3/user?username=%s", c.BaseURL, id), nil) + if err != nil { + return nil, err + } + + req = req.WithContext(ctx) + res := UserResponse{} if _, err := c.sendRequest(req, &res); err != nil { return nil, errors.New(err.Message) } @@ -79,134 +93,6 @@ func (c *Client) sendRequest(req *http.Request, v interface{}) (*successResponse return &fullResponse, nil } -type Response struct { - PostView PostView `json:"post_view"` - CommunityView CommunityView `json:"community_view"` - Moderators []Moderator `json:"moderators"` - CrossPosts []Post `json:"cross_posts"` -} - -type PostCounts struct { - Id int `json:"id"` - PostId int `json:"post_id"` - Comments int `json:"comments"` - Score int `json:"score"` - Upvotes int `json:"upvotes"` - Downvotes int `json:"downvotes"` - Published time.Time `json:"published"` - NewestCommentTimeNecro time.Time `json:"newest_comment_time_necro"` - NewestCommentTime time.Time `json:"newest_comment_time"` - FeaturedCommunity bool `json:"featured_community"` - FeaturedLocal bool `json:"featured_local"` - HotRank int `json:"hot_rank"` - HotRankActive int `json:"hot_rank_active"` -} - -type CommunityCounts struct { - Id int `json:"id"` - CommunityId int `json:"community_id"` - Subscribers int `json:"subscribers"` - Posts int `json:"posts"` - Comments int `json:"comments"` - Published string `json:"published"` - UsersActiveDay int `json:"users_active_day"` - UsersActiveWeek int `json:"users_active_week"` - UsersActiveMonth int `json:"users_active_month"` - UsersActiveHalfYear int `json:"users_active_half_year"` - Hot_rank int `json:"hot_rank"` -} - -type CommunityView struct { - Community Community `json:"community"` - Subscribed string `json:"subscribed"` - Blocked bool `json:"blocked"` - Counts CommunityCounts `json:"counts"` -} - -type Moderator struct { - Community Community `json:"community"` - Moderator Person `json:"moderator"` -} - -type PostView struct { - Post Post `json:"post"` - Creator Person `json:"creator"` - Community Community `json:"community"` - CreatorBannedFromCommunity bool `json:"creator_banned_from_community"` - Counts PostCounts `json:"counts"` - Subscribed string `json:"subscribed"` - Saved bool `json:"saved"` - Read bool `json:"read"` - CreatorBlocked bool `json:"creator_blocked"` - MyVote int `json:"my_vote"` - UnreadComments int `json:"unread_comments"` -} -type Community struct { - Id int `json:"id"` - Name string `json:"name"` - Title string `json:"title"` - Description string `json:"description"` - Removed bool `json:"removed"` - Published time.Time `json:"published"` - Updated time.Time `json:"updated"` - Deleted bool `json:"deleted"` - Nsfw bool `json:"nsfw"` - ActorId string `json:"actor_id"` - Local bool `json:"local"` - Icon string `json:"icon"` - Banner string `json:"banner"` - FollowersUrl string `json:"followers_url"` - InboxUrl string `json:"inbox_url"` - Hidden bool `json:"hidden"` - PostingRestrictedToMods bool `json:"posting_restricted_to_mods"` - InstanceId int `json:"instance_id"` -} -type Person struct { - Id int `json:"id"` - Name string `json:"name"` - DisplayName string `json:"display_name"` - Avatar string `json:"avatar"` - Banned bool `json:"banned"` - Published time.Time `json:"published"` - Updated time.Time `json:"updated"` - ActorId string `json:"actor_id"` - Bio string `json:"bio"` - Local bool `json:"local"` - Banner string `json:"banner"` - Deleted bool `json:"deleted"` - InboxUrl string `json:"inbox_url"` - SharedInboxUrl string `json:"shared_inbox_url"` - MatrixUserId string `json:"matrix_user_id"` - Admin bool `json:"admin"` - BotAccount bool `json:"bot_account"` - BanExpires string `json:"ban_expires"` - InstanceId int `json:"instance_id"` -} - -type Post struct { - Id int `json:"id"` - Name string `json:"name"` - Url string `json:"url"` - Body string `json:"body"` - CreatorId int `json:"creator_id"` - CommunityId int `json:"community_id"` - Removed bool `json:"removed"` - Locked bool `json:"locked"` - Published time.Time `json:"published"` - Updated time.Time `json:"update"` - Deleted bool `json:"deleted"` - Nsfw bool `json:"nsfw"` - EmbedTitle string `json:"embed_title"` - EmbedDescription string `json:"embed_description"` - ThumbnailUrl string `json:"thumbnail_url"` - ApId string `json:"ap_id"` - Local bool `json:"local"` - EmbedVideoUrl string `json:"embed_video_url"` - LanguageId int `json:"language_id"` - FeaturedCommunity bool `json:"featured_community"` - FeaturedLocal bool `json:"featured_local"` -} - type errorResponse struct { Code int `json:"code"` Message string `json:"message"` diff --git a/internal/lemmy/comment.go b/internal/lemmy/comment.go new file mode 100644 index 0000000..934b408 --- /dev/null +++ b/internal/lemmy/comment.go @@ -0,0 +1,3 @@ +package lemmy + +type Comment struct{} diff --git a/internal/lemmy/community.go b/internal/lemmy/community.go new file mode 100644 index 0000000..dfab306 --- /dev/null +++ b/internal/lemmy/community.go @@ -0,0 +1,45 @@ +package lemmy + +import "time" + +type CommunityCounts struct { + Id int `json:"id"` + CommunityId int `json:"community_id"` + Subscribers int `json:"subscribers"` + Posts int `json:"posts"` + Comments int `json:"comments"` + Published string `json:"published"` + UsersActiveDay int `json:"users_active_day"` + UsersActiveWeek int `json:"users_active_week"` + UsersActiveMonth int `json:"users_active_month"` + UsersActiveHalfYear int `json:"users_active_half_year"` + Hot_rank int `json:"hot_rank"` +} + +type CommunityView struct { + Community Community `json:"community"` + Subscribed string `json:"subscribed"` + Blocked bool `json:"blocked"` + Counts CommunityCounts `json:"counts"` +} + +type Community struct { + Id int `json:"id"` + Name string `json:"name"` + Title string `json:"title"` + Description string `json:"description"` + Removed bool `json:"removed"` + Published time.Time `json:"published"` + Updated time.Time `json:"updated"` + Deleted bool `json:"deleted"` + Nsfw bool `json:"nsfw"` + ActorId string `json:"actor_id"` + Local bool `json:"local"` + Icon string `json:"icon"` + Banner string `json:"banner"` + FollowersUrl string `json:"followers_url"` + InboxUrl string `json:"inbox_url"` + Hidden bool `json:"hidden"` + PostingRestrictedToMods bool `json:"posting_restricted_to_mods"` + InstanceId int `json:"instance_id"` +} diff --git a/internal/lemmy/post.go b/internal/lemmy/post.go new file mode 100644 index 0000000..27c3abf --- /dev/null +++ b/internal/lemmy/post.go @@ -0,0 +1,66 @@ +package lemmy + +import ( + "time" +) + +type PostResponse struct { + PostView PostView `json:"post_view"` + CommunityView CommunityView `json:"community_view"` + Moderators []Moderator `json:"moderators"` + CrossPosts []Post `json:"cross_posts"` +} + +type PostCounts struct { + Id int `json:"id"` + PostId int `json:"post_id"` + Comments int `json:"comments"` + Score int `json:"score"` + Upvotes int `json:"upvotes"` + Downvotes int `json:"downvotes"` + Published time.Time `json:"published"` + NewestCommentTimeNecro time.Time `json:"newest_comment_time_necro"` + NewestCommentTime time.Time `json:"newest_comment_time"` + FeaturedCommunity bool `json:"featured_community"` + FeaturedLocal bool `json:"featured_local"` + HotRank int `json:"hot_rank"` + HotRankActive int `json:"hot_rank_active"` +} + +type PostView struct { + Post Post `json:"post"` + Creator Person `json:"creator"` + Community Community `json:"community"` + CreatorBannedFromCommunity bool `json:"creator_banned_from_community"` + Counts PostCounts `json:"counts"` + Subscribed string `json:"subscribed"` + Saved bool `json:"saved"` + Read bool `json:"read"` + CreatorBlocked bool `json:"creator_blocked"` + MyVote int `json:"my_vote"` + UnreadComments int `json:"unread_comments"` +} + +type Post struct { + Id int `json:"id"` + Name string `json:"name"` + Url string `json:"url"` + Body string `json:"body"` + CreatorId int `json:"creator_id"` + CommunityId int `json:"community_id"` + Removed bool `json:"removed"` + Locked bool `json:"locked"` + Published time.Time `json:"published"` + Updated time.Time `json:"update"` + Deleted bool `json:"deleted"` + Nsfw bool `json:"nsfw"` + EmbedTitle string `json:"embed_title"` + EmbedDescription string `json:"embed_description"` + ThumbnailUrl string `json:"thumbnail_url"` + ApId string `json:"ap_id"` + Local bool `json:"local"` + EmbedVideoUrl string `json:"embed_video_url"` + LanguageId int `json:"language_id"` + FeaturedCommunity bool `json:"featured_community"` + FeaturedLocal bool `json:"featured_local"` +} diff --git a/internal/lemmy/user.go b/internal/lemmy/user.go new file mode 100644 index 0000000..ebe4bbe --- /dev/null +++ b/internal/lemmy/user.go @@ -0,0 +1,54 @@ +package lemmy + +import "time" + +type UserResponse struct { + PersonView PersonView `json:"person_view"` + CommunityView CommunityView `json:"community_view"` + Moderators []Moderator `json:"moderators"` + CrossPosts []Post `json:"cross_posts"` +} + +type PersonView struct { + Person Person `json:"person"` + PersonCounts PersonCounts `json:"counts"` + Comments []Comment `json:"comments"` + Posts []Post `json:"posts"` + Moderates []Community `json:"moderates"` +} + +type PersonCounts struct { + Id int `json:"id"` + PersonId int `json:"person_id"` + PostCount int `json:"post_count"` + PostScore int `json:"post_score"` + CommentCount int `json:"comment_count"` + CommentScore int `json:"comment_score"` +} + +type Moderator struct { + Community Community `json:"community"` + Moderator Person `json:"moderator"` +} + +type Person struct { + Id int `json:"id"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + Avatar string `json:"avatar"` + Banned bool `json:"banned"` + Published time.Time `json:"published"` + Updated time.Time `json:"updated"` + ActorId string `json:"actor_id"` + Bio string `json:"bio"` + Local bool `json:"local"` + Banner string `json:"banner"` + Deleted bool `json:"deleted"` + InboxUrl string `json:"inbox_url"` + SharedInboxUrl string `json:"shared_inbox_url"` + MatrixUserId string `json:"matrix_user_id"` + Admin bool `json:"admin"` + BotAccount bool `json:"bot_account"` + BanExpires string `json:"ban_expires"` + InstanceId int `json:"instance_id"` +} From 4953820db2fcf973edae16ce4efe847e0e691c41 Mon Sep 17 00:00:00 2001 From: Joe Constant Date: Sun, 26 Nov 2023 08:52:35 +0000 Subject: [PATCH 05/12] Add note for where to populate public key on user object --- internal/activitypub/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/activitypub/user.go b/internal/activitypub/user.go index 0cb2638..f75fd73 100644 --- a/internal/activitypub/user.go +++ b/internal/activitypub/user.go @@ -64,6 +64,6 @@ func ConvertUserToApub(u *lemmy.UserResponse) User { u.PersonView.Person.Name, u.PersonView.Person.MatrixUserId, u.PersonView.Person.Bio, - "", + "", //TODO: Public key goes here ) } From 9175406861a0718577ea8d5144847e0423cc5e14 Mon Sep 17 00:00:00 2001 From: Joe Constant Date: Sun, 26 Nov 2023 08:56:09 +0000 Subject: [PATCH 06/12] Cleanup dependencies after removing DB code --- .devcontainer/Dockerfile | 7 - .devcontainer/install-turso.sh | 158 ---------------------- .env-sample | 1 - go.mod | 16 --- go.sum | 231 --------------------------------- 5 files changed, 413 deletions(-) delete mode 100644 .devcontainer/install-turso.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 91aeda2..8333b3f 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,8 +1 @@ FROM golang:1.21.4-bullseye - -COPY install-turso.sh /tmp/install-turso.sh - -RUN chmod +x /tmp/install-turso.sh && \ - /tmp/install-turso.sh && \ - rm /tmp/install-turso.sh && \ - ln -s /root/.turso/turso /bin/turso diff --git a/.devcontainer/install-turso.sh b/.devcontainer/install-turso.sh deleted file mode 100644 index d5e7908..0000000 --- a/.devcontainer/install-turso.sh +++ /dev/null @@ -1,158 +0,0 @@ -#!/bin/sh - -# This script is for installing the latest version of Turso CLI on your machine. - -set -e - -# Terminal ANSI escape codes. -reset="\033[0m" -bright_blue="${reset}\033[34;1m" - -probe_arch() { - ARCH=$(uname -m) - case $ARCH in - x86_64) ARCH="x86_64" ;; - aarch64) ARCH="arm64" ;; - arm64) ARCH="arm64" ;; - *) printf "Architecture ${ARCH} is not supported by this installation script\n"; exit 1 ;; - esac -} - -probe_os() { - OS=$(uname -s) - case $OS in - Darwin) OS="Darwin" ;; - Linux) OS="Linux" ;; - *) printf "Operating system ${OS} is not supported by this installation script\n"; exit 1 ;; - esac -} - -print_logo() { - printf "${bright_blue} - .: .: - .\$\$. \$\$: .\$\$\$: \$\$\$^ \$\$: ~\$^ - .\$\$\$!:\$\$\$ .\$\$\$\$~ .\$\$\$\$^ !\$\$~^\$\$\$~ - \$\$\$\$\$\$ .\$\$\$\$\$~ .\$\$\$\$\$^ \$\$\$\$\$\$: - !\$\$\$\$\$\$\$\$\$\$~ .\$\$\$\$\$\$\$\$\$\$\$ - :\$\$\$\$\$\$\$\$~ .\$\$\$\$\$\$\$\$! - .\$\$\$\$\$\$\$\$~ .\$\$\$\$\$\$\$\$^ - .\$\$\$\$\$\$\$\$! ~\$! :\$\$. :\$\$\$\$\$\$\$\$^ - \$\$\$\$\$\$\$\$\$\$\$!^::\$\$\$\$\$^...................:\$\$\$\$\$!.^~\$\$\$\$\$\$\$\$\$\$\$: - \$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$ - :\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$! - :^!\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$~:. - :\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$! - \$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$: - :\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$~ - ^\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$~. - :\$\$\$\$\$: .^~!\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$!^:. \$\$\$\$\$! - :\$\$\$\$\$!. .!\$\$\$\$\$\$\$\$\$\$\$\$. .^\$\$\$\$\$! - :\$\$\$\$\$\$\$\$\$\$!^:. ~\$\$\$\$\$\$\$\$\$\$\$\$ .^~\$\$\$\$\$\$\$\$\$\$! - :\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$. ~\$\$\$\$\$\$\$\$\$\$\$\$ \$\$\$\$\$\$\$\$\$\$\$\$\$\$\$! - :\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$: ~\$\$\$\$\$\$\$\$\$\$\$\$ \$\$\$\$\$\$\$\$\$\$\$\$\$\$\$! - :\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$^ ~\$\$\$\$\$\$\$\$\$\$\$\$ \$\$\$\$\$\$\$\$\$\$\$\$\$\$\$! - :\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$~ ~\$\$\$\$\$\$\$\$\$\$\$\$ \$\$\$\$\$\$\$\$\$\$\$\$\$\$\$! - :\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$~^^:. ..:^~!\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$! - ^\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$! - :\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$~ - :\$\$\$\$\$\$\$\$\$\$\$\$\$:\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$~~\$\$\$\$\$\$\$\$\$\$\$\$~ - !\$\$\$\$\$\$\$\$\$\$. :\$\$..\$\$! :\$\$^ !\$! ~\$\$\$\$\$\$\$\$\$\$. - ^\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$! - \$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$: - ~\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$ - "\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$" - \$\$\$\$\$~\$\$\$\$\$^\$\$\$\$\$~\$\$\$\$\$\$~\$\$\$\$: - \$\$^ .\$\$\$ \$\$\$: ~\$\$^ .\$\$^ - .. : : :. : -${reset} -" -} - -detect_profile() { - local DETECTED_PROFILE - DETECTED_PROFILE='' - local SHELLTYPE - SHELLTYPE="$(basename "/$SHELL")" - - if [ "$SHELLTYPE" = "bash" ]; then - if [ -f "$HOME/.bashrc" ]; then - DETECTED_PROFILE="$HOME/.bashrc" - elif [ -f "$HOME/.bash_profile" ]; then - DETECTED_PROFILE="$HOME/.bash_profile" - fi - elif [ "$SHELLTYPE" = "zsh" ]; then - DETECTED_PROFILE="$HOME/.zshrc" - elif [ "$SHELLTYPE" = "fish" ]; then - DETECTED_PROFILE="$HOME/.config/fish/conf.d/turso.fish" - fi - - if [ -z "$DETECTED_PROFILE" ]; then - if [ -f "$HOME/.profile" ]; then - DETECTED_PROFILE="$HOME/.profile" - elif [ -f "$HOME/.bashrc" ]; then - DETECTED_PROFILE="$HOME/.bashrc" - elif [ -f "$HOME/.bash_profile" ]; then - DETECTED_PROFILE="$HOME/.bash_profile" - elif [ -f "$HOME/.zshrc" ]; then - DETECTED_PROFILE="$HOME/.zshrc" - elif [ -d "$HOME/.config/fish" ]; then - DETECTED_PROFILE="$HOME/.config/fish/conf.d/turso.fish" - fi - fi - - if [ ! -z "$DETECTED_PROFILE" ]; then - echo "$DETECTED_PROFILE" - fi -} - -update_profile() { - PROFILE_FILE=$(detect_profile) - if [[ -n "$PROFILE_FILE" ]]; then - if ! grep -q "\.turso" $PROFILE_FILE; then - printf "\n${bright_blue}Updating profile ${reset}$PROFILE_FILE\n" - printf "\n# Turso\nexport PATH=\"$INSTALL_DIRECTORY:\$PATH\"\n" >> $PROFILE_FILE - printf "\nTurso will be available when you open a new terminal.\n" - printf "If you want to make Turso available in this terminal, please run:\n" - printf "\nsource $PROFILE_FILE\n" - fi - else - printf "\n${bright_blue}Unable to detect profile file location. ${reset}Please add the following to your profile file:\n" - printf "\nexport PATH=\"$INSTALL_DIRECTORY:\$PATH\"\n" - fi -} - -printf "\nWelcome to the Turso installer!\n" - -print_logo -probe_arch -probe_os - -URL_PREFIX="https://github.com/chiselstrike/homebrew-tap/releases/latest/download/" - -TARGET="${OS}_$ARCH" - -printf "${bright_blue}Downloading ${reset}$TARGET ...\n" - -URL="$URL_PREFIX/homebrew-tap_$TARGET.tar.gz" - -DOWNLOAD_FILE=$(mktemp -t turso.XXXXXXXXXX) - -curl --progress-bar -L "$URL" -o "$DOWNLOAD_FILE" - -INSTALL_DIRECTORY="$HOME/.turso" - -printf "\n${bright_blue}Installing to ${reset}$INSTALL_DIRECTORY\n" - -mkdir -p $INSTALL_DIRECTORY - -tar -C $INSTALL_DIRECTORY -zxf $DOWNLOAD_FILE turso - -rm -f $DOWNLOAD_FILE - -update_profile - -printf "\nTurso CLI installed!\n\n" -printf "If you are a new user, you can sign up with ${bright_blue}turso auth signup${reset}.\n\n" -printf "If you already have an account, please login with ${bright_blue}turso auth login${reset}.\n\n" - -# DON'T RUN SIGNUP '$INSTALL_DIRECTORY/turso auth signup diff --git a/.env-sample b/.env-sample index 39d5390..58a5e72 100644 --- a/.env-sample +++ b/.env-sample @@ -1,3 +1,2 @@ -export DB_URL="libsql://.turso.io?authToken=" export LEMMY_USER="" export LEMMY_PASSWORD="" diff --git a/go.mod b/go.mod index 346855a..50d440e 100644 --- a/go.mod +++ b/go.mod @@ -3,19 +3,3 @@ module participating-online/sublinks-federation go 1.21.4 require github.com/gorilla/mux v1.8.1 - -require ( - github.com/sethvargo/go-retry v0.2.4 // indirect - go.uber.org/multierr v1.11.0 // indirect - golang.org/x/sync v0.5.0 // indirect -) - -require ( - github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 // indirect - github.com/klauspost/compress v1.17.2 // indirect - github.com/libsql/libsql-client-go v0.0.0-20231026052543-fce76c0f39a7 - github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475 // indirect - github.com/pressly/goose/v3 v3.16.0 - golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect - nhooyr.io/websocket v1.8.7 // indirect -) diff --git a/go.sum b/go.sum index 6bf66e6..7128337 100644 --- a/go.sum +++ b/go.sum @@ -1,233 +1,2 @@ -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/ClickHouse/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0= -github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw= -github.com/ClickHouse/clickhouse-go/v2 v2.15.0 h1:G0hTKyO8fXXR1bGnZ0DY3vTG01xYfOGW76zgjg5tmC4= -github.com/ClickHouse/clickhouse-go/v2 v2.15.0/go.mod h1:kXt1SRq0PIRa6aKZD7TnFnY9PQKmc2b13sHtOYcK6cQ= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= -github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 h1:goHVqTbFX3AIo0tzGr14pgfAW2ZfPChKO21Z9MGf/gk= -github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= -github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg= -github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -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/elastic/go-sysinfo v1.11.1 h1:g9mwl05njS4r69TisC+vwHWTSKywZFYYUu3so3T/Lao= -github.com/elastic/go-sysinfo v1.11.1/go.mod h1:6KQb31j0QeWBDF88jIdWSxE8cwoOB9tO4Y4osN7Q70E= -github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0= -github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= -github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= -github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI= -github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= -github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -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/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -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.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/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.5.0 h1:NxstgwndsTRy7eq9/kqYc/BZh5w2hHJV86wjvO+1xPw= -github.com/jackc/pgx/v5 v5.5.0/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= -github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= -github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4= -github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= -github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= -github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/libsql/libsql-client-go v0.0.0-20231026052543-fce76c0f39a7 h1:9hrdl9Xib8AWubXmFx2KxxHhd9Snx2aCgouU9wsvWts= -github.com/libsql/libsql-client-go v0.0.0-20231026052543-fce76c0f39a7/go.mod h1:T+1lRvREkstNW7bmF1PTiDhV6hji0mrlfZkZuk/UPhw= -github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475 h1:6PfEMwfInASh9hkN83aR0j4W/eKaAZt/AURtXAXlas0= -github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475/go.mod h1:20nXSmcf0nAscrzqsXeC2/tA3KkV2eCiJqYuyAgl+ss= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -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 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= -github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/opencontainers/runc v1.1.10 h1:EaL5WeO9lv9wmS6SASjszOeQdSctvpbu0DdBQBizE40= -github.com/opencontainers/runc v1.1.10/go.mod h1:+/R6+KmDlh+hOO8NkjmgkG9Qzvypzk0yXxAPYYR65+M= -github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= -github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= -github.com/paulmach/orb v0.10.0 h1:guVYVqzxHE/CQ1KpfGO077TR0ATHSNjp4s6XGLn3W9s= -github.com/paulmach/orb v0.10.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= -github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= -github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pressly/goose/v3 v3.16.0 h1:xMJUsZdHLqSnCqESyKSqEfcYVYsUuup1nrOhaEFftQg= -github.com/pressly/goose/v3 v3.16.0/go.mod h1:JwdKVnmCRhnF6XLQs2mHEQtucFD49cQBdRM4UiwkxsM= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -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/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= -github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= -github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08Ocec= -github.com/sethvargo/go-retry v0.2.4/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw= -github.com/vertica/vertica-sql-go v1.3.3/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/ydb-platform/ydb-go-genproto v0.0.0-20231012155159-f85a672542fd h1:dzWP1Lu+A40W883dK/Mr3xyDSM/2MggS8GtHT0qgAnE= -github.com/ydb-platform/ydb-go-genproto v0.0.0-20231012155159-f85a672542fd/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= -github.com/ydb-platform/ydb-go-sdk/v3 v3.54.2 h1:E0yUuuX7UmPxXm92+yQCjMveLFO3zfvYFIJVuAqsVRA= -github.com/ydb-platform/ydb-go-sdk/v3 v3.54.2/go.mod h1:fjBLQ2TdQNl4bMjuWl9adoTGBypwUTPoGC+EqYqiIcU= -go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc= -go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs= -go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ= -go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= -golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= -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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= -howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= -lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= -lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= -modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= -modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0= -modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= -modernc.org/libc v1.32.0 h1:yXatHTrACp3WaKNRCoZwUK7qj5V8ep1XyY0ka4oYcNc= -modernc.org/libc v1.32.0/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8= -modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= -modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= -modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= -modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= -modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= -nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= From c6a33f4f26a06b9019397955268336689070a226 Mon Sep 17 00:00:00 2001 From: Joe Constant Date: Sun, 26 Nov 2023 09:08:46 +0000 Subject: [PATCH 07/12] Reorganize HTTP routes Reorganize the HTTP routes into separate files and functions. --- internal/http/routes.go | 67 ---------------------------------- internal/http/routes/apub.go | 34 +++++++++++++++++ internal/http/routes/post.go | 32 ++++++++++++++++ internal/http/routes/routes.go | 11 ++++++ internal/http/routes/user.go | 35 ++++++++++++++++++ internal/http/server.go | 10 +---- 6 files changed, 114 insertions(+), 75 deletions(-) delete mode 100644 internal/http/routes.go create mode 100644 internal/http/routes/apub.go create mode 100644 internal/http/routes/post.go create mode 100644 internal/http/routes/routes.go create mode 100644 internal/http/routes/user.go diff --git a/internal/http/routes.go b/internal/http/routes.go deleted file mode 100644 index 77e9070..0000000 --- a/internal/http/routes.go +++ /dev/null @@ -1,67 +0,0 @@ -package http - -import ( - "context" - "encoding/json" - "fmt" - "log" - "net/http" - "participating-online/sublinks-federation/internal/activitypub" - "participating-online/sublinks-federation/internal/lemmy" - - "github.com/gorilla/mux" -) - -func GetPostHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - ctx := context.Background() - c := lemmy.GetLemmyClient(ctx) - post, err := c.GetPost(ctx, vars["postId"]) - if err != nil { - log.Println("Error reading post", err) - return - } - postLd := activitypub.ConvertPostToApub(post) - w.WriteHeader(http.StatusOK) - w.Header().Add("content-type", "application/activity+json") - content, _ := json.MarshalIndent(postLd, "", " ") - w.Write(content) -} - -func GetUserInfoHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - ctx := context.Background() - c := lemmy.GetLemmyClient(ctx) - log.Println(fmt.Sprintf("Looking up user %s", vars["user"])) - user, err := c.GetUser(ctx, vars["user"]) - if err != nil { - log.Println("Error reading user", err) - return - } - - userLd := activitypub.ConvertUserToApub(user) - w.WriteHeader(http.StatusOK) - w.Header().Add("content-type", "application/activity+json") - content, _ := json.MarshalIndent(userLd, "", " ") - w.Write(content) -} - -func GetInboxHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Header().Add("content-type", "application/activity+json") -} - -func PostInboxHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Header().Add("content-type", "application/activity+json") -} - -func GetOutboxHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Header().Add("content-type", "application/activity+json") -} - -func PostOutboxHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Header().Add("content-type", "application/activity+json") -} diff --git a/internal/http/routes/apub.go b/internal/http/routes/apub.go new file mode 100644 index 0000000..b1d3016 --- /dev/null +++ b/internal/http/routes/apub.go @@ -0,0 +1,34 @@ +package routes + +import ( + "net/http" + + "github.com/gorilla/mux" +) + +func SetupApubRoutes(r *mux.Router) { + r.HandleFunc("/users/{user}/inbox", getInboxHandler).Methods("GET") + r.HandleFunc("/users/{user}/inbox", postInboxHandler).Methods("POST") + r.HandleFunc("/users/{user}/outbox", getOutboxHandler).Methods("GET") + r.HandleFunc("/users/{user}/outbox", postOutboxHandler).Methods("POST") +} + +func getInboxHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Header().Add("content-type", "application/activity+json") +} + +func postInboxHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Header().Add("content-type", "application/activity+json") +} + +func getOutboxHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Header().Add("content-type", "application/activity+json") +} + +func postOutboxHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Header().Add("content-type", "application/activity+json") +} diff --git a/internal/http/routes/post.go b/internal/http/routes/post.go new file mode 100644 index 0000000..82c5ca0 --- /dev/null +++ b/internal/http/routes/post.go @@ -0,0 +1,32 @@ +package routes + +import ( + "context" + "encoding/json" + "log" + "net/http" + "participating-online/sublinks-federation/internal/activitypub" + "participating-online/sublinks-federation/internal/lemmy" + + "github.com/gorilla/mux" +) + +func SetupPostRoutes(r *mux.Router) { + r.HandleFunc("/post/{postId}", getPostHandler).Methods("GET") +} + +func getPostHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + ctx := context.Background() + c := lemmy.GetLemmyClient(ctx) + post, err := c.GetPost(ctx, vars["postId"]) + if err != nil { + log.Println("Error reading post", err) + return + } + postLd := activitypub.ConvertPostToApub(post) + w.WriteHeader(http.StatusOK) + w.Header().Add("content-type", "application/activity+json") + content, _ := json.MarshalIndent(postLd, "", " ") + w.Write(content) +} diff --git a/internal/http/routes/routes.go b/internal/http/routes/routes.go new file mode 100644 index 0000000..0b84013 --- /dev/null +++ b/internal/http/routes/routes.go @@ -0,0 +1,11 @@ +package routes + +import "github.com/gorilla/mux" + +func SetupRoutes() *mux.Router { + r := mux.NewRouter() + SetupUserRoutes(r) + SetupPostRoutes(r) + SetupApubRoutes(r) + return r +} diff --git a/internal/http/routes/user.go b/internal/http/routes/user.go new file mode 100644 index 0000000..e63bc79 --- /dev/null +++ b/internal/http/routes/user.go @@ -0,0 +1,35 @@ +package routes + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "participating-online/sublinks-federation/internal/activitypub" + "participating-online/sublinks-federation/internal/lemmy" + + "github.com/gorilla/mux" +) + +func SetupUserRoutes(r *mux.Router) { + r.HandleFunc("/users/{user}", getUserInfoHandler).Methods("GET") +} + +func getUserInfoHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + ctx := context.Background() + c := lemmy.GetLemmyClient(ctx) + log.Println(fmt.Sprintf("Looking up user %s", vars["user"])) + user, err := c.GetUser(ctx, vars["user"]) + if err != nil { + log.Println("Error reading user", err) + return + } + + userLd := activitypub.ConvertUserToApub(user) + w.WriteHeader(http.StatusOK) + w.Header().Add("content-type", "application/activity+json") + content, _ := json.MarshalIndent(userLd, "", " ") + w.Write(content) +} diff --git a/internal/http/server.go b/internal/http/server.go index cf9a0ad..4524287 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -3,11 +3,11 @@ package http import ( "context" "flag" - "github.com/gorilla/mux" "log" "net/http" "os" "os/signal" + "participating-online/sublinks-federation/internal/http/routes" "time" ) @@ -16,13 +16,7 @@ func RunServer() { flag.DurationVar(&wait, "graceful-timeout", time.Second*15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m") flag.Parse() - r := mux.NewRouter() - r.HandleFunc("/users/{user}", GetUserInfoHandler).Methods("GET") - r.HandleFunc("/users/{user}/inbox", GetInboxHandler).Methods("GET") - r.HandleFunc("/users/{user}/inbox", PostInboxHandler).Methods("POST") - r.HandleFunc("/users/{user}/outbox", GetOutboxHandler).Methods("GET") - r.HandleFunc("/users/{user}/outbox", PostOutboxHandler).Methods("POST") - r.HandleFunc("/post/{postId}", GetPostHandler).Methods("GET") + r := routes.SetupRoutes() srv := &http.Server{ Addr: "0.0.0.0:8080", From 0ba4cd69219bd0bacffcb64030e366c22f04d13a Mon Sep 17 00:00:00 2001 From: Joe Constant Date: Mon, 27 Nov 2023 04:41:05 +0000 Subject: [PATCH 08/12] Add Docker actions for deployment Also "fixes" tests by adding a dummy test --- .github/workflows/go.yml | 53 +++++++++++++++++++++++++++++++++++++++- .gitignore | 1 + Dockerfile | 7 ++++++ tests/dummy_test.go | 15 ++++++++++++ 4 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 Dockerfile create mode 100644 tests/dummy_test.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index a53942e..f4d730f 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -4,14 +4,28 @@ name: Go on: + schedule: + - cron: '29 0 * * *' push: branches: [ "main" ] + # Publish semver tags as releases. + tags: [ 'v*.*.*' ] pull_request: branches: [ "main" ] +env: + # Use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + # The API requires write permission on the repository to submit coverage reports permissions: contents: write + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write jobs: @@ -32,7 +46,7 @@ jobs: run: gofmt -l -e ./ - name: Build - run: go build -v ./... + run: go build -v cmd/federation.go - name: Test run: go test -v ./... @@ -48,3 +62,40 @@ jobs: add-comment: true # One or more regular expressions matching filenames to exclude from coverage statistics (e.g. for generated Go files) #ignore-pattern: + + # Set up BuildKit Docker container builder to be able to build + # multi-platform images and export cache + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + # Login against a Docker registry except on PR + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index e6afd45..7209c54 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # Binaries for programs and plugins +/federation *.exe *.exe~ *.dll diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f91026c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM golang:1.21.4-bullseye + +COPY federation /app/ + +EXPOSE 8080 + +ENTRYPOINT [ "/app/federation" ] \ No newline at end of file diff --git a/tests/dummy_test.go b/tests/dummy_test.go new file mode 100644 index 0000000..5bc2d14 --- /dev/null +++ b/tests/dummy_test.go @@ -0,0 +1,15 @@ +package tests + +import ( + "testing" +) + +/* +This test exists purely to avoid coverage percentage equally NaN +TODO: Delete this file after real tests get added +*/ +func Test_true_is_true(t *testing.T) { + if true != true { + t.Error("true did not equal true") + } +} From b95f5895df381cafbe930b7d4639a15e0cef6333 Mon Sep 17 00:00:00 2001 From: Joe Constant Date: Mon, 27 Nov 2023 05:15:12 +0000 Subject: [PATCH 09/12] Add unit test for NewClient --- internal/lemmy/client_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 internal/lemmy/client_test.go diff --git a/internal/lemmy/client_test.go b/internal/lemmy/client_test.go new file mode 100644 index 0000000..c6bf834 --- /dev/null +++ b/internal/lemmy/client_test.go @@ -0,0 +1,10 @@ +package lemmy + +import "testing" + +func TestNewClient(t *testing.T) { + client := NewClient("url", "user", "pass") + if client.User != "user" { + t.Error("user not set") + } +} From b4a3274c07f1ea4888a1d1944371c8c95f7c1f83 Mon Sep 17 00:00:00 2001 From: Joe Constant Date: Mon, 27 Nov 2023 21:54:35 +0000 Subject: [PATCH 10/12] Remove dummy test --- tests/dummy_test.go | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 tests/dummy_test.go diff --git a/tests/dummy_test.go b/tests/dummy_test.go deleted file mode 100644 index 5bc2d14..0000000 --- a/tests/dummy_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package tests - -import ( - "testing" -) - -/* -This test exists purely to avoid coverage percentage equally NaN -TODO: Delete this file after real tests get added -*/ -func Test_true_is_true(t *testing.T) { - if true != true { - t.Error("true did not equal true") - } -} From d4cc39a3b8443f7f9fd9052a3d6f3e5b19ee0d8f Mon Sep 17 00:00:00 2001 From: Joe Constant Date: Mon, 27 Nov 2023 21:55:03 +0000 Subject: [PATCH 11/12] Update permissions on workflow --- .github/workflows/go.yml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index f4d730f..fb88509 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -19,14 +19,6 @@ env: # github.repository as / IMAGE_NAME: ${{ github.repository }} -# The API requires write permission on the repository to submit coverage reports -permissions: - contents: write - packages: write - # This is used to complete the identity challenge - # with sigstore/fulcio when running outside of PRs. - id-token: write - jobs: build: @@ -53,6 +45,11 @@ jobs: - name: Coverage uses: gwatts/go-coverage-action@v1.3.0 + # The API requires write permission on the repository to submit coverage reports + permissions: + contents: write + checks: write + pull-requests: write with: # Fail the build if the coverage drops below supplied percentage coverage-threshold: 0 # Change this as coverage improves @@ -74,6 +71,12 @@ jobs: - name: Log into registry ${{ env.REGISTRY }} if: github.event_name != 'pull_request' uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} From 4d45a0ce74946ac83c803b737e9cd7c322c166d2 Mon Sep 17 00:00:00 2001 From: Joe Constant Date: Mon, 27 Nov 2023 21:59:44 +0000 Subject: [PATCH 12/12] Fix GitHub workflow file structure --- .github/workflows/go.yml | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index fb88509..c82b627 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -21,8 +21,13 @@ env: jobs: - build: + checks: runs-on: ubuntu-latest + # The API requires write permission on the repository to submit coverage reports + permissions: + contents: write + checks: write + pull-requests: write steps: - uses: actions/checkout@v4 @@ -37,19 +42,11 @@ jobs: - name: Lint run: gofmt -l -e ./ - - name: Build - run: go build -v cmd/federation.go - - name: Test run: go test -v ./... - name: Coverage uses: gwatts/go-coverage-action@v1.3.0 - # The API requires write permission on the repository to submit coverage reports - permissions: - contents: write - checks: write - pull-requests: write with: # Fail the build if the coverage drops below supplied percentage coverage-threshold: 0 # Change this as coverage improves @@ -60,6 +57,25 @@ jobs: # One or more regular expressions matching filenames to exclude from coverage statistics (e.g. for generated Go files) #ignore-pattern: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21.4' + + - name: Build + run: go build -v cmd/federation.go + # Set up BuildKit Docker container builder to be able to build # multi-platform images and export cache # https://github.com/docker/setup-buildx-action @@ -71,12 +87,6 @@ jobs: - name: Log into registry ${{ env.REGISTRY }} if: github.event_name != 'pull_request' uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 - permissions: - contents: read - packages: write - # This is used to complete the identity challenge - # with sigstore/fulcio when running outside of PRs. - id-token: write with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }}