Skip to content

Commit

Permalink
Feature/add health check #113 (#115)
Browse files Browse the repository at this point in the history
* check token earlier

Signed-off-by: Ahmet Turkmen <[email protected]>

* add /health endpoint

Signed-off-by: Ahmet Turkmen <[email protected]>

* add workflow to run tests

Signed-off-by: Ahmet Turkmen <[email protected]>

* return NoToken Err

Signed-off-by: Ahmet Turkmen <[email protected]>

* remove suffix

Signed-off-by: Ahmet Turkmen <[email protected]>
  • Loading branch information
mrtrkmn authored Jun 3, 2020
1 parent 91f15f5 commit f3c3baa
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 37 deletions.
52 changes: 52 additions & 0 deletions .github/workflows/buildtest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Build Server & Test
on: [push, pull_request]

jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-16.04, ubuntu-18.04]
services:
postgres:
image: postgres:alpine
env:
POSTGRES_PASSWORD: password
POSTGRES_USER: user
POSTGRES_DB: passwall
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432

steps:
- name: Set up Go 1.14
uses: actions/setup-go@v1
with:
go-version: 1.14
id: go


- name: Check out code into the Go module directory
uses: actions/checkout@v1

- name: Get dependencies
run: |
go get -v -t -d ./...
if [ -f Gopkg.toml ]; then
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure
fi
- name: Build and Run Pass-Wall Server
run: |
go build -o passwall ./cmd/passwall-server/main.go
chmod +x ./passwall
./passwall &
- name: Run Tests
run: go test -v --race ./...

1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/onsi/ginkgo v1.12.0
github.com/onsi/gomega v1.9.0
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pkg/errors v0.9.1
github.com/robfig/cron/v3 v3.0.0
github.com/satori/go.uuid v1.2.0
github.com/sethvargo/go-password v0.1.3
Expand Down
7 changes: 6 additions & 1 deletion internal/api/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var (
InvalidUser = "Invalid user"
ValidToken = "Token is valid"
InvalidToken = "Token is expired or not valid!"
NoToken = "Token could not found! "
TokenCreateErr = "Token could not be created"
)

Expand Down Expand Up @@ -144,7 +145,11 @@ func CheckToken(w http.ResponseWriter, r *http.Request) {
token = strArr[1]
}

//TODO: maybe check if there is any token? If not, return early.
if token != "" {
RespondWithError(w, http.StatusUnauthorized, NoToken)
return
}


_, err := app.TokenValid(token)
if err != nil {
Expand Down
69 changes: 69 additions & 0 deletions internal/api/health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package api

import (
"net/http"

"github.com/pass-wall/passwall-server/internal/config"
"github.com/pass-wall/passwall-server/internal/storage"
)

var (
// should be improved
Port = config.SetupConfigDefaults().Server.Port
ServerAddress = "0.0.0.0" + ":" + Port
)

type HealthProp struct {
StatusCode int
Err error
}

type Services struct {
API *HealthProp
Database *HealthProp
}

func HealthCheck(s storage.Store) http.HandlerFunc {

return func(w http.ResponseWriter, r *http.Request) {

var checkResult Services
var APIStatus *HealthProp
var DBStatus *HealthProp

if err := checkEndPoint(ServerAddress); err != nil {
APIStatus = getStatus(http.StatusInternalServerError, err)
}

APIStatus = getStatus(http.StatusOK, nil)

if err := s.Ping(); err != nil {
DBStatus = getStatus(http.StatusInternalServerError, err)
}

DBStatus = getStatus(http.StatusOK, nil)

checkResult = Services{
API: APIStatus,
Database: DBStatus,
}

RespondWithJSON(w, checkResult.Database.StatusCode, checkResult)
}

}

func checkEndPoint(url string) error {
_, err := http.Get(url)
if err != nil {
return err
}
return nil
}

func getStatus(state int, err error) *HealthProp {
return &HealthProp{
StatusCode: state,
Err: err,
}
}
44 changes: 44 additions & 0 deletions internal/api/health_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package api

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/pass-wall/passwall-server/internal/config"
"github.com/pass-wall/passwall-server/internal/storage"
)

func TestHealthCheck(t *testing.T) {
// create valid database config
// should be same with the one on github actions

mockDBConfig := &config.DatabaseConfiguration{
Driver: "postgres",
Name: "passwall",
Username: "user",
Password: "password",
Host: "localhost",
Port: "5432",
LogMode: false,
}

db, err := storage.New(mockDBConfig)

req, err := http.NewRequest("GET", "/health", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := HealthCheck(db)

handler.ServeHTTP(rr, req)
// more test cases could be added
expected := `{"API":{"StatusCode":200,"Err":null},"Database":{"StatusCode":200,"Err":null}}`

if rr.Body.String() != expected {
t.Errorf("handler returned unexpected body: got %v want %v",
rr.Body.String(), expected)
}

}
6 changes: 4 additions & 2 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import (
"github.com/spf13/viper"
)

var (
configuration *Configuration
)

// Configuration ...
type Configuration struct {
Server ServerConfiguration
Expand Down Expand Up @@ -40,8 +44,6 @@ type DatabaseConfiguration struct {
// SetupConfigDefaults ...
func SetupConfigDefaults() *Configuration {

var configuration *Configuration

viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath("./store")
Expand Down
70 changes: 36 additions & 34 deletions internal/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,51 +36,51 @@ func (r *Router) initRoutes() {
apiRouter := mux.NewRouter().PathPrefix("/api").Subrouter()

// Login endpoints
apiRouter.HandleFunc("/logins", api.FindAllLogins(r.store)).Methods("GET")
apiRouter.HandleFunc("/logins", api.CreateLogin(r.store)).Methods("POST")
apiRouter.HandleFunc("/logins/{id:[0-9]+}", api.FindLoginsByID(r.store)).Methods("GET")
apiRouter.HandleFunc("/logins/{id:[0-9]+}", api.UpdateLogin(r.store)).Methods("PUT")
apiRouter.HandleFunc("/logins/{id:[0-9]+}", api.DeleteLogin(r.store)).Methods("DELETE")
apiRouter.HandleFunc("/logins", api.FindAllLogins(r.store)).Methods(http.MethodGet)
apiRouter.HandleFunc("/logins", api.CreateLogin(r.store)).Methods(http.MethodPost)
apiRouter.HandleFunc("/logins/{id:[0-9]+}", api.FindLoginsByID(r.store)).Methods(http.MethodGet)
apiRouter.HandleFunc("/logins/{id:[0-9]+}", api.UpdateLogin(r.store)).Methods(http.MethodPut)
apiRouter.HandleFunc("/logins/{id:[0-9]+}", api.DeleteLogin(r.store)).Methods(http.MethodDelete)

// Bank Account endpoints
apiRouter.HandleFunc("/bank-accounts", api.FindAllBankAccounts(r.store)).Methods("GET")
apiRouter.HandleFunc("/bank-accounts", api.CreateBankAccount(r.store)).Methods("POST")
apiRouter.HandleFunc("/bank-accounts/{id:[0-9]+}", api.FindBankAccountByID(r.store)).Methods("GET")
apiRouter.HandleFunc("/bank-accounts/{id:[0-9]+}", api.UpdateBankAccount(r.store)).Methods("PUT")
apiRouter.HandleFunc("/bank-accounts/{id:[0-9]+}", api.DeleteBankAccount(r.store)).Methods("DELETE")
apiRouter.HandleFunc("/bank-accounts", api.FindAllBankAccounts(r.store)).Methods(http.MethodGet)
apiRouter.HandleFunc("/bank-accounts", api.CreateBankAccount(r.store)).Methods(http.MethodPost)
apiRouter.HandleFunc("/bank-accounts/{id:[0-9]+}", api.FindBankAccountByID(r.store)).Methods(http.MethodGet)
apiRouter.HandleFunc("/bank-accounts/{id:[0-9]+}", api.UpdateBankAccount(r.store)).Methods(http.MethodPut)
apiRouter.HandleFunc("/bank-accounts/{id:[0-9]+}", api.DeleteBankAccount(r.store)).Methods(http.MethodDelete)

// Credit Card endpoints
apiRouter.HandleFunc("/credit-cards", api.FindAllCreditCards(r.store)).Methods("GET")
apiRouter.HandleFunc("/credit-cards", api.CreateCreditCard(r.store)).Methods("POST")
apiRouter.HandleFunc("/credit-cards/{id:[0-9]+}", api.FindCreditCardByID(r.store)).Methods("GET")
apiRouter.HandleFunc("/credit-cards/{id:[0-9]+}", api.UpdateCreditCard(r.store)).Methods("PUT")
apiRouter.HandleFunc("/credit-cards/{id:[0-9]+}", api.DeleteCreditCard(r.store)).Methods("DELETE")
apiRouter.HandleFunc("/credit-cards", api.FindAllCreditCards(r.store)).Methods(http.MethodGet)
apiRouter.HandleFunc("/credit-cards", api.CreateCreditCard(r.store)).Methods(http.MethodPost)
apiRouter.HandleFunc("/credit-cards/{id:[0-9]+}", api.FindCreditCardByID(r.store)).Methods(http.MethodGet)
apiRouter.HandleFunc("/credit-cards/{id:[0-9]+}", api.UpdateCreditCard(r.store)).Methods(http.MethodPut)
apiRouter.HandleFunc("/credit-cards/{id:[0-9]+}", api.DeleteCreditCard(r.store)).Methods(http.MethodDelete)

// Note endpoints
apiRouter.HandleFunc("/notes", api.FindAllNotes(r.store)).Methods("GET")
apiRouter.HandleFunc("/notes", api.CreateNote(r.store)).Methods("POST")
apiRouter.HandleFunc("/notes/{id:[0-9]+}", api.FindNoteByID(r.store)).Methods("GET")
apiRouter.HandleFunc("/notes/{id:[0-9]+}", api.UpdateNote(r.store)).Methods("PUT")
apiRouter.HandleFunc("/notes/{id:[0-9]+}", api.DeleteNote(r.store)).Methods("DELETE")
apiRouter.HandleFunc("/notes", api.FindAllNotes(r.store)).Methods(http.MethodGet)
apiRouter.HandleFunc("/notes", api.CreateNote(r.store)).Methods(http.MethodPost)
apiRouter.HandleFunc("/notes/{id:[0-9]+}", api.FindNoteByID(r.store)).Methods(http.MethodGet)
apiRouter.HandleFunc("/notes/{id:[0-9]+}", api.UpdateNote(r.store)).Methods(http.MethodPut)
apiRouter.HandleFunc("/notes/{id:[0-9]+}", api.DeleteNote(r.store)).Methods(http.MethodDelete)

// Email endpoints
apiRouter.HandleFunc("/emails", api.FindAllEmails(r.store)).Methods("GET")
apiRouter.HandleFunc("/emails", api.CreateEmail(r.store)).Methods("POST")
apiRouter.HandleFunc("/emails/{id:[0-9]+}", api.FindEmailByID(r.store)).Methods("GET")
apiRouter.HandleFunc("/emails/{id:[0-9]+}", api.UpdateEmail(r.store)).Methods("PUT")
apiRouter.HandleFunc("/emails/{id:[0-9]+}", api.DeleteEmail(r.store)).Methods("DELETE")
apiRouter.HandleFunc("/emails", api.FindAllEmails(r.store)).Methods(http.MethodGet)
apiRouter.HandleFunc("/emails", api.CreateEmail(r.store)).Methods(http.MethodPost)
apiRouter.HandleFunc("/emails/{id:[0-9]+}", api.FindEmailByID(r.store)).Methods(http.MethodGet)
apiRouter.HandleFunc("/emails/{id:[0-9]+}", api.UpdateEmail(r.store)).Methods(http.MethodPut)
apiRouter.HandleFunc("/emails/{id:[0-9]+}", api.DeleteEmail(r.store)).Methods(http.MethodDelete)

// System endpoint
// TODO: Change these to system endpoints
apiRouter.HandleFunc("/logins/check-password", api.FindSamePassword(r.store)).Methods("POST")
apiRouter.HandleFunc("/logins/generate-password", api.GeneratePassword).Methods("POST")
apiRouter.HandleFunc("/logins/check-password", api.FindSamePassword(r.store)).Methods(http.MethodPost)
apiRouter.HandleFunc("/logins/generate-password", api.GeneratePassword).Methods(http.MethodPost)

apiRouter.HandleFunc("/logins/backup", api.Backup(r.store)).Methods("POST")
apiRouter.HandleFunc("/logins/backup", api.ListBackup).Methods("GET")
apiRouter.HandleFunc("/logins/restore", api.Restore(r.store)).Methods("POST")
apiRouter.HandleFunc("/logins/backup", api.Backup(r.store)).Methods(http.MethodPost)
apiRouter.HandleFunc("/logins/backup", api.ListBackup).Methods(http.MethodGet)
apiRouter.HandleFunc("/logins/restore", api.Restore(r.store)).Methods(http.MethodPost)

apiRouter.HandleFunc("/logins/import", api.Import(r.store)).Methods("POST")
apiRouter.HandleFunc("/logins/export", api.Export(r.store)).Methods("POST")
apiRouter.HandleFunc("/logins/import", api.Import(r.store)).Methods(http.MethodPost)
apiRouter.HandleFunc("/logins/export", api.Export(r.store)).Methods(http.MethodPost)

// Auth endpoints
authRouter := mux.NewRouter().PathPrefix("/auth").Subrouter()
Expand All @@ -93,13 +93,15 @@ func (r *Router) initRoutes() {
n.Use(negroni.HandlerFunc(Secure))

r.router.PathPrefix("/api").Handler(n.With(
negroni.HandlerFunc(Auth(r.store)),
Auth(r.store),
negroni.Wrap(apiRouter),
))

r.router.PathPrefix("/auth").Handler(n.With(

negroni.HandlerFunc(LimitHandler()),
LimitHandler(),
negroni.Wrap(authRouter),
))

r.router.HandleFunc("/health", api.HealthCheck(r.store)).Methods(http.MethodGet)
}
4 changes: 4 additions & 0 deletions internal/storage/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,7 @@ func (db *Database) Emails() EmailRepository {
func (db *Database) Tokens() TokenRepository {
return db.tokens
}

func (db *Database) Ping() error {
return db.db.DB().Ping()
}
2 changes: 2 additions & 0 deletions internal/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ type Store interface {
Notes() NoteRepository
Emails() EmailRepository
Tokens() TokenRepository
// used to ping database
Ping() error
}

0 comments on commit f3c3baa

Please sign in to comment.