Skip to content

Commit

Permalink
wip: providers and adapters interface
Browse files Browse the repository at this point in the history
  • Loading branch information
katallaxie committed Feb 7, 2024
1 parent 14227df commit 4ff6785
Show file tree
Hide file tree
Showing 12 changed files with 1,597 additions and 270 deletions.
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ generate: ## Generate code.
fmt: ## Run go fmt against code.
$(GO_RUN_TOOLS) mvdan.cc/gofumpt -w .

.PHONY: generate
generate: ## Generate code.
$(GO) generate ./...
$(GO_RUN_TOOLS) github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -config ./examples/api/config.models.yml ./examples/api/api.yml
$(GO_RUN_TOOLS) github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -config ./examples/api/config.client.yml ./examples/api/api.yml
$(GO_RUN_TOOLS) github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -config ./examples/api/config.server.yml ./examples/api/api.yml

.PHONY: vet
vet: ## Run go vet against code.
$(GO) vet ./...
Expand Down
184 changes: 184 additions & 0 deletions adapters/adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package adapters

import (
"encoding/gob"
"errors"
"time"

"github.com/google/uuid"
)

func init() {
gob.Register(&Account{})
gob.Register(&User{})
gob.Register(&Session{})
gob.Register(&VerificationToken{})
}

// AccountType represents the type of an account.
type AccountType string

// ErrUnimplemented is returned when a method is not implemented.
var ErrUnimplemented = errors.New("not implemented")

const (
// AccountTypeOAuth2 represents an OAuth2 account type.
AccountTypeOAuth2 AccountType = "oauth2"
// AccountTypeOIDC represents an OIDC account type.
AccountTypeOIDC AccountType = "oidc"
// AccountTypeSAML represents a SAML account type.
AccountTypeSAML AccountType = "saml"
// AccountTypeEmail represents an email account type.
AccountTypeEmail AccountType = "email"
// AccountTypeWebAuthn represents a WebAuthn account type.
AccountTypeWebAuthn AccountType = "webauthn"
)

// Account represents an account.
type Account struct {
ID uuid.UUID `json:"id"`
Type AccountType `json:"type"`
Provider string `json:"provider"`
ProviderAccountID string `json:"provider_account_id"`
RefreshToken string `json:"refresh_token"`
AccessToken string `json:"access_token"`
ExpiresAt time.Time `json:"expires_at"`
TokenType string `json:"token_type"`
Scope string `json:"scope"`
IDToken string `json:"id_token"`
SessionState string `json:"session_state"`
UserID uuid.UUID `json:"user_id"`
RawData map[string]interface{} `json:"raw_data"`
}

// User represents a user.
type User struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
EmailVerified string `json:"email_verified"`
Image string `json:"image"`
RawData map[string]interface{} `json:"raw_data"`
}

// Session represents a session.
type Session struct {
ID uuid.UUID `json:"id"`
ExpiresAt time.Time `json:"expires_at"`
SessionToken string `json:"session_token"`
UserID uuid.UUID `json:"user_id"`
}

// VerificationToken represents a verification token.
type VerificationToken struct {
Token string `json:"token"`
Identifier string `json:"identifier"`
ExpiresAt time.Time `json:"expires_at"`
}

// Adapter is an interface that defines the methods for interacting with the underlying data storage.
type Adapter interface {
// CreateAccount creates a new account.
CreateUser(user *User) (*User, error)
// GetUser retrieves a user by ID.
GetUser(ID string) (*User, error)
// GetUserByEmail retrieves a user by email.
GetUserByEmail(email string) (*User, error)
// GetUserByAccount retrieves a user by account.
GetUserByAccount(provider string, providerAccountID string) (*User, error)
// UpdateUser updates a user.
UpdateUser(user *User) (*User, error)
// DeleteUser deletes a user by ID.
DeleteUser(ID string) error
// LinkAccount links an account to a user.
LinkAccount(accountID, userID uuid.UUID) error
// UnlinkAccount unlinks an account from a user.
UnlinkAccount(accountID, userID uuid.UUID) error
// CreateSession creates a new session.
CreateSession(session *Session) (*Session, error)
// GetSession retrieves a session by session token.
GetSession(sessionToken string) (*Session, error)
// UpdateSession updates a session.
UpdateSession(session *Session) (*Session, error)
// DeleteSession deletes a session by session token.
DeleteSession(sessionToken string) error
// CreateVerificationToken creates a new verification token.
CreateVerificationToken(verficationToken *VerificationToken) (*VerificationToken, error)
// UseVerficationToken uses a verification token.
UseVerficationToken(identifier string, token string) (*VerificationToken, error)
}

var _ Adapter = (*UnimplementedAdapter)(nil)

// UnimplementedAdapter is an adapter that does not implement any of the methods.
type UnimplementedAdapter struct{}

// CreateUser creates a new user.
func (a *UnimplementedAdapter) CreateUser(user *User) (*User, error) {
return nil, ErrUnimplemented
}

// GetUser retrieves a user by ID.
func (a *UnimplementedAdapter) GetUser(id string) (*User, error) {
return nil, ErrUnimplemented
}

// GetUserByEmail retrieves a user by email.
func (a *UnimplementedAdapter) GetUserByEmail(email string) (*User, error) {
return nil, ErrUnimplemented
}

// GetUserByAccount retrieves a user by account.
func (a *UnimplementedAdapter) GetUserByAccount(provider string, providerAccountID string) (*User, error) {
return nil, ErrUnimplemented
}

// UpdateUser updates a user.
func (a *UnimplementedAdapter) UpdateUser(user *User) (*User, error) {
return nil, ErrUnimplemented
}

// DeleteUser deletes a user by ID.
func (a *UnimplementedAdapter) DeleteUser(id string) error {
return ErrUnimplemented
}

// LinkAccount links an account to a user.
func (a *UnimplementedAdapter) LinkAccount(accountID, userID uuid.UUID) error {
return ErrUnimplemented
}

// UnlinkAccount unlinks an account from a user.
func (a *UnimplementedAdapter) UnlinkAccount(accountID, userID uuid.UUID) error {
return ErrUnimplemented
}

// CreateSession creates a new session.
func (a *UnimplementedAdapter) CreateSession(session *Session) (*Session, error) {
return nil, ErrUnimplemented
}

// GetSession retrieves a session by session token.
func (a *UnimplementedAdapter) GetSession(sessionToken string) (*Session, error) {
return nil, ErrUnimplemented
}

// UpdateSession updates a session.
func (a *UnimplementedAdapter) UpdateSession(session *Session) (*Session, error) {
return nil, ErrUnimplemented
}

// DeleteSession deletes a session by session token.
func (a *UnimplementedAdapter) DeleteSession(sessionToken string) error {
return ErrUnimplemented
}

// CreateVerificationToken creates a new verification token.
func (a *UnimplementedAdapter) CreateVerificationToken(verficationToken *VerificationToken) (*VerificationToken, error) {
return nil, ErrUnimplemented
}

// UseVerficationToken uses a verification token.
func (a *UnimplementedAdapter) UseVerficationToken(identifier string, token string) (*VerificationToken, error) {
return nil, ErrUnimplemented
}
119 changes: 119 additions & 0 deletions adapters/gorm/gorm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package gorm_adapter

import (
"time"

"github.com/zeiss/fiber-goth/adapters"

"github.com/google/uuid"
"gorm.io/gorm"
)

// RunMigrations ...
func RunMigrations(db *gorm.DB) error {
err := db.AutoMigrate(
&Account{},
&User{},
&Session{},
&VerificationToken{},
)
if err != nil {
return err
}

return nil
}

var _ adapters.Adapter = (*gormAdapter)(nil)

type gormAdapter struct {
db *gorm.DB

adapters.UnimplementedAdapter
}

// New ...
func New(db *gorm.DB) (*gormAdapter, error) {
err := RunMigrations(db)
if err != nil {
return nil, err
}

return &gormAdapter{db, adapters.UnimplementedAdapter{}}, nil
}

// Account ...
type Account struct {
ID uuid.UUID `gorm:"primaryKey;type:uuid;column:id;default:gen_random_uuid();"`
Type string
Provider string
ProviderAccountID *string
RefreshToken *string
AccessToken *string
ExpiresAt *time.Time
TokenType *string
Scope *string
IDToken *string
SessionState string
UserID uuid.UUID
User User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"`

CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}

// User ...
type User struct {
ID uuid.UUID ` gorm:"primaryKey;unique;type:uuid;column:id;default:gen_random_uuid()"`
Name string
Email string
EmailVerified *string
Image *string

CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}

// Session ...
type Session struct {
ID uuid.UUID `gorm:"primaryKey;unique;type:uuid;column:id;default:gen_random_uuid()"`
ExpiresAt time.Time
SessionToken string
UserID uuid.UUID
User User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"`

CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}

// VerificationToken ...
type VerificationToken struct {
Token string `gorm:"primaryKey"`
Identifier string
ExpiresAt time.Time

CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}

// CreateUser ...
func (a *gormAdapter) CreateUser(user *adapters.User) (*adapters.User, error) {
u := User{
Name: user.Name,
Email: user.Email,
EmailVerified: &user.EmailVerified,
Image: &user.Image,
}
u.ID = uuid.New()

err := a.db.Create(&u).Error
if err != nil {
return nil, err
}

return user, nil
}
15 changes: 15 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: '3'

services:
db:
image: postgres
restart: always
environment:
POSTGRES_USER: example
POSTGRES_PASSWORD: example
POSTGRES_DB: example
ports:
- "5432:5432"

volumes:
postgres: {}
Loading

0 comments on commit 4ff6785

Please sign in to comment.