Browse Source

complete (t)re(e)-factor; updated structure and updated absolute paths

cli-archive
nic 2 years ago
parent
commit
b951922304
  1. 75
      apps/cli/main.go
  2. 36
      apps/web/main.go
  3. 222
      internal/api/api.go
  4. 48
      internal/api/invoices.go
  5. 63
      internal/api/jobs.go
  6. 46
      internal/api/session_store.go
  7. 58
      internal/handlers/cli/admin.go
  8. 68
      internal/handlers/cli/assets.go
  9. 68
      internal/handlers/cli/companies.go
  10. 68
      internal/handlers/cli/contacts.go
  11. 68
      internal/handlers/cli/contracts.go
  12. 34
      internal/handlers/cli/dashboard.go
  13. 58
      internal/handlers/cli/generic.go
  14. 97
      internal/handlers/cli/invoices.go
  15. 130
      internal/handlers/cli/jobs.go
  16. 68
      internal/handlers/cli/locations.go
  17. 2
      internal/handlers/cli/login.go
  18. 58
      internal/handlers/cli/notifications.go
  19. 68
      internal/handlers/cli/quotes.go
  20. 68
      internal/handlers/cli/services.go
  21. 68
      internal/handlers/cli/tags.go
  22. 68
      internal/handlers/cli/users.go
  23. 21
      internal/handlers/web/admin.go
  24. 14
      internal/handlers/web/assets.go
  25. 14
      internal/handlers/web/companies.go
  26. 14
      internal/handlers/web/contacts.go
  27. 14
      internal/handlers/web/contracts.go
  28. 2
      internal/handlers/web/dashboard.go
  29. 14
      internal/handlers/web/generic.go
  30. 18
      internal/handlers/web/invoices.go
  31. 2
      internal/handlers/web/jobs.go
  32. 14
      internal/handlers/web/locations.go
  33. 2
      internal/handlers/web/login.go
  34. 14
      internal/handlers/web/notifications.go
  35. 14
      internal/handlers/web/quotes.go
  36. 14
      internal/handlers/web/services.go
  37. 14
      internal/handlers/web/tags.go
  38. 14
      internal/handlers/web/users.go
  39. 36
      internal/middleware/auth_middleware.go
  40. 37
      internal/ui/ui.go
  41. 79
      internal/utils/ui.go
  42. 39
      internal/utils/utils.go

75
apps/cli/main.go

@ -1,17 +1,18 @@
package main
import (
"os"
"fmt"
"marmic/servicetrade-toolbox/internal/api"
"marmic/servicetrade-toolbox/internal/menu"
"marmic/servicetrade-toolbox/internal/handlers/cli"
"marmic/servicetrade-toolbox/internal/ui"
"marmic/servicetrade-toolbox/internal/utils"
"os"
)
func main() {
ui.DisplayStartScreen()
email, password, err := ui.PromptCredentials()
email, password, err := utils.PromptCredentials()
if err != nil {
ui.DisplayError("Error getting credentials:", err)
os.Exit(1)
@ -24,23 +25,71 @@ func main() {
os.Exit(1)
}
ui.DisplayMessage("Login successful!")
fmt.Println("Login successful!")
mainMenu := menu.GetMainMenu()
for {
choice := menu.DisplayMenuAndGetChoice(mainMenu, "Main Menu")
if choice == len(mainMenu)+1 {
ui.ClearScreen()
ui.DisplayMessage("Logging out...")
ui.ClearScreen()
fmt.Println("Main Menu:")
fmt.Println("1. Jobs")
fmt.Println("2. Invoices")
fmt.Println("3. Companies")
fmt.Println("4. Assets")
fmt.Println("5. Contacts")
fmt.Println("6. Contracts")
fmt.Println("7. Generic Tools")
fmt.Println("8. Locations")
fmt.Println("9. Notifications")
fmt.Println("10. Quotes")
fmt.Println("11. Services")
fmt.Println("12. Tags")
fmt.Println("13. Users")
fmt.Println("14. Admin")
fmt.Println("15. Logout")
choice, err := utils.GetUserChoice(15)
if err != nil {
ui.DisplayError("Invalid input:", err)
utils.PressEnterToContinue()
continue
}
switch choice {
case 1:
cli.HandleJobs(session)
case 2:
cli.HandleInvoices(session)
case 3:
cli.HandleCompanies(session)
case 4:
cli.HandleAssets(session)
case 5:
cli.HandleContacts(session)
case 6:
cli.HandleContracts(session)
case 7:
cli.HandleGenericTools(session)
case 8:
cli.HandleLocations(session)
case 9:
cli.HandleNotifications(session)
case 10:
cli.HandleQuotes(session)
case 11:
cli.HandleServices(session)
case 12:
cli.HandleTags(session)
case 13:
cli.HandleUsers(session)
case 14:
cli.HandleAdmin(session)
case 15:
err := session.Logout()
if err != nil {
ui.DisplayError("Error during logout: ", err)
} else {
ui.DisplayMessage("Logout successful.")
fmt.Println("Logout successful.")
}
ui.DisplayMessage("Exiting ServiceTrade CLI Toolbox. Goodbye!")
fmt.Println("Exiting ServiceTrade CLI Toolbox. Goodbye!")
return
}
mainMenu[choice-1].Handler(session)
}
}

36
apps/web/main.go

@ -4,7 +4,7 @@ import (
"log"
"net/http"
"marmic/servicetrade-toolbox/internal/handlers"
"marmic/servicetrade-toolbox/internal/handlers/web"
"marmic/servicetrade-toolbox/internal/middleware"
"github.com/gorilla/mux"
@ -17,28 +17,28 @@ func main() {
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
// Auth routes
r.HandleFunc("/login", handlers.LoginHandler).Methods("GET", "POST")
r.HandleFunc("/logout", handlers.LogoutHandler).Methods("GET", "POST")
r.HandleFunc("/login", web.LoginHandler).Methods("GET", "POST")
r.HandleFunc("/logout", web.LogoutHandler).Methods("GET", "POST")
// Protected routes
protected := r.PathPrefix("/").Subrouter()
protected.Use(middleware.AuthMiddleware)
protected.HandleFunc("/", handlers.DashboardHandler).Methods("GET")
protected.HandleFunc("/jobs", handlers.JobsHandler).Methods("GET")
protected.HandleFunc("/admin", handlers.AdminHandler).Methods("GET")
protected.HandleFunc("/assets", handlers.AssetsHandler).Methods("GET")
protected.HandleFunc("/companies", handlers.CompaniesHandler).Methods("GET")
protected.HandleFunc("/contacts", handlers.ContactsHandler).Methods("GET")
protected.HandleFunc("/contracts", handlers.ContractsHandler).Methods("GET")
protected.HandleFunc("/generic", handlers.GenericHandler).Methods("GET")
protected.HandleFunc("/invoices", handlers.InvoicesHandler).Methods("GET", "POST")
protected.HandleFunc("/locations", handlers.LocationsHandler).Methods("GET")
protected.HandleFunc("/notifications", handlers.NotificationsHandler).Methods("GET")
protected.HandleFunc("/quotes", handlers.QuotesHandler).Methods("GET")
protected.HandleFunc("/services", handlers.ServicesHandler).Methods("GET")
protected.HandleFunc("/tags", handlers.TagsHandler).Methods("GET")
protected.HandleFunc("/users", handlers.UsersHandler).Methods("GET")
protected.HandleFunc("/", web.DashboardHandler).Methods("GET")
protected.HandleFunc("/jobs", web.JobsHandler).Methods("GET")
protected.HandleFunc("/invoices", web.InvoicesHandler).Methods("GET", "POST")
protected.HandleFunc("/admin", web.AdminHandler).Methods("GET")
protected.HandleFunc("/assets", web.AssetsHandler).Methods("GET")
protected.HandleFunc("/companies", web.CompaniesHandler).Methods("GET")
protected.HandleFunc("/contacts", web.ContactsHandler).Methods("GET")
protected.HandleFunc("/contracts", web.ContractsHandler).Methods("GET")
protected.HandleFunc("/generic", web.GenericHandler).Methods("GET")
protected.HandleFunc("/locations", web.LocationsHandler).Methods("GET")
protected.HandleFunc("/notifications", web.NotificationsHandler).Methods("GET")
protected.HandleFunc("/quotes", web.QuotesHandler).Methods("GET")
protected.HandleFunc("/services", web.ServicesHandler).Methods("GET")
protected.HandleFunc("/tags", web.TagsHandler).Methods("GET")
protected.HandleFunc("/users", web.UsersHandler).Methods("GET")
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", r))

222
internal/api/api.go

@ -6,12 +6,11 @@ import (
"fmt"
"io"
"net/http"
"regexp"
"strconv"
"strings"
"time"
)
const BaseURL = "https://api.servicetrade.com/api"
type Session struct {
Client *http.Client
Cookie string
@ -26,7 +25,7 @@ func NewSession() *Session {
}
func (s *Session) ValidateSession() error {
url := "https://api.servicetrade.com/api/auth/validate"
url := fmt.Sprintf("%s/auth/validate", BaseURL)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
@ -47,61 +46,54 @@ func (s *Session) ValidateSession() error {
return nil
}
func (s *Session) DoRequest(method, endpoint string, body io.Reader) (*http.Response, error) {
url := fmt.Sprintf("%s%s", BaseURL, endpoint)
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, fmt.Errorf("error creating request: %v", err)
}
req.Header.Set("Cookie", s.Cookie)
req.Header.Set("Content-Type", "application/json")
return s.Client.Do(req)
}
func (s *Session) Login(email, password string) error {
url := "https://api.servicetrade.com/api/auth"
payload := map[string]string{
"username": email,
"password": password,
}
payloadBytes, _ := json.Marshal(payload)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(payloadBytes))
resp, err := s.DoRequest("POST", "/auth", bytes.NewBuffer(payloadBytes))
if err != nil {
return fmt.Errorf("error creating request: %v", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := s.Client.Do(req)
if err != nil {
return fmt.Errorf("error sending request: %v", err)
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("failed to authenticate: %s, response: %s", resp.Status, string(body))
}
for _, cookie := range resp.Cookies() {
if strings.Contains(cookie.Name, "PHPSESSID") {
if cookie.Name == "PHPSESSID" {
s.Cookie = cookie.String()
fmt.Println(s.Cookie)
return nil
}
}
return fmt.Errorf("failed to retrieve session cookie; authentication may have failed")
return fmt.Errorf("failed to retrieve session cookie")
}
func (s *Session) Logout() error {
if s.Cookie == "" {
return fmt.Errorf("no active session to end")
}
url := "https://api.servicetrade.com/api/auth"
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
return fmt.Errorf("failed to create DELETE request to end session: %v", err)
}
req.Header.Set("Cookie", s.Cookie)
resp, err := s.Client.Do(req)
resp, err := s.DoRequest("DELETE", "/auth", nil)
if err != nil {
return fmt.Errorf("failed to send DELETE request to end session: %v", err)
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 && resp.StatusCode != 204 {
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("failed to end session: %s, response: %s", resp.Status, string(body))
}
@ -109,171 +101,3 @@ func (s *Session) Logout() error {
s.Cookie = ""
return nil
}
func (s *Session) GetAttachmentsForJob(jobID string) (map[string]interface{}, error) {
url := fmt.Sprintf("https://api.servicetrade.com/api/job/%s/paperwork", jobID)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %v", err)
}
req.Header.Set("Cookie", s.Cookie)
resp, err := s.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("error sending request: %v", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to get attachments: %s, response: %s", resp.Status, string(body))
}
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("error unmarshalling response: %v, body: %s", err, string(body))
}
return result, nil
}
func (s *Session) DeleteAttachment(endpoint string) error {
req, err := http.NewRequest("DELETE", endpoint, nil)
if err != nil {
return fmt.Errorf("failed to create DELETE request: %v", err)
}
req.Header.Set("Cookie", s.Cookie)
resp, err := s.Client.Do(req)
if err != nil {
return fmt.Errorf("failed to send DELETE request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 && resp.StatusCode != 204 {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("failed to delete attachment: %s, response: %s", resp.Status, string(body))
}
return nil
}
func (s *Session) GetDeficiencyInfoForJob(jobID string) ([][]string, error) {
url := fmt.Sprintf("https://api.servicetrade.com/api/deficiency/%s", jobID)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %v", err)
}
req.Header.Set("Cookie", s.Cookie)
resp, err := s.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("error sending request: %v", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to get deficiency info: %s, response: %s", resp.Status, string(body))
}
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("error unmarshalling response: %v, body: %s", err, string(body))
}
var deficiencyLogs [][]string
if data, ok := result["data"].([]interface{}); ok {
for _, item := range data {
if deficiency, ok := item.(map[string]interface{}); ok {
id := fmt.Sprintf("%v", deficiency["id"])
description := fmt.Sprintf("%v", deficiency["description"])
status := fmt.Sprintf("%v", deficiency["status"])
deficiencyLogs = append(deficiencyLogs, []string{jobID, id, description, status})
}
}
}
return deficiencyLogs, nil
}
func (s *Session) GetDeficiencyById(deficiencyId string) (map[string]interface{}, error) {
url := fmt.Sprintf("https://api.servicetrade.com/api/deficiency/%s", deficiencyId)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %v", err)
}
req.Header.Set("Cookie", s.Cookie)
resp, err := s.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("error sending request: %v", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to get deficiency info: %s, response: %s", resp.Status, string(body))
}
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("error unmarshalling response: %v, body: %s", err, string(body))
}
return result, nil
}
func (s *Session) GetInvoice(identifier string) (map[string]interface{}, error) {
var url string
// Regular expression to check if the identifier starts with a letter
isInvoiceNumber, _ := regexp.MatchString(`^[A-Za-z]`, identifier)
if isInvoiceNumber {
url = fmt.Sprintf("https://api.servicetrade.com/api/invoice?invoiceNumber=%s", identifier)
} else {
// Check if the identifier is a valid number
if _, err := strconv.Atoi(identifier); err != nil {
return nil, fmt.Errorf("invalid invoice identifier: %s", identifier)
}
url = fmt.Sprintf("https://api.servicetrade.com/api/invoice/%s", identifier)
}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %v", err)
}
req.Header.Set("Cookie", s.Cookie)
resp, err := s.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("error sending request: %v", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to get invoice info: %s, response: %s", resp.Status, string(body))
}
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("error unmarshalling response: %v, body: %s", err, string(body))
}
// Check if the response contains a 'data' field
if data, ok := result["data"].(map[string]interface{}); ok {
// If 'invoices' field exists, it's a search by invoice number
if invoices, ok := data["invoices"].([]interface{}); ok && len(invoices) > 0 {
if invoice, ok := invoices[0].(map[string]interface{}); ok {
return invoice, nil
}
} else {
// If 'invoices' doesn't exist, it's a direct invoice lookup by ID
return data, nil
}
}
return nil, fmt.Errorf("no invoice found in the response")
}

48
internal/api/invoices.go

@ -0,0 +1,48 @@
package api
import (
"encoding/json"
"fmt"
"io"
"regexp"
)
func (s *Session) GetInvoice(identifier string) (map[string]interface{}, error) {
var endpoint string
isInvoiceNumber, _ := regexp.MatchString(`^[A-Za-z]`, identifier)
if isInvoiceNumber {
endpoint = fmt.Sprintf("/invoice?invoiceNumber=%s", identifier)
} else {
endpoint = fmt.Sprintf("/invoice/%s", identifier)
}
resp, err := s.DoRequest("GET", endpoint, nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to get invoice info: %s, response: %s", resp.Status, string(body))
}
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("error unmarshalling response: %v", err)
}
if data, ok := result["data"].(map[string]interface{}); ok {
if invoices, ok := data["invoices"].([]interface{}); ok && len(invoices) > 0 {
if invoice, ok := invoices[0].(map[string]interface{}); ok {
return invoice, nil
}
} else {
return data, nil
}
}
return nil, fmt.Errorf("no invoice found in the response")
}

63
internal/api/jobs.go

@ -0,0 +1,63 @@
package api
import (
"encoding/json"
"fmt"
"io"
)
func (s *Session) GetAttachmentsForJob(jobID string) (map[string]interface{}, error) {
resp, err := s.DoRequest("GET", fmt.Sprintf("/job/%s/paperwork", jobID), nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to get attachments: %s, response: %s", resp.Status, string(body))
}
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("error unmarshalling response: %v", err)
}
return result, nil
}
func (s *Session) DeleteAttachment(attachmentID string) error {
resp, err := s.DoRequest("DELETE", fmt.Sprintf("/attachment/%s", attachmentID), nil)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 && resp.StatusCode != 204 {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("failed to delete attachment: %s, response: %s", resp.Status, string(body))
}
return nil
}
func (s *Session) GetDeficiencyInfoForJob(jobID string) ([]map[string]interface{}, error) {
resp, err := s.DoRequest("GET", fmt.Sprintf("/deficiency/%s", jobID), nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to get deficiency info: %s, response: %s", resp.Status, string(body))
}
var result struct {
Data []map[string]interface{} `json:"data"`
}
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("error unmarshalling response: %v", err)
}
return result.Data, nil
}

46
internal/api/session_store.go

@ -0,0 +1,46 @@
package api
import (
"sync"
"time"
)
type SessionStore struct {
sessions map[string]*Session
mu sync.RWMutex
}
func NewSessionStore() *SessionStore {
return &SessionStore{
sessions: make(map[string]*Session),
}
}
func (store *SessionStore) Set(sessionID string, session *Session) {
store.mu.Lock()
defer store.mu.Unlock()
store.sessions[sessionID] = session
}
func (store *SessionStore) Get(sessionID string) (*Session, bool) {
store.mu.RLock()
defer store.mu.RUnlock()
session, ok := store.sessions[sessionID]
return session, ok
}
func (store *SessionStore) Delete(sessionID string) {
store.mu.Lock()
defer store.mu.Unlock()
delete(store.sessions, sessionID)
}
func (store *SessionStore) CleanupSessions() {
store.mu.Lock()
defer store.mu.Unlock()
for id, session := range store.sessions {
if time.Since(session.LastAccessed) > 24*time.Hour {
delete(store.sessions, id)
}
}
}

58
internal/handlers/cli/admin.go

@ -1,24 +1,48 @@
package handlers
package cli
import (
"html/template"
"net/http"
"marmic/servicetrade-toolbox/internal/api"
"marmic/servicetrade-toolbox/internal/ui"
"marmic/servicetrade-toolbox/internal/utils"
)
func AdminHandler(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("HX-Request") == "true" {
// This is an HTMX request, return only the jobs partial
tmpl := template.Must(template.ParseFiles("templates/partials/jobs.html"))
jobs := r.Cookies() // Replace with actual data fetching
tmpl.Execute(w, jobs)
} else {
// This is a full page request
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/admin.html"))
jobs := []string{"Job 1", "Job 2", "Job 3"} // Replace with actual data fetching
func HandleAdmin(session *api.Session) {
for {
ui.ClearScreen()
ui.DisplayMenu([]string{
"User Management",
"System Settings",
"Back to Main Menu",
}, "Admin Menu")
tmpl.Execute(w, map[string]interface{}{
"Title": "Jobs",
"Jobs": jobs,
})
choice, err := utils.GetUserChoice(3)
if err != nil {
ui.DisplayError("Invalid input:", err)
utils.PressEnterToContinue()
continue
}
switch choice {
case 1:
userManagement(session)
case 2:
systemSettings(session)
case 3:
return
}
}
}
func userManagement(session *api.Session) {
ui.ClearScreen()
ui.DisplayMessage("User Management")
// TODO: Implement user management logic
utils.PressEnterToContinue()
}
func systemSettings(session *api.Session) {
ui.ClearScreen()
ui.DisplayMessage("System Settings")
// TODO: Implement system settings logic
utils.PressEnterToContinue()
}

68
internal/handlers/cli/assets.go

@ -1,11 +1,67 @@
package handlers
package cli
import (
"html/template"
"net/http"
"fmt"
"marmic/servicetrade-toolbox/internal/api"
"marmic/servicetrade-toolbox/internal/ui"
"marmic/servicetrade-toolbox/internal/utils"
)
func AssetsHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/assets.html"))
tmpl.Execute(w, nil)
func HandleAssets(session *api.Session) {
for {
ui.ClearScreen()
fmt.Println("Assets Menu:")
fmt.Println("1. List Assets")
fmt.Println("2. Add Asset")
fmt.Println("3. Edit Asset")
fmt.Println("4. Delete Asset")
fmt.Println("5. Back to Main Menu")
choice, err := utils.GetUserChoice(5)
if err != nil {
ui.DisplayError("Invalid input:", err)
utils.PressEnterToContinue()
continue
}
switch choice {
case 1:
listAssets(session)
case 2:
addAsset(session)
case 3:
editAsset(session)
case 4:
deleteAsset(session)
case 5:
return
}
}
}
func listAssets(session *api.Session) {
ui.ClearScreen()
fmt.Println("Listing assets...")
// TODO: Implement asset listing logic using the API
utils.PressEnterToContinue()
}
func addAsset(session *api.Session) {
ui.ClearScreen()
fmt.Println("Adding a new asset...")
// TODO: Implement asset creation logic using the API
utils.PressEnterToContinue()
}
func editAsset(session *api.Session) {
ui.ClearScreen()
fmt.Println("Editing an asset...")
// TODO: Implement asset editing logic using the API
utils.PressEnterToContinue()
}
func deleteAsset(session *api.Session) {
ui.ClearScreen()
fmt.Println("Deleting an asset...")
// TODO: Implement asset deletion logic using the API
utils.PressEnterToContinue()
}

68
internal/handlers/cli/companies.go

@ -1,11 +1,67 @@
package handlers
package cli
import (
"html/template"
"net/http"
"fmt"
"marmic/servicetrade-toolbox/internal/api"
"marmic/servicetrade-toolbox/internal/ui"
"marmic/servicetrade-toolbox/internal/utils"
)
func CompaniesHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/companies.html"))
tmpl.Execute(w, nil)
func HandleCompanies(session *api.Session) {
for {
ui.ClearScreen()
fmt.Println("Companies Menu:")
fmt.Println("1. List Companies")
fmt.Println("2. Add Company")
fmt.Println("3. Edit Company")
fmt.Println("4. Delete Company")
fmt.Println("5. Back to Main Menu")
choice, err := utils.GetUserChoice(5)
if err != nil {
ui.DisplayError("Invalid input:", err)
utils.PressEnterToContinue()
continue
}
switch choice {
case 1:
listCompanies(session)
case 2:
addCompany(session)
case 3:
editCompany(session)
case 4:
deleteCompany(session)
case 5:
return
}
}
}
func listCompanies(session *api.Session) {
ui.ClearScreen()
fmt.Println("Listing companies...")
// TODO: Implement company listing logic using the API
utils.PressEnterToContinue()
}
func addCompany(session *api.Session) {
ui.ClearScreen()
fmt.Println("Adding a new company...")
// TODO: Implement company creation logic using the API
utils.PressEnterToContinue()
}
func editCompany(session *api.Session) {
ui.ClearScreen()
fmt.Println("Editing a company...")
// TODO: Implement company editing logic using the API
utils.PressEnterToContinue()
}
func deleteCompany(session *api.Session) {
ui.ClearScreen()
fmt.Println("Deleting a company...")
// TODO: Implement company deletion logic using the API
utils.PressEnterToContinue()
}

68
internal/handlers/cli/contacts.go

@ -1,11 +1,67 @@
package handlers
package cli
import (
"html/template"
"net/http"
"fmt"
"marmic/servicetrade-toolbox/internal/api"
"marmic/servicetrade-toolbox/internal/ui"
"marmic/servicetrade-toolbox/internal/utils"
)
func ContactsHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/contacts.html"))
tmpl.Execute(w, nil)
func HandleContacts(session *api.Session) {
for {
ui.ClearScreen()
fmt.Println("Contacts Menu:")
fmt.Println("1. List Contacts")
fmt.Println("2. Add Contact")
fmt.Println("3. Edit Contact")
fmt.Println("4. Delete Contact")
fmt.Println("5. Back to Main Menu")
choice, err := utils.GetUserChoice(5)
if err != nil {
ui.DisplayError("Invalid input:", err)
utils.PressEnterToContinue()
continue
}
switch choice {
case 1:
listContacts(session)
case 2:
addContact(session)
case 3:
editContact(session)
case 4:
deleteContact(session)
case 5:
return
}
}
}
func listContacts(session *api.Session) {
ui.ClearScreen()
fmt.Println("Listing contacts...")
// TODO: Implement contact listing logic using the API
utils.PressEnterToContinue()
}
func addContact(session *api.Session) {
ui.ClearScreen()
fmt.Println("Adding a new contact...")
// TODO: Implement contact creation logic using the API
utils.PressEnterToContinue()
}
func editContact(session *api.Session) {
ui.ClearScreen()
fmt.Println("Editing a contact...")
// TODO: Implement contact editing logic using the API
utils.PressEnterToContinue()
}
func deleteContact(session *api.Session) {
ui.ClearScreen()
fmt.Println("Deleting a contact...")
// TODO: Implement contact deletion logic using the API
utils.PressEnterToContinue()
}

68
internal/handlers/cli/contracts.go

@ -1,11 +1,67 @@
package handlers
package cli
import (
"html/template"
"net/http"
"fmt"
"marmic/servicetrade-toolbox/internal/api"
"marmic/servicetrade-toolbox/internal/ui"
"marmic/servicetrade-toolbox/internal/utils"
)
func ContractsHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/contracts.html"))
tmpl.Execute(w, nil)
func HandleContracts(session *api.Session) {
for {
ui.ClearScreen()
fmt.Println("Contracts Menu:")
fmt.Println("1. List Contracts")
fmt.Println("2. Add Contract")
fmt.Println("3. Edit Contract")
fmt.Println("4. Delete Contract")
fmt.Println("5. Back to Main Menu")
choice, err := utils.GetUserChoice(5)
if err != nil {
ui.DisplayError("Invalid input:", err)
utils.PressEnterToContinue()
continue
}
switch choice {
case 1:
listContracts(session)
case 2:
addContract(session)
case 3:
editContract(session)
case 4:
deleteContract(session)
case 5:
return
}
}
}
func listContracts(session *api.Session) {
ui.ClearScreen()
fmt.Println("Listing contracts...")
// TODO: Implement contract listing logic using the API
utils.PressEnterToContinue()
}
func addContract(session *api.Session) {
ui.ClearScreen()
fmt.Println("Adding a new contract...")
// TODO: Implement contract creation logic using the API
utils.PressEnterToContinue()
}
func editContract(session *api.Session) {
ui.ClearScreen()
fmt.Println("Editing a contract...")
// TODO: Implement contract editing logic using the API
utils.PressEnterToContinue()
}
func deleteContract(session *api.Session) {
ui.ClearScreen()
fmt.Println("Deleting a contract...")
// TODO: Implement contract deletion logic using the API
utils.PressEnterToContinue()
}

34
internal/handlers/cli/dashboard.go

@ -1,34 +0,0 @@
package handlers
import (
"html/template"
"log"
"net/http"
)
func DashboardHandler(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles(
"templates/layout.html",
"templates/dashboard.html",
"templates/partials/invoice_search.html",
"templates/partials/invoice_search_results.html",
)
if err != nil {
log.Printf("Template parsing error: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
data := struct{}{} // Empty struct as data
if r.Header.Get("HX-Request") == "true" {
err = tmpl.ExecuteTemplate(w, "content", data)
} else {
err = tmpl.ExecuteTemplate(w, "layout.html", data)
}
if err != nil {
log.Printf("Template execution error: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
}

58
internal/handlers/cli/generic.go

@ -1,11 +1,57 @@
package handlers
package cli
import (
"html/template"
"net/http"
"fmt"
"marmic/servicetrade-toolbox/internal/api"
"marmic/servicetrade-toolbox/internal/ui"
"marmic/servicetrade-toolbox/internal/utils"
)
func GenericHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/generic.html"))
tmpl.Execute(w, nil)
func HandleGenericTools(session *api.Session) {
for {
ui.ClearScreen()
fmt.Println("Generic Tools Menu:")
fmt.Println("1. Tool 1")
fmt.Println("2. Tool 2")
fmt.Println("3. Tool 3")
fmt.Println("4. Back to Main Menu")
choice, err := utils.GetUserChoice(4)
if err != nil {
ui.DisplayError("Invalid input:", err)
utils.PressEnterToContinue()
continue
}
switch choice {
case 1:
runTool1(session)
case 2:
runTool2(session)
case 3:
runTool3(session)
case 4:
return
}
}
}
func runTool1(session *api.Session) {
ui.ClearScreen()
fmt.Println("Running Tool 1...")
// TODO: Implement Tool 1 logic
utils.PressEnterToContinue()
}
func runTool2(session *api.Session) {
ui.ClearScreen()
fmt.Println("Running Tool 2...")
// TODO: Implement Tool 2 logic
utils.PressEnterToContinue()
}
func runTool3(session *api.Session) {
ui.ClearScreen()
fmt.Println("Running Tool 3...")
// TODO: Implement Tool 3 logic
utils.PressEnterToContinue()
}

97
internal/handlers/cli/invoices.go

@ -1,57 +1,70 @@
package handlers
package cli
import (
"html/template"
"log"
"fmt"
"marmic/servicetrade-toolbox/internal/api"
"net/http"
"strings"
"marmic/servicetrade-toolbox/internal/ui"
"marmic/servicetrade-toolbox/internal/utils"
)
func InvoicesHandler(w http.ResponseWriter, r *http.Request) {
session, ok := r.Context().Value("session").(*api.Session)
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Handle the search request
if r.Method == "GET" && r.URL.Query().Get("search") != "" {
handleInvoiceSearch(w, r, session)
return
}
func HandleInvoices(session *api.Session) {
for {
ui.ClearScreen()
fmt.Println("Invoices Menu:")
fmt.Println("1. Search Invoice")
fmt.Println("2. List Recent Invoices")
fmt.Println("3. Create Invoice")
fmt.Println("4. Back to Main Menu")
// Handle the initial page load
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/invoices.html"))
err := tmpl.Execute(w, nil)
if err != nil {
log.Printf("Error executing template: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
choice, err := utils.GetUserChoice(4)
if err != nil {
ui.DisplayError("Invalid input:", err)
utils.PressEnterToContinue()
continue
}
switch choice {
case 1:
searchInvoice(session)
case 2:
listRecentInvoices(session)
case 3:
createInvoice(session)
case 4:
return
}
}
}
func handleInvoiceSearch(w http.ResponseWriter, r *http.Request, session *api.Session) {
searchTerm := strings.TrimSpace(r.URL.Query().Get("search"))
if searchTerm == "" {
log.Println("Empty search term, returning empty response")
w.WriteHeader(http.StatusOK)
return
}
func searchInvoice(session *api.Session) {
ui.ClearScreen()
fmt.Println("Search Invoice:")
identifier := utils.PromptForInput("Enter Invoice Number or ID: ")
invoice, err := session.GetInvoice(searchTerm)
invoice, err := session.GetInvoice(identifier)
if err != nil {
log.Printf("Error fetching invoice: %v", err)
w.WriteHeader(http.StatusInternalServerError)
tmpl := template.Must(template.ParseFiles("templates/partials/invoice_search_results.html"))
tmpl.ExecuteTemplate(w, "invoice_search_results", map[string]interface{}{"Error": err.Error()})
fmt.Printf("Error fetching invoice: %v\n", err)
utils.PressEnterToContinue()
return
}
tmpl := template.Must(template.ParseFiles("templates/partials/invoice_search_results.html"))
err = tmpl.ExecuteTemplate(w, "invoice_search_results", invoice)
if err != nil {
log.Printf("Error executing template: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
fmt.Println("Invoice Details:")
fmt.Printf("Invoice Number: %v\n", invoice["invoiceNumber"])
fmt.Printf("Total Price: $%v\n", invoice["totalPrice"])
fmt.Printf("Status: %v\n", invoice["status"])
utils.PressEnterToContinue()
}
func listRecentInvoices(session *api.Session) {
ui.ClearScreen()
fmt.Println("Listing recent invoices...")
// TODO: Implement recent invoices listing logic using the API
utils.PressEnterToContinue()
}
func createInvoice(session *api.Session) {
ui.ClearScreen()
fmt.Println("Creating a new invoice...")
// TODO: Implement invoice creation logic using the API
utils.PressEnterToContinue()
}

130
internal/handlers/cli/jobs.go

@ -1,26 +1,116 @@
package handlers
package cli
import (
"html/template"
"net/http"
"fmt"
"marmic/servicetrade-toolbox/internal/api"
"marmic/servicetrade-toolbox/internal/ui"
"marmic/servicetrade-toolbox/internal/utils"
)
func JobsHandler(w http.ResponseWriter, r *http.Request) {
jobs := []string{"Job 1", "Job 2", "Job 3"} // Replace with actual data fetching
if r.Header.Get("HX-Request") == "true" {
// This is an HTMX request, return the jobs content wrapped in the content template
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/jobs.html"))
tmpl.ExecuteTemplate(w, "content", map[string]interface{}{
"Title": "Jobs",
"Jobs": jobs,
})
} else {
// This is a full page request
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/jobs.html"))
tmpl.Execute(w, map[string]interface{}{
"Title": "Jobs",
"Jobs": jobs,
})
func HandleJobs(session *api.Session) {
for {
ui.DisplayMenu([]string{
"Search Job by ID",
"List Recent Jobs",
"Create New Job",
"Manage Job Attachments",
"View Deficiencies",
"Back to Main Menu",
}, "Jobs Menu")
choice, err := utils.GetUserChoice(6)
if err != nil {
ui.DisplayError("Invalid input:", err)
utils.PressEnterToContinue()
continue
}
switch choice {
case 1:
searchJobByID(session)
case 2:
listRecentJobs(session)
case 3:
createNewJob(session)
case 4:
manageJobAttachments(session)
case 5:
viewDeficiencyById(session)
case 6:
return
}
}
}
func searchJobByID(session *api.Session) {
ui.ClearScreen()
ui.DisplayMessage("Search Job by ID:")
jobID := utils.PromptForInput("Enter Job ID: ")
// TODO: Implement job search logic using the API
ui.DisplayMessage(fmt.Sprintf("Searching for job with ID: %s", jobID))
utils.PressEnterToContinue()
}
func listRecentJobs(session *api.Session) {
ui.ClearScreen()
ui.DisplayMessage("Listing recent jobs...")
// TODO: Implement recent jobs listing logic using the API
utils.PressEnterToContinue()
}
func createNewJob(session *api.Session) {
ui.ClearScreen()
ui.DisplayMessage("Creating a new job...")
// TODO: Implement job creation logic using the API
utils.PressEnterToContinue()
}
func manageJobAttachments(session *api.Session) {
ui.ClearScreen()
jobID := utils.PromptForInput("Enter Job ID: ")
attachments, err := session.GetAttachmentsForJob(jobID)
if err != nil {
ui.DisplayError("Failed to retrieve attachments:", err)
utils.PressEnterToContinue()
return
}
ui.DisplayMessage(fmt.Sprintf("Attachments for Job %s:", jobID))
if dataMap, ok := attachments["data"].(map[string]interface{}); ok {
if attachmentsList, ok := dataMap["attachments"].([]interface{}); ok {
for i, attachment := range attachmentsList {
if att, ok := attachment.(map[string]interface{}); ok {
ui.DisplayMessage(fmt.Sprintf("%d. %s", i+1, att["fileName"]))
}
}
}
}
// TODO: Implement attachment deletion logic
utils.PressEnterToContinue()
}
func viewDeficiencyById(session *api.Session) {
ui.ClearScreen()
deficiencyID := utils.PromptForInput("Enter Deficiency ID: ")
ui.DisplayMessage(fmt.Sprintf("Fetching information for Deficiency %s...", deficiencyID))
deficiencies, err := session.GetDeficiencyInfoForJob(deficiencyID)
if err != nil {
ui.DisplayError("Failed to retrieve deficiency information:", err)
utils.PressEnterToContinue()
return
}
for _, deficiency := range deficiencies {
ui.DisplayMessage(fmt.Sprintf("ID: %v", deficiency["id"]))
ui.DisplayMessage(fmt.Sprintf("Description: %v", deficiency["description"]))
ui.DisplayMessage(fmt.Sprintf("Status: %v", deficiency["status"]))
ui.DisplayMessage("---")
}
utils.PressEnterToContinue()
}

68
internal/handlers/cli/locations.go

@ -1,11 +1,67 @@
package handlers
package cli
import (
"html/template"
"net/http"
"fmt"
"marmic/servicetrade-toolbox/internal/api"
"marmic/servicetrade-toolbox/internal/ui"
"marmic/servicetrade-toolbox/internal/utils"
)
func LocationsHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/locations.html"))
tmpl.Execute(w, nil)
func HandleLocations(session *api.Session) {
for {
ui.ClearScreen()
fmt.Println("Locations Menu:")
fmt.Println("1. List Locations")
fmt.Println("2. Add Location")
fmt.Println("3. Edit Location")
fmt.Println("4. Delete Location")
fmt.Println("5. Back to Main Menu")
choice, err := utils.GetUserChoice(5)
if err != nil {
ui.DisplayError("Invalid input:", err)
utils.PressEnterToContinue()
continue
}
switch choice {
case 1:
listLocations(session)
case 2:
addLocation(session)
case 3:
editLocation(session)
case 4:
deleteLocation(session)
case 5:
return
}
}
}
func listLocations(session *api.Session) {
ui.ClearScreen()
fmt.Println("Listing locations...")
// TODO: Implement location listing logic using the API
utils.PressEnterToContinue()
}
func addLocation(session *api.Session) {
ui.ClearScreen()
fmt.Println("Adding a new location...")
// TODO: Implement location creation logic using the API
utils.PressEnterToContinue()
}
func editLocation(session *api.Session) {
ui.ClearScreen()
fmt.Println("Editing a location...")
// TODO: Implement location editing logic using the API
utils.PressEnterToContinue()
}
func deleteLocation(session *api.Session) {
ui.ClearScreen()
fmt.Println("Deleting a location...")
// TODO: Implement location deletion logic using the API
utils.PressEnterToContinue()
}

2
internal/handlers/cli/login.go

@ -1,4 +1,4 @@
package handlers
package cli
import (
"html/template"

58
internal/handlers/cli/notifications.go

@ -1,11 +1,57 @@
package handlers
package cli
import (
"html/template"
"net/http"
"fmt"
"marmic/servicetrade-toolbox/internal/api"
"marmic/servicetrade-toolbox/internal/ui"
"marmic/servicetrade-toolbox/internal/utils"
)
func NotificationsHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/notifications.html"))
tmpl.Execute(w, nil)
func HandleNotifications(session *api.Session) {
for {
ui.ClearScreen()
fmt.Println("Notifications Menu:")
fmt.Println("1. View Notifications")
fmt.Println("2. Mark Notification as Read")
fmt.Println("3. Delete Notification")
fmt.Println("4. Back to Main Menu")
choice, err := utils.GetUserChoice(4)
if err != nil {
ui.DisplayError("Invalid input:", err)
utils.PressEnterToContinue()
continue
}
switch choice {
case 1:
viewNotifications(session)
case 2:
markNotificationAsRead(session)
case 3:
deleteNotification(session)
case 4:
return
}
}
}
func viewNotifications(session *api.Session) {
ui.ClearScreen()
fmt.Println("Viewing notifications...")
// TODO: Implement notification viewing logic using the API
utils.PressEnterToContinue()
}
func markNotificationAsRead(session *api.Session) {
ui.ClearScreen()
fmt.Println("Marking notification as read...")
// TODO: Implement marking notification as read logic using the API
utils.PressEnterToContinue()
}
func deleteNotification(session *api.Session) {
ui.ClearScreen()
fmt.Println("Deleting notification...")
// TODO: Implement notification deletion logic using the API
utils.PressEnterToContinue()
}

68
internal/handlers/cli/quotes.go

@ -1,11 +1,67 @@
package handlers
package cli
import (
"html/template"
"net/http"
"fmt"
"marmic/servicetrade-toolbox/internal/api"
"marmic/servicetrade-toolbox/internal/ui"
"marmic/servicetrade-toolbox/internal/utils"
)
func QuotesHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/quotes.html"))
tmpl.Execute(w, nil)
func HandleQuotes(session *api.Session) {
for {
ui.ClearScreen()
fmt.Println("Quotes Menu:")
fmt.Println("1. List Quotes")
fmt.Println("2. Create Quote")
fmt.Println("3. Edit Quote")
fmt.Println("4. Delete Quote")
fmt.Println("5. Back to Main Menu")
choice, err := utils.GetUserChoice(5)
if err != nil {
ui.DisplayError("Invalid input:", err)
utils.PressEnterToContinue()
continue
}
switch choice {
case 1:
listQuotes(session)
case 2:
createQuote(session)
case 3:
editQuote(session)
case 4:
deleteQuote(session)
case 5:
return
}
}
}
func listQuotes(session *api.Session) {
ui.ClearScreen()
fmt.Println("Listing quotes...")
// TODO: Implement quote listing logic using the API
utils.PressEnterToContinue()
}
func createQuote(session *api.Session) {
ui.ClearScreen()
fmt.Println("Creating a new quote...")
// TODO: Implement quote creation logic using the API
utils.PressEnterToContinue()
}
func editQuote(session *api.Session) {
ui.ClearScreen()
fmt.Println("Editing a quote...")
// TODO: Implement quote editing logic using the API
utils.PressEnterToContinue()
}
func deleteQuote(session *api.Session) {
ui.ClearScreen()
fmt.Println("Deleting a quote...")
// TODO: Implement quote deletion logic using the API
utils.PressEnterToContinue()
}

68
internal/handlers/cli/services.go

@ -1,11 +1,67 @@
package handlers
package cli
import (
"html/template"
"net/http"
"fmt"
"marmic/servicetrade-toolbox/internal/api"
"marmic/servicetrade-toolbox/internal/ui"
"marmic/servicetrade-toolbox/internal/utils"
)
func ServicesHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/services.html"))
tmpl.Execute(w, nil)
func HandleServices(session *api.Session) {
for {
ui.ClearScreen()
fmt.Println("Services Menu:")
fmt.Println("1. List Services")
fmt.Println("2. Add Service")
fmt.Println("3. Edit Service")
fmt.Println("4. Delete Service")
fmt.Println("5. Back to Main Menu")
choice, err := utils.GetUserChoice(5)
if err != nil {
ui.DisplayError("Invalid input:", err)
utils.PressEnterToContinue()
continue
}
switch choice {
case 1:
listServices(session)
case 2:
addService(session)
case 3:
editService(session)
case 4:
deleteService(session)
case 5:
return
}
}
}
func listServices(session *api.Session) {
ui.ClearScreen()
fmt.Println("Listing services...")
// TODO: Implement service listing logic using the API
utils.PressEnterToContinue()
}
func addService(session *api.Session) {
ui.ClearScreen()
fmt.Println("Adding a new service...")
// TODO: Implement service creation logic using the API
utils.PressEnterToContinue()
}
func editService(session *api.Session) {
ui.ClearScreen()
fmt.Println("Editing a service...")
// TODO: Implement service editing logic using the API
utils.PressEnterToContinue()
}
func deleteService(session *api.Session) {
ui.ClearScreen()
fmt.Println("Deleting a service...")
// TODO: Implement service deletion logic using the API
utils.PressEnterToContinue()
}

68
internal/handlers/cli/tags.go

@ -1,11 +1,67 @@
package handlers
package cli
import (
"html/template"
"net/http"
"fmt"
"marmic/servicetrade-toolbox/internal/api"
"marmic/servicetrade-toolbox/internal/ui"
"marmic/servicetrade-toolbox/internal/utils"
)
func TagsHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/tags.html"))
tmpl.Execute(w, nil)
func HandleTags(session *api.Session) {
for {
ui.ClearScreen()
fmt.Println("Tags Menu:")
fmt.Println("1. List Tags")
fmt.Println("2. Add Tag")
fmt.Println("3. Edit Tag")
fmt.Println("4. Delete Tag")
fmt.Println("5. Back to Main Menu")
choice, err := utils.GetUserChoice(5)
if err != nil {
ui.DisplayError("Invalid input:", err)
utils.PressEnterToContinue()
continue
}
switch choice {
case 1:
listTags(session)
case 2:
addTag(session)
case 3:
editTag(session)
case 4:
deleteTag(session)
case 5:
return
}
}
}
func listTags(session *api.Session) {
ui.ClearScreen()
fmt.Println("Listing tags...")
// TODO: Implement tag listing logic using the API
utils.PressEnterToContinue()
}
func addTag(session *api.Session) {
ui.ClearScreen()
fmt.Println("Adding a new tag...")
// TODO: Implement tag creation logic using the API
utils.PressEnterToContinue()
}
func editTag(session *api.Session) {
ui.ClearScreen()
fmt.Println("Editing a tag...")
// TODO: Implement tag editing logic using the API
utils.PressEnterToContinue()
}
func deleteTag(session *api.Session) {
ui.ClearScreen()
fmt.Println("Deleting a tag...")
// TODO: Implement tag deletion logic using the API
utils.PressEnterToContinue()
}

68
internal/handlers/cli/users.go

@ -1,11 +1,67 @@
package handlers
package cli
import (
"html/template"
"net/http"
"fmt"
"marmic/servicetrade-toolbox/internal/api"
"marmic/servicetrade-toolbox/internal/ui"
"marmic/servicetrade-toolbox/internal/utils"
)
func UsersHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/users.html"))
tmpl.Execute(w, nil)
func HandleUsers(session *api.Session) {
for {
ui.ClearScreen()
fmt.Println("Users Menu:")
fmt.Println("1. List Users")
fmt.Println("2. Add User")
fmt.Println("3. Edit User")
fmt.Println("4. Delete User")
fmt.Println("5. Back to Main Menu")
choice, err := utils.GetUserChoice(5)
if err != nil {
ui.DisplayError("Invalid input:", err)
utils.PressEnterToContinue()
continue
}
switch choice {
case 1:
listUsers(session)
case 2:
addUser(session)
case 3:
editUser(session)
case 4:
deleteUser(session)
case 5:
return
}
}
}
func listUsers(session *api.Session) {
ui.ClearScreen()
fmt.Println("Listing users...")
// TODO: Implement user listing logic using the API
utils.PressEnterToContinue()
}
func addUser(session *api.Session) {
ui.ClearScreen()
fmt.Println("Adding a new user...")
// TODO: Implement user creation logic using the API
utils.PressEnterToContinue()
}
func editUser(session *api.Session) {
ui.ClearScreen()
fmt.Println("Editing a user...")
// TODO: Implement user editing logic using the API
utils.PressEnterToContinue()
}
func deleteUser(session *api.Session) {
ui.ClearScreen()
fmt.Println("Deleting a user...")
// TODO: Implement user deletion logic using the API
utils.PressEnterToContinue()
}

21
internal/handlers/web/admin.go

@ -1,4 +1,4 @@
package handlers
package web
import (
"html/template"
@ -6,19 +6,14 @@ import (
)
func AdminHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/admin.html"))
data := map[string]interface{}{
"Title": "Admin",
}
if r.Header.Get("HX-Request") == "true" {
// This is an HTMX request, return only the jobs partial
tmpl := template.Must(template.ParseFiles("templates/partials/jobs.html"))
jobs := r.Cookies() // Replace with actual data fetching
tmpl.Execute(w, jobs)
tmpl.ExecuteTemplate(w, "content", data)
} else {
// This is a full page request
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/admin.html"))
jobs := []string{"Job 1", "Job 2", "Job 3"} // Replace with actual data fetching
tmpl.Execute(w, map[string]interface{}{
"Title": "Jobs",
"Jobs": jobs,
})
tmpl.Execute(w, data)
}
}

14
internal/handlers/web/assets.go

@ -1,4 +1,4 @@
package handlers
package web
import (
"html/template"
@ -6,6 +6,14 @@ import (
)
func AssetsHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/assets.html"))
tmpl.Execute(w, nil)
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/assets.html"))
data := map[string]interface{}{
"Title": "Assets",
}
if r.Header.Get("HX-Request") == "true" {
tmpl.ExecuteTemplate(w, "content", data)
} else {
tmpl.Execute(w, data)
}
}

14
internal/handlers/web/companies.go

@ -1,4 +1,4 @@
package handlers
package web
import (
"html/template"
@ -6,6 +6,14 @@ import (
)
func CompaniesHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/companies.html"))
tmpl.Execute(w, nil)
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/companies.html"))
data := map[string]interface{}{
"Title": "Companies",
}
if r.Header.Get("HX-Request") == "true" {
tmpl.ExecuteTemplate(w, "content", data)
} else {
tmpl.Execute(w, data)
}
}

14
internal/handlers/web/contacts.go

@ -1,4 +1,4 @@
package handlers
package web
import (
"html/template"
@ -6,6 +6,14 @@ import (
)
func ContactsHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/contacts.html"))
tmpl.Execute(w, nil)
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/contacts.html"))
data := map[string]interface{}{
"Title": "Contacts",
}
if r.Header.Get("HX-Request") == "true" {
tmpl.ExecuteTemplate(w, "content", data)
} else {
tmpl.Execute(w, data)
}
}

14
internal/handlers/web/contracts.go

@ -1,4 +1,4 @@
package handlers
package web
import (
"html/template"
@ -6,6 +6,14 @@ import (
)
func ContractsHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/contracts.html"))
tmpl.Execute(w, nil)
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/contracts.html"))
data := map[string]interface{}{
"Title": "Contracts",
}
if r.Header.Get("HX-Request") == "true" {
tmpl.ExecuteTemplate(w, "content", data)
} else {
tmpl.Execute(w, data)
}
}

2
internal/handlers/web/dashboard.go

@ -1,4 +1,4 @@
package handlers
package web
import (
"html/template"

14
internal/handlers/web/generic.go

@ -1,4 +1,4 @@
package handlers
package web
import (
"html/template"
@ -6,6 +6,14 @@ import (
)
func GenericHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/generic.html"))
tmpl.Execute(w, nil)
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/generic.html"))
data := map[string]interface{}{
"Title": "Generic Tools",
}
if r.Header.Get("HX-Request") == "true" {
tmpl.ExecuteTemplate(w, "content", data)
} else {
tmpl.Execute(w, data)
}
}

18
internal/handlers/web/invoices.go

@ -1,4 +1,4 @@
package handlers
package web
import (
"html/template"
@ -15,18 +15,20 @@ func InvoicesHandler(w http.ResponseWriter, r *http.Request) {
return
}
// Handle the search request
if r.Method == "GET" && r.URL.Query().Get("search") != "" {
handleInvoiceSearch(w, r, session)
return
}
// Handle the initial page load
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/invoices.html"))
err := tmpl.Execute(w, nil)
if err != nil {
log.Printf("Error executing template: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/invoices.html"))
data := map[string]interface{}{
"Title": "Invoices",
}
if r.Header.Get("HX-Request") == "true" {
tmpl.ExecuteTemplate(w, "content", data)
} else {
tmpl.Execute(w, data)
}
}

2
internal/handlers/web/jobs.go

@ -1,4 +1,4 @@
package handlers
package web
import (
"html/template"

14
internal/handlers/web/locations.go

@ -1,4 +1,4 @@
package handlers
package web
import (
"html/template"
@ -6,6 +6,14 @@ import (
)
func LocationsHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/locations.html"))
tmpl.Execute(w, nil)
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/locations.html"))
data := map[string]interface{}{
"Title": "Locations",
}
if r.Header.Get("HX-Request") == "true" {
tmpl.ExecuteTemplate(w, "content", data)
} else {
tmpl.Execute(w, data)
}
}

2
internal/handlers/web/login.go

@ -1,4 +1,4 @@
package handlers
package web
import (
"html/template"

14
internal/handlers/web/notifications.go

@ -1,4 +1,4 @@
package handlers
package web
import (
"html/template"
@ -6,6 +6,14 @@ import (
)
func NotificationsHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/notifications.html"))
tmpl.Execute(w, nil)
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/notifications.html"))
data := map[string]interface{}{
"Title": "Notifications",
}
if r.Header.Get("HX-Request") == "true" {
tmpl.ExecuteTemplate(w, "content", data)
} else {
tmpl.Execute(w, data)
}
}

14
internal/handlers/web/quotes.go

@ -1,4 +1,4 @@
package handlers
package web
import (
"html/template"
@ -6,6 +6,14 @@ import (
)
func QuotesHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/quotes.html"))
tmpl.Execute(w, nil)
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/quotes.html"))
data := map[string]interface{}{
"Title": "Quotes",
}
if r.Header.Get("HX-Request") == "true" {
tmpl.ExecuteTemplate(w, "content", data)
} else {
tmpl.Execute(w, data)
}
}

14
internal/handlers/web/services.go

@ -1,4 +1,4 @@
package handlers
package web
import (
"html/template"
@ -6,6 +6,14 @@ import (
)
func ServicesHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/services.html"))
tmpl.Execute(w, nil)
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/services.html"))
data := map[string]interface{}{
"Title": "Services",
}
if r.Header.Get("HX-Request") == "true" {
tmpl.ExecuteTemplate(w, "content", data)
} else {
tmpl.Execute(w, data)
}
}

14
internal/handlers/web/tags.go

@ -1,4 +1,4 @@
package handlers
package web
import (
"html/template"
@ -6,6 +6,14 @@ import (
)
func TagsHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/tags.html"))
tmpl.Execute(w, nil)
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/tags.html"))
data := map[string]interface{}{
"Title": "Tags",
}
if r.Header.Get("HX-Request") == "true" {
tmpl.ExecuteTemplate(w, "content", data)
} else {
tmpl.Execute(w, data)
}
}

14
internal/handlers/web/users.go

@ -1,4 +1,4 @@
package handlers
package web
import (
"html/template"
@ -6,6 +6,14 @@ import (
)
func UsersHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/users.html"))
tmpl.Execute(w, nil)
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/users.html"))
data := map[string]interface{}{
"Title": "Users",
}
if r.Header.Get("HX-Request") == "true" {
tmpl.ExecuteTemplate(w, "content", data)
} else {
tmpl.Execute(w, data)
}
}

36
internal/middleware/auth_middleware.go

@ -0,0 +1,36 @@
package middleware
import (
"context"
"marmic/servicetrade-toolbox/internal/api"
"net/http"
)
var SessionStore = api.NewSessionStore()
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("PHPSESSID")
if err != nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
sessionID := cookie.Value
session, exists := SessionStore.Get(sessionID)
if !exists {
session = api.NewSession()
session.Cookie = "PHPSESSID=" + sessionID
if err := session.ValidateSession(); err != nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
SessionStore.Set(sessionID, session)
}
ctx := context.WithValue(r.Context(), "session", session)
next.ServeHTTP(w, r.WithContext(ctx))
})
}

37
internal/ui/ui.go

@ -0,0 +1,37 @@
package ui
import (
"fmt"
"github.com/inancgumus/screen"
)
func ClearScreen() {
screen.Clear()
screen.MoveTopLeft()
}
func DisplayStartScreen() {
ClearScreen()
fmt.Println("========================================")
fmt.Println(" Welcome to ServiceTrade CLI")
fmt.Println("========================================")
fmt.Println("Please log in with your ServiceTrade credentials to continue.")
fmt.Println()
}
func DisplayMessage(message string) {
fmt.Println(message)
}
func DisplayError(prefix string, err error) {
fmt.Printf("%s %v\n", prefix, err)
}
func DisplayMenu(items []string, title string) {
ClearScreen()
fmt.Printf("\n%s:\n", title)
for i, item := range items {
fmt.Printf("%d. %s\n", i+1, item)
}
}

79
internal/utils/ui.go

@ -1,79 +0,0 @@
package ui
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"github.com/inancgumus/screen"
)
func ClearScreen() {
screen.Clear()
screen.MoveTopLeft()
}
func DisplayStartScreen() {
ClearScreen()
fmt.Println("========================================")
fmt.Println(" Welcome to ServiceTrade CLI")
fmt.Println("========================================")
fmt.Println("Please log in with your ServiceTrade credentials to continue.")
fmt.Println()
}
func PromptCredentials() (string, string, error) {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter your email: ")
email, _ := reader.ReadString('\n')
email = strings.TrimSpace(email)
fmt.Print("Enter your password: ")
password, _ := reader.ReadString('\n')
password = strings.TrimSpace(password)
return email, password, nil
}
func DisplayMessage(message string) {
fmt.Println(message)
}
func DisplayError(prefix string, err error) {
fmt.Printf("%s %v\n", prefix, err)
}
func PressEnterToContinue() {
fmt.Println("Press Enter to continue...")
bufio.NewReader(os.Stdin).ReadBytes('\n')
}
func DisplayMenu(items []string, title string) {
ClearScreen()
fmt.Printf("\n%s:\n", title)
for i, item := range items {
fmt.Printf("%d. %s\n", i+1, item)
}
}
func GetUserChoice(max int) (int, error) {
reader := bufio.NewReader(os.Stdin)
fmt.Printf("\nEnter your choice (1-%d): ", max)
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
choice, err := strconv.Atoi(input)
if err != nil || choice < 1 || choice > max {
return 0, fmt.Errorf("invalid input")
}
return choice, nil
}
func PromptForInput(prompt string) string {
reader := bufio.NewReader(os.Stdin)
fmt.Print(prompt)
input, _ := reader.ReadString('\n')
return strings.TrimSpace(input)
}

39
internal/utils/utils.go

@ -6,9 +6,48 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
)
func PromptCredentials() (string, string, error) {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter your email: ")
email, _ := reader.ReadString('\n')
email = strings.TrimSpace(email)
fmt.Print("Enter your password: ")
password, _ := reader.ReadString('\n')
password = strings.TrimSpace(password)
return email, password, nil
}
func GetUserChoice(max int) (int, error) {
reader := bufio.NewReader(os.Stdin)
fmt.Printf("\nEnter your choice (1-%d): ", max)
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
choice, err := strconv.Atoi(input)
if err != nil || choice < 1 || choice > max {
return 0, fmt.Errorf("invalid input")
}
return choice, nil
}
func PromptForInput(prompt string) string {
reader := bufio.NewReader(os.Stdin)
fmt.Print(prompt)
input, _ := reader.ReadString('\n')
return strings.TrimSpace(input)
}
func PressEnterToContinue() {
fmt.Println("Press Enter to continue...")
bufio.NewReader(os.Stdin).ReadBytes('\n')
}
// ReadCSV reads a CSV file and returns its contents as a slice of string slices
func ReadCSV(filename string) ([][]string, error) {
file, err := os.Open(filename)

Loading…
Cancel
Save