From 0826963742e7070f18c2d12984ac5dfce230bcdb Mon Sep 17 00:00:00 2001 From: dhax Date: Wed, 27 Sep 2017 22:42:43 +0200 Subject: [PATCH] improve documentation --- api/api.go | 4 ++-- api/server.go | 4 ++-- auth/logintoken.go | 16 ++++++++-------- database/accountStore.go | 8 ++++++++ database/admAccountStore.go | 8 ++++++++ database/authStore.go | 9 +++++++++ database/migrate/1_initial.go | 8 ++++---- database/migrate/3_add_profile_table.go | 4 ++-- database/migrate/main.go | 2 ++ database/postgres.go | 2 +- email/auth.go | 4 ++-- email/email.go | 12 ++++++------ logging/logger.go | 11 ++++++++++- models/account.go | 9 +++++++++ models/profile.go | 4 ++++ models/token.go | 3 +++ 16 files changed, 80 insertions(+), 28 deletions(-) diff --git a/api/api.go b/api/api.go index d8dca2b..9042382 100644 --- a/api/api.go +++ b/api/api.go @@ -18,7 +18,7 @@ import ( "github.com/go-chi/render" ) -// NewAPI configures application resources and routes +// NewAPI configures application resources and routes. func NewAPI() (*chi.Mux, error) { logger := logging.NewLogger() @@ -94,7 +94,7 @@ func corsConfig() *cors.Cors { }) } -// SPAHandler serves the public Single Page Application +// SPAHandler serves the public Single Page Application. func SPAHandler(publicDir string) http.HandlerFunc { handler := http.FileServer(http.Dir(publicDir)) diff --git a/api/server.go b/api/server.go index 51af668..9759005 100644 --- a/api/server.go +++ b/api/server.go @@ -11,7 +11,7 @@ import ( "github.com/spf13/viper" ) -// Server provides an http.Server +// Server provides an http.Server. type Server struct { *http.Server } @@ -42,7 +42,7 @@ func NewServer() (*Server, error) { return &Server{&srv}, nil } -// Start runs ListenAndServe on the http.Server with graceful shutdown +// Start runs ListenAndServe on the http.Server with graceful shutdown. func (srv *Server) Start() { log.Println("starting server...") go func() { diff --git a/auth/logintoken.go b/auth/logintoken.go index 89a3e63..b3f3aa2 100644 --- a/auth/logintoken.go +++ b/auth/logintoken.go @@ -12,8 +12,8 @@ var ( errTokenNotFound = errors.New("login token not found") ) -// loginToken is an in-memory saved token referencing an account ID and an expiry date. -type loginToken struct { +// LoginToken is an in-memory saved token referencing an account ID and an expiry date. +type LoginToken struct { Token string AccountID int Expiry time.Time @@ -21,7 +21,7 @@ type loginToken struct { // LoginTokenAuth implements passwordless login authentication flow using temporary in-memory stored tokens. type LoginTokenAuth struct { - token map[string]loginToken + token map[string]LoginToken mux sync.RWMutex loginURL string loginTokenLength int @@ -31,7 +31,7 @@ type LoginTokenAuth struct { // NewLoginTokenAuth configures and returns a LoginToken authentication instance. func NewLoginTokenAuth() (*LoginTokenAuth, error) { a := &LoginTokenAuth{ - token: make(map[string]loginToken), + token: make(map[string]LoginToken), loginURL: viper.GetString("auth_login_url"), loginTokenLength: viper.GetInt("auth_login_token_length"), loginTokenExpiry: viper.GetDuration("auth_login_token_expiry"), @@ -40,8 +40,8 @@ func NewLoginTokenAuth() (*LoginTokenAuth, error) { } // CreateToken creates an in-memory login token referencing account ID. It returns a token containing a random tokenstring and expiry date. -func (a *LoginTokenAuth) CreateToken(id int) loginToken { - lt := loginToken{ +func (a *LoginTokenAuth) CreateToken(id int) LoginToken { + lt := LoginToken{ Token: randStringBytes(a.loginTokenLength), AccountID: id, Expiry: time.Now().Add(time.Minute * a.loginTokenExpiry), @@ -61,14 +61,14 @@ func (a *LoginTokenAuth) GetAccountID(token string) (int, error) { return lt.AccountID, nil } -func (a *LoginTokenAuth) get(token string) (loginToken, bool) { +func (a *LoginTokenAuth) get(token string) (LoginToken, bool) { a.mux.RLock() lt, ok := a.token[token] a.mux.RUnlock() return lt, ok } -func (a *LoginTokenAuth) add(lt loginToken) { +func (a *LoginTokenAuth) add(lt LoginToken) { a.mux.Lock() a.token[lt.Token] = lt a.mux.Unlock() diff --git a/database/accountStore.go b/database/accountStore.go index 7f12af0..048426a 100644 --- a/database/accountStore.go +++ b/database/accountStore.go @@ -5,16 +5,19 @@ import ( "github.com/go-pg/pg" ) +// AccountStore implements database operations for account management by user. type AccountStore struct { db *pg.DB } +// NewAccountStore returns an AccountStore. func NewAccountStore(db *pg.DB) *AccountStore { return &AccountStore{ db: db, } } +// Get an account by ID. func (s *AccountStore) Get(id int) (*models.Account, error) { a := models.Account{ID: id} err := s.db.Model(&a). @@ -24,6 +27,7 @@ func (s *AccountStore) Get(id int) (*models.Account, error) { return &a, err } +// Update an account. func (s *AccountStore) Update(a *models.Account) error { _, err := s.db.Model(a). Column("email", "name"). @@ -31,6 +35,7 @@ func (s *AccountStore) Update(a *models.Account) error { return err } +// Delete an account. func (s *AccountStore) Delete(a *models.Account) error { err := s.db.RunInTransaction(func(tx *pg.Tx) error { if _, err := tx.Model(&models.Token{}). @@ -48,6 +53,7 @@ func (s *AccountStore) Delete(a *models.Account) error { return err } +// UpdateToken updates a jwt refresh token. func (s *AccountStore) UpdateToken(t *models.Token) error { _, err := s.db.Model(t). Column("identifier"). @@ -55,11 +61,13 @@ func (s *AccountStore) UpdateToken(t *models.Token) error { return err } +// DeleteToken deletes a jwt refresh token. func (s *AccountStore) DeleteToken(t *models.Token) error { err := s.db.Delete(t) return err } +// UpdateProfile updates corresponding account profile. func (s *AccountStore) UpdateProfile(p *models.Profile) error { err := s.db.Update(p) return err diff --git a/database/admAccountStore.go b/database/admAccountStore.go index d11450d..35b1efd 100644 --- a/database/admAccountStore.go +++ b/database/admAccountStore.go @@ -8,19 +8,23 @@ import ( ) var ( + // ErrUniqueEmailConstraint provides error message for already registered email address. ErrUniqueEmailConstraint = errors.New("email already registered") ) +// AdmAccountStore implements database operations for account management by admin. type AdmAccountStore struct { db *pg.DB } +// NewAdmAccountStore returns an AccountStore. func NewAdmAccountStore(db *pg.DB) *AdmAccountStore { return &AdmAccountStore{ db: db, } } +// List applies a filter and returns paginated array of matching results and total count. func (s *AdmAccountStore) List(f models.AccountFilter) (*[]models.Account, int, error) { var a []models.Account count, err := s.db.Model(&a). @@ -32,6 +36,7 @@ func (s *AdmAccountStore) List(f models.AccountFilter) (*[]models.Account, int, return &a, count, nil } +// Create creates a new account. func (s *AdmAccountStore) Create(a *models.Account) error { count, _ := s.db.Model(a). Where("email = ?email"). @@ -55,17 +60,20 @@ func (s *AdmAccountStore) Create(a *models.Account) error { return err } +// Get account by ID. func (s *AdmAccountStore) Get(id int) (*models.Account, error) { a := models.Account{ID: id} err := s.db.Select(&a) return &a, err } +// Update account. func (s *AdmAccountStore) Update(a *models.Account) error { err := s.db.Update(a) return err } +// Delete account. func (s *AdmAccountStore) Delete(a *models.Account) error { err := s.db.RunInTransaction(func(tx *pg.Tx) error { if _, err := tx.Model(&models.Token{}). diff --git a/database/authStore.go b/database/authStore.go index 94a8ab7..2b01175 100644 --- a/database/authStore.go +++ b/database/authStore.go @@ -7,16 +7,19 @@ import ( "github.com/go-pg/pg" ) +// AuthStore implements database operations for account authentication. type AuthStore struct { db *pg.DB } +// NewAuthStore return an AuthStore. func NewAuthStore(db *pg.DB) *AuthStore { return &AuthStore{ db: db, } } +// GetByID returns an account by ID. func (s *AuthStore) GetByID(id int) (*models.Account, error) { a := models.Account{ID: id} err := s.db.Model(&a). @@ -26,6 +29,7 @@ func (s *AuthStore) GetByID(id int) (*models.Account, error) { return &a, err } +// GetByEmail returns an account by email. func (s *AuthStore) GetByEmail(e string) (*models.Account, error) { a := models.Account{Email: e} err := s.db.Model(&a). @@ -35,6 +39,7 @@ func (s *AuthStore) GetByEmail(e string) (*models.Account, error) { return &a, err } +// GetByRefreshToken returns an account and refresh token by token identifier. func (s *AuthStore) GetByRefreshToken(t string) (*models.Account, *models.Token, error) { token := models.Token{Token: t} err := s.db.Model(&token). @@ -53,6 +58,7 @@ func (s *AuthStore) GetByRefreshToken(t string) (*models.Account, *models.Token, return &a, &token, err } +// UpdateAccount upates account data related to authentication. func (s *AuthStore) UpdateAccount(a *models.Account) error { _, err := s.db.Model(a). Column("last_login"). @@ -60,6 +66,7 @@ func (s *AuthStore) UpdateAccount(a *models.Account) error { return err } +// SaveRefreshToken creates or updates a refresh token. func (s *AuthStore) SaveRefreshToken(t *models.Token) error { var err error if t.ID == 0 { @@ -70,11 +77,13 @@ func (s *AuthStore) SaveRefreshToken(t *models.Token) error { return err } +// DeleteRefreshToken deletes a refresh token. func (s *AuthStore) DeleteRefreshToken(t *models.Token) error { err := s.db.Delete(t) return err } +// PurgeExpiredToken deletes expired refresh token. func (s *AuthStore) PurgeExpiredToken() error { _, err := s.db.Model(&models.Token{}). Where("expiry < ?", time.Now()). diff --git a/database/migrate/1_initial.go b/database/migrate/1_initial.go index d7b5a07..2952d4e 100644 --- a/database/migrate/1_initial.go +++ b/database/migrate/1_initial.go @@ -6,7 +6,7 @@ import ( "github.com/go-pg/migrations" ) -const AccountTable = ` +const accountTable = ` CREATE TABLE accounts ( id serial NOT NULL, created_at timestamp with time zone NOT NULL DEFAULT current_timestamp, @@ -19,7 +19,7 @@ roles text[] NOT NULL DEFAULT '{"user"}', PRIMARY KEY (id) )` -const TokenTable = ` +const tokenTable = ` CREATE TABLE tokens ( id serial NOT NULL, created_at timestamp with time zone NOT NULL DEFAULT current_timestamp, @@ -34,8 +34,8 @@ PRIMARY KEY (id) func init() { up := []string{ - AccountTable, - TokenTable, + accountTable, + tokenTable, } down := []string{ diff --git a/database/migrate/3_add_profile_table.go b/database/migrate/3_add_profile_table.go index 5876d2a..1100414 100644 --- a/database/migrate/3_add_profile_table.go +++ b/database/migrate/3_add_profile_table.go @@ -6,7 +6,7 @@ import ( "github.com/go-pg/migrations" ) -const ProfileTable = ` +const profileTable = ` CREATE TABLE profiles ( id serial NOT NULL, created_at timestamp with time zone NOT NULL DEFAULT current_timestamp, @@ -23,7 +23,7 @@ INSERT INTO profiles(account_id) VALUES(2); func init() { up := []string{ - ProfileTable, + profileTable, bootstrapAccountProfiles, } diff --git a/database/migrate/main.go b/database/migrate/main.go index 12054f0..51f6f11 100644 --- a/database/migrate/main.go +++ b/database/migrate/main.go @@ -9,6 +9,7 @@ import ( "github.com/go-pg/pg" ) +// Migrate runs go-pg migrations func Migrate(args []string) { db, err := database.DBConn() if err != nil { @@ -33,6 +34,7 @@ func Migrate(args []string) { } +// Reset runs reverts all migrations to version 0 and then applies all migrations to latest func Reset() { db, err := database.DBConn() if err != nil { diff --git a/database/postgres.go b/database/postgres.go index 30eae05..a206baa 100644 --- a/database/postgres.go +++ b/database/postgres.go @@ -9,7 +9,7 @@ import ( "github.com/go-pg/pg" ) -// DBConn returns a postgres connection pool +// DBConn returns a postgres connection pool. func DBConn() (*pg.DB, error) { opts, err := pg.ParseURL(viper.GetString("database_url")) diff --git a/email/auth.go b/email/auth.go index b59d590..8660465 100644 --- a/email/auth.go +++ b/email/auth.go @@ -2,7 +2,7 @@ package email import "time" -// ContentLoginToken defines content for login token email template +// ContentLoginToken defines content for login token email template. type ContentLoginToken struct { Email string Name string @@ -11,7 +11,7 @@ type ContentLoginToken struct { Expiry time.Time } -// LoginToken creates and sends a login token email with provided template content +// LoginToken creates and sends a login token email with provided template content. func (m *Mailer) LoginToken(name, address string, content ContentLoginToken) error { msg := &Mail{ from: NewEmail(m.fromName, m.from), diff --git a/email/email.go b/email/email.go index a89708a..94240fa 100644 --- a/email/email.go +++ b/email/email.go @@ -21,14 +21,14 @@ var ( debug bool ) -// Mailer is a SMTP mailer +// Mailer is a SMTP mailer. type Mailer struct { client *gomail.Dialer templates *template.Template from, fromName string } -// NewMailer returns a configured SMTP Mailer +// NewMailer returns a configured SMTP Mailer. func NewMailer() (*Mailer, error) { templates, err := parseTemplates() if err != nil { @@ -58,7 +58,7 @@ func NewMailer() (*Mailer, error) { return s, nil } -// Send parses the corrsponding template and send the mail via smtp +// Send parses the corrsponding template and sends the mail via smtp. func (m *Mailer) Send(mail *Mail) error { buf := new(bytes.Buffer) if err := m.templates.ExecuteTemplate(buf, mail.template, mail.content); err != nil { @@ -95,7 +95,7 @@ func (m *Mailer) Send(mail *Mail) error { return nil } -// Mail struct holds all parts of a specific email +// Mail struct holds all parts of a specific email. type Mail struct { from *Email to *Email @@ -104,13 +104,13 @@ type Mail struct { content interface{} } -// Email struct holds email address and recipient name +// Email struct holds email address and recipient name. type Email struct { Name string Address string } -// NewEmail returns an email address +// NewEmail returns an email address. func NewEmail(name string, address string) *Email { return &Email{ Name: name, diff --git a/logging/logger.go b/logging/logger.go index a319afd..8222b86 100644 --- a/logging/logger.go +++ b/logging/logger.go @@ -10,7 +10,10 @@ import ( "github.com/spf13/viper" ) -var Logger *logrus.Logger +var ( + // Logger is a configured logrus.Logger. + Logger *logrus.Logger +) // StructuredLogger is a structured logrus Logger. type StructuredLogger struct { @@ -47,6 +50,7 @@ func NewStructuredLogger(logger *logrus.Logger) func(next http.Handler) http.Han return middleware.RequestLogger(&StructuredLogger{Logger}) } +// NewLogEntry sets default request log fields. func (l *StructuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry { entry := &StructuredLoggerEntry{Logger: logrus.NewEntry(l.Logger)} logFields := logrus.Fields{} @@ -79,6 +83,7 @@ func (l *StructuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry { return entry } +// StructuredLoggerEntry is a logrus.FieldLogger. type StructuredLoggerEntry struct { Logger logrus.FieldLogger } @@ -93,6 +98,7 @@ func (l *StructuredLoggerEntry) Write(status, bytes int, elapsed time.Duration) l.Logger.Infoln("request complete") } +// Panic prints stack trace func (l *StructuredLoggerEntry) Panic(v interface{}, stack []byte) { l.Logger = l.Logger.WithFields(logrus.Fields{ "stack": string(stack), @@ -103,17 +109,20 @@ func (l *StructuredLoggerEntry) Panic(v interface{}, stack []byte) { // Helper methods used by the application to get the request-scoped // logger entry and set additional fields between handlers. +// GetLogEntry return the request scoped logrus.FieldLogger. func GetLogEntry(r *http.Request) logrus.FieldLogger { entry := middleware.GetLogEntry(r).(*StructuredLoggerEntry) return entry.Logger } +// LogEntrySetField adds a field to the request scoped logrus.FieldLogger. func LogEntrySetField(r *http.Request, key string, value interface{}) { if entry, ok := r.Context().Value(middleware.LogEntryCtxKey).(*StructuredLoggerEntry); ok { entry.Logger = entry.Logger.WithField(key, value) } } +// LogEntrySetFields adds multiple fields to the request scoped logrus.FieldLogger. func LogEntrySetFields(r *http.Request, fields map[string]interface{}) { if entry, ok := r.Context().Value(middleware.LogEntryCtxKey).(*StructuredLoggerEntry); ok { entry.Logger = entry.Logger.WithFields(fields) diff --git a/models/account.go b/models/account.go index a69751f..31e0d7a 100644 --- a/models/account.go +++ b/models/account.go @@ -10,6 +10,7 @@ import ( "github.com/go-pg/pg/orm" ) +// Account represents an authenticated application user type Account struct { ID int `json:"id"` CreatedAt time.Time `json:"created_at,omitempty"` @@ -25,6 +26,7 @@ type Account struct { Token []*Token `json:"token,omitempty"` } +// BeforeInsert hook executed before database insert operation. func (a *Account) BeforeInsert(db orm.DB) error { now := time.Now() if a.CreatedAt.IsZero() { @@ -37,6 +39,7 @@ func (a *Account) BeforeInsert(db orm.DB) error { return nil } +// BeforeUpdate hook executed before database update operation. func (a *Account) BeforeUpdate(db orm.DB) error { if err := a.Validate(); err != nil { return err @@ -45,10 +48,12 @@ func (a *Account) BeforeUpdate(db orm.DB) error { return nil } +// BeforeDelete hook executed before database delete operation. func (a *Account) BeforeDelete(db orm.DB) error { return nil } +// Validate validates Account struct and returns validation errors. func (a *Account) Validate() error { a.Email = strings.TrimSpace(a.Email) a.Email = strings.ToLower(a.Email) @@ -60,16 +65,19 @@ func (a *Account) Validate() error { ) } +// CanLogin returns true if is user is allowed to login. func (a *Account) CanLogin() bool { return a.Active } +// AccountFilter provides pagination and filtering options on accounts. type AccountFilter struct { orm.Pager Filters url.Values Order []string } +// Filter applies an AccountFilter on an orm.Query. func (f *AccountFilter) Filter(q *orm.Query) (*orm.Query, error) { q = q.Apply(f.Pager.Paginate) q = q.Apply(orm.URLFilters(f.Filters)) @@ -77,6 +85,7 @@ func (f *AccountFilter) Filter(q *orm.Query) (*orm.Query, error) { return q, nil } +// NewAccountFilter returns an AccountFilter with options parsed from request url values. func NewAccountFilter(v url.Values) AccountFilter { var f AccountFilter f.SetURLValues(v) diff --git a/models/profile.go b/models/profile.go index f74f585..2fb3983 100644 --- a/models/profile.go +++ b/models/profile.go @@ -8,6 +8,7 @@ import ( "github.com/go-pg/pg/orm" ) +// Profile holds specific application settings linked to an Account. type Profile struct { ID int `json:"id,omitempty"` AccountID int `json:"-"` @@ -17,6 +18,7 @@ type Profile struct { Theme string `json:"theme,omitempty"` } +// BeforeInsert hook executed before database insert operation. func (p *Profile) BeforeInsert(db orm.DB) error { now := time.Now() if p.CreatedAt.IsZero() { @@ -26,6 +28,7 @@ func (p *Profile) BeforeInsert(db orm.DB) error { return nil } +// BeforeUpdate hook executed before database update operation. func (p *Profile) BeforeUpdate(db orm.DB) error { if err := p.Validate(); err != nil { return err @@ -34,6 +37,7 @@ func (p *Profile) BeforeUpdate(db orm.DB) error { return nil } +// Validate validates Profile struct and returns validation errors. func (p *Profile) Validate() error { return validation.ValidateStruct(p, diff --git a/models/token.go b/models/token.go index 48c0b9a..63b435f 100644 --- a/models/token.go +++ b/models/token.go @@ -6,6 +6,7 @@ import ( "github.com/go-pg/pg/orm" ) +// Token holds refresh jwt information. type Token struct { ID int `json:"id,omitempty"` CreatedAt time.Time `json:"created_at,omitempty"` @@ -18,6 +19,7 @@ type Token struct { Identifier string `json:"identifier,omitempty"` } +// BeforeInsert hook executed before database insert operation. func (t *Token) BeforeInsert(db orm.DB) error { now := time.Now() if t.CreatedAt.IsZero() { @@ -27,6 +29,7 @@ func (t *Token) BeforeInsert(db orm.DB) error { return nil } +// BeforeUpdate hook executed before database update operation. func (t *Token) BeforeUpdate(db orm.DB) error { t.UpdatedAt = time.Now() return nil