Skip to content

Commit

Permalink
feat: basic functioning setup
Browse files Browse the repository at this point in the history
  • Loading branch information
dhth committed Mar 12, 2024
0 parents commit 4bfa1bf
Show file tree
Hide file tree
Showing 23 changed files with 1,513 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
dist
cosign.key
cosign.pub
punchout
debug.log
punchout.v*.db
.quickrun
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# punchout

✨ Overview
---

`punchout` takes the suck out of tracking work on JIRA.

Install
---

**go**:

```sh
go install github.com/dhth/punchout@latest
```

Acknowledgements
---

`punchout` is built using the awesome TUI framework [bubbletea][1].

[1]: https://github.com/charmbracelet/bubbletea
39 changes: 39 additions & 0 deletions cmd/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cmd

import "database/sql"

const (
PUNCHOUT_DB_VERSION = "1"
)

func setupDB(dbpath string) (*sql.DB, error) {

db, err := sql.Open("sqlite", dbpath)
if err != nil {
return nil, err
}

if _, err = db.Exec(`
CREATE TABLE IF NOT EXISTS issue_log (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
issue_key TEXT NOT NULL,
begin_ts TIMESTAMP NOT NULL,
end_ts TIMESTAMP,
comment VARCHAR(255),
active BOOLEAN NOT NULL,
synced BOOLEAN NOT NULL
);
CREATE TRIGGER IF NOT EXISTS prevent_duplicate_active_insert
BEFORE INSERT ON issue_log
BEGIN
SELECT CASE
WHEN EXISTS (SELECT 1 FROM issue_log WHERE active = 1)
THEN RAISE(ABORT, 'Only one row with active=1 is allowed')
END;
END;
`); err != nil {
return nil, err
}
return db, nil
}
71 changes: 71 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package cmd

import (
"flag"
"fmt"
"os"
"os/user"

jira "github.com/andygrunwald/go-jira/v2/onpremise"
"github.com/dhth/punchout/ui"
)

func die(msg string, args ...any) {
fmt.Fprintf(os.Stderr, msg+"\n", args...)
os.Exit(1)
}

var (
jiraURL = flag.String("jira-url", "https://jira.company.com", "URL of the JIRA server")
jiraToken = flag.String("jira-token", "", "personal access token for the JIRA server")
jql = flag.String("jql", "assignee = currentUser() AND updatedDate >= -14d ORDER BY updatedDate DESC", "JQL to use to query issues at startup")
jiraTimeDeltaMins = flag.Int("jira-time-delta-mins", 0, "Time delta (in minutes) between your timezone and the timezone of the server; can be +/-")
)

func Execute() {
currentUser, err := user.Current()
var defaultDBPath string
if err == nil {
defaultDBPath = fmt.Sprintf("%s/punchout.v%s.db", currentUser.HomeDir, PUNCHOUT_DB_VERSION)
}
dbPath := flag.String("db-path", defaultDBPath, "location where punchout should create its DB file")

flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Take the suck out of logging time on JIRA.\n\nFlags:\n")
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\n------\n%s", ui.HelpText)
}
flag.Parse()

if *dbPath == "" {
die("db-path cannot be empty")
}

if *jql == "" {
die("jql cannot be empty")
}

if *jiraURL == "" {
die("jira-url cannot be empty")
}

if *jiraToken == "" {
die("jira-token cannot be empty")
}

db, err := setupDB(*dbPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't set up punchout database. This is a fatal error")
os.Exit(1)
}

tp := jira.BearerAuthTransport{
Token: *jiraToken,
}
cl, err := jira.NewClient(*jiraURL, tp.Client())
if err != nil {
panic(err)
}
ui.RenderUI(db, cl, *jql, *jiraTimeDeltaMins)

}
115 changes: 115 additions & 0 deletions db/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package main

import (
"database/sql"
"fmt"
"os"
"time"

_ "modernc.org/sqlite"
)

type IssueLogEntry struct {
Id int
IssueKey string
BeginTS time.Time
EndTS time.Time
Active bool
Synced bool
LastUpdated time.Time
}

func main() {
db, err := sql.Open("sqlite", "punchoutdb")
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}

if _, err = db.Exec(`
CREATE TABLE IF NOT EXISTS issue_log (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
issue_key TEXT NOT NULL,
begin_ts TIMESTAMP NOT NULL,
end_ts TIMESTAMP,
active BOOLEAN NOT NULL,
synced BOOLEAN NOT NULL,
last_updated TIMESTAMP NOT NULL
);
CREATE TRIGGER IF NOT EXISTS prevent_duplicate_active_insert
BEFORE INSERT ON issue_log
BEGIN
SELECT CASE
WHEN EXISTS (SELECT 1 FROM issue_log WHERE active = 1)
THEN RAISE(ABORT, 'Only one row with active=1 is allowed')
END;
END;
`); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}

entry := IssueLogEntry{
IssueKey: "WEBENG-1099",
BeginTS: time.Now().Add(time.Hour * -3),
Active: true,
Synced: false,
LastUpdated: time.Now(),
}

insertEntry(db, entry)
time.Sleep(time.Second * 10)
entry.EndTS = time.Now()
entry.Active = false
entry.LastUpdated = time.Now()
updateEntry(db, entry)

}

func insertEntry(db *sql.DB, entry IssueLogEntry) {
stmt, err := db.Prepare(`
INSERT INTO issue_log (issue_key, begin_ts, active, synced, last_updated)
VALUES (?, ?, ?, ?, ?);
`)

if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
defer stmt.Close()

_, err = stmt.Exec(entry.IssueKey, entry.BeginTS, entry.Active, entry.Synced, entry.LastUpdated)
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
}

func updateEntry(db *sql.DB, entry IssueLogEntry) {
stmt, err := db.Prepare(`
UPDATE issue_log
SET active = 0,
end_ts = ?,
last_updated = ?
WHERE issue_key = ?
AND active = 1;
`)
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
defer stmt.Close()

if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
defer stmt.Close()

_, err = stmt.Exec(entry.EndTS, entry.LastUpdated, entry.IssueKey)
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
}
46 changes: 46 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
module github.com/dhth/punchout

go 1.22.0

require (
github.com/andygrunwald/go-jira/v2 v2.0.0-20240116150243-50d59fe116d6
github.com/charmbracelet/bubbles v0.18.0
github.com/charmbracelet/bubbletea v0.25.0
github.com/charmbracelet/lipgloss v0.10.0
modernc.org/sqlite v1.29.3
)

require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect
github.com/trivago/tgo v1.0.7 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.3.8 // indirect
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/libc v1.41.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
)
Loading

0 comments on commit 4bfa1bf

Please sign in to comment.