From a1b442a36ad5dcf8aeec1c4cc5c4ce1536e4f20a Mon Sep 17 00:00:00 2001 From: aliraza556 Date: Fri, 29 Nov 2024 16:36:02 +0500 Subject: [PATCH] make ticket fields optional except UUID --- db/structs.go | 20 +++++------ db/tickets.go | 48 ++++++++++++++++--------- handlers/ticket.go | 73 ++++++++++++++++++--------------------- handlers/ticket_test.go | 38 +++++++++++++------- routes/ticket_routes.go | 6 +--- utils/ticket_processor.go | 8 ----- 6 files changed, 102 insertions(+), 91 deletions(-) diff --git a/db/structs.go b/db/structs.go index 4dd24946d..075cd5210 100644 --- a/db/structs.go +++ b/db/structs.go @@ -959,18 +959,18 @@ const ( type Tickets struct { UUID uuid.UUID `gorm:"primaryKey;type:uuid;default:gen_random_uuid()"` - FeatureUUID string `gorm:"type:uuid;not null;index:composite_index" json:"feature_uuid" validate:"required"` + FeatureUUID string `gorm:"type:uuid;index:composite_index" json:"feature_uuid"` Features WorkspaceFeatures `gorm:"foreignKey:FeatureUUID;references:Uuid"` - PhaseUUID string `gorm:"type:uuid;not null;index:phase_index" json:"phase_uuid" validate:"required"` + PhaseUUID string `gorm:"type:uuid;index:phase_index" json:"phase_uuid"` FeaturePhase FeaturePhase `gorm:"foreignKey:PhaseUUID;references:Uuid"` - Name string `gorm:"type:varchar(255);not null"` - Sequence int `gorm:"type:integer;not null;index:composite_index"` - Dependency []int `gorm:"type:integer[]"` - Description string `gorm:"type:text"` - Status TicketStatus `gorm:"type:varchar(50);not null;default:'DRAFT'"` - Version int `gorm:"type:integer" json:"version"` - CreatedAt time.Time `gorm:"type:timestamp;not null;default:current_timestamp" json:"created_at"` - UpdatedAt time.Time `gorm:"type:timestamp;not null;default:current_timestamp" json:"updated_at"` + Name string `gorm:"type:varchar(255)" json:"name"` + Sequence int `gorm:"type:integer;index:composite_index;default:0" json:"sequence"` + Dependency []int `gorm:"type:integer[]" json:"dependency"` + Description string `gorm:"type:text" json:"description"` + Status TicketStatus `gorm:"type:varchar(50);default:'DRAFT'" json:"status"` + Version int `gorm:"type:integer;default:0" json:"version"` + CreatedAt time.Time `gorm:"type:timestamp;default:current_timestamp" json:"created_at"` + UpdatedAt time.Time `gorm:"type:timestamp;default:current_timestamp" json:"updated_at"` } func (Person) TableName() string { diff --git a/db/tickets.go b/db/tickets.go index 3f836193a..f826dc511 100644 --- a/db/tickets.go +++ b/db/tickets.go @@ -11,29 +11,47 @@ import ( func (db database) CreateOrEditTicket(ticket *Tickets) (Tickets, error) { - if ticket.UUID == uuid.Nil || ticket.FeatureUUID == "" || ticket.PhaseUUID == "" || ticket.Name == "" { - return Tickets{}, errors.New("required fields are missing") + if ticket.UUID == uuid.Nil { + return Tickets{}, errors.New("ticket UUID is required") } - // check if ticket exists and update it - if db.db.Model(&Tickets{}).Where("uuid = ?", ticket.UUID).First(&ticket).RowsAffected != 0 { - now := time.Now() - ticket.UpdatedAt = now + if ticket.Status != "" && !IsValidTicketStatus(ticket.Status) { + return Tickets{}, errors.New("invalid ticket status") + } + + var existingTicket Tickets + result := db.db.Where("uuid = ?", ticket.UUID).First(&existingTicket) + + now := time.Now() + ticket.UpdatedAt = now - // update ticket - if db.db.Model(&ticket).Where("uuid = ?", ticket.UUID).Updates(&ticket).RowsAffected == 0 { - return Tickets{}, errors.New("failed to update ticket") + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + ticket.CreatedAt = now + + if ticket.Status == "" { + ticket.Status = DraftTicket } + if err := db.db.Create(&ticket).Error; err != nil { + return Tickets{}, fmt.Errorf("failed to create ticket: %w", err) + } return *ticket, nil } - // create ticket and return error if it fails - if db.db.Create(&ticket).Error != nil { - return Tickets{}, db.db.Create(&ticket).Error + if result.Error != nil { + return Tickets{}, fmt.Errorf("database error: %w", result.Error) } - return *ticket, nil + if err := db.db.Model(&existingTicket).Updates(ticket).Error; err != nil { + return Tickets{}, fmt.Errorf("failed to update ticket: %w", err) + } + + var updatedTicket Tickets + if err := db.db.Where("uuid = ?", ticket.UUID).First(&updatedTicket).Error; err != nil { + return Tickets{}, fmt.Errorf("failed to fetch updated ticket: %w", err) + } + + return updatedTicket, nil } func (db database) GetTicket(uuid string) (Tickets, error) { @@ -66,10 +84,6 @@ func (db database) UpdateTicket(ticket Tickets) (Tickets, error) { return Tickets{}, errors.New("ticket UUID is required") } - if ticket.FeatureUUID == "" || ticket.PhaseUUID == "" || ticket.Name == "" { - return Tickets{}, errors.New("feature_uuid, phase_uuid, and name are required") - } - if ticket.Status != "" && !IsValidTicketStatus(ticket.Status) { return Tickets{}, errors.New("invalid ticket status") } diff --git a/handlers/ticket.go b/handlers/ticket.go index 920efd535..49af1aa85 100644 --- a/handlers/ticket.go +++ b/handlers/ticket.go @@ -107,7 +107,7 @@ func (th *ticketHandler) UpdateTicket(w http.ResponseWriter, r *http.Request) { return } - updatedTicket, err := th.db.UpdateTicket(ticket) + updatedTicket, err := th.db.CreateOrEditTicket(&ticket) if err != nil { if err.Error() == "feature_uuid, phase_uuid, and name are required" { w.WriteHeader(http.StatusBadRequest) @@ -199,15 +199,6 @@ func (th *ticketHandler) PostTicketDataToStakwork(w http.ResponseWriter, r *http validationErrors = append(validationErrors, "Invalid UUID format") } } - if ticket.FeatureUUID == "" { - validationErrors = append(validationErrors, "FeatureUUID is required") - } - if ticket.PhaseUUID == "" { - validationErrors = append(validationErrors, "PhaseUUID is required") - } - if ticket.Name == "" { - validationErrors = append(validationErrors, "Name is required") - } if len(validationErrors) > 0 { w.WriteHeader(http.StatusBadRequest) @@ -219,37 +210,41 @@ func (th *ticketHandler) PostTicketDataToStakwork(w http.ResponseWriter, r *http return } - feature := th.db.GetFeatureByUuid(ticket.FeatureUUID) - if feature.Uuid == "" { - w.WriteHeader(http.StatusInternalServerError) - json.NewEncoder(w).Encode(TicketResponse{ - Success: false, - Message: "Error retrieving feature details", - Errors: []string{"Feature not found with the provided UUID"}, - }) - return - } + var productBrief, featureBrief string + if ticket.FeatureUUID != "" { + feature := th.db.GetFeatureByUuid(ticket.FeatureUUID) + if feature.Uuid == "" { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(TicketResponse{ + Success: false, + Message: "Error retrieving feature details", + Errors: []string{"Feature not found with the provided UUID"}, + }) + return + } - productBrief, err := th.db.GetProductBrief(feature.WorkspaceUuid) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - json.NewEncoder(w).Encode(TicketResponse{ - Success: false, - Message: "Error retrieving product brief", - Errors: []string{err.Error()}, - }) - return - } + var err error + productBrief, err = th.db.GetProductBrief(feature.WorkspaceUuid) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(TicketResponse{ + Success: false, + Message: "Error retrieving product brief", + Errors: []string{err.Error()}, + }) + return + } - featureBrief, err := th.db.GetFeatureBrief(ticket.FeatureUUID) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - json.NewEncoder(w).Encode(TicketResponse{ - Success: false, - Message: "Error retrieving feature brief", - Errors: []string{err.Error()}, - }) - return + featureBrief, err = th.db.GetFeatureBrief(ticket.FeatureUUID) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(TicketResponse{ + Success: false, + Message: "Error retrieving feature brief", + Errors: []string{err.Error()}, + }) + return + } } host := os.Getenv("HOST") diff --git a/handlers/ticket_test.go b/handlers/ticket_test.go index 4fcb481e4..890a405c0 100644 --- a/handlers/ticket_test.go +++ b/handlers/ticket_test.go @@ -184,7 +184,7 @@ func TestUpdateTicket(t *testing.T) { rr := httptest.NewRecorder() handler := http.HandlerFunc(tHandler.UpdateTicket) - req, err := http.NewRequest(http.MethodPut, "/tickets/", nil) + req, err := http.NewRequest(http.MethodPost, "/tickets/", nil) if err != nil { t.Fatal(err) } @@ -197,7 +197,7 @@ func TestUpdateTicket(t *testing.T) { rr := httptest.NewRecorder() handler := http.HandlerFunc(tHandler.UpdateTicket) - req, err := http.NewRequest(http.MethodPut, "/tickets/", nil) + req, err := http.NewRequest(http.MethodPost, "/tickets/", nil) if err != nil { t.Fatal(err) } @@ -216,7 +216,7 @@ func TestUpdateTicket(t *testing.T) { rctx := chi.NewRouteContext() rctx.URLParams.Add("uuid", "invalid-uuid") - req, err := http.NewRequest(http.MethodPut, "/tickets/invalid-uuid", bytes.NewReader([]byte("{}"))) + req, err := http.NewRequest(http.MethodPost, "/tickets/invalid-uuid", bytes.NewReader([]byte("{}"))) if err != nil { t.Fatal(err) } @@ -235,7 +235,7 @@ func TestUpdateTicket(t *testing.T) { invalidJson := []byte(`{"key": "value"`) rctx := chi.NewRouteContext() rctx.URLParams.Add("uuid", createdTicket.UUID.String()) - req, err := http.NewRequest(http.MethodPut, "/tickets/"+createdTicket.UUID.String(), bytes.NewReader(invalidJson)) + req, err := http.NewRequest(http.MethodPost, "/tickets/"+createdTicket.UUID.String(), bytes.NewReader(invalidJson)) if err != nil { t.Fatal(err) } @@ -247,19 +247,21 @@ func TestUpdateTicket(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rr.Code) }) - t.Run("should return 400 if required fields are missing", func(t *testing.T) { + t.Run("should update ticket with only UUID and optional fields", func(t *testing.T) { rr := httptest.NewRecorder() handler := http.HandlerFunc(tHandler.UpdateTicket) - invalidTicket := db.Tickets{ - UUID: uuid.New(), - // Missing required fields + // Create a ticket with only UUID and some optional fields + updateTicket := db.Tickets{ + UUID: createdTicket.UUID, + Description: "Updated description", // Optional field + Status: db.ReadyTicket, // Optional field } - requestBody, _ := json.Marshal(invalidTicket) + requestBody, _ := json.Marshal(updateTicket) rctx := chi.NewRouteContext() - rctx.URLParams.Add("uuid", invalidTicket.UUID.String()) - req, err := http.NewRequest(http.MethodPut, "/tickets/"+invalidTicket.UUID.String(), bytes.NewReader(requestBody)) + rctx.URLParams.Add("uuid", updateTicket.UUID.String()) + req, err := http.NewRequest(http.MethodPost, "/tickets/"+updateTicket.UUID.String(), bytes.NewReader(requestBody)) if err != nil { t.Fatal(err) } @@ -268,7 +270,19 @@ func TestUpdateTicket(t *testing.T) { req = req.WithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx)) handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusBadRequest, rr.Code) + assert.Equal(t, http.StatusOK, rr.Code) + + var returnedTicket db.Tickets + err = json.Unmarshal(rr.Body.Bytes(), &returnedTicket) + assert.NoError(t, err) + + // Verify that only the provided fields were updated + assert.Equal(t, updateTicket.Description, returnedTicket.Description) + assert.Equal(t, updateTicket.Status, returnedTicket.Status) + // Original fields should remain unchanged + assert.Equal(t, createdTicket.FeatureUUID, returnedTicket.FeatureUUID) + assert.Equal(t, createdTicket.PhaseUUID, returnedTicket.PhaseUUID) + assert.Equal(t, createdTicket.Name, returnedTicket.Name) }) t.Run("should update ticket successfully", func(t *testing.T) { diff --git a/routes/ticket_routes.go b/routes/ticket_routes.go index 91d440442..a47fc0e16 100644 --- a/routes/ticket_routes.go +++ b/routes/ticket_routes.go @@ -13,10 +13,6 @@ func TicketRoutes() chi.Router { r := chi.NewRouter() ticketHandler := handlers.NewTicketHandler(http.DefaultClient, db.DB) - r.Options("/*", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }) - r.Group(func(r chi.Router) { r.Get("/{uuid}", ticketHandler.GetTicket) }) @@ -27,7 +23,7 @@ func TicketRoutes() chi.Router { r.Post("/review/send", ticketHandler.PostTicketDataToStakwork) r.Post("/review", ticketHandler.ProcessTicketReview) - r.Put("/{uuid}", ticketHandler.UpdateTicket) + r.Post("/{uuid}", ticketHandler.UpdateTicket) r.Delete("/{uuid}", ticketHandler.DeleteTicket) }) diff --git a/utils/ticket_processor.go b/utils/ticket_processor.go index 82d78b2f0..89b6d82d5 100644 --- a/utils/ticket_processor.go +++ b/utils/ticket_processor.go @@ -5,19 +5,11 @@ import ( ) type TicketReviewRequest struct { - FeatureUUID string `json:"featureUUID" validate:"required"` - PhaseUUID string `json:"phaseUUID" validate:"required"` TicketUUID string `json:"ticketUUID" validate:"required"` TicketDescription string `json:"ticketDescription" validate:"required"` } func ValidateTicketReviewRequest(req *TicketReviewRequest) error { - if req.FeatureUUID == "" { - return errors.New("featureUUID is required") - } - if req.PhaseUUID == "" { - return errors.New("phaseUUID is required") - } if req.TicketUUID == "" { return errors.New("ticketUUID is required") }