initial commit 2
This commit is contained in:
153
infra/auth/auth.go
Normal file
153
infra/auth/auth.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/pquerna/ffjson/ffjson"
|
||||
|
||||
"strings"
|
||||
|
||||
"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
|
||||
"bitbucket.org/nemt/nemt-portal-api/infra/config"
|
||||
"bitbucket.org/nemt/nemt-portal-api/infra/errors"
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
const (
|
||||
// AppKeyHeaderName is the header name for application keys
|
||||
AppKeyHeaderName = "App"
|
||||
// AppTokenHeaderName is the header name for application token
|
||||
AppTokenHeaderName = "Token"
|
||||
// TokenExpiration is the auth token default expiration time
|
||||
TokenExpiration = 24 * time.Hour
|
||||
)
|
||||
|
||||
var (
|
||||
// TokenSigningMethod is the auth token signing algorithm
|
||||
TokenSigningMethod = jwt.SigningMethodRS256
|
||||
)
|
||||
|
||||
// GetAppKeyFromContext returns the application key from request header
|
||||
func GetAppKeyFromContext(ctx echo.Context) (appKey string, err error) {
|
||||
appKey = ctx.Request().Header.Get(AppKeyHeaderName)
|
||||
|
||||
if strings.TrimSpace(appKey) == "" {
|
||||
return "", errors.NewNotAuthorizedError("Application key not found")
|
||||
}
|
||||
|
||||
return appKey, nil
|
||||
}
|
||||
|
||||
// ValidateAppKey validates the presence and validity of App key
|
||||
func ValidateAppKey(ctx echo.Context, cfg *config.Config) (err error) {
|
||||
appKey, err := GetAppKeyFromContext(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
if appKey != cfg.HTTP.Auth.AppKey {
|
||||
return errors.NewNotAuthorizedError("Invalid API Key")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCertPrivateKey returns the private key for the token authentication certificate
|
||||
func GetCertPrivateKey(path string) (pk *rsa.PrivateKey, err error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
|
||||
pk, err = jwt.ParseRSAPrivateKeyFromPEM(data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
// GetCertPublicKey returns the public key for the token authentication certificate
|
||||
func GetCertPublicKey(path string) (pk *rsa.PublicKey, err error) {
|
||||
privateKey, err := GetCertPrivateKey(path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
|
||||
return &privateKey.PublicKey, nil
|
||||
}
|
||||
|
||||
// GenerateToken creates a token based on certificate
|
||||
func GenerateToken(cfg *config.Config, data interface{}) (tokenString string, err error) {
|
||||
key, _ := GetCertPrivateKey(cfg.HTTP.Auth.CertificatePath)
|
||||
|
||||
claims := jwt.MapClaims{
|
||||
"iat": time.Now().Unix(),
|
||||
"exp": time.Now().Add(TokenExpiration).Unix(),
|
||||
"iss": "BDC",
|
||||
"sub": "NEMT",
|
||||
"data": data,
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(TokenSigningMethod, claims)
|
||||
tokenString, err = token.SignedString(key)
|
||||
if err != nil {
|
||||
return tokenString, errors.Wrap(err)
|
||||
}
|
||||
|
||||
return tokenString, nil
|
||||
}
|
||||
|
||||
func GetTokenDetail(ctx echo.Context, cfg *config.Config) (interface{}, error) {
|
||||
key, _ := GetCertPublicKey(cfg.HTTP.Auth.CertificatePath)
|
||||
|
||||
tokenString := ctx.Request().Header.Get("Token")
|
||||
tokenString = strings.Replace(tokenString, "Bearer ", "", -1)
|
||||
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return key, nil
|
||||
})
|
||||
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||
return claims["data"], nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func GetUserDetail(ctx echo.Context, cfg *config.Config) (viewmodel.User, error) {
|
||||
key, _ := GetCertPublicKey(cfg.HTTP.Auth.CertificatePath)
|
||||
|
||||
tokenString := ctx.Request().Header.Get("Token")
|
||||
tokenString = strings.Replace(tokenString, "Bearer ", "", -1)
|
||||
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return key, nil
|
||||
})
|
||||
if err != nil {
|
||||
return viewmodel.User{}, err
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||
byteData, _ := ffjson.Marshal(claims["data"])
|
||||
user := viewmodel.User{}
|
||||
err = ffjson.Unmarshal(byteData, &user)
|
||||
if err != nil {
|
||||
return viewmodel.User{}, err
|
||||
} else {
|
||||
return user, nil
|
||||
}
|
||||
} else {
|
||||
return viewmodel.User{}, err
|
||||
}
|
||||
}
|
||||
109
infra/aws/aws.go
Normal file
109
infra/aws/aws.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"bitbucket.org/nemt/nemt-portal-api/infra/config"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
"github.com/aws/aws-sdk-go/service/ssm"
|
||||
)
|
||||
|
||||
//AWSUtil return the methods to interact with AWS Services
|
||||
type AWSUtil struct {
|
||||
configApp *config.Config
|
||||
awsConfig *aws.Config
|
||||
s3Service *s3.S3
|
||||
ssmService *ssm.SSM
|
||||
awsSession *session.Session
|
||||
}
|
||||
|
||||
//New return an instance of AWSUtil
|
||||
func New(configApp *config.Config) *AWSUtil {
|
||||
cfg := aws.NewConfig().WithRegion("sa-east-1")
|
||||
cfg.DisableRestProtocolURICleaning = aws.Bool(true)
|
||||
|
||||
return &AWSUtil{
|
||||
configApp: configApp,
|
||||
awsConfig: cfg,
|
||||
s3Service: s3.New(session.New(), cfg),
|
||||
ssmService: ssm.New(session.New(), cfg),
|
||||
awsSession: session.New(cfg),
|
||||
}
|
||||
}
|
||||
|
||||
//DownloadFromS3Bucket will return the file on the S3 Bucket
|
||||
func (a AWSUtil) DownloadFromS3Bucket(filePath string) ([]byte, error) {
|
||||
downloader := s3manager.NewDownloader(a.awsSession)
|
||||
|
||||
b := &aws.WriteAtBuffer{}
|
||||
_, err := downloader.Download(b, &s3.GetObjectInput{
|
||||
Bucket: aws.String(a.configApp.Aws.S3Bucket),
|
||||
Key: aws.String(filePath),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
} else {
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
}
|
||||
|
||||
//UploadToS3Bucket will upload a file to the S3 Bucket
|
||||
func (a AWSUtil) UploadToS3Bucket(filePath string, fileName string, buff []byte) error {
|
||||
fileBytes := bytes.NewReader(buff)
|
||||
fileType := http.DetectContentType(buff)
|
||||
|
||||
fullPath := path.Join(filePath, fileName)
|
||||
|
||||
params := &s3.PutObjectInput{
|
||||
Bucket: aws.String(a.configApp.Aws.S3Bucket),
|
||||
Key: aws.String(fullPath),
|
||||
Body: fileBytes,
|
||||
ContentLength: aws.Int64(int64(len(buff))),
|
||||
ContentType: aws.String(fileType),
|
||||
}
|
||||
|
||||
_, err := a.s3Service.PutObject(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//SsmPutParameter put parameter to SSM
|
||||
func (a AWSUtil) SsmPutParameter(parameterName string, parameterValue string) error {
|
||||
params := &ssm.PutParameterInput{
|
||||
Name: aws.String(parameterName),
|
||||
Type: aws.String("SecureString"),
|
||||
Value: aws.String(parameterValue),
|
||||
}
|
||||
|
||||
_, err := a.ssmService.PutParameter(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//SsmGetParameter get parameter from SSM
|
||||
func (a AWSUtil) SsmGetParameter(parameterName string, withDecryption bool) (string, error) {
|
||||
params := &ssm.GetParameterInput{
|
||||
Name: aws.String(parameterName),
|
||||
WithDecryption: aws.Bool(withDecryption),
|
||||
}
|
||||
|
||||
output, err := a.ssmService.GetParameter(params)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return (*output.Parameter.Value), nil
|
||||
}
|
||||
144
infra/cache/cache.go
vendored
Normal file
144
infra/cache/cache.go
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
redis "gopkg.in/redis.v5"
|
||||
|
||||
"time"
|
||||
|
||||
"sync"
|
||||
|
||||
"bitbucket.org/nemt/nemt-portal-api/domain"
|
||||
"bitbucket.org/nemt/nemt-portal-api/domain/contract"
|
||||
"bitbucket.org/nemt/nemt-portal-api/infra/config"
|
||||
"bitbucket.org/nemt/nemt-portal-api/infra/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
instance *RedisCache
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// RedisCache implements the CacheManager interface
|
||||
type RedisCache struct {
|
||||
cfg *config.Config
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
// Instance returns a CacheManager instance
|
||||
func Instance(cfg *config.Config) contract.CacheManager {
|
||||
once.Do(func() {
|
||||
client := redis.NewFailoverClient(&redis.FailoverOptions{
|
||||
MasterName: "master01",
|
||||
SentinelAddrs: []string{fmt.Sprintf("%s:%v", cfg.Cache.Server, cfg.Cache.Port)},
|
||||
Password: cfg.Cache.Pass,
|
||||
DB: cfg.Cache.DB,
|
||||
})
|
||||
|
||||
instance = &RedisCache{cfg, client}
|
||||
})
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
func (r *RedisCache) buildKey(key string) string {
|
||||
return fmt.Sprintf("%s-%s", r.cfg.Cache.Prefix, key)
|
||||
}
|
||||
|
||||
// GetItem returns an Item from cache
|
||||
func (r *RedisCache) GetItem(key string) (data []byte, err error) {
|
||||
val, err := r.redis.Get(r.buildKey(key)).Bytes()
|
||||
if err == redis.Nil {
|
||||
return val, domain.ErrCacheMiss
|
||||
} else if err != nil {
|
||||
return val, errors.Wrap(err)
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// SetItem sets an item in cache
|
||||
func (r *RedisCache) SetItem(key string, data []byte) error {
|
||||
err := r.redis.Set(r.buildKey(key), data, r.cfg.Cache.DefaultExpiration).Err()
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetString returns an string from cache
|
||||
func (r *RedisCache) GetString(key string) (data string, err error) {
|
||||
val, err := r.GetItem(key)
|
||||
if err == domain.ErrCacheMiss {
|
||||
return data, domain.ErrCacheMiss
|
||||
} else if err != nil {
|
||||
return data, errors.Wrap(err)
|
||||
}
|
||||
|
||||
return string(val), nil
|
||||
}
|
||||
|
||||
// SetString sets an item in cache
|
||||
func (r *RedisCache) SetString(key string, data string) error {
|
||||
err := r.SetItem(key, []byte(data))
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStruct returns an struct from cache
|
||||
func (r *RedisCache) GetStruct(key string, data interface{}) (err error) {
|
||||
val, err := r.GetItem(key)
|
||||
if err == domain.ErrCacheMiss {
|
||||
return domain.ErrCacheMiss
|
||||
} else if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(val, &data)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetStruct sets an item in cache
|
||||
func (r *RedisCache) SetStruct(key string, data interface{}) error {
|
||||
dataString, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
err = r.SetItem(key, dataString)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetExpiration returns the expiration time for a key
|
||||
func (r *RedisCache) GetExpiration(key string) (expiration time.Duration, err error) {
|
||||
expiration, err = r.redis.TTL(r.buildKey(key)).Result()
|
||||
if err != nil {
|
||||
return expiration, errors.Wrap(err)
|
||||
}
|
||||
|
||||
return expiration, nil
|
||||
}
|
||||
|
||||
// SetExpiration sets the expiration time for a key
|
||||
func (r *RedisCache) SetExpiration(key string, expiration time.Duration) (err error) {
|
||||
err = r.redis.Expire(r.buildKey(key), expiration).Err()
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
249
infra/config/config.go
Normal file
249
infra/config/config.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"os"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Config represents the configuration values for the whole service.
|
||||
type Config struct {
|
||||
App AppConfig
|
||||
DB DBConfig
|
||||
HTTP HTTPConfig
|
||||
Log LogConfig
|
||||
Aws AwsConfig
|
||||
Cache CacheConfig
|
||||
Twilio TwilioConfig
|
||||
Lyft LyftConfig
|
||||
LyftProd LyftProdConfig
|
||||
BXE BXEConfig
|
||||
Blue365 Blue365Config
|
||||
Email EmailConfig
|
||||
GoogleShortener GoogleShortenerConfig
|
||||
}
|
||||
|
||||
// AppConfig represents the configuration values about the application.
|
||||
type AppConfig struct {
|
||||
Name string
|
||||
Debug bool
|
||||
Docs DocsConfig
|
||||
}
|
||||
|
||||
// TwilioConfig represents the configuration values about the twilio.
|
||||
type TwilioConfig struct {
|
||||
Account string
|
||||
Token string
|
||||
Sender string
|
||||
TwiMLSID string
|
||||
}
|
||||
|
||||
// LyftConfig represents the configuration values about the lyft.
|
||||
type LyftConfig struct {
|
||||
Client string
|
||||
Secret string
|
||||
RefreshToken string
|
||||
}
|
||||
|
||||
type LyftProdConfig struct {
|
||||
Lyft LyftConfig
|
||||
UserUUID string
|
||||
}
|
||||
|
||||
// BXEConfig represents the configuration values about the BXE.
|
||||
type BXEConfig struct {
|
||||
URL string
|
||||
APIKey string
|
||||
Secret string
|
||||
}
|
||||
|
||||
// Blue365Config represents the configuration values about the Blue 365.
|
||||
type Blue365Config struct {
|
||||
URL string
|
||||
APIKey string
|
||||
Secret string
|
||||
}
|
||||
|
||||
// DBConfig represents the configuration values about the DB.
|
||||
type DBConfig struct {
|
||||
User string
|
||||
Pass string
|
||||
Name string
|
||||
Host string
|
||||
Port int
|
||||
MaxLifeInMinutes int
|
||||
MaxIdleConns int
|
||||
MaxOpenConns int
|
||||
}
|
||||
|
||||
// HTTPConfig represents the configuration values about the HTTP server.
|
||||
type HTTPConfig struct {
|
||||
Port int
|
||||
Prefix string
|
||||
Auth HTTPAuthConfig
|
||||
}
|
||||
|
||||
// HTTPAuthConfig represents the configuration values about the HTTP authentication (JWT and CORS).
|
||||
type HTTPAuthConfig struct {
|
||||
AppKey string
|
||||
CertificatePath string
|
||||
FrontendURLs []string
|
||||
}
|
||||
|
||||
// LogConfig represents the configuration values about the logging config.
|
||||
type LogConfig struct {
|
||||
LogToFile bool
|
||||
Path string
|
||||
}
|
||||
|
||||
// AwsConfig represents the configuration values about the aws config.
|
||||
type AwsConfig struct {
|
||||
S3Bucket string
|
||||
}
|
||||
|
||||
// DocsConfig represents the configuration values about the documentation config.
|
||||
type DocsConfig struct {
|
||||
YAMLPath string
|
||||
SwaggerPath string
|
||||
}
|
||||
|
||||
// CacheConfig represents the configuration values about the documentation config.
|
||||
type CacheConfig struct {
|
||||
Server string
|
||||
Port int
|
||||
DB int
|
||||
Pass string
|
||||
Prefix string
|
||||
DefaultExpiration time.Duration
|
||||
}
|
||||
|
||||
// CacheConfig represents the configuration values about the documentation config.
|
||||
type EmailConfig struct {
|
||||
Server string
|
||||
Port int
|
||||
User string
|
||||
Pass string
|
||||
Sender string
|
||||
}
|
||||
|
||||
type GoogleShortenerConfig struct {
|
||||
APIKey string
|
||||
ClientID string
|
||||
SecretKey string
|
||||
}
|
||||
|
||||
// Read returns the configuration values,
|
||||
// based on the configuration files and environment variables.
|
||||
func Read() (*Config, error) {
|
||||
setup()
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
if err == os.ErrNotExist {
|
||||
viper.SetConfigName("config.local")
|
||||
err = viper.ReadInConfig()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &Config{
|
||||
App: AppConfig{
|
||||
Name: viper.GetString("app.name"),
|
||||
Debug: viper.GetBool("app.debug"),
|
||||
Docs: DocsConfig{
|
||||
YAMLPath: viper.GetString("app.docs.yaml-path"),
|
||||
SwaggerPath: viper.GetString("app.docs.swagger-path"),
|
||||
},
|
||||
},
|
||||
DB: DBConfig{
|
||||
User: viper.GetString("db.user"),
|
||||
Pass: viper.GetString("db.pass"),
|
||||
Name: viper.GetString("db.name"),
|
||||
Host: viper.GetString("db.host"),
|
||||
Port: viper.GetInt("db.port"),
|
||||
MaxLifeInMinutes: viper.GetInt("db.max-life-minutes"),
|
||||
MaxIdleConns: viper.GetInt("db.max-idle-conns"),
|
||||
MaxOpenConns: viper.GetInt("db.max-open-conns"),
|
||||
},
|
||||
HTTP: HTTPConfig{
|
||||
Port: viper.GetInt("http.port"),
|
||||
Prefix: viper.GetString("http.prefix"),
|
||||
Auth: HTTPAuthConfig{
|
||||
AppKey: viper.GetString("http.auth.app-key"),
|
||||
CertificatePath: viper.GetString("http.auth.certificate-path"),
|
||||
FrontendURLs: viper.GetStringSlice("http.auth.frontend-urls"),
|
||||
},
|
||||
},
|
||||
Log: LogConfig{
|
||||
LogToFile: viper.GetBool("log.log-to-file"),
|
||||
Path: viper.GetString("log.path"),
|
||||
},
|
||||
Aws: AwsConfig{
|
||||
S3Bucket: viper.GetString("aws.s3-bucket"),
|
||||
},
|
||||
Cache: CacheConfig{
|
||||
Server: viper.GetString("cache.server"),
|
||||
Port: viper.GetInt("cache.port"),
|
||||
DB: viper.GetInt("cache.db"),
|
||||
Pass: viper.GetString("cache.pass"),
|
||||
Prefix: viper.GetString("cache.prefix"),
|
||||
DefaultExpiration: viper.GetDuration("cache.default-expiration"),
|
||||
},
|
||||
Lyft: LyftConfig{
|
||||
Client: viper.GetString("lyft.key"),
|
||||
Secret: viper.GetString("lyft.secret"),
|
||||
RefreshToken: viper.GetString("lyft.token"),
|
||||
},
|
||||
LyftProd: LyftProdConfig{
|
||||
Lyft: LyftConfig{
|
||||
Client: viper.GetString("lyft-prod.key"),
|
||||
Secret: viper.GetString("lyft-prod.secret"),
|
||||
RefreshToken: viper.GetString("lyft-prod.token"),
|
||||
},
|
||||
UserUUID: viper.GetString("lyft-prod.user-uuid"),
|
||||
},
|
||||
Twilio: TwilioConfig{
|
||||
Account: viper.GetString("twilio.account"),
|
||||
Token: viper.GetString("twilio.token"),
|
||||
Sender: viper.GetString("twilio.sender"),
|
||||
TwiMLSID: viper.GetString("twilio.twiml-sid"),
|
||||
},
|
||||
BXE: BXEConfig{
|
||||
URL: viper.GetString("bxe.url"),
|
||||
APIKey: viper.GetString("bxe.key"),
|
||||
Secret: viper.GetString("bxe.secret"),
|
||||
},
|
||||
Blue365: Blue365Config{
|
||||
URL: viper.GetString("blue365.url"),
|
||||
APIKey: viper.GetString("blue365.key"),
|
||||
Secret: viper.GetString("blue365.secret"),
|
||||
},
|
||||
Email: EmailConfig{
|
||||
Server: viper.GetString("email.server"),
|
||||
Port: viper.GetInt("email.port"),
|
||||
User: viper.GetString("email.user"),
|
||||
Pass: viper.GetString("email.pass"),
|
||||
Sender: viper.GetString("email.sender"),
|
||||
},
|
||||
GoogleShortener: GoogleShortenerConfig{
|
||||
APIKey: viper.GetString("google-shortener.api-key"),
|
||||
ClientID: viper.GetString("google-shortener.client-id"),
|
||||
SecretKey: viper.GetString("google-shortener.secret-key"),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setup() {
|
||||
viper.SetEnvPrefix("api")
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
viper.AutomaticEnv()
|
||||
|
||||
viper.SetConfigName("config")
|
||||
viper.AddConfigPath(".")
|
||||
}
|
||||
177
infra/errors/errors.go
Normal file
177
infra/errors/errors.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type errorWrapper interface {
|
||||
Error() string
|
||||
GetOriginalError() *error
|
||||
}
|
||||
|
||||
// WrappedError holds an error wrapped with a context message
|
||||
type WrappedError struct {
|
||||
originalError *error
|
||||
path string
|
||||
messages []string
|
||||
}
|
||||
|
||||
func (err WrappedError) Error() string {
|
||||
if len(err.messages) > 0 {
|
||||
retVal := fmt.Sprintf("%s: ", err.path)
|
||||
|
||||
for _, message := range err.messages {
|
||||
retVal += message + "; "
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s => %v", retVal, *err.originalError)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s => %v", err.path, *err.originalError)
|
||||
}
|
||||
|
||||
// GetOriginalError returns the original error
|
||||
func (err WrappedError) GetOriginalError() *error {
|
||||
if err.originalError != nil {
|
||||
if originalError, ok := (*err.originalError).(errorWrapper); ok {
|
||||
return originalError.GetOriginalError()
|
||||
}
|
||||
}
|
||||
|
||||
return err.originalError
|
||||
}
|
||||
|
||||
// Wrap wraps an error with a context message
|
||||
func Wrap(err error, messages ...string) error {
|
||||
if err != nil {
|
||||
// get caller function path
|
||||
pc := make([]uintptr, 10)
|
||||
runtime.Callers(2, pc)
|
||||
funcRef := runtime.FuncForPC(pc[0])
|
||||
|
||||
pathArr := strings.Split(funcRef.Name(), "/")
|
||||
|
||||
path := pathArr[len(pathArr)-1]
|
||||
|
||||
return &WrappedError{
|
||||
originalError: &err,
|
||||
path: path,
|
||||
messages: messages,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewValidationError returns a ValidationError instance with the provided parameters
|
||||
func NewValidationError(field string, message string) *ValidationError {
|
||||
return &ValidationError{
|
||||
Field: field,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// NewNullArgumentError returns a preformatted error for null arguments
|
||||
func NewNullArgumentError(argumentName string) *NullArgumentError {
|
||||
return &NullArgumentError{argumentName}
|
||||
}
|
||||
|
||||
// NewApplicationError returns a ApplicationError instance
|
||||
func NewApplicationError(path string, message string) *ApplicationError {
|
||||
return &ApplicationError{
|
||||
Message: message,
|
||||
Path: path,
|
||||
}
|
||||
}
|
||||
|
||||
// NewNotAuthorizedError returns a NotAuthorizedError instance
|
||||
func NewNotAuthorizedError(message string) *NotAuthorizedError {
|
||||
return &NotAuthorizedError{
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// NewHTTPError returns a HTTPError instance
|
||||
func NewHTTPError(httpStatus int, message string) *HTTPError {
|
||||
return &HTTPError{
|
||||
Status: httpStatus,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// NewNotFoundError returns a Not Found HTTPError instance
|
||||
func NewNotFoundError() *HTTPError {
|
||||
return NewHTTPError(http.StatusNotFound, "Not Found")
|
||||
}
|
||||
|
||||
// HTTPError represents a generic HTTP error response
|
||||
type HTTPError struct {
|
||||
Status int
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *HTTPError) Error() string {
|
||||
return fmt.Sprintf("%v - %s", e.Status, e.Message)
|
||||
}
|
||||
|
||||
// NullArgumentError represents a error that is used to sinalize that a provided argument is null
|
||||
type NullArgumentError struct {
|
||||
ArgumentName string
|
||||
}
|
||||
|
||||
func (e *NullArgumentError) Error() string {
|
||||
return fmt.Sprintf("Parameter %s can't be null", e.ArgumentName)
|
||||
}
|
||||
|
||||
// ValidationError represents an input validation error
|
||||
type ValidationError struct {
|
||||
Field string `json:"field_name,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Errors []ValidationError `json:"errors,omitempty"`
|
||||
}
|
||||
|
||||
func (e *ValidationError) Error() string {
|
||||
var errorList []string
|
||||
|
||||
for _, err := range e.Errors {
|
||||
errorList = append(errorList, err.Error())
|
||||
}
|
||||
|
||||
output := e.Message
|
||||
|
||||
if len(errorList) > 0 {
|
||||
output += fmt.Sprintf("\n - %v", strings.Join(errorList, ";\n - "))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v: %v", e.Field, output)
|
||||
}
|
||||
|
||||
// AddError adds a new validation error to the chain
|
||||
func (e *ValidationError) AddError(field string, message string) {
|
||||
e.Errors = append(e.Errors, ValidationError{
|
||||
Field: field,
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
// ApplicationError represents a common applicatino error structure
|
||||
type ApplicationError struct {
|
||||
Message string
|
||||
Path string
|
||||
}
|
||||
|
||||
func (e *ApplicationError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// NotAuthorizedError represents an access restriction error structure
|
||||
type NotAuthorizedError struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *NotAuthorizedError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
53
infra/logger/logger.go
Normal file
53
infra/logger/logger.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"io"
|
||||
|
||||
"bitbucket.org/nemt/nemt-portal-api/infra/config"
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Logger is the default application logger compatible with the echo.Logger interface
|
||||
type Logger struct {
|
||||
cfg *config.Config
|
||||
*logrus.Entry
|
||||
}
|
||||
|
||||
// InfoWriter returns the io.Writer for info level
|
||||
func (l *Logger) InfoWriter() io.Writer {
|
||||
return l.WriterLevel(logrus.InfoLevel)
|
||||
}
|
||||
|
||||
// ErrorWriter returns the io.Writer for error level
|
||||
func (l *Logger) ErrorWriter() io.Writer {
|
||||
return l.WriterLevel(logrus.ErrorLevel)
|
||||
}
|
||||
|
||||
// New returns a new Logger instance
|
||||
func New(cfg *config.Config) (*Logger, error) {
|
||||
if cfg.Log.LogToFile {
|
||||
file, err := os.Create(cfg.Log.Path)
|
||||
if err != nil {
|
||||
fmt.Printf("Error to create log file for library: %s\n", err.Error())
|
||||
panic(err)
|
||||
}
|
||||
logrus.SetOutput(file)
|
||||
}
|
||||
|
||||
logrus.SetFormatter(&logrus.JSONFormatter{})
|
||||
|
||||
if cfg.App.Debug {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
} else {
|
||||
logrus.SetLevel(logrus.InfoLevel)
|
||||
}
|
||||
|
||||
entry := logrus.WithFields(logrus.Fields{
|
||||
"app": cfg.App.Name,
|
||||
})
|
||||
|
||||
return &Logger{cfg, entry}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user