Refactor and Enhance Database and OAuth Handling
Update Go version in go.mod to 1.21.3 Introduce new DBTables struct to hold database tables Add context package in sqliteinit.go for better DB connection management Replace old and very wrong DB initialization and migration logic in sqliteinit.go Update SQL query in tokeninit.go to test logic Remove unused database migration and query files (bootstrapdb.go, inspectdb.go, userdb.go) Update OAuth callback to fetch user information and enhance state parameter handling Simplify logout logic in router.go
- commit
- 10ffb9df7cfe8739c2a52386301f292693d37a6d
- author
- bnema · · 2023-10-13 06:59 UTC
- parent
- 86ea5a2
- signature
- unsigned
12 files · +237 -492
M go.mod +5 -11
| @@ -1,17 +1,17 @@ | |||
| 1 | 1 | module github.com/bnema/gordon | |
| 2 | 2 | ||
| 3 | - | go 1.21.1 | |
| 3 | + | go 1.21.3 | |
| 4 | 4 | ||
| 5 | 5 | require ( | |
| 6 | 6 | github.com/PuerkitoBio/goquery v1.8.1 | |
| 7 | 7 | github.com/docker/docker v24.0.6+incompatible | |
| 8 | - | github.com/google/safehtml v0.1.0 | |
| 8 | + | github.com/docker/go-connections v0.4.0 | |
| 9 | 9 | github.com/gorilla/sessions v1.2.1 | |
| 10 | 10 | github.com/joho/godotenv v1.5.1 | |
| 11 | 11 | github.com/labstack/echo-contrib v0.15.0 | |
| 12 | 12 | github.com/labstack/echo/v4 v4.11.1 | |
| 13 | - | github.com/markbates/goth v1.78.0 | |
| 14 | 13 | github.com/mattn/go-sqlite3 v1.14.17 | |
| 14 | + | github.com/microcosm-cc/bluemonday v1.0.25 | |
| 15 | 15 | gopkg.in/yaml.v3 v3.0.1 | |
| 16 | 16 | ) | |
| 17 | 17 | ||
| @@ -20,20 +20,16 @@ require ( | |||
| 20 | 20 | github.com/andybalholm/cascadia v1.3.1 // indirect | |
| 21 | 21 | github.com/aymerick/douceur v0.2.0 // indirect | |
| 22 | 22 | github.com/docker/distribution v2.8.2+incompatible // indirect | |
| 23 | - | github.com/docker/go-connections v0.4.0 // indirect | |
| 24 | 23 | github.com/docker/go-units v0.5.0 // indirect | |
| 25 | 24 | github.com/gogo/protobuf v1.3.2 // indirect | |
| 26 | 25 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect | |
| 27 | - | github.com/golang/protobuf v1.5.3 // indirect | |
| 28 | - | github.com/google/go-cmp v0.5.9 // indirect | |
| 29 | 26 | github.com/gorilla/context v1.1.1 // indirect | |
| 30 | 27 | github.com/gorilla/css v1.0.0 // indirect | |
| 31 | - | github.com/gorilla/mux v1.6.2 // indirect | |
| 32 | 28 | github.com/gorilla/securecookie v1.1.1 // indirect | |
| 29 | + | github.com/kr/text v0.1.0 // indirect | |
| 33 | 30 | github.com/labstack/gommon v0.4.0 // indirect | |
| 34 | 31 | github.com/mattn/go-colorable v0.1.13 // indirect | |
| 35 | 32 | github.com/mattn/go-isatty v0.0.19 // indirect | |
| 36 | - | github.com/microcosm-cc/bluemonday v1.0.25 // indirect | |
| 37 | 33 | github.com/moby/term v0.5.0 // indirect | |
| 38 | 34 | github.com/morikuni/aec v1.0.0 // indirect | |
| 39 | 35 | github.com/opencontainers/go-digest v1.0.0 // indirect | |
| @@ -46,12 +42,10 @@ require ( | |||
| 46 | 42 | golang.org/x/crypto v0.11.0 // indirect | |
| 47 | 43 | golang.org/x/mod v0.9.0 // indirect | |
| 48 | 44 | golang.org/x/net v0.12.0 // indirect | |
| 49 | - | golang.org/x/oauth2 v0.4.0 // indirect | |
| 50 | 45 | golang.org/x/sys v0.10.0 // indirect | |
| 51 | 46 | golang.org/x/text v0.11.0 // indirect | |
| 52 | 47 | golang.org/x/time v0.3.0 // indirect | |
| 53 | 48 | golang.org/x/tools v0.7.0 // indirect | |
| 54 | - | google.golang.org/appengine v1.6.7 // indirect | |
| 55 | - | google.golang.org/protobuf v1.29.1 // indirect | |
| 49 | + | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect | |
| 56 | 50 | gotest.tools/v3 v3.5.0 // indirect | |
| 57 | 51 | ) | |
| @@ -1,17 +1,17 @@ | |||||
| 1 | module github.com/bnema/gordon | 1 | module github.com/bnema/gordon | ||
| 2 | 2 | ||||
| 3 | - | go 1.21.1 | 3 | + | go 1.21.3 |
| 4 | 4 | ||||
| 5 | require ( | 5 | require ( | ||
| 6 | github.com/PuerkitoBio/goquery v1.8.1 | 6 | github.com/PuerkitoBio/goquery v1.8.1 | ||
| 7 | github.com/docker/docker v24.0.6+incompatible | 7 | github.com/docker/docker v24.0.6+incompatible | ||
| 8 | - | github.com/google/safehtml v0.1.0 | 8 | + | github.com/docker/go-connections v0.4.0 |
| 9 | github.com/gorilla/sessions v1.2.1 | 9 | github.com/gorilla/sessions v1.2.1 | ||
| 10 | github.com/joho/godotenv v1.5.1 | 10 | github.com/joho/godotenv v1.5.1 | ||
| 11 | github.com/labstack/echo-contrib v0.15.0 | 11 | github.com/labstack/echo-contrib v0.15.0 | ||
| 12 | github.com/labstack/echo/v4 v4.11.1 | 12 | github.com/labstack/echo/v4 v4.11.1 | ||
| 13 | - | github.com/markbates/goth v1.78.0 | |||
| 14 | github.com/mattn/go-sqlite3 v1.14.17 | 13 | github.com/mattn/go-sqlite3 v1.14.17 | ||
| 14 | + | github.com/microcosm-cc/bluemonday v1.0.25 | |||
| 15 | gopkg.in/yaml.v3 v3.0.1 | 15 | gopkg.in/yaml.v3 v3.0.1 | ||
| 16 | ) | 16 | ) | ||
| 17 | 17 | ||||
| @@ -20,20 +20,16 @@ require ( | |||||
| 20 | github.com/andybalholm/cascadia v1.3.1 // indirect | 20 | github.com/andybalholm/cascadia v1.3.1 // indirect | ||
| 21 | github.com/aymerick/douceur v0.2.0 // indirect | 21 | github.com/aymerick/douceur v0.2.0 // indirect | ||
| 22 | github.com/docker/distribution v2.8.2+incompatible // indirect | 22 | github.com/docker/distribution v2.8.2+incompatible // indirect | ||
| 23 | - | github.com/docker/go-connections v0.4.0 // indirect | |||
| 24 | github.com/docker/go-units v0.5.0 // indirect | 23 | github.com/docker/go-units v0.5.0 // indirect | ||
| 25 | github.com/gogo/protobuf v1.3.2 // indirect | 24 | github.com/gogo/protobuf v1.3.2 // indirect | ||
| 26 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect | 25 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect | ||
| 27 | - | github.com/golang/protobuf v1.5.3 // indirect | |||
| 28 | - | github.com/google/go-cmp v0.5.9 // indirect | |||
| 29 | github.com/gorilla/context v1.1.1 // indirect | 26 | github.com/gorilla/context v1.1.1 // indirect | ||
| 30 | github.com/gorilla/css v1.0.0 // indirect | 27 | github.com/gorilla/css v1.0.0 // indirect | ||
| 31 | - | github.com/gorilla/mux v1.6.2 // indirect | |||
| 32 | github.com/gorilla/securecookie v1.1.1 // indirect | 28 | github.com/gorilla/securecookie v1.1.1 // indirect | ||
| 29 | + | github.com/kr/text v0.1.0 // indirect | |||
| 33 | github.com/labstack/gommon v0.4.0 // indirect | 30 | github.com/labstack/gommon v0.4.0 // indirect | ||
| 34 | github.com/mattn/go-colorable v0.1.13 // indirect | 31 | github.com/mattn/go-colorable v0.1.13 // indirect | ||
| 35 | github.com/mattn/go-isatty v0.0.19 // indirect | 32 | github.com/mattn/go-isatty v0.0.19 // indirect | ||
| 36 | - | github.com/microcosm-cc/bluemonday v1.0.25 // indirect | |||
| 37 | github.com/moby/term v0.5.0 // indirect | 33 | github.com/moby/term v0.5.0 // indirect | ||
| 38 | github.com/morikuni/aec v1.0.0 // indirect | 34 | github.com/morikuni/aec v1.0.0 // indirect | ||
| 39 | github.com/opencontainers/go-digest v1.0.0 // indirect | 35 | github.com/opencontainers/go-digest v1.0.0 // indirect | ||
| @@ -46,12 +42,10 @@ require ( | |||||
| 46 | golang.org/x/crypto v0.11.0 // indirect | 42 | golang.org/x/crypto v0.11.0 // indirect | ||
| 47 | golang.org/x/mod v0.9.0 // indirect | 43 | golang.org/x/mod v0.9.0 // indirect | ||
| 48 | golang.org/x/net v0.12.0 // indirect | 44 | golang.org/x/net v0.12.0 // indirect | ||
| 49 | - | golang.org/x/oauth2 v0.4.0 // indirect | |||
| 50 | golang.org/x/sys v0.10.0 // indirect | 45 | golang.org/x/sys v0.10.0 // indirect | ||
| 51 | golang.org/x/text v0.11.0 // indirect | 46 | golang.org/x/text v0.11.0 // indirect | ||
| 52 | golang.org/x/time v0.3.0 // indirect | 47 | golang.org/x/time v0.3.0 // indirect | ||
| 53 | golang.org/x/tools v0.7.0 // indirect | 48 | golang.org/x/tools v0.7.0 // indirect | ||
| 54 | - | google.golang.org/appengine v1.6.7 // indirect | 49 | + | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect |
| 55 | - | google.golang.org/protobuf v1.29.1 // indirect | |||
| 56 | gotest.tools/v3 v3.5.0 // indirect | 50 | gotest.tools/v3 v3.5.0 // indirect | ||
| 57 | ) | 51 | ) | ||
M go.sum +0 -375 generated file: collapsed
M internal/app/app.go +10 -1
| @@ -8,6 +8,7 @@ import ( | |||
| 8 | 8 | "log" | |
| 9 | 9 | "os" | |
| 10 | 10 | ||
| 11 | + | "github.com/bnema/gordon/internal/db" | |
| 11 | 12 | "github.com/bnema/gordon/internal/templates" | |
| 12 | 13 | "github.com/bnema/gordon/internal/webui" | |
| 13 | 14 | "github.com/bnema/gordon/pkg/docker" | |
| @@ -27,8 +28,9 @@ type App struct { | |||
| 27 | 28 | DBFilename string | |
| 28 | 29 | DBPath string | |
| 29 | 30 | InitialChecksum string | |
| 30 | - | DB *sql.DB | |
| 31 | 31 | Config Config | |
| 32 | + | DB *sql.DB | |
| 33 | + | DBTables DBTables | |
| 32 | 34 | } | |
| 33 | 35 | type Config struct { | |
| 34 | 36 | General GeneralConfig `yaml:"General"` | |
| @@ -62,6 +64,13 @@ type ContainerEngineConfig struct { | |||
| 62 | 64 | Network string `yaml:"network"` | |
| 63 | 65 | } | |
| 64 | 66 | ||
| 67 | + | type DBTables struct { | |
| 68 | + | User db.User | |
| 69 | + | Account db.Account | |
| 70 | + | Session db.Session | |
| 71 | + | Provider db.Provider | |
| 72 | + | } | |
| 73 | + | ||
| 65 | 74 | func LoadConfig(config *Config) (*Config, error) { | |
| 66 | 75 | // Load env elements | |
| 67 | 76 | config.General.BuildVersion = os.Getenv("BUILD_VERSION") | |
| @@ -8,6 +8,7 @@ import ( | |||||
| 8 | "log" | 8 | "log" | ||
| 9 | "os" | 9 | "os" | ||
| 10 | 10 | ||||
| 11 | + | "github.com/bnema/gordon/internal/db" | |||
| 11 | "github.com/bnema/gordon/internal/templates" | 12 | "github.com/bnema/gordon/internal/templates" | ||
| 12 | "github.com/bnema/gordon/internal/webui" | 13 | "github.com/bnema/gordon/internal/webui" | ||
| 13 | "github.com/bnema/gordon/pkg/docker" | 14 | "github.com/bnema/gordon/pkg/docker" | ||
| @@ -27,8 +28,9 @@ type App struct { | |||||
| 27 | DBFilename string | 28 | DBFilename string | ||
| 28 | DBPath string | 29 | DBPath string | ||
| 29 | InitialChecksum string | 30 | InitialChecksum string | ||
| 30 | - | DB *sql.DB | |||
| 31 | Config Config | 31 | Config Config | ||
| 32 | + | DB *sql.DB | |||
| 33 | + | DBTables DBTables | |||
| 32 | } | 34 | } | ||
| 33 | type Config struct { | 35 | type Config struct { | ||
| 34 | General GeneralConfig `yaml:"General"` | 36 | General GeneralConfig `yaml:"General"` | ||
| @@ -62,6 +64,13 @@ type ContainerEngineConfig struct { | |||||
| 62 | Network string `yaml:"network"` | 64 | Network string `yaml:"network"` | ||
| 63 | } | 65 | } | ||
| 64 | 66 | ||||
| 67 | + | type DBTables struct { | |||
| 68 | + | User db.User | |||
| 69 | + | Account db.Account | |||
| 70 | + | Session db.Session | |||
| 71 | + | Provider db.Provider | |||
| 72 | + | } | |||
| 73 | + | ||||
| 65 | func LoadConfig(config *Config) (*Config, error) { | 74 | func LoadConfig(config *Config) (*Config, error) { | ||
| 66 | // Load env elements | 75 | // Load env elements | ||
| 67 | config.General.BuildVersion = os.Getenv("BUILD_VERSION") | 76 | config.General.BuildVersion = os.Getenv("BUILD_VERSION") | ||
M internal/app/sqliteinit.go +51 -5
| @@ -1,6 +1,7 @@ | |||
| 1 | 1 | package app | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "context" | |
| 4 | 5 | "crypto/md5" | |
| 5 | 6 | "database/sql" | |
| 6 | 7 | "encoding/hex" | |
| @@ -8,8 +9,8 @@ import ( | |||
| 8 | 9 | "os" | |
| 9 | 10 | "path/filepath" | |
| 10 | 11 | ||
| 11 | - | "github.com/bnema/gordon/internal/database/migrate" | |
| 12 | - | _ "github.com/mattn/go-sqlite3" | |
| 12 | + | "github.com/bnema/gordon/internal/db/migrate" | |
| 13 | + | "github.com/mattn/go-sqlite3" | |
| 13 | 14 | ) | |
| 14 | 15 | ||
| 15 | 16 | const ( | |
| @@ -49,6 +50,9 @@ func InitializeDB(a *App) (*sql.DB, error) { | |||
| 49 | 50 | if err != nil { | |
| 50 | 51 | return nil, fmt.Errorf("failed to generate initial checksum: %w", err) | |
| 51 | 52 | } | |
| 53 | + | // Initialize the DB field in the App struct | |
| 54 | + | a.DB = memDb | |
| 55 | + | ||
| 52 | 56 | return memDb, nil | |
| 53 | 57 | } | |
| 54 | 58 | ||
| @@ -90,6 +94,10 @@ func bootstrapDB(dbPath string) error { | |||
| 90 | 94 | return err | |
| 91 | 95 | } | |
| 92 | 96 | ||
| 97 | + | if err := migrate.CreateProviderTable(db); err != nil { | |
| 98 | + | return err | |
| 99 | + | } | |
| 100 | + | ||
| 93 | 101 | return nil | |
| 94 | 102 | } | |
| 95 | 103 | ||
| @@ -100,17 +108,55 @@ func loadDBToMemory(dbPath string) (*sql.DB, error) { | |||
| 100 | 108 | return nil, err | |
| 101 | 109 | } | |
| 102 | 110 | ||
| 103 | - | _, err = memDb.Exec("ATTACH DATABASE '" + dbPath + "' AS diskdb") | |
| 111 | + | diskDb, err := sql.Open("sqlite3", dbPath) | |
| 112 | + | if err != nil { | |
| 113 | + | return nil, err | |
| 114 | + | } | |
| 115 | + | defer diskDb.Close() | |
| 116 | + | ||
| 117 | + | // Get a single connection from each *sql.DB | |
| 118 | + | memConn, err := memDb.Conn(context.Background()) | |
| 104 | 119 | if err != nil { | |
| 105 | 120 | return nil, err | |
| 106 | 121 | } | |
| 122 | + | defer memConn.Close() | |
| 107 | 123 | ||
| 108 | - | _, err = memDb.Exec("CREATE TABLE users AS SELECT * FROM diskdb.users") | |
| 124 | + | diskConn, err := diskDb.Conn(context.Background()) | |
| 109 | 125 | if err != nil { | |
| 110 | 126 | return nil, err | |
| 111 | 127 | } | |
| 128 | + | defer diskConn.Close() | |
| 129 | + | ||
| 130 | + | err = memConn.Raw(func(driverConn interface{}) error { | |
| 131 | + | memSqliteConn, ok := driverConn.(*sqlite3.SQLiteConn) | |
| 132 | + | if !ok { | |
| 133 | + | return fmt.Errorf("failed to cast to *sqlite3.SQLiteConn") | |
| 134 | + | } | |
| 135 | + | ||
| 136 | + | return diskConn.Raw(func(driverConn interface{}) error { | |
| 137 | + | diskSqliteConn, ok := driverConn.(*sqlite3.SQLiteConn) | |
| 138 | + | if !ok { | |
| 139 | + | return fmt.Errorf("failed to cast to *sqlite3.SQLiteConn") | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | backup, err := memSqliteConn.Backup("main", diskSqliteConn, "main") | |
| 143 | + | if err != nil { | |
| 144 | + | return err | |
| 145 | + | } | |
| 146 | + | defer backup.Close() | |
| 147 | + | ||
| 148 | + | done, err := backup.Step(-1) | |
| 149 | + | if err != nil { | |
| 150 | + | return err | |
| 151 | + | } | |
| 152 | + | if !done { | |
| 153 | + | return fmt.Errorf("backup not fully completed") | |
| 154 | + | } | |
| 155 | + | ||
| 156 | + | return backup.Finish() | |
| 157 | + | }) | |
| 158 | + | }) | |
| 112 | 159 | ||
| 113 | - | _, err = memDb.Exec("DETACH DATABASE diskdb") | |
| 114 | 160 | if err != nil { | |
| 115 | 161 | return nil, err | |
| 116 | 162 | } | |
| @@ -1,6 +1,7 @@ | |||||
| 1 | package app | 1 | package app | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "context" | |||
| 4 | "crypto/md5" | 5 | "crypto/md5" | ||
| 5 | "database/sql" | 6 | "database/sql" | ||
| 6 | "encoding/hex" | 7 | "encoding/hex" | ||
| @@ -8,8 +9,8 @@ import ( | |||||
| 8 | "os" | 9 | "os" | ||
| 9 | "path/filepath" | 10 | "path/filepath" | ||
| 10 | 11 | ||||
| 11 | - | "github.com/bnema/gordon/internal/database/migrate" | 12 | + | "github.com/bnema/gordon/internal/db/migrate" |
| 12 | - | _ "github.com/mattn/go-sqlite3" | 13 | + | "github.com/mattn/go-sqlite3" |
| 13 | ) | 14 | ) | ||
| 14 | 15 | ||||
| 15 | const ( | 16 | const ( | ||
| @@ -49,6 +50,9 @@ func InitializeDB(a *App) (*sql.DB, error) { | |||||
| 49 | if err != nil { | 50 | if err != nil { | ||
| 50 | return nil, fmt.Errorf("failed to generate initial checksum: %w", err) | 51 | return nil, fmt.Errorf("failed to generate initial checksum: %w", err) | ||
| 51 | } | 52 | } | ||
| 53 | + | // Initialize the DB field in the App struct | |||
| 54 | + | a.DB = memDb | |||
| 55 | + | ||||
| 52 | return memDb, nil | 56 | return memDb, nil | ||
| 53 | } | 57 | } | ||
| 54 | 58 | ||||
| @@ -90,6 +94,10 @@ func bootstrapDB(dbPath string) error { | |||||
| 90 | return err | 94 | return err | ||
| 91 | } | 95 | } | ||
| 92 | 96 | ||||
| 97 | + | if err := migrate.CreateProviderTable(db); err != nil { | |||
| 98 | + | return err | |||
| 99 | + | } | |||
| 100 | + | ||||
| 93 | return nil | 101 | return nil | ||
| 94 | } | 102 | } | ||
| 95 | 103 | ||||
| @@ -100,17 +108,55 @@ func loadDBToMemory(dbPath string) (*sql.DB, error) { | |||||
| 100 | return nil, err | 108 | return nil, err | ||
| 101 | } | 109 | } | ||
| 102 | 110 | ||||
| 103 | - | _, err = memDb.Exec("ATTACH DATABASE '" + dbPath + "' AS diskdb") | 111 | + | diskDb, err := sql.Open("sqlite3", dbPath) |
| 112 | + | if err != nil { | |||
| 113 | + | return nil, err | |||
| 114 | + | } | |||
| 115 | + | defer diskDb.Close() | |||
| 116 | + | ||||
| 117 | + | // Get a single connection from each *sql.DB | |||
| 118 | + | memConn, err := memDb.Conn(context.Background()) | |||
| 104 | if err != nil { | 119 | if err != nil { | ||
| 105 | return nil, err | 120 | return nil, err | ||
| 106 | } | 121 | } | ||
| 122 | + | defer memConn.Close() | |||
| 107 | 123 | ||||
| 108 | - | _, err = memDb.Exec("CREATE TABLE users AS SELECT * FROM diskdb.users") | 124 | + | diskConn, err := diskDb.Conn(context.Background()) |
| 109 | if err != nil { | 125 | if err != nil { | ||
| 110 | return nil, err | 126 | return nil, err | ||
| 111 | } | 127 | } | ||
| 128 | + | defer diskConn.Close() | |||
| 129 | + | ||||
| 130 | + | err = memConn.Raw(func(driverConn interface{}) error { | |||
| 131 | + | memSqliteConn, ok := driverConn.(*sqlite3.SQLiteConn) | |||
| 132 | + | if !ok { | |||
| 133 | + | return fmt.Errorf("failed to cast to *sqlite3.SQLiteConn") | |||
| 134 | + | } | |||
| 135 | + | ||||
| 136 | + | return diskConn.Raw(func(driverConn interface{}) error { | |||
| 137 | + | diskSqliteConn, ok := driverConn.(*sqlite3.SQLiteConn) | |||
| 138 | + | if !ok { | |||
| 139 | + | return fmt.Errorf("failed to cast to *sqlite3.SQLiteConn") | |||
| 140 | + | } | |||
| 141 | + | ||||
| 142 | + | backup, err := memSqliteConn.Backup("main", diskSqliteConn, "main") | |||
| 143 | + | if err != nil { | |||
| 144 | + | return err | |||
| 145 | + | } | |||
| 146 | + | defer backup.Close() | |||
| 147 | + | ||||
| 148 | + | done, err := backup.Step(-1) | |||
| 149 | + | if err != nil { | |||
| 150 | + | return err | |||
| 151 | + | } | |||
| 152 | + | if !done { | |||
| 153 | + | return fmt.Errorf("backup not fully completed") | |||
| 154 | + | } | |||
| 155 | + | ||||
| 156 | + | return backup.Finish() | |||
| 157 | + | }) | |||
| 158 | + | }) | |||
| 112 | 159 | ||||
| 113 | - | _, err = memDb.Exec("DETACH DATABASE diskdb") | |||
| 114 | if err != nil { | 160 | if err != nil { | ||
| 115 | return nil, err | 161 | return nil, err | ||
| 116 | } | 162 | } | ||
M internal/app/tokeninit.go +4 -2
| @@ -9,7 +9,7 @@ import ( | |||
| 9 | 9 | // HandleNewInitialization checks if an admin user exists in db, and if not, generates a token for the initial login and stores it in the config file | |
| 10 | 10 | func (a *App) HandleNewTokenInitialization() (string, error) { | |
| 11 | 11 | // Query to check if a user with id=1 exists | |
| 12 | - | query := `SELECT COUNT(id) FROM users WHERE id = "1"` | |
| 12 | + | query := `SELECT COUNT(id) FROM user WHERE id = "1"` | |
| 13 | 13 | var count int | |
| 14 | 14 | err := a.DB.QueryRow(query).Scan(&count) | |
| 15 | 15 | if err != nil { | |
| @@ -27,7 +27,9 @@ func (a *App) HandleNewTokenInitialization() (string, error) { | |||
| 27 | 27 | return "", fmt.Errorf("failed to generate token: %v", err) | |
| 28 | 28 | } | |
| 29 | 29 | ||
| 30 | - | fmt.Printf("Initial login token: %s\n", token) | |
| 30 | + | // url = config.Http.SubDomain + config.Http.TopDomain + config.Http.Port + config.Admin.Path | |
| 31 | + | ||
| 32 | + | fmt.Printf("Login with the new token: %s\n", token) | |
| 31 | 33 | ||
| 32 | 34 | // Store the token in the config file | |
| 33 | 35 | a.Config.General.GordonToken = token | |
| @@ -9,7 +9,7 @@ import ( | |||||
| 9 | // HandleNewInitialization checks if an admin user exists in db, and if not, generates a token for the initial login and stores it in the config file | 9 | // HandleNewInitialization checks if an admin user exists in db, and if not, generates a token for the initial login and stores it in the config file | ||
| 10 | func (a *App) HandleNewTokenInitialization() (string, error) { | 10 | func (a *App) HandleNewTokenInitialization() (string, error) { | ||
| 11 | // Query to check if a user with id=1 exists | 11 | // Query to check if a user with id=1 exists | ||
| 12 | - | query := `SELECT COUNT(id) FROM users WHERE id = "1"` | 12 | + | query := `SELECT COUNT(id) FROM user WHERE id = "1"` |
| 13 | var count int | 13 | var count int | ||
| 14 | err := a.DB.QueryRow(query).Scan(&count) | 14 | err := a.DB.QueryRow(query).Scan(&count) | ||
| 15 | if err != nil { | 15 | if err != nil { | ||
| @@ -27,7 +27,9 @@ func (a *App) HandleNewTokenInitialization() (string, error) { | |||||
| 27 | return "", fmt.Errorf("failed to generate token: %v", err) | 27 | return "", fmt.Errorf("failed to generate token: %v", err) | ||
| 28 | } | 28 | } | ||
| 29 | 29 | ||||
| 30 | - | fmt.Printf("Initial login token: %s\n", token) | 30 | + | // url = config.Http.SubDomain + config.Http.TopDomain + config.Http.Port + config.Admin.Path |
| 31 | + | ||||
| 32 | + | fmt.Printf("Login with the new token: %s\n", token) | |||
| 31 | 33 | ||||
| 32 | // Store the token in the config file | 34 | // Store the token in the config file | ||
| 33 | a.Config.General.GordonToken = token | 35 | a.Config.General.GordonToken = token | ||
D internal/database/migrate/bootstrapdb.go +0 -70
| @@ -1,70 +0,0 @@ | |||
| 1 | - | package migrate | |
| 2 | - | ||
| 3 | - | import ( | |
| 4 | - | "database/sql" | |
| 5 | - | "fmt" | |
| 6 | - | ) | |
| 7 | - | ||
| 8 | - | // CreateUserTable creates the 'users' table. | |
| 9 | - | func CreateUserTable(db *sql.DB) error { | |
| 10 | - | createTableSQL := ` | |
| 11 | - | CREATE TABLE users ( | |
| 12 | - | id VARCHAR(255) PRIMARY KEY, | |
| 13 | - | name VARCHAR(255) UNIQUE NOT NULL, | |
| 14 | - | email VARCHAR(255) UNIQUE NOT NULL | |
| 15 | - | );` | |
| 16 | - | ||
| 17 | - | _, err := db.Exec(createTableSQL) | |
| 18 | - | if err != nil { | |
| 19 | - | return fmt.Errorf("failed to create user table: %v", err) | |
| 20 | - | } | |
| 21 | - | ||
| 22 | - | return nil | |
| 23 | - | } | |
| 24 | - | ||
| 25 | - | // CreateAccountTable creates the 'accounts' table. | |
| 26 | - | func CreateAccountTable(db *sql.DB) error { | |
| 27 | - | createTableSQL := ` | |
| 28 | - | CREATE TABLE accounts ( | |
| 29 | - | id VARCHAR(255) PRIMARY KEY, | |
| 30 | - | user_id VARCHAR(255) NOT NULL, | |
| 31 | - | type VARCHAR(50) NOT NULL, | |
| 32 | - | provider VARCHAR(50) NOT NULL, | |
| 33 | - | provider_account_id VARCHAR(255) NOT NULL, | |
| 34 | - | refresh_token VARCHAR(255), | |
| 35 | - | access_token VARCHAR(255), | |
| 36 | - | expires_at DATETIME, | |
| 37 | - | token_type VARCHAR(50), | |
| 38 | - | scope VARCHAR(255), | |
| 39 | - | id_token VARCHAR(255), | |
| 40 | - | FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE | |
| 41 | - | );` | |
| 42 | - | ||
| 43 | - | _, err := db.Exec(createTableSQL) | |
| 44 | - | if err != nil { | |
| 45 | - | return fmt.Errorf("failed to create account table: %v", err) | |
| 46 | - | } | |
| 47 | - | ||
| 48 | - | return nil | |
| 49 | - | } | |
| 50 | - | ||
| 51 | - | // CreateSessionTable creates the 'sessions' table. | |
| 52 | - | func CreateSessionTable(db *sql.DB) error { | |
| 53 | - | createTableSQL := ` | |
| 54 | - | CREATE TABLE sessions ( | |
| 55 | - | id VARCHAR(255) PRIMARY KEY, | |
| 56 | - | session_token VARCHAR(255) UNIQUE NOT NULL, | |
| 57 | - | user_id VARCHAR(255) NOT NULL, | |
| 58 | - | expires DATETIME, | |
| 59 | - | is_online BOOLEAN DEFAULT FALSE, | |
| 60 | - | FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE | |
| 61 | - | ); | |
| 62 | - | CREATE INDEX idx_sessions_user_id ON sessions(user_id);` | |
| 63 | - | ||
| 64 | - | _, err := db.Exec(createTableSQL) | |
| 65 | - | if err != nil { | |
| 66 | - | return fmt.Errorf("failed to create session table: %v", err) | |
| 67 | - | } | |
| 68 | - | ||
| 69 | - | return nil | |
| 70 | - | } | |
| @@ -1,70 +0,0 @@ | |||||
| 1 | - | package migrate | |||
| 2 | - | ||||
| 3 | - | import ( | |||
| 4 | - | "database/sql" | |||
| 5 | - | "fmt" | |||
| 6 | - | ) | |||
| 7 | - | ||||
| 8 | - | // CreateUserTable creates the 'users' table. | |||
| 9 | - | func CreateUserTable(db *sql.DB) error { | |||
| 10 | - | createTableSQL := ` | |||
| 11 | - | CREATE TABLE users ( | |||
| 12 | - | id VARCHAR(255) PRIMARY KEY, | |||
| 13 | - | name VARCHAR(255) UNIQUE NOT NULL, | |||
| 14 | - | email VARCHAR(255) UNIQUE NOT NULL | |||
| 15 | - | );` | |||
| 16 | - | ||||
| 17 | - | _, err := db.Exec(createTableSQL) | |||
| 18 | - | if err != nil { | |||
| 19 | - | return fmt.Errorf("failed to create user table: %v", err) | |||
| 20 | - | } | |||
| 21 | - | ||||
| 22 | - | return nil | |||
| 23 | - | } | |||
| 24 | - | ||||
| 25 | - | // CreateAccountTable creates the 'accounts' table. | |||
| 26 | - | func CreateAccountTable(db *sql.DB) error { | |||
| 27 | - | createTableSQL := ` | |||
| 28 | - | CREATE TABLE accounts ( | |||
| 29 | - | id VARCHAR(255) PRIMARY KEY, | |||
| 30 | - | user_id VARCHAR(255) NOT NULL, | |||
| 31 | - | type VARCHAR(50) NOT NULL, | |||
| 32 | - | provider VARCHAR(50) NOT NULL, | |||
| 33 | - | provider_account_id VARCHAR(255) NOT NULL, | |||
| 34 | - | refresh_token VARCHAR(255), | |||
| 35 | - | access_token VARCHAR(255), | |||
| 36 | - | expires_at DATETIME, | |||
| 37 | - | token_type VARCHAR(50), | |||
| 38 | - | scope VARCHAR(255), | |||
| 39 | - | id_token VARCHAR(255), | |||
| 40 | - | FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE | |||
| 41 | - | );` | |||
| 42 | - | ||||
| 43 | - | _, err := db.Exec(createTableSQL) | |||
| 44 | - | if err != nil { | |||
| 45 | - | return fmt.Errorf("failed to create account table: %v", err) | |||
| 46 | - | } | |||
| 47 | - | ||||
| 48 | - | return nil | |||
| 49 | - | } | |||
| 50 | - | ||||
| 51 | - | // CreateSessionTable creates the 'sessions' table. | |||
| 52 | - | func CreateSessionTable(db *sql.DB) error { | |||
| 53 | - | createTableSQL := ` | |||
| 54 | - | CREATE TABLE sessions ( | |||
| 55 | - | id VARCHAR(255) PRIMARY KEY, | |||
| 56 | - | session_token VARCHAR(255) UNIQUE NOT NULL, | |||
| 57 | - | user_id VARCHAR(255) NOT NULL, | |||
| 58 | - | expires DATETIME, | |||
| 59 | - | is_online BOOLEAN DEFAULT FALSE, | |||
| 60 | - | FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE | |||
| 61 | - | ); | |||
| 62 | - | CREATE INDEX idx_sessions_user_id ON sessions(user_id);` | |||
| 63 | - | ||||
| 64 | - | _, err := db.Exec(createTableSQL) | |||
| 65 | - | if err != nil { | |||
| 66 | - | return fmt.Errorf("failed to create session table: %v", err) | |||
| 67 | - | } | |||
| 68 | - | ||||
| 69 | - | return nil | |||
| 70 | - | } | |||
A internal/db/migrate/bootstrapdb.go +81 -0
| @@ -0,0 +1,81 @@ | |||
| 1 | + | package migrate | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "database/sql" | |
| 5 | + | "fmt" | |
| 6 | + | ) | |
| 7 | + | ||
| 8 | + | // CreateUserTable creates the 'users' table. | |
| 9 | + | func CreateUserTable(db *sql.DB) error { | |
| 10 | + | createTableSQL := ` | |
| 11 | + | CREATE TABLE user ( | |
| 12 | + | id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| 13 | + | name TEXT NOT NULL, | |
| 14 | + | email TEXT NOT NULL UNIQUE | |
| 15 | + | );` | |
| 16 | + | ||
| 17 | + | _, err := db.Exec(createTableSQL) | |
| 18 | + | if err != nil { | |
| 19 | + | return fmt.Errorf("failed to create user table: %v", err) | |
| 20 | + | } | |
| 21 | + | ||
| 22 | + | return nil | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | // CreateAccountTable creates the 'accounts' table. | |
| 26 | + | func CreateAccountTable(db *sql.DB) error { | |
| 27 | + | createTableSQL := ` | |
| 28 | + | CREATE TABLE account ( | |
| 29 | + | id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| 30 | + | user_id INTEGER UNIQUE, | |
| 31 | + | FOREIGN KEY (user_id) REFERENCES user(id) | |
| 32 | + | );` | |
| 33 | + | ||
| 34 | + | _, err := db.Exec(createTableSQL) | |
| 35 | + | if err != nil { | |
| 36 | + | return fmt.Errorf("failed to create account table: %v", err) | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | return nil | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | // CreateSessionTable creates the 'sessions' table. | |
| 43 | + | func CreateSessionTable(db *sql.DB) error { | |
| 44 | + | createTableSQL := ` | |
| 45 | + | CREATE TABLE session ( | |
| 46 | + | id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| 47 | + | account_id INTEGER, | |
| 48 | + | session_token TEXT NOT NULL, | |
| 49 | + | expires TEXT NOT NULL, | |
| 50 | + | is_online BOOLEAN, | |
| 51 | + | FOREIGN KEY (account_id) REFERENCES account(id) | |
| 52 | + | );` | |
| 53 | + | ||
| 54 | + | _, err := db.Exec(createTableSQL) | |
| 55 | + | if err != nil { | |
| 56 | + | return fmt.Errorf("failed to create session table: %v", err) | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | return nil | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | // CreateProviderTable creates the 'providers' table. | |
| 63 | + | func CreateProviderTable(db *sql.DB) error { | |
| 64 | + | createTableSQL := ` | |
| 65 | + | CREATE TABLE provider ( | |
| 66 | + | id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| 67 | + | account_id INTEGER, | |
| 68 | + | name TEXT NOT NULL, | |
| 69 | + | access_token TEXT NOT NULL, | |
| 70 | + | refresh_token TEXT, | |
| 71 | + | expires TEXT NOT NULL, | |
| 72 | + | FOREIGN KEY (account_id) REFERENCES account(id) | |
| 73 | + | );` | |
| 74 | + | ||
| 75 | + | _, err := db.Exec(createTableSQL) | |
| 76 | + | if err != nil { | |
| 77 | + | return fmt.Errorf("failed to create provider table: %v", err) | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | return nil | |
| 81 | + | } | |
| @@ -0,0 +1,81 @@ | |||||
| 1 | + | package migrate | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "database/sql" | |||
| 5 | + | "fmt" | |||
| 6 | + | ) | |||
| 7 | + | ||||
| 8 | + | // CreateUserTable creates the 'users' table. | |||
| 9 | + | func CreateUserTable(db *sql.DB) error { | |||
| 10 | + | createTableSQL := ` | |||
| 11 | + | CREATE TABLE user ( | |||
| 12 | + | id INTEGER PRIMARY KEY AUTOINCREMENT, | |||
| 13 | + | name TEXT NOT NULL, | |||
| 14 | + | email TEXT NOT NULL UNIQUE | |||
| 15 | + | );` | |||
| 16 | + | ||||
| 17 | + | _, err := db.Exec(createTableSQL) | |||
| 18 | + | if err != nil { | |||
| 19 | + | return fmt.Errorf("failed to create user table: %v", err) | |||
| 20 | + | } | |||
| 21 | + | ||||
| 22 | + | return nil | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | // CreateAccountTable creates the 'accounts' table. | |||
| 26 | + | func CreateAccountTable(db *sql.DB) error { | |||
| 27 | + | createTableSQL := ` | |||
| 28 | + | CREATE TABLE account ( | |||
| 29 | + | id INTEGER PRIMARY KEY AUTOINCREMENT, | |||
| 30 | + | user_id INTEGER UNIQUE, | |||
| 31 | + | FOREIGN KEY (user_id) REFERENCES user(id) | |||
| 32 | + | );` | |||
| 33 | + | ||||
| 34 | + | _, err := db.Exec(createTableSQL) | |||
| 35 | + | if err != nil { | |||
| 36 | + | return fmt.Errorf("failed to create account table: %v", err) | |||
| 37 | + | } | |||
| 38 | + | ||||
| 39 | + | return nil | |||
| 40 | + | } | |||
| 41 | + | ||||
| 42 | + | // CreateSessionTable creates the 'sessions' table. | |||
| 43 | + | func CreateSessionTable(db *sql.DB) error { | |||
| 44 | + | createTableSQL := ` | |||
| 45 | + | CREATE TABLE session ( | |||
| 46 | + | id INTEGER PRIMARY KEY AUTOINCREMENT, | |||
| 47 | + | account_id INTEGER, | |||
| 48 | + | session_token TEXT NOT NULL, | |||
| 49 | + | expires TEXT NOT NULL, | |||
| 50 | + | is_online BOOLEAN, | |||
| 51 | + | FOREIGN KEY (account_id) REFERENCES account(id) | |||
| 52 | + | );` | |||
| 53 | + | ||||
| 54 | + | _, err := db.Exec(createTableSQL) | |||
| 55 | + | if err != nil { | |||
| 56 | + | return fmt.Errorf("failed to create session table: %v", err) | |||
| 57 | + | } | |||
| 58 | + | ||||
| 59 | + | return nil | |||
| 60 | + | } | |||
| 61 | + | ||||
| 62 | + | // CreateProviderTable creates the 'providers' table. | |||
| 63 | + | func CreateProviderTable(db *sql.DB) error { | |||
| 64 | + | createTableSQL := ` | |||
| 65 | + | CREATE TABLE provider ( | |||
| 66 | + | id INTEGER PRIMARY KEY AUTOINCREMENT, | |||
| 67 | + | account_id INTEGER, | |||
| 68 | + | name TEXT NOT NULL, | |||
| 69 | + | access_token TEXT NOT NULL, | |||
| 70 | + | refresh_token TEXT, | |||
| 71 | + | expires TEXT NOT NULL, | |||
| 72 | + | FOREIGN KEY (account_id) REFERENCES account(id) | |||
| 73 | + | );` | |||
| 74 | + | ||||
| 75 | + | _, err := db.Exec(createTableSQL) | |||
| 76 | + | if err != nil { | |||
| 77 | + | return fmt.Errorf("failed to create provider table: %v", err) | |||
| 78 | + | } | |||
| 79 | + | ||||
| 80 | + | return nil | |||
| 81 | + | } | |||
R internal/database/queries/inspectdb.go → internal/db/queries/inspectdb.go +25 -6 renamed, 58% similar
| @@ -1,12 +1,14 @@ | |||
| 1 | 1 | package queries | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | - | "database/sql" | |
| 5 | 4 | "fmt" | |
| 5 | + | ||
| 6 | + | "github.com/bnema/gordon/internal/app" | |
| 6 | 7 | ) | |
| 7 | 8 | ||
| 8 | 9 | // InspectInMemoryDB inspects the in-memory database. (for debug purpose) | |
| 9 | - | func InspectInMemoryDB(memDb *sql.DB) error { | |
| 10 | + | func InspectInMemoryDB(a *app.App) error { | |
| 11 | + | memDb := a.DB | |
| 10 | 12 | // Query the sqlite_master table to get a list of all tables | |
| 11 | 13 | rows, err := memDb.Query("SELECT name FROM sqlite_master WHERE type='table'") | |
| 12 | 14 | fmt.Println("rows", rows) | |
| @@ -33,15 +35,14 @@ func InspectInMemoryDB(memDb *sql.DB) error { | |||
| 33 | 35 | for _, table := range tables { | |
| 34 | 36 | fmt.Println(table) | |
| 35 | 37 | } | |
| 36 | - | ||
| 37 | - | // If you want to see the content of a specific table (e.g., users): | |
| 38 | - | userRows, err := memDb.Query("SELECT * FROM users") | |
| 38 | + | // Print the content of the users table | |
| 39 | + | userRows, err := memDb.Query("SELECT * FROM user") | |
| 39 | 40 | if err != nil { | |
| 40 | 41 | return err | |
| 41 | 42 | } | |
| 42 | 43 | defer userRows.Close() | |
| 43 | 44 | ||
| 44 | - | fmt.Println("\nContent of the 'users' table:") | |
| 45 | + | fmt.Println("\nContent of the 'user' table:") | |
| 45 | 46 | for userRows.Next() { | |
| 46 | 47 | // Assuming users table has columns: id, name | |
| 47 | 48 | var id int | |
| @@ -52,5 +53,23 @@ func InspectInMemoryDB(memDb *sql.DB) error { | |||
| 52 | 53 | fmt.Printf("ID: %d, Name: %s\n", id, name) | |
| 53 | 54 | } | |
| 54 | 55 | ||
| 56 | + | // Print the content of accounts table | |
| 57 | + | accountRows, err := memDb.Query("SELECT * FROM account") | |
| 58 | + | if err != nil { | |
| 59 | + | return err | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | fmt.Println("\nContent of the 'account' table:") | |
| 63 | + | for accountRows.Next() { | |
| 64 | + | // Assuming accounts table has columns: id, name, user_id | |
| 65 | + | var id int | |
| 66 | + | var name string | |
| 67 | + | var userID int | |
| 68 | + | if err := accountRows.Scan(&id, &name, &userID); err != nil { | |
| 69 | + | return err | |
| 70 | + | } | |
| 71 | + | fmt.Printf("ID: %d, Name: %s, UserID: %d\n", id, name, userID) | |
| 72 | + | } | |
| 73 | + | ||
| 55 | 74 | return nil | |
| 56 | 75 | } | |
| @@ -1,12 +1,14 @@ | |||||
| 1 | package queries | 1 | package queries | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | - | "database/sql" | |||
| 5 | "fmt" | 4 | "fmt" | ||
| 5 | + | ||||
| 6 | + | "github.com/bnema/gordon/internal/app" | |||
| 6 | ) | 7 | ) | ||
| 7 | 8 | ||||
| 8 | // InspectInMemoryDB inspects the in-memory database. (for debug purpose) | 9 | // InspectInMemoryDB inspects the in-memory database. (for debug purpose) | ||
| 9 | - | func InspectInMemoryDB(memDb *sql.DB) error { | 10 | + | func InspectInMemoryDB(a *app.App) error { |
| 11 | + | memDb := a.DB | |||
| 10 | // Query the sqlite_master table to get a list of all tables | 12 | // Query the sqlite_master table to get a list of all tables | ||
| 11 | rows, err := memDb.Query("SELECT name FROM sqlite_master WHERE type='table'") | 13 | rows, err := memDb.Query("SELECT name FROM sqlite_master WHERE type='table'") | ||
| 12 | fmt.Println("rows", rows) | 14 | fmt.Println("rows", rows) | ||
| @@ -33,15 +35,14 @@ func InspectInMemoryDB(memDb *sql.DB) error { | |||||
| 33 | for _, table := range tables { | 35 | for _, table := range tables { | ||
| 34 | fmt.Println(table) | 36 | fmt.Println(table) | ||
| 35 | } | 37 | } | ||
| 36 | - | 38 | + | // Print the content of the users table | |
| 37 | - | // If you want to see the content of a specific table (e.g., users): | 39 | + | userRows, err := memDb.Query("SELECT * FROM user") |
| 38 | - | userRows, err := memDb.Query("SELECT * FROM users") | |||
| 39 | if err != nil { | 40 | if err != nil { | ||
| 40 | return err | 41 | return err | ||
| 41 | } | 42 | } | ||
| 42 | defer userRows.Close() | 43 | defer userRows.Close() | ||
| 43 | 44 | ||||
| 44 | - | fmt.Println("\nContent of the 'users' table:") | 45 | + | fmt.Println("\nContent of the 'user' table:") |
| 45 | for userRows.Next() { | 46 | for userRows.Next() { | ||
| 46 | // Assuming users table has columns: id, name | 47 | // Assuming users table has columns: id, name | ||
| 47 | var id int | 48 | var id int | ||
| @@ -52,5 +53,23 @@ func InspectInMemoryDB(memDb *sql.DB) error { | |||||
| 52 | fmt.Printf("ID: %d, Name: %s\n", id, name) | 53 | fmt.Printf("ID: %d, Name: %s\n", id, name) | ||
| 53 | } | 54 | } | ||
| 54 | 55 | ||||
| 56 | + | // Print the content of accounts table | |||
| 57 | + | accountRows, err := memDb.Query("SELECT * FROM account") | |||
| 58 | + | if err != nil { | |||
| 59 | + | return err | |||
| 60 | + | } | |||
| 61 | + | ||||
| 62 | + | fmt.Println("\nContent of the 'account' table:") | |||
| 63 | + | for accountRows.Next() { | |||
| 64 | + | // Assuming accounts table has columns: id, name, user_id | |||
| 65 | + | var id int | |||
| 66 | + | var name string | |||
| 67 | + | var userID int | |||
| 68 | + | if err := accountRows.Scan(&id, &name, &userID); err != nil { | |||
| 69 | + | return err | |||
| 70 | + | } | |||
| 71 | + | fmt.Printf("ID: %d, Name: %s, UserID: %d\n", id, name, userID) | |||
| 72 | + | } | |||
| 73 | + | ||||
| 55 | return nil | 74 | return nil | ||
| 56 | } | 75 | } | ||
R internal/database/queries/userdb.go → internal/db/queries/userdb.go +13 -3 renamed, 72% similar
| @@ -1,9 +1,8 @@ | |||
| 1 | 1 | package queries | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | - | "database/sql" | |
| 5 | - | ||
| 6 | 4 | "github.com/bnema/gordon/internal/app" | |
| 5 | + | "github.com/bnema/gordon/internal/db" | |
| 7 | 6 | ) | |
| 8 | 7 | ||
| 9 | 8 | // Pseudo-code for all queries that will be needed on the user database | |
| @@ -13,7 +12,8 @@ import ( | |||
| 13 | 12 | // 1. Create a user (one only, if id=1 then it's the admin) | |
| 14 | 13 | // This happens when the user logs in for the first time | |
| 15 | 14 | // TODO: prevent anyone from creating another user | |
| 16 | - | func CreateUser(a *app.App, db *sql.DB) error { | |
| 15 | + | func CreateUser(a *app.App) error { | |
| 16 | + | ||
| 17 | 17 | // To create a user i need to : | |
| 18 | 18 | // 1. check if gordontoken == gordontoken inside the yaml config file | |
| 19 | 19 | // 2. check if user already exists | |
| @@ -24,3 +24,13 @@ func CreateUser(a *app.App, db *sql.DB) error { | |||
| 24 | 24 | // 7. return the user informations (id, name, email, image, is_admin) | |
| 25 | 25 | return nil | |
| 26 | 26 | } | |
| 27 | + | ||
| 28 | + | // GetUserInformations gets the user informations based on the access token of the oauth provider | |
| 29 | + | func GetUserInformations(a *app.App, accessToken string) (db.User, error) { | |
| 30 | + | ||
| 31 | + | InspectInMemoryDB(a) | |
| 32 | + | ||
| 33 | + | user := db.User{} | |
| 34 | + | ||
| 35 | + | return user, nil | |
| 36 | + | } | |
| @@ -1,9 +1,8 @@ | |||||
| 1 | package queries | 1 | package queries | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | - | "database/sql" | |||
| 5 | - | ||||
| 6 | "github.com/bnema/gordon/internal/app" | 4 | "github.com/bnema/gordon/internal/app" | ||
| 5 | + | "github.com/bnema/gordon/internal/db" | |||
| 7 | ) | 6 | ) | ||
| 8 | 7 | ||||
| 9 | // Pseudo-code for all queries that will be needed on the user database | 8 | // Pseudo-code for all queries that will be needed on the user database | ||
| @@ -13,7 +12,8 @@ import ( | |||||
| 13 | // 1. Create a user (one only, if id=1 then it's the admin) | 12 | // 1. Create a user (one only, if id=1 then it's the admin) | ||
| 14 | // This happens when the user logs in for the first time | 13 | // This happens when the user logs in for the first time | ||
| 15 | // TODO: prevent anyone from creating another user | 14 | // TODO: prevent anyone from creating another user | ||
| 16 | - | func CreateUser(a *app.App, db *sql.DB) error { | 15 | + | func CreateUser(a *app.App) error { |
| 16 | + | ||||
| 17 | // To create a user i need to : | 17 | // To create a user i need to : | ||
| 18 | // 1. check if gordontoken == gordontoken inside the yaml config file | 18 | // 1. check if gordontoken == gordontoken inside the yaml config file | ||
| 19 | // 2. check if user already exists | 19 | // 2. check if user already exists | ||
| @@ -24,3 +24,13 @@ func CreateUser(a *app.App, db *sql.DB) error { | |||||
| 24 | // 7. return the user informations (id, name, email, image, is_admin) | 24 | // 7. return the user informations (id, name, email, image, is_admin) | ||
| 25 | return nil | 25 | return nil | ||
| 26 | } | 26 | } | ||
| 27 | + | ||||
| 28 | + | // GetUserInformations gets the user informations based on the access token of the oauth provider | |||
| 29 | + | func GetUserInformations(a *app.App, accessToken string) (db.User, error) { | |||
| 30 | + | ||||
| 31 | + | InspectInMemoryDB(a) | |||
| 32 | + | ||||
| 33 | + | user := db.User{} | |||
| 34 | + | ||||
| 35 | + | return user, nil | |||
| 36 | + | } | |||
A internal/db/struct.go +32 -0
| @@ -0,0 +1,32 @@ | |||
| 1 | + | package db | |
| 2 | + | ||
| 3 | + | type User struct { | |
| 4 | + | ID int64 | |
| 5 | + | Name string | |
| 6 | + | Email string | |
| 7 | + | Account Account | |
| 8 | + | } | |
| 9 | + | ||
| 10 | + | type Account struct { | |
| 11 | + | ID int64 | |
| 12 | + | UserID int64 | |
| 13 | + | Sessions []Session | |
| 14 | + | Providers []Provider | |
| 15 | + | } | |
| 16 | + | ||
| 17 | + | type Session struct { | |
| 18 | + | ID int64 | |
| 19 | + | AccountID int64 | |
| 20 | + | SessionToken string | |
| 21 | + | Expires string | |
| 22 | + | IsOnline bool | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | type Provider struct { | |
| 26 | + | ID int64 | |
| 27 | + | AccountID int64 | |
| 28 | + | Name string | |
| 29 | + | AccessToken string | |
| 30 | + | RefreshToken string | |
| 31 | + | Expires string | |
| 32 | + | } | |
| @@ -0,0 +1,32 @@ | |||||
| 1 | + | package db | |||
| 2 | + | ||||
| 3 | + | type User struct { | |||
| 4 | + | ID int64 | |||
| 5 | + | Name string | |||
| 6 | + | Email string | |||
| 7 | + | Account Account | |||
| 8 | + | } | |||
| 9 | + | ||||
| 10 | + | type Account struct { | |||
| 11 | + | ID int64 | |||
| 12 | + | UserID int64 | |||
| 13 | + | Sessions []Session | |||
| 14 | + | Providers []Provider | |||
| 15 | + | } | |||
| 16 | + | ||||
| 17 | + | type Session struct { | |||
| 18 | + | ID int64 | |||
| 19 | + | AccountID int64 | |||
| 20 | + | SessionToken string | |||
| 21 | + | Expires string | |||
| 22 | + | IsOnline bool | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | type Provider struct { | |||
| 26 | + | ID int64 | |||
| 27 | + | AccountID int64 | |||
| 28 | + | Name string | |||
| 29 | + | AccessToken string | |||
| 30 | + | RefreshToken string | |||
| 31 | + | Expires string | |||
| 32 | + | } | |||
M internal/httpserve/handler/oauth.go +15 -18
| @@ -3,16 +3,16 @@ package handler | |||
| 3 | 3 | import ( | |
| 4 | 4 | "encoding/base64" | |
| 5 | 5 | "fmt" | |
| 6 | + | "log" | |
| 6 | 7 | "net/http" | |
| 7 | 8 | "os" | |
| 8 | - | "strings" | |
| 9 | 9 | ||
| 10 | 10 | "github.com/bnema/gordon/internal/app" | |
| 11 | + | "github.com/bnema/gordon/internal/db/queries" | |
| 11 | 12 | "github.com/bnema/gordon/internal/templates/render" | |
| 12 | 13 | _ "github.com/joho/godotenv/autoload" | |
| 13 | 14 | "github.com/labstack/echo-contrib/session" | |
| 14 | 15 | "github.com/labstack/echo/v4" | |
| 15 | - | "github.com/markbates/goth/gothic" | |
| 16 | 16 | ) | |
| 17 | 17 | ||
| 18 | 18 | // compareGordonToken compares the token from the URL query parameter with the one from the config.yml | |
| @@ -28,7 +28,7 @@ func compareGordonToken(c echo.Context, a *app.App) error { | |||
| 28 | 28 | // Check if there is already a user in the db which means that the admin is already setup | |
| 29 | 29 | func checkAdmin(a *app.App) (bool, error) { | |
| 30 | 30 | var count int | |
| 31 | - | err := a.DB.QueryRow("SELECT COUNT(*) FROM users").Scan(&count) | |
| 31 | + | err := a.DB.QueryRow("SELECT COUNT(*) FROM user").Scan(&count) | |
| 32 | 32 | if err != nil { | |
| 33 | 33 | return false, err | |
| 34 | 34 | } | |
| @@ -94,25 +94,28 @@ func StartOAuthGithub(c echo.Context, a *app.App) error { | |||
| 94 | 94 | } | |
| 95 | 95 | ||
| 96 | 96 | func OAuthCallback(c echo.Context, a *app.App) error { | |
| 97 | + | ||
| 97 | 98 | accessToken := c.QueryParam("access_token") | |
| 98 | 99 | encodedState := c.QueryParam("state") | |
| 99 | 100 | ||
| 100 | 101 | // Decode the state parameter to get the original redirectDomain | |
| 101 | - | decodedState, err := base64.StdEncoding.DecodeString(encodedState) | |
| 102 | + | _, err := base64.StdEncoding.DecodeString(encodedState) | |
| 102 | 103 | if err != nil { | |
| 103 | 104 | return c.String(http.StatusBadRequest, "Invalid state parameter") | |
| 104 | 105 | } | |
| 105 | 106 | ||
| 106 | - | state := string(decodedState) | |
| 107 | - | parts := strings.SplitN(state, ":", 2) | |
| 108 | - | if len(parts) != 2 || parts[0] != "redirectDomain" { | |
| 109 | - | return c.String(http.StatusBadRequest, "Invalid state format") | |
| 107 | + | // Get the user information from the OAuth provider | |
| 108 | + | userInfo, err := queries.GetUserInformations(a, accessToken) | |
| 109 | + | if err != nil { | |
| 110 | + | log.Printf("Failed to get user information: %v\n", err) | |
| 111 | + | return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get user information") | |
| 110 | 112 | } | |
| 111 | 113 | ||
| 112 | - | // Retrieve the original redirectDomain | |
| 113 | - | redirectDomain := parts[1] | |
| 114 | - | fmt.Print(redirectDomain) | |
| 115 | - | // Now, you can use accessToken for whatever you need in your application | |
| 114 | + | fmt.Printf("User info: %+v\n", userInfo) | |
| 115 | + | ||
| 116 | + | // If the user does not exist, create it | |
| 117 | + | ||
| 118 | + | // If the user exists, update the access_token for this provider | |
| 116 | 119 | ||
| 117 | 120 | // Also, you can save the authentication state in the session | |
| 118 | 121 | sess, err := session.Get("session", c) | |
| @@ -129,9 +132,3 @@ func OAuthCallback(c echo.Context, a *app.App) error { | |||
| 129 | 132 | ||
| 130 | 133 | return c.Redirect(http.StatusFound, "/admin") | |
| 131 | 134 | } | |
| 132 | - | ||
| 133 | - | // Logout handles user logout and clears session | |
| 134 | - | func Logout(c echo.Context, a *app.App) error { | |
| 135 | - | gothic.Logout(c.Response(), c.Request()) | |
| 136 | - | return c.Redirect(http.StatusMovedPermanently, "/") | |
| 137 | - | } | |
| @@ -3,16 +3,16 @@ package handler | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "encoding/base64" | 4 | "encoding/base64" | ||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | + | "log" | |||
| 6 | "net/http" | 7 | "net/http" | ||
| 7 | "os" | 8 | "os" | ||
| 8 | - | "strings" | |||
| 9 | 9 | ||||
| 10 | "github.com/bnema/gordon/internal/app" | 10 | "github.com/bnema/gordon/internal/app" | ||
| 11 | + | "github.com/bnema/gordon/internal/db/queries" | |||
| 11 | "github.com/bnema/gordon/internal/templates/render" | 12 | "github.com/bnema/gordon/internal/templates/render" | ||
| 12 | _ "github.com/joho/godotenv/autoload" | 13 | _ "github.com/joho/godotenv/autoload" | ||
| 13 | "github.com/labstack/echo-contrib/session" | 14 | "github.com/labstack/echo-contrib/session" | ||
| 14 | "github.com/labstack/echo/v4" | 15 | "github.com/labstack/echo/v4" | ||
| 15 | - | "github.com/markbates/goth/gothic" | |||
| 16 | ) | 16 | ) | ||
| 17 | 17 | ||||
| 18 | // compareGordonToken compares the token from the URL query parameter with the one from the config.yml | 18 | // compareGordonToken compares the token from the URL query parameter with the one from the config.yml | ||
| @@ -28,7 +28,7 @@ func compareGordonToken(c echo.Context, a *app.App) error { | |||||
| 28 | // Check if there is already a user in the db which means that the admin is already setup | 28 | // Check if there is already a user in the db which means that the admin is already setup | ||
| 29 | func checkAdmin(a *app.App) (bool, error) { | 29 | func checkAdmin(a *app.App) (bool, error) { | ||
| 30 | var count int | 30 | var count int | ||
| 31 | - | err := a.DB.QueryRow("SELECT COUNT(*) FROM users").Scan(&count) | 31 | + | err := a.DB.QueryRow("SELECT COUNT(*) FROM user").Scan(&count) |
| 32 | if err != nil { | 32 | if err != nil { | ||
| 33 | return false, err | 33 | return false, err | ||
| 34 | } | 34 | } | ||
| @@ -94,25 +94,28 @@ func StartOAuthGithub(c echo.Context, a *app.App) error { | |||||
| 94 | } | 94 | } | ||
| 95 | 95 | ||||
| 96 | func OAuthCallback(c echo.Context, a *app.App) error { | 96 | func OAuthCallback(c echo.Context, a *app.App) error { | ||
| 97 | + | ||||
| 97 | accessToken := c.QueryParam("access_token") | 98 | accessToken := c.QueryParam("access_token") | ||
| 98 | encodedState := c.QueryParam("state") | 99 | encodedState := c.QueryParam("state") | ||
| 99 | 100 | ||||
| 100 | // Decode the state parameter to get the original redirectDomain | 101 | // Decode the state parameter to get the original redirectDomain | ||
| 101 | - | decodedState, err := base64.StdEncoding.DecodeString(encodedState) | 102 | + | _, err := base64.StdEncoding.DecodeString(encodedState) |
| 102 | if err != nil { | 103 | if err != nil { | ||
| 103 | return c.String(http.StatusBadRequest, "Invalid state parameter") | 104 | return c.String(http.StatusBadRequest, "Invalid state parameter") | ||
| 104 | } | 105 | } | ||
| 105 | 106 | ||||
| 106 | - | state := string(decodedState) | 107 | + | // Get the user information from the OAuth provider |
| 107 | - | parts := strings.SplitN(state, ":", 2) | 108 | + | userInfo, err := queries.GetUserInformations(a, accessToken) |
| 108 | - | if len(parts) != 2 || parts[0] != "redirectDomain" { | 109 | + | if err != nil { |
| 109 | - | return c.String(http.StatusBadRequest, "Invalid state format") | 110 | + | log.Printf("Failed to get user information: %v\n", err) |
| 111 | + | return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get user information") | |||
| 110 | } | 112 | } | ||
| 111 | 113 | ||||
| 112 | - | // Retrieve the original redirectDomain | 114 | + | fmt.Printf("User info: %+v\n", userInfo) |
| 113 | - | redirectDomain := parts[1] | 115 | + | |
| 114 | - | fmt.Print(redirectDomain) | 116 | + | // If the user does not exist, create it |
| 115 | - | // Now, you can use accessToken for whatever you need in your application | 117 | + | |
| 118 | + | // If the user exists, update the access_token for this provider | |||
| 116 | 119 | ||||
| 117 | // Also, you can save the authentication state in the session | 120 | // Also, you can save the authentication state in the session | ||
| 118 | sess, err := session.Get("session", c) | 121 | sess, err := session.Get("session", c) | ||
| @@ -129,9 +132,3 @@ func OAuthCallback(c echo.Context, a *app.App) error { | |||||
| 129 | 132 | ||||
| 130 | return c.Redirect(http.StatusFound, "/admin") | 133 | return c.Redirect(http.StatusFound, "/admin") | ||
| 131 | } | 134 | } | ||
| 132 | - | ||||
| 133 | - | // Logout handles user logout and clears session | |||
| 134 | - | func Logout(c echo.Context, a *app.App) error { | |||
| 135 | - | gothic.Logout(c.Response(), c.Request()) | |||
| 136 | - | return c.Redirect(http.StatusMovedPermanently, "/") | |||
| 137 | - | } | |||
M internal/httpserve/router.go +1 -1
| @@ -68,7 +68,7 @@ func bindLoginRoute(e *echo.Echo, a *app.App, adminPath string) { | |||
| 68 | 68 | return handler.OAuthCallback(c, a) | |
| 69 | 69 | }) | |
| 70 | 70 | e.GET(adminPath+"/logout", func(c echo.Context) error { | |
| 71 | - | return handler.Logout(c, a) | |
| 71 | + | return c.HTML(200, "<h1>Logout</h1>") | |
| 72 | 72 | }) | |
| 73 | 73 | } | |
| 74 | 74 | ||
| @@ -68,7 +68,7 @@ func bindLoginRoute(e *echo.Echo, a *app.App, adminPath string) { | |||||
| 68 | return handler.OAuthCallback(c, a) | 68 | return handler.OAuthCallback(c, a) | ||
| 69 | }) | 69 | }) | ||
| 70 | e.GET(adminPath+"/logout", func(c echo.Context) error { | 70 | e.GET(adminPath+"/logout", func(c echo.Context) error { | ||
| 71 | - | return handler.Logout(c, a) | 71 | + | return c.HTML(200, "<h1>Logout</h1>") |
| 72 | }) | 72 | }) | ||
| 73 | } | 73 | } | ||
| 74 | 74 | ||||