From 827d5e7e240ff2f24998a0092bfe535887f8f077 Mon Sep 17 00:00:00 2001 From: Mandresy RABENJAHARISON Date: Mon, 11 Aug 2025 16:34:00 +0300 Subject: [PATCH] initial commit --- Dockerfile | 24 +++++++++++++ README.md | 66 ++++++++++++++++++++++++++++++++++ action.yml | 25 +++++++++++++ go.mod | 3 ++ main.go | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 222 insertions(+) create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 action.yml create mode 100644 go.mod create mode 100644 main.go diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..385a1e7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +######################## +# — Build stage — +######################## +FROM golang:1.23.4-alpine AS builder + +RUN apk add --no-cache git ca-certificates + +WORKDIR /src + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/action ./main.go + +######################## +# — Runtime stage — +######################## +FROM alpine:3.20 +RUN apk add --no-cache ca-certificates +ENV PATH="/usr/bin:/usr/sbin:/bin:/sbin" +COPY --from=builder /out/action /usr/local/bin/action +ENTRYPOINT ["/usr/local/bin/action"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..04b2aa7 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# Create Odoo Timesheet (Gitea Action) + +Create a new Odoo timesheet entry via a FastAPI middleware whenever time is added on a Gitea Issue. + +The action reads time-tracking details directly from the issue event context and POSTs the following JSON to your FastAPI endpoint: + +```json +{ + "date": "2025-05-06", + "description": "/", + "gitea_username": "Tom", + "hour_spent": 1.3, + "task_id": 1 +} +``` + +--- + +## Inputs + +- `base_url` (required): Base URL of the FastAPI server, e.g. `https://fastapi.example.com`. + +--- + +## Event context used + +- `${{ gitea.event.issue.number }}` → `task_id` +- `${{ gitea.event.issue.title }}` → description fallback +- `${{ gitea.event.sender.login }}` → `gitea_username` +- `${{ gitea.event.tracked_time.time }}` → seconds spent (converted to hours) +- `${{ gitea.event.tracked_time.created }}` → ISO timestamp (date extracted) +- `${{ gitea.event.action }}` → filtered to time-related actions + +No Gitea API or tokens are required; all data comes from the event payload. + +--- + +## Usage + +Trigger on the Gitea issues event when time is added. If your instance emits a generic `issues: edited` event for time tracking, include it as shown: + +```yaml +name: Create Odoo Timesheet + +on: + issues: + types: [edited] + +jobs: + timesheet: + runs-on: ubuntu-latest + steps: + - name: Create timesheet in Odoo + uses: https://gitea.ethumada.com/gitea/create-odoo-timesheet + with: + base_url: ${{ vars.FASTAPI_BASE_URL }} +``` + +--- + +## Endpoint contract (FastAPI) + +- Method: POST +- URL: `{base_url}/api/v1/account_analytic_gitea_odoo/` +- Body: JSON object with fields as shown above +- 2xx indicates success; response body is logged diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..98a762b --- /dev/null +++ b/action.yml @@ -0,0 +1,25 @@ +name: 'create-odoo-timesheet' +description: 'Create an Odoo timesheet via FastAPI when time is added on a Gitea Issue.' + +author: 'Mandresy RABENJAHARISON ' +branding: + icon: clock + color: blue + +inputs: + base_url: + description: 'Base URL of the FastAPI server.' + required: true + +runs: + using: 'docker' + image: 'Dockerfile' + env: + BASE_URL: ${{ inputs.base_url }} + ISSUE_NUMBER: ${{ gitea.event.issue.number }} + ISSUE_TITLE: ${{ gitea.event.issue.title }} + SENDER_LOGIN: ${{ gitea.event.sender.login }} + # Issue time tracking fields (from the event payload) + TRACKED_SECONDS: ${{ gitea.event.tracked_time.time }} + TRACKED_CREATED: ${{ gitea.event.tracked_time.created }} + EVENT_ACTION: ${{ gitea.event.action }} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7cdba72 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module create-odoo-timesheet + +go 1.23.4 diff --git a/main.go b/main.go new file mode 100644 index 0000000..d193578 --- /dev/null +++ b/main.go @@ -0,0 +1,104 @@ +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"` +} + +func main() { + baseURL := os.Getenv("BASE_URL") + issueNumber := os.Getenv("ISSUE_NUMBER") + 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 == "" || issueNumber == "" || 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) + + var taskID int64 + fmt.Sscanf(issueNumber, "%d", &taskID) + + 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) + } +}