From 035b72a5d10a6fc6ec0884b42b79a9445e8dcd9c Mon Sep 17 00:00:00 2001 From: Mirza Hanan Date: Tue, 14 May 2024 04:12:42 +0500 Subject: [PATCH] Add User Stories to Features --- cypress/e2e/04_user_stories.cy.ts | 31 ++++++------- db/config.go | 1 + db/features.go | 56 +++++++++++++++++++++++ db/interface.go | 4 ++ db/structs.go | 12 +++++ handlers/features.go | 70 +++++++++++++++++++++++++++++ mocks/Database.go | 75 +++++++++++++++++++++++++++++++ routes/features.go | 4 ++ 8 files changed, 238 insertions(+), 15 deletions(-) diff --git a/cypress/e2e/04_user_stories.cy.ts b/cypress/e2e/04_user_stories.cy.ts index 537a6764d..1321ce441 100644 --- a/cypress/e2e/04_user_stories.cy.ts +++ b/cypress/e2e/04_user_stories.cy.ts @@ -35,7 +35,7 @@ describe('Modify user story description', () => { }).its('body').then(body => { expect(body).to.have.property('uuid').and.equal(UserStories[i].uuid.trim()); expect(body).to.have.property('feature_uuid').and.equal(UserStories[i].feature_uuid.trim()); - expect(body).to.have.property('description').and.equal(UserStories[i].description.trim() + "_addtext"); + expect(body).to.have.property('description').and.equal(UserStories[i].description.trim() + " _addtext"); expect(body).to.have.property('priority').and.equal(UserStories[i].priority); }); } @@ -56,7 +56,7 @@ describe('Get user stories for feature', () => { for(let i = 0; i <= 5; i++) { expect(resp.body[i]).to.have.property('uuid').and.equal(UserStories[i].uuid.trim()); expect(resp.body[i]).to.have.property('feature_uuid').and.equal(UserStories[i].feature_uuid.trim()); - expect(resp.body[i]).to.have.property('description').and.equal(UserStories[i].description.trim() + "_addtext"); + expect(resp.body[i]).to.have.property('description').and.equal(UserStories[i].description.trim() + " _addtext"); expect(resp.body[i]).to.have.property('priority').and.equal(UserStories[i].priority); } }) @@ -71,19 +71,19 @@ describe('Get story by uuid', () => { cy.request({ method: 'GET', url: `${HostName}/features/${UserStories[0].feature_uuid}/story/${UserStories[i].uuid}`, - headers: { 'x-jwt': `${ value }` }, - body: {} + headers: { 'x-jwt': `${value}` }, + body: {} }).then((resp) => { - expect(resp.status).to.eq(200) - expect(resp.body[i]).to.have.property('uuid').and.equal(UserStories[i].uuid.trim()); - expect(resp.body[i]).to.have.property('feature_uuid').and.equal(UserStories[i].feature_uuid.trim()); - expect(resp.body[i]).to.have.property('description').and.equal(UserStories[i].description.trim() + "_addtext"); - expect(resp.body[i]).to.have.property('priority').and.equal(UserStories[i].priority); - }) + expect(resp.status).to.eq(200); + expect(resp.body).to.have.property('uuid').and.equal(UserStories[i].uuid.trim()); + expect(resp.body).to.have.property('feature_uuid').and.equal(UserStories[i].feature_uuid.trim()); + expect(resp.body).to.have.property('description').and.equal(UserStories[i].description.trim() + " _addtext"); + expect(resp.body).to.have.property('priority').and.equal(UserStories[i].priority); + }); } - }) - }) -}) + }); + }); +}); describe('Delete story by uuid', () => { it('passes', () => { @@ -104,10 +104,11 @@ describe('Check delete by uuid', () => { it('passes', () => { cy.upsertlogin(User).then(value => { cy.request({ - method: 'GET', + method: 'DELETE', url: `${HostName}/features/${UserStories[0].feature_uuid}/story/${UserStories[0].uuid}`, headers: { 'x-jwt': `${ value }` }, - body: {} + body: {}, + failOnStatusCode: false }).then((resp) => { expect(resp.status).to.eq(404); }) diff --git a/db/config.go b/db/config.go index 5496e4b8f..5f7954487 100644 --- a/db/config.go +++ b/db/config.go @@ -68,6 +68,7 @@ func InitDB() { db.AutoMigrate(&BountyRoles{}) db.AutoMigrate(&UserInvoiceData{}) db.AutoMigrate(&WorkspaceFeatures{}) + db.AutoMigrate(&FeatureStory{}) DB.MigrateTablesWithOrgUuid() DB.MigrateOrganizationToWorkspace() diff --git a/db/features.go b/db/features.go index c978124be..a92ce7984 100644 --- a/db/features.go +++ b/db/features.go @@ -1,6 +1,7 @@ package db import ( + "errors" "strings" "time" ) @@ -39,3 +40,58 @@ func (db database) CreateOrEditFeature(m WorkspaceFeatures) (WorkspaceFeatures, return m, nil } + +func (db database) CreateOrEditFeatureStory(story FeatureStory) (FeatureStory, error) { + story.Description = strings.TrimSpace(story.Description) + + now := time.Now() + story.Updated = &now + + existingStory := FeatureStory{} + result := db.db.Model(&FeatureStory{}).Where("uuid = ?", story.Uuid).First(&existingStory) + + if result.RowsAffected == 0 { + + story.Created = &now + db.db.Create(&story) + } else { + + db.db.Model(&FeatureStory{}).Where("uuid = ?", story.Uuid).Updates(story) + } + + db.db.Model(&FeatureStory{}).Where("uuid = ?", story.Uuid).Find(&story) + + return story, nil +} + +func (db database) GetFeatureStoriesByFeatureUuid(featureUuid string) ([]FeatureStory, error) { + var stories []FeatureStory + result := db.db.Where("feature_uuid = ?", featureUuid).Find(&stories) + if result.Error != nil { + return nil, result.Error + } + + for i := range stories { + stories[i].Description = strings.TrimSpace(stories[i].Description) + } + return stories, nil +} + +func (db database) GetFeatureStoryByUuid(uuid string) (FeatureStory, error) { + var story FeatureStory + result := db.db.Where("uuid = ?", uuid).First(&story) + if result.Error != nil { + return FeatureStory{}, result.Error + } + + story.Description = strings.TrimSpace(story.Description) + return story, nil +} + +func (db database) DeleteFeatureStoryByUuid(featureUuid, storyUuid string) error { + result := db.db.Where("feature_uuid = ? AND uuid = ?", featureUuid, storyUuid).Delete(&FeatureStory{}) + if result.RowsAffected == 0 { + return errors.New("no story found to delete") + } + return nil +} diff --git a/db/interface.go b/db/interface.go index ff3a4479e..e20bdea85 100644 --- a/db/interface.go +++ b/db/interface.go @@ -143,4 +143,8 @@ type Database interface { CreateOrEditFeature(m WorkspaceFeatures) (WorkspaceFeatures, error) GetFeaturesByWorkspaceUuid(uuid string) []WorkspaceFeatures GetFeatureByUuid(uuid string) WorkspaceFeatures + CreateOrEditFeatureStory(story FeatureStory) (FeatureStory, error) + GetFeatureStoriesByFeatureUuid(featureUuid string) ([]FeatureStory, error) + GetFeatureStoryByUuid(uuid string) (FeatureStory, error) + DeleteFeatureStoryByUuid(featureUuid, storyUuid string) error } diff --git a/db/structs.go b/db/structs.go index ddae676fc..e02723b3f 100644 --- a/db/structs.go +++ b/db/structs.go @@ -656,6 +656,18 @@ type BudgetHistory struct { PaymentType PaymentType `json:"payment_type"` } +type FeatureStory struct { + ID uint `json:"id"` + Uuid string `json:"uuid"` + FeatureUuid string `json:"feature_uuid"` + Description string `json:"description"` + Priority int `json:"priority"` + Created *time.Time `json:"created"` + Updated *time.Time `json:"updated"` + CreatedBy string `json:"created_by"` + UpdatedBy string `json:"updated_by"` +} + type BudgetHistoryData struct { BudgetHistory SenderName string `json:"sender_name"` diff --git a/handlers/features.go b/handlers/features.go index 9c4f9cd68..1db212588 100644 --- a/handlers/features.go +++ b/handlers/features.go @@ -93,3 +93,73 @@ func (oh *featureHandler) GetFeatureByUuid(w http.ResponseWriter, r *http.Reques w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(workspaceFeature) } + +func (oh *featureHandler) CreateOrEditStory(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) + if pubKeyFromAuth == "" { + fmt.Println("no pubkey from auth") + w.WriteHeader(http.StatusUnauthorized) + return + } + + newStory := db.FeatureStory{} + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&newStory) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "Error decoding request body: %v", err) + return + } + + newStory.CreatedBy = pubKeyFromAuth + + story, err := oh.db.CreateOrEditFeatureStory(newStory) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error creating feature story: %v", err) + return + } + + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(story) +} + +func (oh *featureHandler) GetStoriesByFeatureUuid(w http.ResponseWriter, r *http.Request) { + featureUuid := chi.URLParam(r, "feature_uuid") + stories, err := oh.db.GetFeatureStoriesByFeatureUuid(featureUuid) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(stories) +} + +func (oh *featureHandler) GetStoryByUuid(w http.ResponseWriter, r *http.Request) { + storyUuid := chi.URLParam(r, "story_uuid") + story, err := oh.db.GetFeatureStoryByUuid(storyUuid) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(story) +} + +func (oh *featureHandler) DeleteStory(w http.ResponseWriter, r *http.Request) { + featureUuid := chi.URLParam(r, "feature_uuid") + storyUuid := chi.URLParam(r, "story_uuid") + + err := oh.db.DeleteFeatureStoryByUuid(featureUuid, storyUuid) + if err != nil { + w.WriteHeader(http.StatusNotFound) + json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{"message": "Story deleted successfully"}) +} diff --git a/mocks/Database.go b/mocks/Database.go index f3a3587d7..466ff2ecd 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -6660,3 +6660,78 @@ func NewDatabase(t interface { return mock } + +func (_m *Database) CreateOrEditFeatureStory(story db.FeatureStory) (db.FeatureStory, error) { + ret := _m.Called(story) + + var r0 db.FeatureStory + var r1 error + if rf, ok := ret.Get(0).(func(db.FeatureStory) db.FeatureStory); ok { + r0 = rf(story) + } else { + r0 = ret.Get(0).(db.FeatureStory) + } + + if rf, ok := ret.Get(1).(func(db.FeatureStory) error); ok { + r1 = rf(story) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +func (_m *Database) GetFeatureStoriesByFeatureUuid(featureUuid string) ([]db.FeatureStory, error) { + ret := _m.Called(featureUuid) + + var r0 []db.FeatureStory + var r1 error + if rf, ok := ret.Get(0).(func(string) []db.FeatureStory); ok { + r0 = rf(featureUuid) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]db.FeatureStory) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(featureUuid) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +func (_m *Database) GetFeatureStoryByUuid(uuid string) (db.FeatureStory, error) { + ret := _m.Called(uuid) + + var r0 db.FeatureStory + var r1 error + if rf, ok := ret.Get(0).(func(string) db.FeatureStory); ok { + r0 = rf(uuid) + } else { + r0 = ret.Get(0).(db.FeatureStory) + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(uuid) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +func (_m *Database) DeleteFeatureStoryByUuid(featureUuid string, storyUuid string) error { + ret := _m.Called(featureUuid, storyUuid) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string) error); ok { + r0 = rf(featureUuid, storyUuid) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/routes/features.go b/routes/features.go index 0b2449399..3658aa7dc 100644 --- a/routes/features.go +++ b/routes/features.go @@ -16,6 +16,10 @@ func FeatureRoutes() chi.Router { r.Post("/", featureHandlers.CreateOrEditFeatures) r.Get("/forworkspace/{uuid}", featureHandlers.GetFeaturesByWorkspaceUuid) r.Get("/{uuid}", featureHandlers.GetFeatureByUuid) + r.Post("/story", featureHandlers.CreateOrEditStory) + r.Get("/{feature_uuid}/story", featureHandlers.GetStoriesByFeatureUuid) + r.Get("/{feature_uuid}/story/{story_uuid}", featureHandlers.GetStoryByUuid) + r.Delete("/{feature_uuid}/story/{story_uuid}", featureHandlers.DeleteStory) }) return r }