diff --git a/api/cmd/console-api/api.go b/api/cmd/console-api/api.go index 1d0c734..68c4a89 100644 --- a/api/cmd/console-api/api.go +++ b/api/cmd/console-api/api.go @@ -5,6 +5,7 @@ import ( controllers "github.com/plutov/formulosity/api/pkg/controllers" "github.com/plutov/formulosity/api/pkg/log" + "github.com/plutov/formulosity/api/pkg/middleware" "github.com/plutov/formulosity/api/pkg/services" "github.com/plutov/formulosity/api/pkg/surveys" ) @@ -21,17 +22,25 @@ func main() { if err != nil { log.WithError(err).Fatal("unable to init dependencies") } + jwtSecret := os.Getenv("JWT_SECRET") + if jwtSecret == "" { + log.WithError(err).Fatal("JWT_SECRET environment variable is not set") + } + jwtSvc := services.NewJWTService(jwtSecret, svc) if err := surveys.SyncSurveys(svc); err != nil { log.WithError(err).Fatal("unable to sync surveys") } - handler := controllers.NewHandler(svc) + handler := controllers.NewHandler(svc, jwtSvc) if err != nil { log.WithError(err).Fatal("unable to start server") } r := controllers.NewRouter(handler) + authMiddleware := middleware.AuthMiddleware(jwtSvc) + + r.Group("/app").Use(authMiddleware) if err := r.Start(":8080"); err != nil { log.WithError(err).Fatal("shutting down the server") diff --git a/api/migrations/postgres/000001_schema.up.sql b/api/migrations/postgres/000001_schema.up.sql index d3cf024..7b322b7 100644 --- a/api/migrations/postgres/000001_schema.up.sql +++ b/api/migrations/postgres/000001_schema.up.sql @@ -57,3 +57,12 @@ CREATE TABLE ); CREATE UNIQUE INDEX surveys_answers_unique ON surveys_answers (session_id, question_id); + +CREATE TABLE IF NOT EXISTS + users( + id bigserial PRIMARY KEY, + created_at timestamp(0) with time zone NOT NULL DEFAULT NOW(), + name text NOT NULL, + email citext UNIQUE NOT NULL, + password_hash bytea NOT NULL, +) \ No newline at end of file diff --git a/api/migrations/sqlite/000001_schema.up.sql b/api/migrations/sqlite/000001_schema.up.sql index 66428cf..57d7fe8 100644 --- a/api/migrations/sqlite/000001_schema.up.sql +++ b/api/migrations/sqlite/000001_schema.up.sql @@ -43,3 +43,12 @@ CREATE TABLE surveys_answers ( ); CREATE UNIQUE INDEX surveys_answers_unique ON surveys_answers (session_id, question_id); + +CREATE TABLE IF NOT EXISTS + users( + id bigserial PRIMARY KEY, + created_at timestamp(0) with time zone NOT NULL DEFAULT NOW(), + name text NOT NULL, + email citext UNIQUE NOT NULL, + password_hash bytea NOT NULL, +) \ No newline at end of file diff --git a/api/mocks/answer.go b/api/mocks/answer.go deleted file mode 100644 index d2b670f..0000000 --- a/api/mocks/answer.go +++ /dev/null @@ -1,133 +0,0 @@ -// Code generated by mockery v2.30.1. DO NOT EDIT. - -package mocks - -import ( - driver "database/sql/driver" - - mock "github.com/stretchr/testify/mock" - - types "github.com/plutov/formulosity/api/pkg/types" -) - -// Answer is an autogenerated mock type for the Answer type -type Answer struct { - mock.Mock -} - -type Answer_Expecter struct { - mock *mock.Mock -} - -func (_m *Answer) EXPECT() *Answer_Expecter { - return &Answer_Expecter{mock: &_m.Mock} -} - -// Validate provides a mock function with given fields: q -func (_m *Answer) Validate(q types.Question) error { - ret := _m.Called(q) - - var r0 error - if rf, ok := ret.Get(0).(func(types.Question) error); ok { - r0 = rf(q) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Answer_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate' -type Answer_Validate_Call struct { - *mock.Call -} - -// Validate is a helper method to define mock.On call -// - q types.Question -func (_e *Answer_Expecter) Validate(q interface{}) *Answer_Validate_Call { - return &Answer_Validate_Call{Call: _e.mock.On("Validate", q)} -} - -func (_c *Answer_Validate_Call) Run(run func(q types.Question)) *Answer_Validate_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(types.Question)) - }) - return _c -} - -func (_c *Answer_Validate_Call) Return(_a0 error) *Answer_Validate_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Answer_Validate_Call) RunAndReturn(run func(types.Question) error) *Answer_Validate_Call { - _c.Call.Return(run) - return _c -} - -// Value provides a mock function with given fields: -func (_m *Answer) Value() (driver.Value, error) { - ret := _m.Called() - - var r0 driver.Value - var r1 error - if rf, ok := ret.Get(0).(func() (driver.Value, error)); ok { - return rf() - } - if rf, ok := ret.Get(0).(func() driver.Value); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(driver.Value) - } - } - - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Answer_Value_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Value' -type Answer_Value_Call struct { - *mock.Call -} - -// Value is a helper method to define mock.On call -func (_e *Answer_Expecter) Value() *Answer_Value_Call { - return &Answer_Value_Call{Call: _e.mock.On("Value")} -} - -func (_c *Answer_Value_Call) Run(run func()) *Answer_Value_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *Answer_Value_Call) Return(_a0 driver.Value, _a1 error) *Answer_Value_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Answer_Value_Call) RunAndReturn(run func() (driver.Value, error)) *Answer_Value_Call { - _c.Call.Return(run) - return _c -} - -// NewAnswer creates a new instance of Answer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewAnswer(t interface { - mock.TestingT - Cleanup(func()) -}) *Answer { - mock := &Answer{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/api/mocks/interface.go b/api/mocks/interface.go deleted file mode 100644 index 68bb060..0000000 --- a/api/mocks/interface.go +++ /dev/null @@ -1,843 +0,0 @@ -// Code generated by mockery v2.30.1. DO NOT EDIT. - -package mocks - -import ( - mock "github.com/stretchr/testify/mock" - - types "github.com/plutov/formulosity/api/pkg/types" -) - -// Interface is an autogenerated mock type for the Interface type -type Interface struct { - mock.Mock -} - -type Interface_Expecter struct { - mock *mock.Mock -} - -func (_m *Interface) EXPECT() *Interface_Expecter { - return &Interface_Expecter{mock: &_m.Mock} -} - -// Close provides a mock function with given fields: -func (_m *Interface) Close() error { - ret := _m.Called() - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Interface_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' -type Interface_Close_Call struct { - *mock.Call -} - -// Close is a helper method to define mock.On call -func (_e *Interface_Expecter) Close() *Interface_Close_Call { - return &Interface_Close_Call{Call: _e.mock.On("Close")} -} - -func (_c *Interface_Close_Call) Run(run func()) *Interface_Close_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *Interface_Close_Call) Return(_a0 error) *Interface_Close_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Interface_Close_Call) RunAndReturn(run func() error) *Interface_Close_Call { - _c.Call.Return(run) - return _c -} - -// CreateSurvey provides a mock function with given fields: survey -func (_m *Interface) CreateSurvey(survey *types.Survey) error { - ret := _m.Called(survey) - - var r0 error - if rf, ok := ret.Get(0).(func(*types.Survey) error); ok { - r0 = rf(survey) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Interface_CreateSurvey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateSurvey' -type Interface_CreateSurvey_Call struct { - *mock.Call -} - -// CreateSurvey is a helper method to define mock.On call -// - survey *types.Survey -func (_e *Interface_Expecter) CreateSurvey(survey interface{}) *Interface_CreateSurvey_Call { - return &Interface_CreateSurvey_Call{Call: _e.mock.On("CreateSurvey", survey)} -} - -func (_c *Interface_CreateSurvey_Call) Run(run func(survey *types.Survey)) *Interface_CreateSurvey_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*types.Survey)) - }) - return _c -} - -func (_c *Interface_CreateSurvey_Call) Return(_a0 error) *Interface_CreateSurvey_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Interface_CreateSurvey_Call) RunAndReturn(run func(*types.Survey) error) *Interface_CreateSurvey_Call { - _c.Call.Return(run) - return _c -} - -// CreateSurveySession provides a mock function with given fields: session -func (_m *Interface) CreateSurveySession(session *types.SurveySession) error { - ret := _m.Called(session) - - var r0 error - if rf, ok := ret.Get(0).(func(*types.SurveySession) error); ok { - r0 = rf(session) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Interface_CreateSurveySession_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateSurveySession' -type Interface_CreateSurveySession_Call struct { - *mock.Call -} - -// CreateSurveySession is a helper method to define mock.On call -// - session *types.SurveySession -func (_e *Interface_Expecter) CreateSurveySession(session interface{}) *Interface_CreateSurveySession_Call { - return &Interface_CreateSurveySession_Call{Call: _e.mock.On("CreateSurveySession", session)} -} - -func (_c *Interface_CreateSurveySession_Call) Run(run func(session *types.SurveySession)) *Interface_CreateSurveySession_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*types.SurveySession)) - }) - return _c -} - -func (_c *Interface_CreateSurveySession_Call) Return(_a0 error) *Interface_CreateSurveySession_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Interface_CreateSurveySession_Call) RunAndReturn(run func(*types.SurveySession) error) *Interface_CreateSurveySession_Call { - _c.Call.Return(run) - return _c -} - -// GetSurveyByField provides a mock function with given fields: field, value -func (_m *Interface) GetSurveyByField(field string, value interface{}) (*types.Survey, error) { - ret := _m.Called(field, value) - - var r0 *types.Survey - var r1 error - if rf, ok := ret.Get(0).(func(string, interface{}) (*types.Survey, error)); ok { - return rf(field, value) - } - if rf, ok := ret.Get(0).(func(string, interface{}) *types.Survey); ok { - r0 = rf(field, value) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*types.Survey) - } - } - - if rf, ok := ret.Get(1).(func(string, interface{}) error); ok { - r1 = rf(field, value) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Interface_GetSurveyByField_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSurveyByField' -type Interface_GetSurveyByField_Call struct { - *mock.Call -} - -// GetSurveyByField is a helper method to define mock.On call -// - field string -// - value interface{} -func (_e *Interface_Expecter) GetSurveyByField(field interface{}, value interface{}) *Interface_GetSurveyByField_Call { - return &Interface_GetSurveyByField_Call{Call: _e.mock.On("GetSurveyByField", field, value)} -} - -func (_c *Interface_GetSurveyByField_Call) Run(run func(field string, value interface{})) *Interface_GetSurveyByField_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(interface{})) - }) - return _c -} - -func (_c *Interface_GetSurveyByField_Call) Return(_a0 *types.Survey, _a1 error) *Interface_GetSurveyByField_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Interface_GetSurveyByField_Call) RunAndReturn(run func(string, interface{}) (*types.Survey, error)) *Interface_GetSurveyByField_Call { - _c.Call.Return(run) - return _c -} - -// GetSurveyQuestions provides a mock function with given fields: surveyID -func (_m *Interface) GetSurveyQuestions(surveyID int64) ([]types.Question, error) { - ret := _m.Called(surveyID) - - var r0 []types.Question - var r1 error - if rf, ok := ret.Get(0).(func(int64) ([]types.Question, error)); ok { - return rf(surveyID) - } - if rf, ok := ret.Get(0).(func(int64) []types.Question); ok { - r0 = rf(surveyID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]types.Question) - } - } - - if rf, ok := ret.Get(1).(func(int64) error); ok { - r1 = rf(surveyID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Interface_GetSurveyQuestions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSurveyQuestions' -type Interface_GetSurveyQuestions_Call struct { - *mock.Call -} - -// GetSurveyQuestions is a helper method to define mock.On call -// - surveyID int64 -func (_e *Interface_Expecter) GetSurveyQuestions(surveyID interface{}) *Interface_GetSurveyQuestions_Call { - return &Interface_GetSurveyQuestions_Call{Call: _e.mock.On("GetSurveyQuestions", surveyID)} -} - -func (_c *Interface_GetSurveyQuestions_Call) Run(run func(surveyID int64)) *Interface_GetSurveyQuestions_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(int64)) - }) - return _c -} - -func (_c *Interface_GetSurveyQuestions_Call) Return(_a0 []types.Question, _a1 error) *Interface_GetSurveyQuestions_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Interface_GetSurveyQuestions_Call) RunAndReturn(run func(int64) ([]types.Question, error)) *Interface_GetSurveyQuestions_Call { - _c.Call.Return(run) - return _c -} - -// GetSurveySession provides a mock function with given fields: surveyUUID, sessionUUID -func (_m *Interface) GetSurveySession(surveyUUID string, sessionUUID string) (*types.SurveySession, error) { - ret := _m.Called(surveyUUID, sessionUUID) - - var r0 *types.SurveySession - var r1 error - if rf, ok := ret.Get(0).(func(string, string) (*types.SurveySession, error)); ok { - return rf(surveyUUID, sessionUUID) - } - if rf, ok := ret.Get(0).(func(string, string) *types.SurveySession); ok { - r0 = rf(surveyUUID, sessionUUID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*types.SurveySession) - } - } - - if rf, ok := ret.Get(1).(func(string, string) error); ok { - r1 = rf(surveyUUID, sessionUUID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Interface_GetSurveySession_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSurveySession' -type Interface_GetSurveySession_Call struct { - *mock.Call -} - -// GetSurveySession is a helper method to define mock.On call -// - surveyUUID string -// - sessionUUID string -func (_e *Interface_Expecter) GetSurveySession(surveyUUID interface{}, sessionUUID interface{}) *Interface_GetSurveySession_Call { - return &Interface_GetSurveySession_Call{Call: _e.mock.On("GetSurveySession", surveyUUID, sessionUUID)} -} - -func (_c *Interface_GetSurveySession_Call) Run(run func(surveyUUID string, sessionUUID string)) *Interface_GetSurveySession_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string)) - }) - return _c -} - -func (_c *Interface_GetSurveySession_Call) Return(_a0 *types.SurveySession, _a1 error) *Interface_GetSurveySession_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Interface_GetSurveySession_Call) RunAndReturn(run func(string, string) (*types.SurveySession, error)) *Interface_GetSurveySession_Call { - _c.Call.Return(run) - return _c -} - -// GetSurveySessionAnswers provides a mock function with given fields: sessionUUID -func (_m *Interface) GetSurveySessionAnswers(sessionUUID string) ([]types.QuestionAnswer, error) { - ret := _m.Called(sessionUUID) - - var r0 []types.QuestionAnswer - var r1 error - if rf, ok := ret.Get(0).(func(string) ([]types.QuestionAnswer, error)); ok { - return rf(sessionUUID) - } - if rf, ok := ret.Get(0).(func(string) []types.QuestionAnswer); ok { - r0 = rf(sessionUUID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]types.QuestionAnswer) - } - } - - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(sessionUUID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Interface_GetSurveySessionAnswers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSurveySessionAnswers' -type Interface_GetSurveySessionAnswers_Call struct { - *mock.Call -} - -// GetSurveySessionAnswers is a helper method to define mock.On call -// - sessionUUID string -func (_e *Interface_Expecter) GetSurveySessionAnswers(sessionUUID interface{}) *Interface_GetSurveySessionAnswers_Call { - return &Interface_GetSurveySessionAnswers_Call{Call: _e.mock.On("GetSurveySessionAnswers", sessionUUID)} -} - -func (_c *Interface_GetSurveySessionAnswers_Call) Run(run func(sessionUUID string)) *Interface_GetSurveySessionAnswers_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) - }) - return _c -} - -func (_c *Interface_GetSurveySessionAnswers_Call) Return(_a0 []types.QuestionAnswer, _a1 error) *Interface_GetSurveySessionAnswers_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Interface_GetSurveySessionAnswers_Call) RunAndReturn(run func(string) ([]types.QuestionAnswer, error)) *Interface_GetSurveySessionAnswers_Call { - _c.Call.Return(run) - return _c -} - -// GetSurveySessionByIPAddress provides a mock function with given fields: surveyUUID, ipAddr -func (_m *Interface) GetSurveySessionByIPAddress(surveyUUID string, ipAddr string) (*types.SurveySession, error) { - ret := _m.Called(surveyUUID, ipAddr) - - var r0 *types.SurveySession - var r1 error - if rf, ok := ret.Get(0).(func(string, string) (*types.SurveySession, error)); ok { - return rf(surveyUUID, ipAddr) - } - if rf, ok := ret.Get(0).(func(string, string) *types.SurveySession); ok { - r0 = rf(surveyUUID, ipAddr) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*types.SurveySession) - } - } - - if rf, ok := ret.Get(1).(func(string, string) error); ok { - r1 = rf(surveyUUID, ipAddr) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Interface_GetSurveySessionByIPAddress_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSurveySessionByIPAddress' -type Interface_GetSurveySessionByIPAddress_Call struct { - *mock.Call -} - -// GetSurveySessionByIPAddress is a helper method to define mock.On call -// - surveyUUID string -// - ipAddr string -func (_e *Interface_Expecter) GetSurveySessionByIPAddress(surveyUUID interface{}, ipAddr interface{}) *Interface_GetSurveySessionByIPAddress_Call { - return &Interface_GetSurveySessionByIPAddress_Call{Call: _e.mock.On("GetSurveySessionByIPAddress", surveyUUID, ipAddr)} -} - -func (_c *Interface_GetSurveySessionByIPAddress_Call) Run(run func(surveyUUID string, ipAddr string)) *Interface_GetSurveySessionByIPAddress_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string)) - }) - return _c -} - -func (_c *Interface_GetSurveySessionByIPAddress_Call) Return(_a0 *types.SurveySession, _a1 error) *Interface_GetSurveySessionByIPAddress_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Interface_GetSurveySessionByIPAddress_Call) RunAndReturn(run func(string, string) (*types.SurveySession, error)) *Interface_GetSurveySessionByIPAddress_Call { - _c.Call.Return(run) - return _c -} - -// GetSurveySessionsWithAnswers provides a mock function with given fields: surveyUUID, filter -func (_m *Interface) GetSurveySessionsWithAnswers(surveyUUID string, filter *types.SurveySessionsFilter) ([]types.SurveySession, int, error) { - ret := _m.Called(surveyUUID, filter) - - var r0 []types.SurveySession - var r1 int - var r2 error - if rf, ok := ret.Get(0).(func(string, *types.SurveySessionsFilter) ([]types.SurveySession, int, error)); ok { - return rf(surveyUUID, filter) - } - if rf, ok := ret.Get(0).(func(string, *types.SurveySessionsFilter) []types.SurveySession); ok { - r0 = rf(surveyUUID, filter) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]types.SurveySession) - } - } - - if rf, ok := ret.Get(1).(func(string, *types.SurveySessionsFilter) int); ok { - r1 = rf(surveyUUID, filter) - } else { - r1 = ret.Get(1).(int) - } - - if rf, ok := ret.Get(2).(func(string, *types.SurveySessionsFilter) error); ok { - r2 = rf(surveyUUID, filter) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// Interface_GetSurveySessionsWithAnswers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSurveySessionsWithAnswers' -type Interface_GetSurveySessionsWithAnswers_Call struct { - *mock.Call -} - -// GetSurveySessionsWithAnswers is a helper method to define mock.On call -// - surveyUUID string -// - filter *types.SurveySessionsFilter -func (_e *Interface_Expecter) GetSurveySessionsWithAnswers(surveyUUID interface{}, filter interface{}) *Interface_GetSurveySessionsWithAnswers_Call { - return &Interface_GetSurveySessionsWithAnswers_Call{Call: _e.mock.On("GetSurveySessionsWithAnswers", surveyUUID, filter)} -} - -func (_c *Interface_GetSurveySessionsWithAnswers_Call) Run(run func(surveyUUID string, filter *types.SurveySessionsFilter)) *Interface_GetSurveySessionsWithAnswers_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(*types.SurveySessionsFilter)) - }) - return _c -} - -func (_c *Interface_GetSurveySessionsWithAnswers_Call) Return(_a0 []types.SurveySession, _a1 int, _a2 error) *Interface_GetSurveySessionsWithAnswers_Call { - _c.Call.Return(_a0, _a1, _a2) - return _c -} - -func (_c *Interface_GetSurveySessionsWithAnswers_Call) RunAndReturn(run func(string, *types.SurveySessionsFilter) ([]types.SurveySession, int, error)) *Interface_GetSurveySessionsWithAnswers_Call { - _c.Call.Return(run) - return _c -} - -// GetSurveys provides a mock function with given fields: -func (_m *Interface) GetSurveys() ([]*types.Survey, error) { - ret := _m.Called() - - var r0 []*types.Survey - var r1 error - if rf, ok := ret.Get(0).(func() ([]*types.Survey, error)); ok { - return rf() - } - if rf, ok := ret.Get(0).(func() []*types.Survey); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*types.Survey) - } - } - - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Interface_GetSurveys_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSurveys' -type Interface_GetSurveys_Call struct { - *mock.Call -} - -// GetSurveys is a helper method to define mock.On call -func (_e *Interface_Expecter) GetSurveys() *Interface_GetSurveys_Call { - return &Interface_GetSurveys_Call{Call: _e.mock.On("GetSurveys")} -} - -func (_c *Interface_GetSurveys_Call) Run(run func()) *Interface_GetSurveys_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *Interface_GetSurveys_Call) Return(_a0 []*types.Survey, _a1 error) *Interface_GetSurveys_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Interface_GetSurveys_Call) RunAndReturn(run func() ([]*types.Survey, error)) *Interface_GetSurveys_Call { - _c.Call.Return(run) - return _c -} - -// Init provides a mock function with given fields: -func (_m *Interface) Init() error { - ret := _m.Called() - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Interface_Init_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Init' -type Interface_Init_Call struct { - *mock.Call -} - -// Init is a helper method to define mock.On call -func (_e *Interface_Expecter) Init() *Interface_Init_Call { - return &Interface_Init_Call{Call: _e.mock.On("Init")} -} - -func (_c *Interface_Init_Call) Run(run func()) *Interface_Init_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *Interface_Init_Call) Return(_a0 error) *Interface_Init_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Interface_Init_Call) RunAndReturn(run func() error) *Interface_Init_Call { - _c.Call.Return(run) - return _c -} - -// Migrate provides a mock function with given fields: -func (_m *Interface) Migrate() error { - ret := _m.Called() - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Interface_Migrate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Migrate' -type Interface_Migrate_Call struct { - *mock.Call -} - -// Migrate is a helper method to define mock.On call -func (_e *Interface_Expecter) Migrate() *Interface_Migrate_Call { - return &Interface_Migrate_Call{Call: _e.mock.On("Migrate")} -} - -func (_c *Interface_Migrate_Call) Run(run func()) *Interface_Migrate_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *Interface_Migrate_Call) Return(_a0 error) *Interface_Migrate_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Interface_Migrate_Call) RunAndReturn(run func() error) *Interface_Migrate_Call { - _c.Call.Return(run) - return _c -} - -// Ping provides a mock function with given fields: -func (_m *Interface) Ping() error { - ret := _m.Called() - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Interface_Ping_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Ping' -type Interface_Ping_Call struct { - *mock.Call -} - -// Ping is a helper method to define mock.On call -func (_e *Interface_Expecter) Ping() *Interface_Ping_Call { - return &Interface_Ping_Call{Call: _e.mock.On("Ping")} -} - -func (_c *Interface_Ping_Call) Run(run func()) *Interface_Ping_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *Interface_Ping_Call) Return(_a0 error) *Interface_Ping_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Interface_Ping_Call) RunAndReturn(run func() error) *Interface_Ping_Call { - _c.Call.Return(run) - return _c -} - -// UpdateSurvey provides a mock function with given fields: survey -func (_m *Interface) UpdateSurvey(survey *types.Survey) error { - ret := _m.Called(survey) - - var r0 error - if rf, ok := ret.Get(0).(func(*types.Survey) error); ok { - r0 = rf(survey) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Interface_UpdateSurvey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateSurvey' -type Interface_UpdateSurvey_Call struct { - *mock.Call -} - -// UpdateSurvey is a helper method to define mock.On call -// - survey *types.Survey -func (_e *Interface_Expecter) UpdateSurvey(survey interface{}) *Interface_UpdateSurvey_Call { - return &Interface_UpdateSurvey_Call{Call: _e.mock.On("UpdateSurvey", survey)} -} - -func (_c *Interface_UpdateSurvey_Call) Run(run func(survey *types.Survey)) *Interface_UpdateSurvey_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*types.Survey)) - }) - return _c -} - -func (_c *Interface_UpdateSurvey_Call) Return(_a0 error) *Interface_UpdateSurvey_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Interface_UpdateSurvey_Call) RunAndReturn(run func(*types.Survey) error) *Interface_UpdateSurvey_Call { - _c.Call.Return(run) - return _c -} - -// UpdateSurveySessionStatus provides a mock function with given fields: sessionUUID, newStatus -func (_m *Interface) UpdateSurveySessionStatus(sessionUUID string, newStatus types.SurveySessionStatus) error { - ret := _m.Called(sessionUUID, newStatus) - - var r0 error - if rf, ok := ret.Get(0).(func(string, types.SurveySessionStatus) error); ok { - r0 = rf(sessionUUID, newStatus) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Interface_UpdateSurveySessionStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateSurveySessionStatus' -type Interface_UpdateSurveySessionStatus_Call struct { - *mock.Call -} - -// UpdateSurveySessionStatus is a helper method to define mock.On call -// - sessionUUID string -// - newStatus types.SurveySessionStatus -func (_e *Interface_Expecter) UpdateSurveySessionStatus(sessionUUID interface{}, newStatus interface{}) *Interface_UpdateSurveySessionStatus_Call { - return &Interface_UpdateSurveySessionStatus_Call{Call: _e.mock.On("UpdateSurveySessionStatus", sessionUUID, newStatus)} -} - -func (_c *Interface_UpdateSurveySessionStatus_Call) Run(run func(sessionUUID string, newStatus types.SurveySessionStatus)) *Interface_UpdateSurveySessionStatus_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(types.SurveySessionStatus)) - }) - return _c -} - -func (_c *Interface_UpdateSurveySessionStatus_Call) Return(_a0 error) *Interface_UpdateSurveySessionStatus_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Interface_UpdateSurveySessionStatus_Call) RunAndReturn(run func(string, types.SurveySessionStatus) error) *Interface_UpdateSurveySessionStatus_Call { - _c.Call.Return(run) - return _c -} - -// UpsertSurveyQuestionAnswer provides a mock function with given fields: sessionUUID, questionUUID, answer -func (_m *Interface) UpsertSurveyQuestionAnswer(sessionUUID string, questionUUID string, answer types.Answer) error { - ret := _m.Called(sessionUUID, questionUUID, answer) - - var r0 error - if rf, ok := ret.Get(0).(func(string, string, types.Answer) error); ok { - r0 = rf(sessionUUID, questionUUID, answer) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Interface_UpsertSurveyQuestionAnswer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpsertSurveyQuestionAnswer' -type Interface_UpsertSurveyQuestionAnswer_Call struct { - *mock.Call -} - -// UpsertSurveyQuestionAnswer is a helper method to define mock.On call -// - sessionUUID string -// - questionUUID string -// - answer types.Answer -func (_e *Interface_Expecter) UpsertSurveyQuestionAnswer(sessionUUID interface{}, questionUUID interface{}, answer interface{}) *Interface_UpsertSurveyQuestionAnswer_Call { - return &Interface_UpsertSurveyQuestionAnswer_Call{Call: _e.mock.On("UpsertSurveyQuestionAnswer", sessionUUID, questionUUID, answer)} -} - -func (_c *Interface_UpsertSurveyQuestionAnswer_Call) Run(run func(sessionUUID string, questionUUID string, answer types.Answer)) *Interface_UpsertSurveyQuestionAnswer_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(types.Answer)) - }) - return _c -} - -func (_c *Interface_UpsertSurveyQuestionAnswer_Call) Return(_a0 error) *Interface_UpsertSurveyQuestionAnswer_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Interface_UpsertSurveyQuestionAnswer_Call) RunAndReturn(run func(string, string, types.Answer) error) *Interface_UpsertSurveyQuestionAnswer_Call { - _c.Call.Return(run) - return _c -} - -// UpsertSurveyQuestions provides a mock function with given fields: survey -func (_m *Interface) UpsertSurveyQuestions(survey *types.Survey) error { - ret := _m.Called(survey) - - var r0 error - if rf, ok := ret.Get(0).(func(*types.Survey) error); ok { - r0 = rf(survey) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Interface_UpsertSurveyQuestions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpsertSurveyQuestions' -type Interface_UpsertSurveyQuestions_Call struct { - *mock.Call -} - -// UpsertSurveyQuestions is a helper method to define mock.On call -// - survey *types.Survey -func (_e *Interface_Expecter) UpsertSurveyQuestions(survey interface{}) *Interface_UpsertSurveyQuestions_Call { - return &Interface_UpsertSurveyQuestions_Call{Call: _e.mock.On("UpsertSurveyQuestions", survey)} -} - -func (_c *Interface_UpsertSurveyQuestions_Call) Run(run func(survey *types.Survey)) *Interface_UpsertSurveyQuestions_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*types.Survey)) - }) - return _c -} - -func (_c *Interface_UpsertSurveyQuestions_Call) Return(_a0 error) *Interface_UpsertSurveyQuestions_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Interface_UpsertSurveyQuestions_Call) RunAndReturn(run func(*types.Survey) error) *Interface_UpsertSurveyQuestions_Call { - _c.Call.Return(run) - return _c -} - -// NewInterface creates a new instance of Interface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewInterface(t interface { - mock.TestingT - Cleanup(func()) -}) *Interface { - mock := &Interface{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/api/pkg/controllers/handler.go b/api/pkg/controllers/handler.go index 5e0a065..91033f2 100644 --- a/api/pkg/controllers/handler.go +++ b/api/pkg/controllers/handler.go @@ -6,11 +6,13 @@ import ( type Handler struct { services.Services + JWTService services.JwtService } -func NewHandler(svc services.Services) *Handler { +func NewHandler(svc services.Services, jwtSvc services.JwtService) *Handler { h := &Handler{ - Services: svc, + Services: svc, + JWTService: jwtSvc, } return h } diff --git a/api/pkg/controllers/router.go b/api/pkg/controllers/router.go index 0de2145..2ba03b4 100644 --- a/api/pkg/controllers/router.go +++ b/api/pkg/controllers/router.go @@ -14,6 +14,8 @@ func NewRouter(h *Handler) *echo.Echo { e.Use(middleware.Logger()) e.Use(middleware.Recover()) e.Use(middleware.CORS()) + e.POST("/signup", h.registerUser) + e.POST("/login", h.loginUser) e.GET("/", h.healthCheckHandler) e.GET("/app/surveys", h.getSurveys) diff --git a/api/pkg/controllers/users.go b/api/pkg/controllers/users.go new file mode 100644 index 0000000..1185791 --- /dev/null +++ b/api/pkg/controllers/users.go @@ -0,0 +1,81 @@ +package controllers + +import ( + "errors" + + "github.com/labstack/echo/v4" + "github.com/plutov/formulosity/api/pkg/http/response" + "github.com/plutov/formulosity/api/pkg/types" +) + +func (h *Handler) registerUser(c echo.Context) error { + var input struct { + Name string `json:"name"` + Email string `json:"email"` + Password string `json:"password"` + } + if err := c.Bind(&input); err != nil { + return response.BadRequestDefaultMessage(c) + } + user := &types.User{ + Name: input.name, + Email:input.email + } + err=user.Password.ValidatePassword(input.password) + + if err != nil{ + return response.BadRequest(err.String()) + } + err=h.Services.Storage.CreateUser(user) + if err!= nil{ + switch{ + case errors.Is(err, types.ErrDuplicateEmail): + return response.BadRequest(types.ErrDuplicateEmail) + default: + return response.BadRequest(err.String()) + } + } + return response.Created(c,"User Created Successfully",user) +} + +func (h *Handler)loginUser(c echo.Context)error{ + var input struct{ + Email string `json:"email"` + Password string `json:"password"` + } + if err:=c.Bind(&input); err!=nil{ + return response.BadRequestDefaultMessage(c) + } + + if input.Email == "" || input.Password == ""{ + return response.BadRequest(c,"Email and password are required") + } + + user, err:= h.Services.Storage.GetUserByEmail(input.Email) + + if err!= nil{ + switch{ + case errors.Is(err, types.ErrRecordNotFound): + return response.Unauthorized(c, types.ErrRecordNotFound.Error()) + default: + return response.Unauthorized(c, "invalid Email") + } + } + ok, err:=user.Password.Matches(input.Password) + if err!= nil{ + response.InternalErrorDefaultMsg(c) + } + + if !ok{ + response.Unauthorized(c, "Incorrect password") + } + token, err:=h.JWTService.GenerateToken(user) + if err!= nil{ + return response.InternalErrorDefaultMsg(c) + } + + return response.Ok(c, echo.Map{ + "token":token, + "user":user, + }) +} \ No newline at end of file diff --git a/api/pkg/middleware/middleware.go b/api/pkg/middleware/middleware.go new file mode 100644 index 0000000..b3c4aac --- /dev/null +++ b/api/pkg/middleware/middleware.go @@ -0,0 +1,23 @@ +package middleware + +import ( + "github.com/google/s2a-go/example/echo" + "github.com/plutov/formulosity/api/pkg/services" +) + +func AuthMiddleware(svc services.JwtService) echo.MiddlewareFunc {} +return func(next echo.HandlerFunc)echo.HandlerFunc{ + return func(c echo.Context)error{ + token:=c.Request().Header.Get("Authorization") + if token == ""{ + return response.Unauthorized(c, "Missing authorization token") + } + user, err:=svc.JWTService.ValidateToken(token) + + if err != nil{ + return response.Unauthorized(c, "Invalid authorization token") + } + c.Set("user", user) + return next(c) + } +} diff --git a/api/pkg/services/jwtSercices.go b/api/pkg/services/jwtSercices.go new file mode 100644 index 0000000..db76e5d --- /dev/null +++ b/api/pkg/services/jwtSercices.go @@ -0,0 +1,47 @@ +package services + +import ( + "time" + + "github.com/golang-jwt/jwt" + "github.com/plutov/formulosity/api/pkg/types" +) + +type JwtService interface { + GenerateToken(user *types.User) (string, error) + ValidateToken(tokenString string) (*types.User, error) +} + +type jwtService struct { + secretKey []byte + Services +} + +func NewJWTService(secretKey string, svc Services) JwtService { + return &jwtService{ + secretKey: []byte(secretKey), + Services: svc, + } +} + +func (j *jwtService) GenerateToken(user *types.User) (string, error) { + token := jwt.New(jwt.SigningMethodHS256) + claims := token.Claims.(jwt.MapClaims) + claims["user_id"] = user.Id + claims["exp"] = time.Now().Add(time.Hour * 24).Unix() + return token.SignedString(j.secretKey) +} + +func (j *jwtService) ValidateToken(tokenString string) (*types.User, error) { + token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) { + return j.secretKey, nil + }) + if err != nil { + return nil, err + } + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + Id := int64(claims["user_id"].(float64)) + return j.Services.Storage.GetUserById(Id) + } + return nil, jwt.ErrSignatureInvalid +} diff --git a/api/pkg/storage/interface.go b/api/pkg/storage/interface.go index 1c53c4e..102142e 100644 --- a/api/pkg/storage/interface.go +++ b/api/pkg/storage/interface.go @@ -22,4 +22,8 @@ type Interface interface { GetSurveySessionsWithAnswers(surveyUUID string, filter *types.SurveySessionsFilter) ([]types.SurveySession, int, error) GetSurveySessionAnswers(sessionUUID string) ([]types.QuestionAnswer, error) UpsertSurveyQuestionAnswer(sessionUUID string, questionUUID string, answer types.Answer) error + + CreateUser(user *types.User) error + GetUserByEmail(email string) (*types.User, error) + GetUserById(id int64) (*types.User, error) } diff --git a/api/pkg/storage/postgres.go b/api/pkg/storage/postgres.go index 64a2310..b73c045 100644 --- a/api/pkg/storage/postgres.go +++ b/api/pkg/storage/postgres.go @@ -69,6 +69,74 @@ func (p *Postgres) Migrate() error { return nil } +func (p *Postgres)GetUserById(id int64)(*types.User, error){ + query:=`SELECT id, created_at, name, email, password_hash, activated, version FROM users WHERE id = $1` + var user types.User + err:=p.conn.QueryRow(query,id).Scan( + &user.Id, + &user.CreatedAt, + &user.Name, + &user.Email, + &user.Password.hash, + &user.Activated, + &user.Version, + ) + if err!= nil{ + switch { + case errors.Is(err, sql.ErrNoRows): + return nil, types.ErrRecordNotFound + default: + return nil, err + } + } + return &user, nil +} + +func (p *Postgres)GetUserByEmail(email string)(*types.User, error){ + query := ` + SELECT id, created_at, name, email, password_hash, activated, version + FROM users + WHERE email = $1` + var user types.User + err := p.conn.QueryRow(query, email).Scan( + &user.Id, + &user.CreatedAt, + &user.Name, + &user.Email, + &user.Password.hash, + &user.Activated, + &user.Version, + ) + if err!= nil{ + switch { + case errors.Is(err, sql.ErrNoRows): + return nil, types.ErrRecordNotFound + default: + return nil, err + } + } + return &user, nil +} + +func (p *Postgres)CreateUser(user *types.User)(error){ + query := ` + INSERT INTO users (name, email, password_hash) + VALUES ($1, $2, $3) + RETURNING id, created_at, version` + args := []any{user.Name, user.Email, user.Password.hash} + err:=p.conn.QueryRow(query,...args).Scan(&user.Id, &user.Created, &user.Version) + + if err != nil{ + switch { + case err.Error() == `pq: duplicate key value violates unique constraint "users_email_key"`: + return types.ErrDuplicateEmail + default: + return err + } + } + return nil +} + func (p *Postgres) CreateSurvey(survey *types.Survey) error { query := `INSERT INTO surveys (parse_status, delivery_status, error_log, name, config, url_slug) @@ -417,3 +485,5 @@ func (p *Postgres) getSurveySessionsCount(surveyUUID string) (int, error) { err := row.Scan(&count) return count, err } + +func (p * Postgres) \ No newline at end of file diff --git a/api/pkg/storage/sqlite.go b/api/pkg/storage/sqlite.go index 773c6cf..a6217f8 100644 --- a/api/pkg/storage/sqlite.go +++ b/api/pkg/storage/sqlite.go @@ -80,6 +80,76 @@ func (p *Sqlite) Migrate() error { return nil } +func (s *Sqlite)GetUserById(id int64)(*types.User, error){ + query:=`SELECT id, created_at, name, email, password_hash, activated, version FROM users WHERE id = $1` + var user types.User + err:=p.conn.QueryRow(query,id).Scan( + &user.Id, + &user.CreatedAt, + &user.Name, + &user.Email, + &user.Password.hash, + &user.Activated, + &user.Version, + ) + if err!= nil{ + switch { + case errors.Is(err, sql.ErrNoRows): + return nil, types.ErrRecordNotFound + default: + return nil, err + } + } + return &user, nil +} + + +func (p *Sqlite)GetUserByEmail(email string)(*types.User, error){ + query := ` + SELECT id, created_at, name, email, password_hash, activated, version + FROM users + WHERE email = $1` + var user types.User + err := p.Db.QueryRow(query, email).Scan( + &user.Id, + &user.CreatedAt, + &user.Name, + &user.Email, + &user.Password.hash, + &user.Activated, + &user.Version, + ) + if err!= nil{ + switch { + case errors.Is(err, sql.ErrNoRows): + return nil, types.ErrRecordNotFound + default: + return nil, err + } + } + return &user, nil +} + + +func (p *Sqlite)CreateUser(user *types.User)(error){ + query := ` + INSERT INTO users (name, email, password_hash) + VALUES ($1, $2, $3) + RETURNING id, created_at, version` + args := []any{user.Name, user.Email, user.Password.hash} + err:=p.conn.QueryRow(query, ...args).Scan(&user.Id, &user.Created, &user.Version) + + if err != nil{ + switch { + case err.Error() == `pq: duplicate key value violates unique constraint "users_email_key"`: + return types.ErrDuplicateEmail + default: + return err + } + } + return nil +} + func (p *Sqlite) CreateSurvey(survey *types.Survey) error { query := `INSERT INTO surveys (parse_status, delivery_status, error_log, name, config, url_slug, uuid, created_at) diff --git a/api/pkg/types/user.go b/api/pkg/types/user.go new file mode 100644 index 0000000..4f8290f --- /dev/null +++ b/api/pkg/types/user.go @@ -0,0 +1,54 @@ +package types + +import ( + "errors" + "time" + + "golang.org/x/crypto/bcrypt" +) + +var ( + ErrDuplicateEmail = errors.New("duplicate email") + ErrRecordNotFound = errors.New("record not found") +) + +type User struct { + Id int64 `json:"id"` + CreatedAt time.Time `json:"created_at"` + Name string `json:"name"` + Email string `json:"email"` + Password Password `json:"-"` + Version int `json:"-"` +} + +type Password struct { + Plaintext *string + Hash []byte +} + +func (p Password) ValidatePassword(value string) error { + if value == "" { + return errors.New("Password must be Provided") + } + hash, err := bcrypt.GenerateFromPassword([]byte(value), 12) + if err != nil { + return err + } + p.Plaintext = &value + p.Hash = hash + return nil +} + +func (p Password) Matches(text string) (bool, error) { + err := bcrypt.CompareHashAndPassword(p.Hash, []byte(text)) + + if err != nil { + switch { + case errors.Is(err, bcrypt.ErrMismatchedHashAndPassword): + return false, err + default: + return false, err + } + } + return true, nil +}