diff --git a/models/models.go b/models/models.go index 0c7ba7a..a9d1a0a 100644 --- a/models/models.go +++ b/models/models.go @@ -17,6 +17,9 @@ type Store interface { SelectActionsByPlanID(plan *Plan, userID int) ([]*Action, error) SelectUserByUsername(username string) (*User, error) InsertUser(user *User) (int, error) + SelectPrimaryPlan(userID int) (*PrimaryPlan, error) + InsertPrimaryPlan(primaryPlan *PrimaryPlan, userID int) error + UpdatePrimaryPlan(primaryPlan *PrimaryPlan, userID int) error } // Model represents a current model item. diff --git a/models/primary_plan.go b/models/primary_plan.go new file mode 100644 index 0000000..406ede5 --- /dev/null +++ b/models/primary_plan.go @@ -0,0 +1,23 @@ +package models + +// PrimaryPlan represents the primary plan ID for a particular user ID. +type PrimaryPlan struct { + UserID int64 `json:"user_id"` + PlanID int64 `json:"plan_id"` +} + +// PrimaryPlan returns the primary plan for a provided user ID in the given model. +func (m *Model) PrimaryPlan(userID int) (*PrimaryPlan, error) { + pp, err := m.SelectPrimaryPlan(userID) + return pp, wrapNotFound(err) +} + +// AddPrimaryPlan inserts a given primary plan into the store, returning nothing. +func (m *Model) AddPrimaryPlan(pp *PrimaryPlan, userID int) error { + return m.InsertPrimaryPlan(pp, userID) +} + +// SavePrimaryPlan saves and updates a primary plan. +func (m *Model) SavePrimaryPlan(pp *PrimaryPlan, userID int) error { + return m.UpdatePrimaryPlan(pp, userID) +} diff --git a/routes/primary_plans.go b/routes/primary_plans.go new file mode 100644 index 0000000..707ac46 --- /dev/null +++ b/routes/primary_plans.go @@ -0,0 +1,160 @@ +package routes + +import ( + "encoding/json" + "gitea.deepak.science/deepak/gogmagog/models" + "gitea.deepak.science/deepak/gogmagog/tokens" + "github.com/go-chi/chi" + "io" + "net/http" +) + +// NewPrimaryPlanRouter returns a new primary plan router +func NewPrimaryPlanRouter(m *models.Model) http.Handler { + router := chi.NewRouter() + router.Get("/", getPrimaryPlanFunc(m)) + router.Post("/", postPrimaryPlanFunc(m)) + router.Put("/", putPrimaryPlanFunc(m)) + return router +} + +func getPrimaryPlanFunc(m *models.Model) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + userID, userErr := tokens.GetUserID(r.Context()) + if userErr != nil { + unauthorizedHandler(w, r) + return + } + + pp, err := m.PrimaryPlan(userID) + if err != nil { + if models.IsNotFoundError(err) { + notFoundHandler(w, r) + return + } + serverError(w, err) + return + } + + w.Header().Add("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(pp); err != nil { + serverError(w, err) + } + } +} + +type createPrimaryPlanResponse struct { + CreatedPrimaryPlan *models.PrimaryPlan `json:"created_current_plan"` +} + +func postPrimaryPlanFunc(m *models.Model) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + userID, err := tokens.GetUserID(r.Context()) + if err != nil { + unauthorizedHandler(w, r) + return + } + + r.Body = http.MaxBytesReader(w, r.Body, 1024) + dec := json.NewDecoder(r.Body) + dec.DisallowUnknownFields() + var pp models.PrimaryPlan + err = dec.Decode(&pp) + if err != nil { + badRequestError(w, err) + return + } + err = dec.Decode(&struct{}{}) + if err != io.EOF { + badRequestError(w, err) + return + } + + newPP := &models.PrimaryPlan{ + PlanID: pp.PlanID, + UserID: int64(userID), + } + err = m.AddPrimaryPlan(newPP, userID) + if err != nil { + serverError(w, err) + return + } + finishedPP, err := m.PrimaryPlan(userID) + if err != nil { + serverError(w, err) + return + } + + response := &createPrimaryPlanResponse{ + CreatedPrimaryPlan: finishedPP, + } + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + if err := json.NewEncoder(w).Encode(response); err != nil { + serverError(w, err) + } + + } +} + +type updatePrimaryPlanResponse struct { + UpdatedPrimaryPlan *models.PrimaryPlan `json:"updated_current_plan"` +} + +func putPrimaryPlanFunc(m *models.Model) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + userID, err := tokens.GetUserID(r.Context()) + if err != nil { + unauthorizedHandler(w, r) + return + } + + _, err = m.PrimaryPlan(userID) + if models.IsNotFoundError(err) { + notFoundHandler(w, r) + return + } + + r.Body = http.MaxBytesReader(w, r.Body, 1024) + dec := json.NewDecoder(r.Body) + dec.DisallowUnknownFields() + var pp models.PrimaryPlan + err = dec.Decode(&pp) + if err != nil { + badRequestError(w, err) + return + } + err = dec.Decode(&struct{}{}) + if err != io.EOF { + badRequestError(w, err) + return + } + + newPP := &models.PrimaryPlan{ + PlanID: pp.PlanID, + UserID: int64(userID), + } + err = m.SavePrimaryPlan(newPP, userID) + if err != nil { + serverError(w, err) + return + } + newPP, err = m.PrimaryPlan(userID) + if err != nil { + serverError(w, err) + return + } + + response := &updatePrimaryPlanResponse{ + UpdatedPrimaryPlan: newPP, + } + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(response); err != nil { + serverError(w, err) + } + + } +} diff --git a/routes/routes.go b/routes/routes.go index dbcd846..96eeef0 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -18,6 +18,7 @@ func NewRouter(m *models.Model, tok tokens.Toker) http.Handler { r.Mount("/actions", NewActionRouter(m)) r.Mount("/plans", NewPlanRouter(m)) r.Mount("/me", NewCurrentUserRouter(m)) + r.Mount("/currentPlan", NewPrimaryPlanRouter(m)) }) router.Mount("/auth", NewAuthRouter(m, tok)) router.Mount("/health", newHealthRouter(m)) diff --git a/store/errorStore.go b/store/errorStore.go index 87bc6c5..7b8657b 100644 --- a/store/errorStore.go +++ b/store/errorStore.go @@ -57,6 +57,24 @@ func (e *errorStore) InsertUser(user *models.User) (int, error) { return 0, nil } +func (e *errorStore) SelectPrimaryPlan(userID int) (*models.PrimaryPlan, error) { + return nil, e.error +} + +func (e *errorStore) InsertPrimaryPlan(primaryPlan *models.PrimaryPlan, userID int) error { + if e.errorOnInsert { + return e.error + } + return nil +} + +func (e *errorStore) UpdatePrimaryPlan(primaryPlan *models.PrimaryPlan, userID int) error { + if e.errorOnInsert { + return e.error + } + return nil +} + func (e *errorStore) ConnectionLive() error { return e.error } diff --git a/store/inmemory.go b/store/inmemory.go index 2168e3c..95305ff 100644 --- a/store/inmemory.go +++ b/store/inmemory.go @@ -2,13 +2,15 @@ package store import ( "database/sql" + "fmt" "gitea.deepak.science/deepak/gogmagog/models" ) type inMemoryStore struct { - actions []*models.Action - plans []*models.Plan - users []*models.User + actions []*models.Action + plans []*models.Plan + users []*models.User + primaryPlans []*models.PrimaryPlan } // GetInMemoryStore provides a purely in memory store, for testing purposes only, with no persistence. @@ -98,6 +100,37 @@ func (store *inMemoryStore) InsertPlan(plan *models.Plan, userID int) (int, erro return id, nil } +func (store *inMemoryStore) SelectPrimaryPlan(userID int) (*models.PrimaryPlan, error) { + for _, primaryPlan := range store.primaryPlans { + if userID == int(primaryPlan.UserID) { + return primaryPlan, nil + } + } + return nil, sql.ErrNoRows +} + +func (store *inMemoryStore) InsertPrimaryPlan(primaryPlan *models.PrimaryPlan, userID int) error { + _, err := store.SelectPrimaryPlan(userID) + if err != sql.ErrNoRows { + return err + } + if err == nil { + return fmt.Errorf("Can't insert primary plan") + } + + store.primaryPlans = append(store.primaryPlans, &models.PrimaryPlan{PlanID: int64(primaryPlan.PlanID), UserID: int64(userID)}) + return nil +} + +func (store *inMemoryStore) UpdatePrimaryPlan(primaryPlan *models.PrimaryPlan, userID int) error { + current, err := store.SelectPrimaryPlan(userID) + if err != nil { + return err + } + current.PlanID = primaryPlan.PlanID + return nil +} + func (store *inMemoryStore) ConnectionLive() error { return nil } diff --git a/store/migrations/000001_create_action_table.down.sql b/store/migrations/000001_create_action_table.down.sql index 6a2c85b..1d07bed 100644 --- a/store/migrations/000001_create_action_table.down.sql +++ b/store/migrations/000001_create_action_table.down.sql @@ -1,4 +1,5 @@ DROP TABLE IF EXISTS actions; +DROP TABLE IF EXISTS user_primary_plan; DROP TABLE IF EXISTS plans; DROP TABLE IF EXISTS users; diff --git a/store/migrations/000001_create_action_table.up.sql b/store/migrations/000001_create_action_table.up.sql index 56e92c1..bec2321 100644 --- a/store/migrations/000001_create_action_table.up.sql +++ b/store/migrations/000001_create_action_table.up.sql @@ -12,7 +12,14 @@ CREATE TABLE IF NOT EXISTS plans( plan_date DATE NOT NULL, user_id int REFERENCES users(user_id), created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + UNIQUE (user_id, plan_id) +); + +CREATE TABLE IF NOT EXISTS user_primary_plan( + user_id int PRIMARY KEY, + plan_id int, + FOREIGN KEY (user_id, plan_id) REFERENCES plans(user_id, plan_id) ); CREATE TABLE IF NOT EXISTS actions( diff --git a/store/postgres.go b/store/postgres.go index 900a212..3d83ef1 100644 --- a/store/postgres.go +++ b/store/postgres.go @@ -174,3 +174,51 @@ func (store *postgresStore) InsertUser(user *models.User) (int, error) { } return id, nil } + +func (store *postgresStore) SelectPrimaryPlan(userID int) (*models.PrimaryPlan, error) { + pp := models.PrimaryPlan{} + queryString := store.db.Rebind(`SELECT user_id, plan_id FROM user_primary_plan WHERE user_id = ?`) + err := store.db.Get(&pp, queryString, userID) + if err != nil { + return nil, err + } + return &pp, nil +} + +func (store *postgresStore) InsertPrimaryPlan(primaryPlan *models.PrimaryPlan, userID int) error { + queryString := store.db.Rebind("INSERT INTO user_primary_plan (user_id, plan_id) VALUES (?, ?) RETURNING user_id") + tx := store.db.MustBegin() + var id int + err := tx.Get(&id, queryString, primaryPlan.PlanID, userID) + if err != nil { + tx.Rollback() + return err + } + err = tx.Commit() + if err != nil { + return err + } + return nil +} + +func (store *postgresStore) UpdatePrimaryPlan(primaryPlan *models.PrimaryPlan, userID int) error { + query := `UPDATE user_primary_plan SET + plan_id = :plan_id + WHERE user_id = :user_id` + tx := store.db.MustBegin() + ppToUse := &models.PrimaryPlan{ + PlanID: primaryPlan.PlanID, + UserID: int64(userID), + } + _, err := store.db.NamedExec(query, ppToUse) + if err != nil { + tx.Rollback() + return err + } + err = tx.Commit() + if err != nil { + return err + } + return nil + +}