refactor: rebuild work without timewarrior dependency
This commit is contained in:
parent
b083a8255c
commit
4ceed6f301
12 changed files with 1879 additions and 626 deletions
455
cmd.go
455
cmd.go
|
|
@ -1,32 +1,48 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func (a *App) setupCommands() *cobra.Command {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "work",
|
||||
Short: "Fast work interactions",
|
||||
Long: `A CLI tool to perform basic work cli tasks.`,
|
||||
Use: "workctl", // Name des CLI-Tools
|
||||
Short: "Manage work time, connections, and tasks",
|
||||
Long: `workctl is a command-line interface to streamline common work-related tasks,
|
||||
including time tracking (using an internal SQLite database), remote connections (SSH, RDP),
|
||||
and other utilities.`,
|
||||
Version: "1.0.0-sqlite", // Beispielversion
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(a.startCommand())
|
||||
rootCmd.AddCommand(a.stopCommand())
|
||||
rootCmd.AddCommand(a.showCommand())
|
||||
rootCmd.AddCommand(a.trackCommand()) // Neuer Befehl für Tracking
|
||||
rootCmd.AddCommand(a.connectCommands()) // Befehle für Verbindungen gruppieren
|
||||
rootCmd.AddCommand(a.wakeCommand())
|
||||
rootCmd.AddCommand(a.importTimewarriorCommand())
|
||||
|
||||
// Verberge das Standard 'completion' Kommando, falls nicht gewünscht
|
||||
rootCmd.CompletionOptions.DisableDefaultCmd = true
|
||||
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
func (a *App) startCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "start work",
|
||||
Long: "command to start the work day",
|
||||
Short: "Start work: Track time, wake PC, connect",
|
||||
Long: "Starts time tracking for 'work', attempts to wake the workstation, and sets up SSH tunnels.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
a.connect()
|
||||
fmt.Println("Starting workday procedures...")
|
||||
a.connect() // Führt Start Tracking, Wake, Tunnel Setup aus
|
||||
fmt.Println("Workday start initiated. Tunnels are running in the background.")
|
||||
fmt.Println("Use 'workctl connect rdp' or connect manually.")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -34,43 +50,422 @@ func (a *App) startCommand() *cobra.Command {
|
|||
func (a *App) stopCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "stop",
|
||||
Short: "stop work",
|
||||
Long: "command to stop the work day",
|
||||
Short: "Stop work: Stop time tracking, kill tunnels",
|
||||
Long: "Stops the current time tracking entry and attempts to kill active SSH tunnels.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
tw := NewTimeWarrior()
|
||||
tw.StopWork()
|
||||
if err := a.killForwardings(); err != nil {
|
||||
log.Printf("error stoping port forwarding: %v", err)
|
||||
fmt.Println("Stopping workday procedures...")
|
||||
if err := a.timeStore.StopTracking(); err != nil {
|
||||
log.Printf("ERROR: Failed to stop time tracking: %v", err)
|
||||
} else {
|
||||
fmt.Println("Time tracking stopped.")
|
||||
}
|
||||
os.Exit(0)
|
||||
|
||||
if err := a.killForwardings(); err != nil {
|
||||
log.Printf("WARN: Could not kill all forwarding processes: %v", err)
|
||||
} else {
|
||||
fmt.Println("Attempted to stop SSH tunnels.")
|
||||
}
|
||||
|
||||
fmt.Println("Workday stop procedures finished.")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// func (a *App) trackCommand() *cobra.Command {
|
||||
// cmd := &cobra.Command{
|
||||
// Use: "track [tag]",
|
||||
// Short: "Start tracking a new tag (stops current)",
|
||||
// Long: `Starts a new time tracking entry with the specified tag (e.g., 'break', 'meeting', 'projectX').
|
||||
// This automatically stops any currently running timer.
|
||||
// If no tag is provided, it stops the current timer and starts 'work'.`,
|
||||
// Args: cobra.MaximumNArgs(1), // 0 oder 1 Argument
|
||||
// Run: func(cmd *cobra.Command, args []string) {
|
||||
// tag := TagWork // Standard-Tag, wenn kein Argument gegeben wird
|
||||
// if len(args) > 0 {
|
||||
// tag = args[0]
|
||||
// }
|
||||
//
|
||||
// if tag == "" {
|
||||
// log.Println("ERROR: Tag cannot be empty.")
|
||||
// fmt.Println("Usage: workctl track <tag_name>")
|
||||
// os.Exit(1) // Fehler signalisieren
|
||||
// }
|
||||
//
|
||||
// fmt.Printf("Attempting to start tracking '%s'...\n", tag)
|
||||
// if err := a.timeStore.StartTracking(tag); err != nil {
|
||||
// log.Printf("ERROR: Failed to start tracking '%s': %v", tag, err)
|
||||
// fmt.Printf("Error: Could not start tracking '%s'.\n", tag)
|
||||
// os.Exit(1)
|
||||
// } else {
|
||||
// fmt.Printf("Successfully started tracking '%s'.\n", tag)
|
||||
// }
|
||||
// },
|
||||
// }
|
||||
// cmd.AddCommand(&cobra.Command{
|
||||
// Use: "break",
|
||||
// Short: "Start tracking 'break'",
|
||||
// Run: func(cmd *cobra.Command, args []string) {
|
||||
// fmt.Println("Starting break...")
|
||||
// if err := a.timeStore.StartTracking(TagBreak); err != nil {
|
||||
// log.Printf("ERROR: Failed to start break tracking: %v", err)
|
||||
// fmt.Println("Error: Could not start break.")
|
||||
// os.Exit(1)
|
||||
// } else {
|
||||
// fmt.Println("Break started.")
|
||||
// }
|
||||
// },
|
||||
// })
|
||||
//
|
||||
// return cmd
|
||||
// }
|
||||
|
||||
func (a *App) trackCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "track [tag]",
|
||||
Short: "Start tracking time with a tag, or log a full day tag",
|
||||
Long: `Starts a new time tracking interval with the specified tag (e.g., 'work', 'break', 'meeting').
|
||||
This automatically stops any currently running timer.
|
||||
If no tag is provided, it stops the current timer and starts 'work'.
|
||||
|
||||
If the provided tag is a special full-day tag ('uni', 'urlaub', 'feiertag', 'krank', 'free'),
|
||||
it will mark the *current day* with that tag instead of starting an interval timer.
|
||||
This also stops any currently running timer.`,
|
||||
Args: cobra.MaximumNArgs(1), // 0 oder 1 Argument
|
||||
RunE: func(cmd *cobra.Command, args []string) error { // RunE für Fehlerbehandlung
|
||||
tag := TagWork // Standard-Tag, wenn kein Argument gegeben wird
|
||||
if len(args) > 0 {
|
||||
tag = args[0]
|
||||
}
|
||||
|
||||
if tag == "" {
|
||||
// Sollte durch Default nicht passieren, aber sicher ist sicher
|
||||
return fmt.Errorf("tag cannot be empty")
|
||||
}
|
||||
|
||||
tagLower := strings.ToLower(tag)
|
||||
|
||||
// Prüfe, ob es ein spezieller Ganztages-Tag ist
|
||||
switch tagLower {
|
||||
case "uni", "urlaub", "feiertag", "krank", "free":
|
||||
today := time.Now()
|
||||
fmt.Printf("Logging '%s' for today (%s)...\n", tagLower, today.Format("2006-01-02"))
|
||||
if err := a.timeStore.LogFullDay(tagLower, today); err != nil {
|
||||
// Loggen passiert in LogFullDay oder bei Fehlern davor
|
||||
// Gib den Fehler zurück, damit Cobra ihn behandelt
|
||||
return fmt.Errorf("could not log '%s' for today: %w", tagLower, err)
|
||||
}
|
||||
// Erfolgsmeldung kommt aus LogFullDay
|
||||
return nil // Erfolg
|
||||
|
||||
default: // Normale Intervalle wie 'work', 'break', oder unbekannt
|
||||
fmt.Printf("Attempting to start tracking interval '%s'...\n", tag)
|
||||
if err := a.timeStore.StartTracking(tag); err != nil {
|
||||
log.Printf("ERROR: Failed to start tracking '%s': %v", tag, err)
|
||||
// Gib Fehler zurück
|
||||
return fmt.Errorf("could not start tracking '%s': %w", tag, err)
|
||||
}
|
||||
// Erfolgsmeldung kommt aus StartTracking
|
||||
return nil // Erfolg
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Alias hinzufügen (bleibt bestehen)
|
||||
cmd.AddCommand(&cobra.Command{
|
||||
Use: "break",
|
||||
Short: "Start tracking 'break'",
|
||||
RunE: func(cmd *cobra.Command, args []string) error { // RunE verwenden
|
||||
fmt.Println("Starting break...")
|
||||
if err := a.timeStore.StartTracking(TagBreak); err != nil {
|
||||
log.Printf("ERROR: Failed to start break tracking: %v", err)
|
||||
return fmt.Errorf("could not start break: %w", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (a *App) showCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "show",
|
||||
Short: "show timetracking",
|
||||
Long: "show different timetracking",
|
||||
Use: "show [period|export]",
|
||||
Short: "Show time summaries or export data",
|
||||
Long: `Shows time tracking summaries for different periods (day, week, month, year)
|
||||
or exports the yearly data to an Excel file.
|
||||
|
||||
Periods: today, day, week, month, year (or YYYY-MM-DD)
|
||||
Export: Use the --export flag or the 'export' subcommand.`,
|
||||
ValidArgs: []string{"day", "week", "month", "year", "today"}, // Zur Autovervollständigung
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
tw := NewTimeWarrior()
|
||||
period := "today" // Standard ist heute
|
||||
if len(args) > 0 {
|
||||
period = args[0]
|
||||
}
|
||||
|
||||
if a.flags.ShowExport {
|
||||
tw.ExportSummary(a.flags.ExportName)
|
||||
filename := a.flags.ExportName
|
||||
if filename == "" || filename == "Arbeitszeiten.xlsx" { // Standardwert aus Flags anpassen
|
||||
filename = "Arbeitszeiten_" + time.Now().Format("2006") + ".xlsx"
|
||||
log.Printf("INFO: No export name specified, using default: %s", filename)
|
||||
}
|
||||
fmt.Printf("Exporting yearly timetable to '%s'...\n", filename)
|
||||
if err := a.timeStore.ExportSummary(filename); err != nil {
|
||||
log.Printf("ERROR: Failed to export summary to '%s': %v", filename, err)
|
||||
fmt.Printf("Error: Could not export to '%s'.\n", filename)
|
||||
}
|
||||
} else if a.flags.ShowWeek {
|
||||
fmt.Println("Showing weekly summary...")
|
||||
if err := a.timeStore.ShowSummary("week"); err != nil {
|
||||
log.Printf("ERROR: Failed to show week summary: %v", err)
|
||||
}
|
||||
} else if a.flags.ShowMonth {
|
||||
fmt.Println("Showing monthly summary...")
|
||||
if err := a.timeStore.ShowSummary("month"); err != nil {
|
||||
log.Printf("ERROR: Failed to show month summary: %v", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Showing summary for period: %s...\n", period)
|
||||
if err := a.timeStore.ShowSummary(period); err != nil {
|
||||
log.Printf("ERROR: Failed to show summary for '%s': %v", period, err)
|
||||
}
|
||||
}
|
||||
if a.flags.ShowWeek {
|
||||
tw.ShowSummary(":week")
|
||||
}
|
||||
if a.flags.ShowMonth {
|
||||
tw.ShowSummary(":month")
|
||||
}
|
||||
os.Exit(0)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVarP(&a.flags.ShowWeek, "week", "w", false, "show timewarrior week summary")
|
||||
cmd.Flags().BoolVarP(&a.flags.ShowMonth, "month", "m", false, "show timewarrior month summary")
|
||||
cmd.Flags().BoolVarP(&a.flags.ShowExport, "export", "e", false, "export timewarrior timetable")
|
||||
cmd.Flags().StringVarP(&a.flags.ExportName, "name", "n", "Arbeitszeiten.xlsx", "name of exported excel table")
|
||||
cmd.Flags().BoolVarP(&a.flags.ShowWeek, "week", "w", false, "Show summary for the current week")
|
||||
cmd.Flags().BoolVarP(&a.flags.ShowMonth, "month", "m", false, "Show summary for the current month")
|
||||
cmd.Flags().BoolVarP(&a.flags.ShowExport, "export", "e", false, "Export yearly timetable to Excel")
|
||||
cmd.Flags().StringVarP(&a.flags.ExportName, "name", "n", "Arbeitszeiten_"+time.Now().Format("2006")+".xlsx", "Filename for the exported Excel table")
|
||||
|
||||
cmd.MarkFlagsMutuallyExclusive("week", "month", "export")
|
||||
|
||||
// Füge einen 'export' Unterbefehl hinzu für klarere Nutzung
|
||||
cmd.AddCommand(&cobra.Command{
|
||||
Use: "export [filename]",
|
||||
Short: "Export yearly timetable to Excel",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
filename := "Arbeitszeiten_" + time.Now().Format("2006") + ".xlsx"
|
||||
if len(args) > 0 {
|
||||
filename = args[0]
|
||||
}
|
||||
fmt.Printf("Exporting yearly timetable to '%s'...\n", filename)
|
||||
if err := a.timeStore.ExportSummary(filename); err != nil {
|
||||
log.Printf("ERROR: Failed to export summary to '%s': %v", filename, err)
|
||||
fmt.Printf("Error: Could not export to '%s'.\n", filename)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (a *App) wakeCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "wake",
|
||||
Short: "Wake the configured workstation via Wake-on-LAN",
|
||||
Long: "Sends a Wake-on-LAN magic packet to the workstation defined in the configuration, using an SSH jump host if configured.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("Attempting to wake workstation...")
|
||||
a.wakeWorkstation()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) connectCommands() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "connect",
|
||||
Short: "Manage remote connections (SSH, RDP)",
|
||||
Long: "Provides subcommands to establish SSH tunnels and RDP connections.",
|
||||
}
|
||||
|
||||
cmd.AddCommand(&cobra.Command{
|
||||
Use: "jump",
|
||||
Short: "Connect to Jump Host (with tunnel to workstation)",
|
||||
Long: "Establishes an SSH connection to the Jump Host and forwards local port 2048 to the workstation's SSH port (22). This command blocks.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
a.connectToJump() // Blockiert
|
||||
},
|
||||
})
|
||||
|
||||
cmd.AddCommand(&cobra.Command{
|
||||
Use: "workstation",
|
||||
Short: "Connect to Workstation via SSH tunnel",
|
||||
Long: "Establishes an SSH connection to the Workstation via the local tunnel on port 2048. Also sets up RDP tunnel on local port 6000. Requires the 'jump' tunnel to be active. This command blocks.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
a.connectToWorkstation() // Blockiert
|
||||
},
|
||||
})
|
||||
|
||||
cmd.AddCommand(&cobra.Command{
|
||||
Use: "rdp",
|
||||
Short: "Start RDP session via tunnel",
|
||||
Long: "Starts an RDP client (xfreerdp) connecting to localhost:6000. Requires an active tunnel forwarding this port to the workstation's RDP port (3389). This command blocks.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
a.startRDPConnection() // Blockiert
|
||||
},
|
||||
})
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (a *App) importTimewarriorCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "import-timew [filepath]",
|
||||
Short: "Import time entries from 'timewarrior summary' output file",
|
||||
Long: `Parses the output of 'timewarrior summary :year' (or similar) stored in a text file
|
||||
and inserts the individual time intervals into the workctl SQLite database.
|
||||
It expects the standard timewarrior summary format.
|
||||
Example: workctl import-timew /path/to/timew-summary.txt`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
filepath := args[0]
|
||||
fmt.Printf("Attempting to import timewarrior data from: %s\n", filepath)
|
||||
|
||||
count, err := a.runImport(filepath)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: Import failed: %v", err)
|
||||
return fmt.Errorf("import failed: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully imported %d time entries.\n", count)
|
||||
log.Printf("INFO: Successfully imported %d time entries from %s", count, filepath)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (a *App) runImport(filepath string) (int, error) {
|
||||
contentBytes, err := os.ReadFile(filepath)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("could not read file '%s': %w", filepath, err)
|
||||
}
|
||||
content := string(contentBytes)
|
||||
lines := strings.Split(content, "\n")
|
||||
|
||||
tx, err := a.timeStore.db.Begin()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("could not begin database transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback() // Rollback wird ausgeführt, wenn Commit nicht erreicht wird
|
||||
|
||||
stmt, err := tx.Prepare("INSERT INTO time_entries (tag, start_time, end_time) VALUES (?, ?, ?)")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("could not prepare insert statement: %w", err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
var current_date_str string
|
||||
imported_count := 0
|
||||
location := time.Local
|
||||
|
||||
for i, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || i < 1 || strings.HasPrefix(line, "Total") || strings.HasPrefix(line, "---") || strings.Contains(line, "Wk Date Day Tags") {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := strings.Fields(line)
|
||||
var tag, start_str, end_str string
|
||||
has_date := false
|
||||
|
||||
// Versuche, das Format zu erkennen (mit oder ohne Datum am Anfang)
|
||||
// Format mit Datum: Wk Date Day Tags Start End Time [Total]
|
||||
// Format ohne Datum: Tags Start End Time [Total]
|
||||
// Mindestens 4 Felder für Tag, Start, End, Time erwartet
|
||||
|
||||
if len(fields) >= 7 && strings.Contains(fields[1], "-") && len(fields[1]) == 10 { // Prüft auf Datum YYYY-MM-DD
|
||||
current_date_str = fields[1] // Datum merken
|
||||
tag = fields[3]
|
||||
start_str = fields[4]
|
||||
end_str = fields[5]
|
||||
has_date = true
|
||||
} else if len(fields) >= 4 && strings.Contains(fields[1], ":") && strings.Contains(fields[2], ":") {
|
||||
if current_date_str == "" {
|
||||
log.Printf("WARN: Skipping line without preceding date: %s", line)
|
||||
continue // Überspringe Zeile, wenn kein Datum bekannt ist
|
||||
}
|
||||
tag = fields[0]
|
||||
start_str = fields[1]
|
||||
end_str = fields[2]
|
||||
has_date = false
|
||||
} else if len(fields) >= 6 && strings.Contains(fields[1], "-") && len(fields[1]) == 10 {
|
||||
current_date_str = fields[1]
|
||||
tag = fields[3]
|
||||
start_str = fields[4]
|
||||
end_str = fields[5]
|
||||
has_date = true
|
||||
if start_str == "0:00:00" && end_str == "0:00:00" {
|
||||
start_time, err := time.ParseInLocation("2006-01-02", current_date_str, location)
|
||||
if err != nil {
|
||||
log.Printf("WARN: Skipping line with invalid date '%s': %v", current_date_str, err)
|
||||
continue
|
||||
}
|
||||
end_time := start_time.Add(24 * time.Hour) // Ende ist Anfang des nächsten Tages
|
||||
|
||||
_, err = stmt.Exec(tag, start_time, end_time)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: Failed to insert full-day entry for %s (%s): %v", current_date_str, tag, err)
|
||||
} else {
|
||||
imported_count++
|
||||
}
|
||||
continue // Gehe zur nächsten Zeile nach Behandlung des ganztägigen Eintrags
|
||||
}
|
||||
|
||||
} else {
|
||||
log.Printf("WARN: Skipping unrecognized line format: %s", line)
|
||||
continue
|
||||
}
|
||||
|
||||
if end_str == "-" {
|
||||
log.Printf("INFO: Skipping currently running entry: %s", line)
|
||||
continue
|
||||
}
|
||||
|
||||
start_datetime_str := current_date_str + " " + start_str
|
||||
end_datetime_str := current_date_str + " " + end_str
|
||||
|
||||
start_time, err_start := time.ParseInLocation("2006-01-02 15:04:05", start_datetime_str, location)
|
||||
end_time, err_end := time.ParseInLocation("2006-01-02 15:04:05", end_datetime_str, location)
|
||||
|
||||
if err_start != nil || err_end != nil {
|
||||
log.Printf("WARN: Skipping line with invalid date/time format ('%s' / '%s'): %v / %v", start_datetime_str, end_datetime_str, err_start, err_end)
|
||||
continue
|
||||
}
|
||||
|
||||
if end_time.Before(start_time) {
|
||||
if has_date {
|
||||
log.Printf("WARN: End time is before start time on the same date line, skipping: %s", line)
|
||||
continue
|
||||
}
|
||||
// Wenn kein Datum da war, gehen wir davon aus, dass es sich um Mitternacht handelt.
|
||||
// Diese Logik ist knifflig und fehleranfällig, da `timew summary` normalerweise aufteilt.
|
||||
// Einfacher Ansatz: Ignoriere diesen Fall vorerst, da er im Standard-Summary selten auftritt.
|
||||
// log.Printf("INFO: Detected potential midnight crossing (end %v < start %v) - adjusting end date might be needed if timew split wasn't done.", endTime, startTime)
|
||||
// endTime = endTime.Add(24 * time.Hour) // Vorsicht mit dieser Annahme!
|
||||
}
|
||||
|
||||
db_tag := strings.ToLower(tag)
|
||||
switch db_tag {
|
||||
case "work":
|
||||
db_tag = TagWork
|
||||
case "break":
|
||||
db_tag = TagBreak
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(db_tag, start_time, end_time)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: Failed to insert entry for %s (%s, %s -> %s): %v", current_date_str, db_tag, start_time.Format(time.RFC3339), end_time.Format(time.RFC3339), err)
|
||||
} else {
|
||||
imported_count++
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return imported_count, fmt.Errorf("failed to commit transaction: %w", err)
|
||||
}
|
||||
|
||||
return imported_count, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue