diff --git a/features/golang_ledger/go.mod b/features/golang_ledger/go.mod new file mode 100644 index 0000000..12708b7 --- /dev/null +++ b/features/golang_ledger/go.mod @@ -0,0 +1,28 @@ +module msudenver.edu/ledger + +go 1.15 + +require ( + github.com/Rhymond/go-money v1.0.2 + github.com/corpix/uarand v0.1.1 // indirect + github.com/cucumber/godog v0.11.0 + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/gin-gonic/gin v1.6.3 + github.com/go-pg/pg/v10 v10.7.7 + github.com/go-playground/validator/v10 v10.4.1 // indirect + github.com/gorilla/mux v1.8.0 + github.com/gorilla/securecookie v1.1.1 + github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428 + github.com/joho/godotenv v1.3.0 + github.com/json-iterator/go v1.1.10 // indirect + github.com/labstack/echo v3.3.10+incompatible // indirect + github.com/labstack/echo/v4 v4.2.0 + github.com/leodido/go-urn v1.2.1 // indirect + github.com/plaid/plaid-go v0.0.0-20210216195344-700b8cfc627d + github.com/sirupsen/logrus v1.2.0 + github.com/ugorji/go v1.2.4 // indirect + github.com/unrolled/render v1.0.3 + golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 + golang.org/x/sys v0.0.0-20210308170721-88b6017d0656 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/features/golang_ledger/repos/account.go b/features/golang_ledger/repos/account.go new file mode 100644 index 0000000..101939c --- /dev/null +++ b/features/golang_ledger/repos/account.go @@ -0,0 +1,42 @@ +package repos + +import ( + "time" + + "github.com/Rhymond/go-money" + "github.com/go-pg/pg/v10" +) + +type AccountRepo struct { + db *pg.DB +} + +type Account struct { + ID uint64 + UserID uint64 + User *User + AccountID string `pg:",notnull"` + Name string `pg:",notnull"` + Mask string `pg:",notnull"` + Balance money.Money + Type string + Subtype string + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt time.Time +} + +func (r *AccountRepo) CreateAccount(accountId, name, mask string, balance money.Money) (*Account, error) { + account := &Account{ + AccountID: accountId, + Name: name, + Mask: mask, + Type: "depository", + Subtype: "checking", + } + _, err := r.db.Model(account).Insert() + if err != nil { + return nil, err + } + return account, nil +} diff --git a/features/golang_ledger/repos/init.go b/features/golang_ledger/repos/init.go new file mode 100644 index 0000000..a964db6 --- /dev/null +++ b/features/golang_ledger/repos/init.go @@ -0,0 +1,49 @@ +package repos + +import ( + "os" + + "github.com/go-pg/pg/v10" + "github.com/go-pg/pg/v10/orm" +) + +var tables = []interface{}{ + (*User)(nil), + (*PlaidItem)(nil), + (*Account)(nil), +} + +type Repo struct { + db *pg.DB + Users interface { + CreateUser(name string, email string, password string) (*User, error) + FetchUserbyID(id int64) (*User, error) + SaveUserbyID(u *User) (*User, error) + } + Accounts interface { + } +} + +func CreateRepo(db *pg.DB) *Repo { + repo := &Repo{ + db: db, + Users: &UserRepo{db: db}, + Accounts: &AccountRepo{db: db}, + } + return repo +} + +// CreateSchema creates the tables in the database +func (r *Repo) CreateSchema(db *pg.DB) error { + temp := os.Getenv("POSTGRES_TEMP") == "true" + for _, table := range tables { + opts := &orm.CreateTableOptions{ + IfNotExists: true, + Temp: temp, + } + if err := db.Model(table).CreateTable(opts); err != nil { + return err + } + } + return nil +} diff --git a/features/golang_ledger/repos/middleware/middleware.go b/features/golang_ledger/repos/middleware/middleware.go new file mode 100644 index 0000000..8104809 --- /dev/null +++ b/features/golang_ledger/repos/middleware/middleware.go @@ -0,0 +1,40 @@ +package middleware + +import ( + "errors" + "fmt" + "net/http" + "os" + "strings" + + "github.com/dgrijalva/jwt-go" + u "msudenver.edu/ledger/repos/utility" +) + +// IsLogin ... +func IsLogin(r http.Header) ([]byte, error) { + + tokenString := r.Get("Authorization") + if len(tokenString) == 0 { + return nil, errors.New("UnAuthorized") + } + tokenString = strings.Replace(tokenString, "Bearer ", "", 1) + claims, err := verifyToken(tokenString) + if err != nil { + + return nil, err + } + return u.Marshal(claims), nil +} + +func verifyToken(tokenString string) (jwt.Claims, error) { + signingKey := []byte(os.Getenv("jwt")) + fmt.Println("key", string(signingKey)) + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + return signingKey, nil + }) + if err != nil { + return nil, err + } + return token.Claims, err +} diff --git a/features/golang_ledger/repos/plaid.go b/features/golang_ledger/repos/plaid.go new file mode 100644 index 0000000..55a7bac --- /dev/null +++ b/features/golang_ledger/repos/plaid.go @@ -0,0 +1,40 @@ +package repos + +import "time" + +type PlaidItem struct { + ID uint64 + User *User `pg:",notnull"` + AccessToken string `pg:",unique,notnull"` + ItemID string `pg:",notnull"` + InstitutionID string `pg:",notnull"` + Status string `pg:",notnull"` + CreatedAt time.Time + UpdatedAt time.Time +} + +type LinkEvent struct { + ID uint64 + Type string + UserID uint64 + LinkSessionID string + RequestID string + ErrorType string + ErrorCode string + CreatedAt time.Time `pg:"default:now()"` +} + +type ApiEvent struct { + ID uint64 + ItemID uint64 + PlaidMethd string + Arguments string + RequestID string + ErrorType string + ErrorCode string + CreatedAt time.Time +} + +func LinkAccount() { + +} diff --git a/features/golang_ledger/repos/user.go b/features/golang_ledger/repos/user.go new file mode 100644 index 0000000..4dac461 --- /dev/null +++ b/features/golang_ledger/repos/user.go @@ -0,0 +1,86 @@ +package repos + +import ( + "fmt" + "regexp" + "time" + + "github.com/go-pg/pg/v10" +) + +// UserRepo is a custom model type which wraps +// the sql.DB connection pool and other needed types +type UserRepo struct { + db *pg.DB +} + +// CreateUser inserts new user records +func (r *UserRepo) CreateUser(name string, email string, password string) (*User, error) { + now := time.Now() + user := &User{ + FullName: name, + Email: email, + Password: password, + CreatedAt: now, + UpdatedAt: now, + } + if err := user.isValid(); err != nil { + return nil, err + } + if _, err := r.db.Model(user).Returning("*").Insert(); err != nil { + return nil, err + } + return user, nil +} + +func (r *UserRepo) FetchUserbyID(id int64) (*User, error) { + user := &User{ID: id} + err := r.db.Model(user).WherePK().Select() + if err != nil { + return nil, err + } + return user, nil +} + +func (r *UserRepo) SaveUserbyID(u *User) (*User, error) { + _, err := r.db.Model(u).WherePK().Update() + if err != nil { + return nil, err + } + return u, nil +} + +// Thanks to Edd Turtle https://golangcode.com/validate-an-email-address/ +var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") + +// User is the struct we use to represent a user of the system +type User struct { + tableName struct{} `pg:"users"` + ID int64 `json:"-"` + FullName string `pg:",notnull"` + Email string `pg:",notnull,unique"` + Password string `pg:"notnull" json:"-"` + CreatedAt time.Time `pg:"default:now()"` + UpdatedAt time.Time + DeletedAt time.Time `pg:",soft_delete"` +} + +func (u User) isValid() error { + var errs []error + if u.FullName == "" { + errs = append(errs, fmt.Errorf("Name is required")) + } + if u.Email == "" { + errs = append(errs, fmt.Errorf("Email is required")) + } else if !emailRegex.MatchString(u.Email) { + errs = append(errs, fmt.Errorf("Email is not a valid address")) + } + if len(errs) > 0 { + return fmt.Errorf("Invalid: %s", errs) + } + return nil +} + +func UpdatePassword() { + +} diff --git a/features/golang_ledger/repos/utility/utils.go b/features/golang_ledger/repos/utility/utils.go new file mode 100644 index 0000000..d1b984c --- /dev/null +++ b/features/golang_ledger/repos/utility/utils.go @@ -0,0 +1,48 @@ +package utility + +import ( + "encoding/json" + + "github.com/sirupsen/logrus" + + "github.com/unrolled/render" +) + +var R *render.Render +var Log *logrus.Entry + +//pakcage for adding for utitliy functions + +// IsBlank ... +func IsBlank(v interface{}) bool { + if v == 0 { + return true + } + if v == "" { + return true + } + if v == nil { + return true + } + return false +} + +// Unmarshal ... +func Unmarshal(body []byte, v interface{}) interface{} { + PrintError(json.Unmarshal(body, v)) + return v +} + +// Marshal ... +func Marshal(v interface{}) []byte { + data, err := json.Marshal(v) + PrintError(err) + return data +} + +// PrintError ... +func PrintError(e error) { + if e != nil { + Log.Println(e) + } +} diff --git a/features/golang_ledger/web/README.md b/features/golang_ledger/web/README.md new file mode 100644 index 0000000..06cbe15 --- /dev/null +++ b/features/golang_ledger/web/README.md @@ -0,0 +1,20 @@ +## Landing page .go & .html files + +Execute landing.go file + +```shell +$ go run landing.go +``` + +Navigate to: http://localhost:8080/ledger.html + +## UI + +The User Interface is a [Single Page App](https://en.wikipedia.org/wiki/Single-page_application) +built using the [React](https://reactjs.org) library, and the [Pure CSS](https://purecss.io/) framework. + +Bundling assets and building the JavaScript application is managed through [snowpack](https://www.snowpack.dev). + +``` +task dev +```