package main import ( "bytes" "encoding/json" "fmt" "io" "log" "net/http" "os" "strconv" "strings" ) type TimesheetPayload struct { Date string `json:"date"` Description string `json:"description"` GiteaUsername string `json:"gitea_username"` HourSpent float64 `json:"hour_spent"` TaskID int64 `json:"task_id"` } // parseOdooTicketID extracts the numeric ticket id from a ref formatted like // "/ticket-". It scans path segments and picks the first segment // that starts with "ticket-" (case-insensitive), then reads leading digits. func parseOdooTicketID(ref string) (int64, error) { if strings.TrimSpace(ref) == "" { return 0, fmt.Errorf("empty ref") } parts := strings.Split(ref, "/") for _, seg := range parts { if seg == "" { continue } lower := strings.ToLower(seg) const pfx = "ticket-" if strings.HasPrefix(lower, pfx) { numPart := seg[len(pfx):] // collect leading digits var digits strings.Builder for _, r := range numPart { if r >= '0' && r <= '9' { digits.WriteRune(r) } else { break } } if digits.Len() == 0 { return 0, fmt.Errorf("no digits after ticket- in segment: %s", seg) } id, err := strconv.ParseInt(digits.String(), 10, 64) if err != nil { return 0, err } return id, nil } } return 0, fmt.Errorf("no ticket- segment found in ref: %s", ref) } func main() { baseURL := os.Getenv("BASE_URL") branchRef := os.Getenv("BRANCH_REF") sender := os.Getenv("SENDER_LOGIN") title := os.Getenv("ISSUE_TITLE") trackedSeconds := os.Getenv("TRACKED_SECONDS") trackedCreated := os.Getenv("TRACKED_CREATED") action := os.Getenv("EVENT_ACTION") if baseURL == "" || branchRef == "" || sender == "" || trackedSeconds == "" || trackedCreated == "" { fmt.Println("Some required environment variables are missing.") os.Exit(1) } if action != "add_time" && action != "edited" && action != "time_tracked" && action != "time_added" { // Best-effort: only proceed for time-related actions; allow edited if your instance sends edited fmt.Printf("Skipping action: %s\n", action) return } // Logger logFile, err := os.OpenFile("action.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { fmt.Printf("Error creating log file: %v\n", err) os.Exit(1) } defer logFile.Close() logger := log.New(io.MultiWriter(os.Stdout, logFile), "", log.LstdFlags) // Extract task ID from branch ref taskID, err := parseOdooTicketID(branchRef) if err != nil { logger.Printf("Failed to parse Odoo ticket from BRANCH_REF '%s': %v\n", branchRef, err) os.Exit(1) } seconds, err := strconv.ParseInt(trackedSeconds, 10, 64) if err != nil { logger.Printf("Invalid TRACKED_SECONDS: %v\n", err) os.Exit(1) } hours := float64(seconds) / 3600.0 desc := title if strings.TrimSpace(desc) == "" { desc = "/" } date := trackedCreated if idx := strings.Index(date, "T"); idx > 0 { date = date[:idx] } payload := TimesheetPayload{ Date: date, Description: desc, GiteaUsername: sender, HourSpent: hours, TaskID: taskID, } body, err := json.Marshal(payload) if err != nil { logger.Printf("Error marshalling payload: %v\n", err) os.Exit(1) } endpoint := strings.TrimRight(baseURL, "/") + "/api/v1/account_analytic_gitea_odoo/" req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(body)) if err != nil { logger.Printf("Error creating POST request: %v\n", err) os.Exit(1) } req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { logger.Printf("Error calling FastAPI: %v\n", err) os.Exit(1) } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) logger.Printf("FastAPI status: %s\nResponse: %s\n", resp.Status, string(respBody)) if resp.StatusCode < 200 || resp.StatusCode >= 300 { os.Exit(1) } }