diff --git a/db/db.go b/db/db.go index fa6de28..2cb70ae 100644 --- a/db/db.go +++ b/db/db.go @@ -3,6 +3,7 @@ package db import ( "go-eve-pi/esi" "go-eve-pi/options" + "go-eve-pi/types" "path/filepath" "gorm.io/driver/sqlite" @@ -14,8 +15,8 @@ import ( type DB interface { DB() *gorm.DB Raw(sql string, args ...any) *gorm.DB - GetCharacterByName(characterName string) (*esi.Character, error) - SaveCharacter(character *esi.Character) error + GetCharacterByName(characterName string) (*types.Character, error) + SaveCharacter(character *types.Character) error AutoMigrate(dst ...interface{}) error GetCacheEntry(urlHash string) (*esi.CacheEntry, error) SaveCacheEntry(entry *esi.CacheEntry) error @@ -59,9 +60,9 @@ func (db *DBWrapper) DB() *gorm.DB { return db.db } -func (db *DBWrapper) GetCharacterByName(characterName string) (*esi.Character, error) { +func (db *DBWrapper) GetCharacterByName(characterName string) (*types.Character, error) { logger.Debug("Fetching token for character %s from database", characterName) - var char esi.Character + var char types.Character err := db.db.Where("character_name = ?", characterName).First(&char).Error if err != nil { logger.Debug("No token found for character %s: %v", characterName, err) @@ -71,7 +72,7 @@ func (db *DBWrapper) GetCharacterByName(characterName string) (*esi.Character, e return &char, nil } -func (db *DBWrapper) SaveCharacter(character *esi.Character) error { +func (db *DBWrapper) SaveCharacter(character *types.Character) error { logger.Debug("Saving token for character %s to database", character.CharacterName) err := db.db.Save(character).Error if err != nil { diff --git a/esi/sso.go b/esi/sso.go index 610eab6..42cc0f5 100644 --- a/esi/sso.go +++ b/esi/sso.go @@ -16,6 +16,7 @@ import ( "sync" "time" + "go-eve-pi/types" "gorm.io/gorm" logger "git.site.quack-lab.dev/dave/cylogger" @@ -28,14 +29,11 @@ const ( issuerTokenURL = "https://login.eveonline.com/v2/oauth/token" ) -type Character struct { - ID int64 `gorm:"primaryKey"` // EVE character ID from JWT token - CharacterName string `gorm:"uniqueIndex"` - AccessToken string - RefreshToken string - ExpiresAt time.Time - UpdatedAt time.Time - CreatedAt time.Time +// DB interface for database operations +type DB interface { + GetCharacterByName(characterName string) (*types.Character, error) + SaveCharacter(character *types.Character) error + AutoMigrate(dst ...interface{}) error } type SSO struct { @@ -54,15 +52,9 @@ type SSO struct { } // NewSSO creates a new SSO instance -func NewSSO(clientID, redirectURI string, scopes []string) (*SSO, error) { +func NewSSO(clientID, redirectURI string, scopes []string, db DB) (*SSO, error) { logger.Info("Creating new SSO instance for clientID %s with redirectURI %s and scopes %v", clientID, redirectURI, scopes) - db, err := GetDB() - if err != nil { - logger.Error("Failed to get database connection %v", err) - return nil, err - } - s := &SSO{ clientID: clientID, redirectURI: redirectURI, @@ -88,7 +80,7 @@ func (s *SSO) SetRouter(r *router.Router) { func (s *SSO) initDB() error { logger.Debug("Initializing SSO database schema") - err := s.db.AutoMigrate(&Character{}) + err := s.db.AutoMigrate(&types.Character{}) if err != nil { logger.Error("Failed to migrate Token table %v", err) return err @@ -100,7 +92,7 @@ func (s *SSO) initDB() error { // GetCharacter returns a valid character object for the given character name // If no token exists, it will start the OAuth flow // If token is expired, it will refresh it automatically -func (s *SSO) GetCharacter(ctx context.Context, characterName string) (Character, error) { +func (s *SSO) GetCharacter(ctx context.Context, characterName string) (types.Character, error) { logger.Debug("Getting token for character %s", characterName) s.mu.Lock() defer s.mu.Unlock() @@ -112,16 +104,16 @@ func (s *SSO) GetCharacter(ctx context.Context, characterName string) (Character logger.Info("No existing token found for character %s, starting authentication flow", characterName) // No token exists, need to authenticate if err := s.startAuthFlow(ctx, characterName); err != nil { - return Character{}, err + return types.Character{}, err } // After authentication, fetch the token from DB char, err = s.db.GetCharacterByName(characterName) if err != nil { - return Character{}, err + return types.Character{}, err } logger.Info("Successfully authenticated character %s", characterName) } else { - return Character{}, err + return types.Character{}, err } } else { logger.Debug("Found existing token for character %s, expires at %v", characterName, char.ExpiresAt) @@ -148,12 +140,12 @@ func (s *SSO) GetCharacter(ctx context.Context, characterName string) (Character logger.Warning("Token refresh failed for character %s, re-authenticating: %v", characterName, err) // Refresh failed, need to re-authenticate if err := s.startAuthFlow(ctx, characterName); err != nil { - return Character{}, err + return types.Character{}, err } // After re-authentication, fetch the token from DB char, err = s.db.GetCharacterByName(characterName) if err != nil { - return Character{}, err + return types.Character{}, err } logger.Info("Successfully re-authenticated character %s", characterName) } else { @@ -320,7 +312,7 @@ func (s *SSO) waitForCallback() (code, state string, err error) { } } -func (s *SSO) exchangeCodeForToken(ctx context.Context, code, verifier string) (*Character, error) { +func (s *SSO) exchangeCodeForToken(ctx context.Context, code, verifier string) (*types.Character, error) { logger.Debug("Exchanging authorization code for access token") form := url.Values{} form.Set("grant_type", "authorization_code") @@ -356,7 +348,7 @@ func (s *SSO) exchangeCodeForToken(ctx context.Context, code, verifier string) ( name, _ := parseTokenCharacter(tr.AccessToken) logger.Info("Successfully exchanged code for token, character: %s", name) - return &Character{ + return &types.Character{ CharacterName: name, AccessToken: tr.AccessToken, RefreshToken: tr.RefreshToken, @@ -364,7 +356,7 @@ func (s *SSO) exchangeCodeForToken(ctx context.Context, code, verifier string) ( }, nil } -func (s *SSO) refreshToken(ctx context.Context, char *Character) error { +func (s *SSO) refreshToken(ctx context.Context, char *types.Character) error { logger.Debug("Refreshing token for character %s", char.CharacterName) form := url.Values{} form.Set("grant_type", "refresh_token") diff --git a/routes/routes.go b/routes/routes.go index df2881a..dd061fd 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -4,9 +4,9 @@ import ( "context" "encoding/json" "strconv" - "time" "go-eve-pi/esi" + "go-eve-pi/types" wh "go-eve-pi/webhook" logger "git.site.quack-lab.dev/dave/cylogger" @@ -23,18 +23,7 @@ type RouteHandler struct { // SSOInterface defines the interface for SSO operations type SSOInterface interface { - GetCharacter(ctx context.Context, characterName string) (Character, error) -} - -// Character represents a character with authentication info -type Character struct { - ID int64 `gorm:"primaryKey"` - CharacterName string `gorm:"uniqueIndex"` - AccessToken string - RefreshToken string - ExpiresAt time.Time - UpdatedAt time.Time - CreatedAt time.Time + GetCharacter(ctx context.Context, characterName string) (types.Character, error) } // NewRouteHandler creates a new route handler with dependencies diff --git a/types/character.go b/types/character.go new file mode 100644 index 0000000..acd5d5a --- /dev/null +++ b/types/character.go @@ -0,0 +1,14 @@ +package types + +import "time" + +// Character represents a character with authentication info +type Character struct { + ID int64 `gorm:"primaryKey"` // EVE character ID from JWT token + CharacterName string `gorm:"uniqueIndex"` + AccessToken string + RefreshToken string + ExpiresAt time.Time + UpdatedAt time.Time + CreatedAt time.Time +}