initial commit
This commit is contained in:
commit
827d5e7e24
24
Dockerfile
Normal file
24
Dockerfile
Normal file
@ -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"]
|
||||||
66
README.md
Normal file
66
README.md
Normal file
@ -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
|
||||||
25
action.yml
Normal file
25
action.yml
Normal file
@ -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 <mandresy.rabenjaharison@gmail.com>'
|
||||||
|
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 }}
|
||||||
104
main.go
Normal file
104
main.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user