From 6f9580d98a8a0f55218abd3ed984a79317a5eb35 Mon Sep 17 00:00:00 2001 From: Anis Ahmad Date: Mon, 25 May 2020 23:18:48 +0600 Subject: [PATCH] First working version. Can add Projects and Tasks --- app/tui.go | 243 ++++++++++++++++++++++++++++++++++++ go.mod | 13 ++ go.sum | 68 ++++++++++ model/project.go | 7 ++ model/task.go | 11 ++ repository/project.go | 13 ++ repository/storm/project.go | 61 +++++++++ repository/storm/task.go | 64 ++++++++++ repository/task.go | 18 +++ util/array.go | 27 ++++ util/env.go | 34 +++++ util/util.go | 51 ++++++++ 12 files changed, 610 insertions(+) create mode 100644 app/tui.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 model/project.go create mode 100644 model/task.go create mode 100644 repository/project.go create mode 100644 repository/storm/project.go create mode 100644 repository/storm/task.go create mode 100644 repository/task.go create mode 100644 util/array.go create mode 100644 util/env.go create mode 100644 util/util.go diff --git a/app/tui.go b/app/tui.go new file mode 100644 index 0000000..3775c17 --- /dev/null +++ b/app/tui.go @@ -0,0 +1,243 @@ +package main + +import ( + "fmt" + "reflect" + "time" + + "github.com/asdine/storm/v3" + "github.com/gdamore/tcell" + "github.com/rivo/tview" + + "github.com/ajaxray/geek-life/model" + "github.com/ajaxray/geek-life/repository" + repo "github.com/ajaxray/geek-life/repository/storm" + "github.com/ajaxray/geek-life/util" +) + +var ( + app *tview.Application + newProject, newTask *tview.InputField + projectList, taskList *tview.List + projectPane, taskPane *tview.Flex + statusBar *tview.Pages + message *tview.TextView + shortcutsPage, messagePage string = "shortcuts", "message" + + db *storm.DB + projectRepo repository.ProjectRepository + taskRepo repository.TaskRepository + + projects []model.Project + currentProject model.Project + + tasks []model.Task + currentTask model.Task +) + +func main() { + app = tview.NewApplication() + + db = util.ConnectStorm() + defer db.Close() + + projectRepo = repo.NewProjectRepository(db) + taskRepo = repo.NewTaskRepository(db) + + titleText := tview.NewTextView().SetText("[lime::b]Geek-life [::-]- life management for geeks!").SetDynamicColors(true) + cloudStatus := tview.NewTextView().SetText("[::d]Cloud Sync: off").SetTextAlign(tview.AlignRight).SetDynamicColors(true) + + prepareStatusBar() + + titleBar := tview.NewFlex(). + AddItem(titleText, 0, 2, false). + AddItem(cloudStatus, 0, 1, false) + + projectPane = makeProjectPane() + taskPane = makeTaskPane() + + layout := tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(titleBar, 2, 1, false). + AddItem(tview.NewFlex(). + AddItem(projectPane, 25, 1, true). + AddItem(taskPane, 0, 2, false). + AddItem(tview.NewBox().SetBorder(true).SetTitle("Details"), 0, 3, false), + 0, 2, true). + AddItem(statusBar, 1, 1, false) + + setKeyboardShortcuts(projectPane, taskPane) + + if err := app.SetRoot(layout, true).EnableMouse(true).Run(); err != nil { + panic(err) + } +} + +func makeProjectPane() *tview.Flex { + var err error + projects, err = projectRepo.GetAll() + util.FatalIfError(err, "Could not load Projects") + + projectList = tview.NewList() + + for i := range projects { + addProjectToList(i, false) + } + + newProject = makeLightTextInput("+[New Project]"). + SetDoneFunc(func(key tcell.Key) { + switch key { + case tcell.KeyEnter: + project, err := projectRepo.Create(newProject.GetText(), "") + if err != nil { + showMessage("[red::]Failed to create Project:" + err.Error()) + } else { + showMessage(fmt.Sprintf("[green::]Project %s created. Press n to start adding new tasks.", newProject.GetText())) + projects = append(projects, project) + addProjectToList(len(projects)-1, true) + newProject.SetText("") + } + case tcell.KeyEsc: + app.SetFocus(projectPane) + } + }) + + projectBar := tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(projectList, 0, 1, true). + AddItem(newProject, 1, 0, false) + + projectBar.SetBorder(true).SetTitle("Projects (p)") + + return projectBar +} + +func addProjectToList(i int, selectItem bool) { + // To avoid overriding of loop variables - https://www.calhoun.io/gotchas-and-common-mistakes-with-closures-in-go/ + projectList.AddItem(projects[i].Title, "", 0, func(idx int) func() { + return func() { loadProject(idx) } + }(i)) + + if selectItem { + projectList.SetCurrentItem(i) + loadProject(i) + } +} + +func makeTaskPane() *tview.Flex { + taskList = tview.NewList().ShowSecondaryText(false) + taskList.SetDoneFunc(func() { + app.SetFocus(projectPane) + }) + + newTask = makeLightTextInput("+[New Task]"). + SetDoneFunc(func(key tcell.Key) { + switch key { + case tcell.KeyEnter: + task, err := taskRepo.Create(currentProject, newTask.GetText(), "", "", time.Now().Unix()) + if err != nil { + showMessage("[red::]Could not create Task:" + err.Error()) + } + + taskList.AddItem(task.Title, "", 0, nil) + newTask.SetText("") + case tcell.KeyEsc: + app.SetFocus(taskPane) + } + + }) + + taskPane := tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(taskList, 0, 1, true). + AddItem(newTask, 1, 0, false) + + taskPane.SetBorder(true).SetTitle("Tasks (t)") + + return taskPane +} + +func loadProject(idx int) { + currentProject = projects[idx] + taskList.Clear() + app.SetFocus(taskPane) + var err error + + if tasks, err = taskRepo.GetAllByProject(currentProject); err != nil && err != storm.ErrNotFound { + showMessage("[red::]Error: " + err.Error()) + } + + for i, task := range tasks { + taskList.AddItem(task.Title, "", 0, func(taskidx int) func() { + return func() { loadTask(taskidx) } + }(i)) + } +} + +func loadTask(idx int) { + currentTask = tasks[idx] + // taskList.Clear() + // app.SetFocus(taskPane) +} + +func makeLightTextInput(placeholder string) *tview.InputField { + return tview.NewInputField(). + SetPlaceholder(placeholder). + SetPlaceholderTextColor(tcell.ColorLightSlateGray). + SetFieldTextColor(tcell.ColorBlack). + SetFieldBackgroundColor(tcell.ColorGray) +} + +func ignoreKeyEvt() bool { + return reflect.TypeOf(app.GetFocus()).String() == "*tview.InputField" +} + +func setKeyboardShortcuts(projectPane *tview.Flex, taskPane *tview.Flex) *tview.Application { + return app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if ignoreKeyEvt() { + return event + } + switch event.Rune() { + case 'p': + app.SetFocus(projectPane) + case 't': + app.SetFocus(taskPane) + case 'n': + if projectPane.HasFocus() { + app.SetFocus(newProject) + } else if taskPane.HasFocus() { + app.SetFocus(newTask) + } + } + + return event + }) +} + +func showMessage(text string) { + message.SetText(text) + statusBar.SwitchToPage(messagePage) + + go func() { + app.QueueUpdateDraw(func() { + time.Sleep(time.Second * 5) + statusBar.SwitchToPage(shortcutsPage) + }) + }() +} + +func prepareStatusBar() { + statusBar = tview.NewPages() + + message = tview.NewTextView().SetDynamicColors(true).SetText("Loading...") + statusBar.AddPage(messagePage, message, true, true) + + statusBar.AddPage(shortcutsPage, + tview.NewGrid(). + SetColumns(0, 0, 0, 0). + SetRows(0). + AddItem(tview.NewTextView().SetText("Shortcuts: Alt+.(dot)"), 0, 0, 1, 1, 0, 0, false). + AddItem(tview.NewTextView().SetText("New Project: n").SetTextAlign(tview.AlignCenter), 0, 1, 1, 1, 0, 0, false). + AddItem(tview.NewTextView().SetText("New Task: t").SetTextAlign(tview.AlignCenter), 0, 2, 1, 1, 0, 0, false). + AddItem(tview.NewTextView().SetText("Quit: Ctrl+C").SetTextAlign(tview.AlignRight), 0, 3, 1, 1, 0, 0, false), + true, + true, + ) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..47fc375 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/ajaxray/geek-life + +go 1.14 + +require ( + github.com/asdine/storm/v3 v3.2.0 + github.com/gdamore/tcell v1.3.0 + github.com/golang/protobuf v1.3.3 // indirect + github.com/rivo/tview v0.0.0-20200507165325-823f280c5426 + github.com/stretchr/testify v1.4.0 // indirect + golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 // indirect + gopkg.in/yaml.v2 v2.2.8 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1f60b46 --- /dev/null +++ b/go.sum @@ -0,0 +1,68 @@ +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM= +github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM= +github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM= +github.com/asdine/storm/v3 v3.2.0 h1:qFpwwlOyIDVVrAgJliML9fEccRO3PJrJe+KpWK199ho= +github.com/asdine/storm/v3 v3.2.0/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM= +github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= +github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= +github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0= +github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/tview v0.0.0-20200507165325-823f280c5426 h1:oUJaa48KPBmtgqppKbVqP4QHRa/cdONfQoXcNWRe7wE= +github.com/rivo/tview v0.0.0-20200507165325-823f280c5426/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84= +github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= +go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20191105084925-a882066a44e0 h1:QPlSTtPE2k6PZPasQUbzuK3p9JbS+vMXYVto8g/yrsg= +golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/model/project.go b/model/project.go new file mode 100644 index 0000000..52a7606 --- /dev/null +++ b/model/project.go @@ -0,0 +1,7 @@ +package model + +type Project struct { + ID int64 `storm:"id,increment",json:"id"` + Title string `storm:"index",json:"title"` + UUID string `storm:"unique",json:"uuid,omitempty"` +} diff --git a/model/task.go b/model/task.go new file mode 100644 index 0000000..aded8a4 --- /dev/null +++ b/model/task.go @@ -0,0 +1,11 @@ +package model + +type Task struct { + ID int64 `storm:"id,increment",json:"id"` + ProjectID int64 `storm:"index",json:"project_id"` + UUID string `storm:"unique",json:"uuid,omitempty"` + Title string `json:"text"` + Details string `json:"notes"` + Completed bool `storm:"index",json:"completed"` + DueDate int64 `storm:"index",json:"due_date,omitempty"` +} diff --git a/repository/project.go b/repository/project.go new file mode 100644 index 0000000..07c26c6 --- /dev/null +++ b/repository/project.go @@ -0,0 +1,13 @@ +package repository + +import "github.com/ajaxray/geek-life/model" + +type ProjectRepository interface { + GetAll() ([]model.Project, error) + GetByID(id int64) (model.Project, error) + GetByTitle(title string) (model.Project, error) + GetByUUID(UUID string) (model.Project, error) + Create(title, UUID string) (model.Project, error) + Update(p *model.Project) error + Delete(p *model.Project) error +} diff --git a/repository/storm/project.go b/repository/storm/project.go new file mode 100644 index 0000000..f8be64e --- /dev/null +++ b/repository/storm/project.go @@ -0,0 +1,61 @@ +package storm + +import ( + "github.com/asdine/storm/v3" + + "github.com/ajaxray/geek-life/model" + "github.com/ajaxray/geek-life/repository" +) + +type projectRepository struct { + DB *storm.DB +} + +// NewProjectRepository will create an object that represent the repository.Project interface +func NewProjectRepository(db *storm.DB) repository.ProjectRepository { + return &projectRepository{db} +} + +func (repo *projectRepository) GetAll() ([]model.Project, error) { + var projects []model.Project + err := repo.DB.All(&projects) + + return projects, err +} + +func (repo *projectRepository) GetByID(id int64) (model.Project, error) { + return repo.getOneByField("ID", id) +} + +func (repo *projectRepository) GetByTitle(title string) (model.Project, error) { + return repo.getOneByField("Title", title) +} + +func (repo *projectRepository) GetByUUID(UUID string) (model.Project, error) { + return repo.getOneByField("CloudId", UUID) +} + +func (repo *projectRepository) Create(title, UUID string) (model.Project, error) { + project := model.Project{ + Title: title, + UUID: UUID, + } + + err := repo.DB.Save(&project) + return project, err +} + +func (repo *projectRepository) Update(project *model.Project) error { + return repo.DB.Save(project) +} + +func (repo *projectRepository) Delete(project *model.Project) error { + return repo.DB.DeleteStruct(project) +} + +func (repo *projectRepository) getOneByField(fieldName string, val interface{}) (model.Project, error) { + var project model.Project + err := repo.DB.One(fieldName, val, &project) + + return project, err +} diff --git a/repository/storm/task.go b/repository/storm/task.go new file mode 100644 index 0000000..4603518 --- /dev/null +++ b/repository/storm/task.go @@ -0,0 +1,64 @@ +package storm + +import ( + "time" + + "github.com/asdine/storm/v3" + + "github.com/ajaxray/geek-life/model" + "github.com/ajaxray/geek-life/repository" +) + +type taskRepository struct { + DB *storm.DB +} + +// NewTaskRepository will create an object that represent the repository.Task interface +func NewTaskRepository(db *storm.DB) repository.TaskRepository { + return &taskRepository{db} +} + +func (t taskRepository) GetAll() ([]model.Task, error) { + panic("implement me") +} + +func (t taskRepository) GetAllByProject(project model.Project) ([]model.Task, error) { + var tasks []model.Task + //err = db.Find("ProjetID", project.ID, &tasks, storm.Limit(10), storm.Skip(10), storm.Reverse()) + err := t.DB.Find("ProjectID", project.ID, &tasks) + + return tasks, err +} + +func (t taskRepository) GetAllByDate(from, to time.Time) ([]model.Task, error) { + panic("implement me") +} + +func (t taskRepository) GetByID(ID string) (model.Task, error) { + panic("implement me") +} + +func (t taskRepository) GetByUUID(UUID string) (model.Task, error) { + panic("implement me") +} + +func (t taskRepository) Create(project model.Project, title, details, UUID string, dueDate int64) (model.Task, error) { + task := model.Task{ + ProjectID: project.ID, + Title: title, + Details: details, + UUID: UUID, + DueDate: dueDate, + } + + err := t.DB.Save(&task) + return task, err +} + +func (t taskRepository) Update(p *model.Task) error { + panic("implement me") +} + +func (t taskRepository) Delete(p *model.Task) error { + panic("implement me") +} diff --git a/repository/task.go b/repository/task.go new file mode 100644 index 0000000..152d8fc --- /dev/null +++ b/repository/task.go @@ -0,0 +1,18 @@ +package repository + +import ( + "time" + + "github.com/ajaxray/geek-life/model" +) + +type TaskRepository interface { + GetAll() ([]model.Task, error) + GetAllByProject(project model.Project) ([]model.Task, error) + GetAllByDate(from, to time.Time) ([]model.Task, error) + GetByID(ID string) (model.Task, error) + GetByUUID(UUID string) (model.Task, error) + Create(project model.Project, title, details, UUID string, dueDate int64) (model.Task, error) + Update(p *model.Task) error + Delete(p *model.Task) error +} diff --git a/util/array.go b/util/array.go new file mode 100644 index 0000000..7c69eb8 --- /dev/null +++ b/util/array.go @@ -0,0 +1,27 @@ +package util + +import "reflect" + +// InArray checks is val exists in a Slice +func InArray(val interface{}, array interface{}) bool { + return AtArrayPosition(val, array) != -1 +} + +// AtArrayPosition find the int position of val in a Slice +func AtArrayPosition(val interface{}, array interface{}) (index int) { + index = -1 + + switch reflect.TypeOf(array).Kind() { + case reflect.Slice: + s := reflect.ValueOf(array) + + for i := 0; i < s.Len(); i++ { + if reflect.DeepEqual(val, s.Index(i).Interface()) == true { + index = i + return + } + } + } + + return +} diff --git a/util/env.go b/util/env.go new file mode 100644 index 0000000..76af107 --- /dev/null +++ b/util/env.go @@ -0,0 +1,34 @@ +package util + +import ( + "log" + "os" + "strconv" + // "github.com/subosito/gotenv" +) + +// func init() { +// gotenv.Load() +// } + +func GetEnvInt(key string, defaultVal int) int { + if v, ok := os.LookupEnv(key); ok { + if i, err := strconv.Atoi(v); err == nil { + return i + } else { + log.Fatal(err) + } + + return 0 + } + + return defaultVal +} + +func GetEnvStr(key, defaultVal string) string { + if v, ok := os.LookupEnv(key); ok { + return v + } + + return defaultVal +} diff --git a/util/util.go b/util/util.go new file mode 100644 index 0000000..311323b --- /dev/null +++ b/util/util.go @@ -0,0 +1,51 @@ +package util + +import ( + "fmt" + "log" + "strconv" + "strings" + "time" + + "github.com/asdine/storm/v3" +) + +// ConnectStorm Create database connection +func ConnectStorm() *storm.DB { + db, err := storm.Open(GetEnvStr("DB_FILE", "geek-life.db")) + FatalIfError(err, "Could not connect Embedded Database File") + + return db +} + +// UnixToTime create time.Time from string timestamp +func UnixToTime(timestamp string) time.Time { + parts := strings.Split(timestamp, ".") + i, err := strconv.ParseInt(parts[0], 10, 64) + if LogIfError(err, "Could not parse timestamp : "+timestamp+" (using current time instead)") { + return time.Unix(i, 0) + } + + return time.Now() +} + +// LogIfError logs the error and returns true on Error. think as IfError +func LogIfError(err error, msgOrPattern string, args ...interface{}) bool { + if err != nil { + message := fmt.Sprintf(msgOrPattern, args...) + log.Printf("%s: %w\n", message, err) + + return true + } + + return false +} + +// FatalIfError logs the error and Exit program on Error +func FatalIfError(err error, msgOrPattern string, args ...interface{}) { + message := fmt.Sprintf(msgOrPattern, args...) + + if LogIfError(err, message) { + log.Fatal("FATAL ERROR: Exiting program! - ", message, "\n") + } +}