package handler import ( "database/sql" "encoding/json" "fmt" "io" "math/rand" "mocksnow/db/queries" "mocksnow/server/models" "net/http" "os" "strconv" "strings" "time" "github.com/google/uuid" ) // NewSnow receives data from the DMSP Snow New() function func (h *Handler) NewSnow(w http.ResponseWriter, r *http.Request) { h.Logger.Debug("NewSnow Request received", "method", r.Method, "url", r.URL, "path", r.URL.Path, "query", r.URL.Query(), "proto", r.Proto) // Print headers for name, values := range r.Header { for _, value := range values { h.Logger.Debug("Header ", "name", name, "value", value) } } // print query for key, values := range r.URL.Query() { for _, value := range values { h.Logger.Debug("Query Paramater", "key", key, "value", value) } } var hostname string reqBody, err := io.ReadAll(r.Body) if err != nil { h.Logger.Error("Invalid data received", "error", err) fmt.Fprintf(w, "Invalid data received") w.WriteHeader(http.StatusInternalServerError) return } else { h.Logger.Debug("received input data", "length", len(reqBody)) } // Decode the JSON body var incoming models.Incoming if err := json.Unmarshal(reqBody, &incoming); err != nil { h.Logger.Error("unable to decode json", "error", err) http.Error(w, "Invalid JSON body", http.StatusBadRequest) return } else { h.Logger.Debug("successfully decoded JSON") prettyPrint(incoming) } ctx := r.Context() createdTime := time.Now() // Always insert data into incoming table params := queries.CreateIncomingParams{ IncidentNumber: nullStr(incoming.IncidentNumber), Description: nullStr(incoming.Description), ShortDescription: nullStr(incoming.ShortDescription), Urgency: nullStr(incoming.Urgency), Impact: nullStr(incoming.Impact), State: nullStr(incoming.State), ExternalID: nullStr(incoming.ExternalID), WorkNotes: nullStr(incoming.WorkNotes), AssignmentGroup: nullStr(incoming.AssignmentGroup), AssignedTo: nullStr(incoming.AssignedTo), Category: nullStr(incoming.Category), SubCategory: nullStr(incoming.SubCategory), CreatedAt: sql.NullTime{Time: createdTime, Valid: true}, } h.Logger.Debug("database params", "params", params) result, err := h.Database.Queries().CreateIncoming(ctx, params) if err != nil { h.Logger.Error("unable to log incoming data", "error", err) w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "Error : %v\n", err) return } else { h.Logger.Debug("created incoming database record", "insert_result", result) } // TODO: // check to see if this record is already in incidents table // if it is then: // - add a worknotes entry in worknotes table // - update status in incidents table // otherwise, create a new incident // External ID only provided when incident not yet created - this might not be correct? if len(incoming.ExternalID) > 0 && len(incoming.IncidentNumber) == 0 { // Create record in incidents table urgency, _ := strconv.ParseInt(incoming.Urgency, 10, 64) impact, _ := strconv.ParseInt(incoming.Impact, 10, 64) state, _ := strconv.ParseInt(incoming.State, 10, 64) sysID := uuid.New().String() params2 := queries.CreateIncidentParams{ //IncidentNumber: nullStr(incoming.IncidentNumber), Description: nullStr(incoming.Description), ShortDescription: nullStr(incoming.ShortDescription), Urgency: nullInt64(urgency), Impact: nullInt64(impact), State: nullInt64(state), ExternalID: incoming.ExternalID, //WorkNotes: nullStr(incoming.WorkNotes), AssignmentGroup: nullStr(incoming.AssignmentGroup), AssignedTo: nullStr(incoming.AssignedTo), Category: nullStr(incoming.Category), SubCategory: nullStr(incoming.SubCategory), SysID: nullStr(sysID), CreatedAt: sql.NullTime{Time: createdTime, Valid: true}, } incidentRecord, err := h.Database.Queries().CreateIncident(ctx, params2) if err != nil { h.Logger.Error("failed to create incident", "error", err) w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "Error : %v\n", err) return } else { h.Logger.Debug("created incident database record", "incident_record", incidentRecord) } // Use the returned incidentRecordId to generate the ticket number, and update the database correspondingly ticket := fmt.Sprintf("TKT%06d", incidentRecord.ID) incNumParams := queries.UpdateIncidentNumberSysIdParams{ SysId: nullStr(sysID), IncidentNumber: nullStr(ticket), } err = h.Database.Queries().UpdateIncidentNumberSysId(ctx, incNumParams) if err != nil { h.Logger.Error("failed to update incident with incident number", "number", ticket, "error", err) w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "Error : %v\n", err) return } else { h.Logger.Debug("updated incident database record") } // Create the worknotes entry wnParms := queries.CreateWorkNoteParams{ IncidentNumber: ticket, Note: nullStr(incoming.WorkNotes), } _, err = h.Database.Queries().CreateWorkNote(ctx, wnParms) if err != nil { h.Logger.Error("failed to create worknotes for incident with incident number", "number", ticket, "error", err) w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "Error : %v\n", err) return } else { h.Logger.Debug("created worknotes database record") } // Send the response // Produce dummy record link hostname, err = os.Hostname() if err != nil { h.Logger.Error("failed to lookup hostname", "error", err) hostname = "localhost" } recordLink := fmt.Sprintf("https://%s/api/now/table/incident/%s", hostname, sysID) response := models.IncidentResponse{ ImportSet: randomImportSet(), StagingTable: "x_dusa2_itom_inc_imp", Result: []models.IncidentResultItem{ { TransformMap: "Incident Import", Table: "incident", DisplayName: "number", DisplayValue: ticket, RecordLink: recordLink, Status: "inserted", SysID: sysID, }, }, } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(response) return } else if len(incoming.IncidentNumber) > 0 { // Incident already exists because we know the incident number, so update status or worknotes h.Logger.Debug("updating incident database record", "number", incoming.IncidentNumber) inc, err := h.Database.Queries().GetIncident(ctx, nullStr(incoming.IncidentNumber)) if err != nil { h.Logger.Error("failed to find existing incident to update", "number", incoming.IncidentNumber, "error", err) w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "Error : %v\n", err) return } else { prettyPrint(inc) } updateParams := h.populateChangedFields(inc, incoming) err = h.Database.Queries().UpdateIncident(ctx, updateParams) if err != nil { h.Logger.Error("failed to update incident", "number", incoming.IncidentNumber, "error", err) w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "Error : %v\n", err) return } else { h.Logger.Debug("updated incident database record", "number", incoming.IncidentNumber) } // add any worknotes specified if len(incoming.WorkNotes) > 0 { //h.Logger.Debug("TODO add worknotes") // Create the worknotes entry wnParms := queries.CreateWorkNoteParams{ IncidentNumber: incoming.IncidentNumber, Note: nullStr(incoming.WorkNotes), } _, err = h.Database.Queries().CreateWorkNote(ctx, wnParms) if err != nil { h.Logger.Error("failed to create worknotes for incident with incident number", "number", incoming.IncidentNumber, "error", err) w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "Error : %v\n", err) return } else { h.Logger.Debug("created worknotes database record", "number", incoming.IncidentNumber) } } // Respond saying we have updated the record response := models.IncidentResponse{ StagingTable: "x_dusa2_itom_inc_imp", Result: []models.IncidentResultItem{ { DisplayValue: incoming.IncidentNumber, Status: "updated", SysID: inc.SysID.String, }, }, } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(response) return } else { // Unexpected condition // TODO - return error h.Logger.Error("Logic error, did not expect to reach here") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{ "status": "ERROR", "message": fmt.Sprintf("Logic error, unexpected condition"), }) return } /* w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "{\"result\": [{}]}") */ } func nullStr(s string) sql.NullString { return sql.NullString{String: s, Valid: s != ""} } func nullInt64(i int64) sql.NullInt64 { return sql.NullInt64{Int64: i, Valid: i > 0} } // Helper to generate random import_set ID func randomImportSet() string { const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, 8) for i := range b { b[i] = letters[rand.Intn(len(letters))] } return string(b) } func (h *Handler) populateChangedFields(incRecord queries.Incident, newData models.Incoming) queries.UpdateIncidentParams { params := queries.UpdateIncidentParams{ IncidentNumber: incRecord.IncidentNumber, // Always required ExternalID: incRecord.ExternalID, } if strings.TrimSpace(newData.Description) != strings.TrimSpace(incRecord.Description.String) && len(strings.TrimSpace(newData.Description)) > 0 { h.Logger.Debug("Updating Description", "new", newData.Description) params.Description = nullStr(newData.Description) } else { params.Description = incRecord.Description } if strings.TrimSpace(newData.ShortDescription) != strings.TrimSpace(incRecord.ShortDescription.String) && len(strings.TrimSpace(newData.ShortDescription)) > 0 { h.Logger.Debug("Updating ShortDescription", "new", newData.ShortDescription) params.ShortDescription = nullStr(newData.ShortDescription) } else { params.ShortDescription = incRecord.ShortDescription } if strings.TrimSpace(newData.Urgency) != strconv.FormatInt(incRecord.Urgency.Int64, 10) { if n, err := strconv.ParseInt(newData.Urgency, 10, 64); err == nil { h.Logger.Debug("Updating Urgency", "new", newData.Urgency) params.Urgency = nullInt64(n) } else { params.Urgency = incRecord.Urgency } } else { params.Urgency = incRecord.Urgency } if strings.TrimSpace(newData.State) != strconv.FormatInt(incRecord.State.Int64, 10) { if n, err := strconv.ParseInt(newData.State, 10, 64); err == nil { h.Logger.Debug("Updating State", "new", newData.State) params.State = nullInt64(n) } else { params.State = incRecord.State } } else { params.State = incRecord.State } if strings.TrimSpace(newData.Impact) != strconv.FormatInt(incRecord.Impact.Int64, 10) { if n, err := strconv.ParseInt(newData.Impact, 10, 64); err == nil { h.Logger.Debug("Updating Impact", "new", newData.Impact) params.Impact = nullInt64(n) } else { params.Impact = incRecord.Impact } } else { params.Impact = incRecord.Impact } if strings.TrimSpace(newData.AssignmentGroup) != strings.TrimSpace(incRecord.AssignmentGroup.String) && len(strings.TrimSpace(newData.AssignmentGroup)) > 0 { h.Logger.Debug("Updating AssignmentGroup", "new", newData.AssignmentGroup) params.AssignmentGroup = nullStr(newData.AssignmentGroup) } else { params.AssignmentGroup = incRecord.AssignmentGroup } if strings.TrimSpace(newData.AssignedTo) != strings.TrimSpace(incRecord.AssignedTo.String) && len(strings.TrimSpace(newData.AssignedTo)) > 0 { h.Logger.Debug("Updating AssignedTo", "new", newData.AssignedTo) params.AssignedTo = nullStr(newData.AssignedTo) } else { params.AssignedTo = incRecord.AssignedTo } if strings.TrimSpace(newData.Category) != strings.TrimSpace(incRecord.Category.String) && len(strings.TrimSpace(newData.Category)) > 0 { h.Logger.Debug("Updating Category", "new", newData.Category) params.Category = nullStr(newData.Category) } else { params.Category = incRecord.Category } if strings.TrimSpace(newData.SubCategory) != strings.TrimSpace(incRecord.SubCategory.String) && len(strings.TrimSpace(newData.SubCategory)) > 0 { h.Logger.Debug("Updating SubCategory", "new", newData.SubCategory) params.Category = nullStr(newData.SubCategory) } else { params.SubCategory = incRecord.SubCategory } // TODO h.Logger.Debug("populateChangedFields returning", "params", params) return params }