From 1dc8ad4e9a2f696c9de835e7337b6380d7bd9420 Mon Sep 17 00:00:00 2001 From: nic Date: Tue, 24 Sep 2024 19:43:19 -0400 Subject: [PATCH] pruned and added files and directories --- Readme.md | 2 +- internal/api/attachments.go | 72 ------- internal/api/common.go | 20 -- internal/api/invoices.go | 0 internal/api/jobs.go | 0 internal/api/session_store.go | 46 ----- internal/handlers/{ => cli}/admin.go | 0 internal/handlers/{ => cli}/assets.go | 0 internal/handlers/{ => cli}/companies.go | 0 internal/handlers/{ => cli}/contacts.go | 0 internal/handlers/{ => cli}/contracts.go | 0 internal/handlers/{ => cli}/dashboard.go | 0 internal/handlers/{ => cli}/generic.go | 0 internal/handlers/{ => cli}/invoices.go | 0 internal/handlers/{ => cli}/jobs.go | 0 internal/handlers/{ => cli}/locations.go | 0 internal/handlers/{ => cli}/login.go | 0 internal/handlers/{ => cli}/notifications.go | 0 internal/handlers/{ => cli}/quotes.go | 0 internal/handlers/{ => cli}/services.go | 0 internal/handlers/{ => cli}/tags.go | 0 internal/handlers/{ => cli}/users.go | 0 internal/handlers/web/admin.go | 24 +++ internal/handlers/web/assets.go | 11 ++ internal/handlers/web/companies.go | 11 ++ internal/handlers/web/contacts.go | 11 ++ internal/handlers/web/contracts.go | 11 ++ internal/handlers/web/dashboard.go | 34 ++++ internal/handlers/web/generic.go | 11 ++ internal/handlers/web/invoices.go | 57 ++++++ internal/handlers/web/jobs.go | 26 +++ internal/handlers/web/locations.go | 11 ++ internal/handlers/web/login.go | 104 ++++++++++ internal/handlers/web/notifications.go | 11 ++ internal/handlers/web/quotes.go | 11 ++ internal/handlers/web/services.go | 11 ++ internal/handlers/web/tags.go | 11 ++ internal/handlers/web/users.go | 11 ++ internal/menu/jobs.go | 189 ------------------- internal/menu/menu.go | 129 ------------- internal/middleware/auth_middleware.go | 36 ---- internal/{ui => utils}/ui.go | 0 42 files changed, 367 insertions(+), 493 deletions(-) delete mode 100644 internal/api/attachments.go delete mode 100644 internal/api/common.go create mode 100644 internal/api/invoices.go create mode 100644 internal/api/jobs.go delete mode 100644 internal/api/session_store.go rename internal/handlers/{ => cli}/admin.go (100%) rename internal/handlers/{ => cli}/assets.go (100%) rename internal/handlers/{ => cli}/companies.go (100%) rename internal/handlers/{ => cli}/contacts.go (100%) rename internal/handlers/{ => cli}/contracts.go (100%) rename internal/handlers/{ => cli}/dashboard.go (100%) rename internal/handlers/{ => cli}/generic.go (100%) rename internal/handlers/{ => cli}/invoices.go (100%) rename internal/handlers/{ => cli}/jobs.go (100%) rename internal/handlers/{ => cli}/locations.go (100%) rename internal/handlers/{ => cli}/login.go (100%) rename internal/handlers/{ => cli}/notifications.go (100%) rename internal/handlers/{ => cli}/quotes.go (100%) rename internal/handlers/{ => cli}/services.go (100%) rename internal/handlers/{ => cli}/tags.go (100%) rename internal/handlers/{ => cli}/users.go (100%) create mode 100644 internal/handlers/web/admin.go create mode 100644 internal/handlers/web/assets.go create mode 100644 internal/handlers/web/companies.go create mode 100644 internal/handlers/web/contacts.go create mode 100644 internal/handlers/web/contracts.go create mode 100644 internal/handlers/web/dashboard.go create mode 100644 internal/handlers/web/generic.go create mode 100644 internal/handlers/web/invoices.go create mode 100644 internal/handlers/web/jobs.go create mode 100644 internal/handlers/web/locations.go create mode 100644 internal/handlers/web/login.go create mode 100644 internal/handlers/web/notifications.go create mode 100644 internal/handlers/web/quotes.go create mode 100644 internal/handlers/web/services.go create mode 100644 internal/handlers/web/tags.go create mode 100644 internal/handlers/web/users.go delete mode 100644 internal/menu/jobs.go delete mode 100644 internal/menu/menu.go delete mode 100644 internal/middleware/auth_middleware.go rename internal/{ui => utils}/ui.go (100%) diff --git a/Readme.md b/Readme.md index aafb400..4ed3dda 100644 --- a/Readme.md +++ b/Readme.md @@ -3,7 +3,7 @@ ## Project Structure ```project_root/ -├── cmd/ +├── apps/ │ ├── cli/ │ │ └── main.go │ └── web/ diff --git a/internal/api/attachments.go b/internal/api/attachments.go deleted file mode 100644 index da96146..0000000 --- a/internal/api/attachments.go +++ /dev/null @@ -1,72 +0,0 @@ -package api - -import ( - "encoding/json" - "fmt" - "marmic/servicetrade-toolbox/internal/auth" - "strings" -) - -func GetAttachmentsForJob(session *auth.Session, jobID string) (map[string]interface{}, error) { - url := fmt.Sprintf("%s/job/%s/paperwork", BaseURL, jobID) - req, err := AuthenticatedRequest(session, "GET", url, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %v", err) - } - - resp, err := DoAuthenticatedRequest(session, req) - if err != nil { - return nil, fmt.Errorf("error sending request: %v", err) - } - defer resp.Body.Close() - - var result map[string]interface{} - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - return nil, fmt.Errorf("error decoding response: %v", err) - } - - return result, nil -} - -func GenerateDeleteEndpoints(data map[string]interface{}, filenames []string) []string { - var endpoints []string - filenamesToDeleteMap := make(map[string]struct{}) - for _, name := range filenames { - filenamesToDeleteMap[strings.ToLower(strings.TrimSpace(name))] = struct{}{} - } - - if dataMap, ok := data["data"].(map[string]interface{}); ok { - if attachments, ok := dataMap["attachments"].([]interface{}); ok { - for _, item := range attachments { - attachment := item.(map[string]interface{}) - if filename, ok := attachment["fileName"].(string); ok { - trimmedFilename := strings.ToLower(strings.TrimSpace(filename)) - if _, exists := filenamesToDeleteMap[trimmedFilename]; exists { - endpoints = append(endpoints, fmt.Sprintf("%s/attachment/%d", BaseURL, int64(attachment["id"].(float64)))) - } - } - } - } - } - - return endpoints -} - -func DeleteAttachment(session *auth.Session, endpoint string) error { - req, err := AuthenticatedRequest(session, "DELETE", endpoint, nil) - if err != nil { - return fmt.Errorf("failed to create DELETE request: %v", err) - } - - resp, err := DoAuthenticatedRequest(session, 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 { - return fmt.Errorf("failed to delete attachment: %s", resp.Status) - } - - return nil -} diff --git a/internal/api/common.go b/internal/api/common.go deleted file mode 100644 index 3245cb6..0000000 --- a/internal/api/common.go +++ /dev/null @@ -1,20 +0,0 @@ -package api - -import ( - "encoding/json" - "fmt" - "net/http" -) - -const BaseURL = "https://api.servicetrade.com/api" - -// DecodeJSONResponse decodes a JSON response into the provided interface -func DecodeJSONResponse(resp *http.Response, v interface{}) error { - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("API request failed with status code: %d", resp.StatusCode) - } - - return json.NewDecoder(resp.Body).Decode(v) -} diff --git a/internal/api/invoices.go b/internal/api/invoices.go new file mode 100644 index 0000000..e69de29 diff --git a/internal/api/jobs.go b/internal/api/jobs.go new file mode 100644 index 0000000..e69de29 diff --git a/internal/api/session_store.go b/internal/api/session_store.go deleted file mode 100644 index 425a3b9..0000000 --- a/internal/api/session_store.go +++ /dev/null @@ -1,46 +0,0 @@ -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) - } - } -} diff --git a/internal/handlers/admin.go b/internal/handlers/cli/admin.go similarity index 100% rename from internal/handlers/admin.go rename to internal/handlers/cli/admin.go diff --git a/internal/handlers/assets.go b/internal/handlers/cli/assets.go similarity index 100% rename from internal/handlers/assets.go rename to internal/handlers/cli/assets.go diff --git a/internal/handlers/companies.go b/internal/handlers/cli/companies.go similarity index 100% rename from internal/handlers/companies.go rename to internal/handlers/cli/companies.go diff --git a/internal/handlers/contacts.go b/internal/handlers/cli/contacts.go similarity index 100% rename from internal/handlers/contacts.go rename to internal/handlers/cli/contacts.go diff --git a/internal/handlers/contracts.go b/internal/handlers/cli/contracts.go similarity index 100% rename from internal/handlers/contracts.go rename to internal/handlers/cli/contracts.go diff --git a/internal/handlers/dashboard.go b/internal/handlers/cli/dashboard.go similarity index 100% rename from internal/handlers/dashboard.go rename to internal/handlers/cli/dashboard.go diff --git a/internal/handlers/generic.go b/internal/handlers/cli/generic.go similarity index 100% rename from internal/handlers/generic.go rename to internal/handlers/cli/generic.go diff --git a/internal/handlers/invoices.go b/internal/handlers/cli/invoices.go similarity index 100% rename from internal/handlers/invoices.go rename to internal/handlers/cli/invoices.go diff --git a/internal/handlers/jobs.go b/internal/handlers/cli/jobs.go similarity index 100% rename from internal/handlers/jobs.go rename to internal/handlers/cli/jobs.go diff --git a/internal/handlers/locations.go b/internal/handlers/cli/locations.go similarity index 100% rename from internal/handlers/locations.go rename to internal/handlers/cli/locations.go diff --git a/internal/handlers/login.go b/internal/handlers/cli/login.go similarity index 100% rename from internal/handlers/login.go rename to internal/handlers/cli/login.go diff --git a/internal/handlers/notifications.go b/internal/handlers/cli/notifications.go similarity index 100% rename from internal/handlers/notifications.go rename to internal/handlers/cli/notifications.go diff --git a/internal/handlers/quotes.go b/internal/handlers/cli/quotes.go similarity index 100% rename from internal/handlers/quotes.go rename to internal/handlers/cli/quotes.go diff --git a/internal/handlers/services.go b/internal/handlers/cli/services.go similarity index 100% rename from internal/handlers/services.go rename to internal/handlers/cli/services.go diff --git a/internal/handlers/tags.go b/internal/handlers/cli/tags.go similarity index 100% rename from internal/handlers/tags.go rename to internal/handlers/cli/tags.go diff --git a/internal/handlers/users.go b/internal/handlers/cli/users.go similarity index 100% rename from internal/handlers/users.go rename to internal/handlers/cli/users.go diff --git a/internal/handlers/web/admin.go b/internal/handlers/web/admin.go new file mode 100644 index 0000000..2ca106b --- /dev/null +++ b/internal/handlers/web/admin.go @@ -0,0 +1,24 @@ +package handlers + +import ( + "html/template" + "net/http" +) + +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 + + tmpl.Execute(w, map[string]interface{}{ + "Title": "Jobs", + "Jobs": jobs, + }) + } +} diff --git a/internal/handlers/web/assets.go b/internal/handlers/web/assets.go new file mode 100644 index 0000000..1f7d07f --- /dev/null +++ b/internal/handlers/web/assets.go @@ -0,0 +1,11 @@ +package handlers + +import ( + "html/template" + "net/http" +) + +func AssetsHandler(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/assets.html")) + tmpl.Execute(w, nil) +} diff --git a/internal/handlers/web/companies.go b/internal/handlers/web/companies.go new file mode 100644 index 0000000..744f039 --- /dev/null +++ b/internal/handlers/web/companies.go @@ -0,0 +1,11 @@ +package handlers + +import ( + "html/template" + "net/http" +) + +func CompaniesHandler(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/companies.html")) + tmpl.Execute(w, nil) +} diff --git a/internal/handlers/web/contacts.go b/internal/handlers/web/contacts.go new file mode 100644 index 0000000..46addef --- /dev/null +++ b/internal/handlers/web/contacts.go @@ -0,0 +1,11 @@ +package handlers + +import ( + "html/template" + "net/http" +) + +func ContactsHandler(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/contacts.html")) + tmpl.Execute(w, nil) +} diff --git a/internal/handlers/web/contracts.go b/internal/handlers/web/contracts.go new file mode 100644 index 0000000..20c3ccd --- /dev/null +++ b/internal/handlers/web/contracts.go @@ -0,0 +1,11 @@ +package handlers + +import ( + "html/template" + "net/http" +) + +func ContractsHandler(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/contracts.html")) + tmpl.Execute(w, nil) +} diff --git a/internal/handlers/web/dashboard.go b/internal/handlers/web/dashboard.go new file mode 100644 index 0000000..ecd687a --- /dev/null +++ b/internal/handlers/web/dashboard.go @@ -0,0 +1,34 @@ +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 + } +} diff --git a/internal/handlers/web/generic.go b/internal/handlers/web/generic.go new file mode 100644 index 0000000..efe5640 --- /dev/null +++ b/internal/handlers/web/generic.go @@ -0,0 +1,11 @@ +package handlers + +import ( + "html/template" + "net/http" +) + +func GenericHandler(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/generic.html")) + tmpl.Execute(w, nil) +} diff --git a/internal/handlers/web/invoices.go b/internal/handlers/web/invoices.go new file mode 100644 index 0000000..3e6e0bd --- /dev/null +++ b/internal/handlers/web/invoices.go @@ -0,0 +1,57 @@ +package handlers + +import ( + "html/template" + "log" + "marmic/servicetrade-toolbox/internal/api" + "net/http" + "strings" +) + +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 + } + + // 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) + } +} + +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 + } + + invoice, err := session.GetInvoice(searchTerm) + 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()}) + 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) + } +} diff --git a/internal/handlers/web/jobs.go b/internal/handlers/web/jobs.go new file mode 100644 index 0000000..6a08fc0 --- /dev/null +++ b/internal/handlers/web/jobs.go @@ -0,0 +1,26 @@ +package handlers + +import ( + "html/template" + "net/http" +) + +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, + }) + } +} diff --git a/internal/handlers/web/locations.go b/internal/handlers/web/locations.go new file mode 100644 index 0000000..000a488 --- /dev/null +++ b/internal/handlers/web/locations.go @@ -0,0 +1,11 @@ +package handlers + +import ( + "html/template" + "net/http" +) + +func LocationsHandler(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/locations.html")) + tmpl.Execute(w, nil) +} diff --git a/internal/handlers/web/login.go b/internal/handlers/web/login.go new file mode 100644 index 0000000..3f31a4e --- /dev/null +++ b/internal/handlers/web/login.go @@ -0,0 +1,104 @@ +package handlers + +import ( + "html/template" + "log" + "marmic/servicetrade-toolbox/internal/api" + "marmic/servicetrade-toolbox/internal/middleware" + "net/http" + "strings" +) + +func LoginHandler(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + tmpl := template.Must(template.ParseFiles("templates/login.html")) + tmpl.Execute(w, nil) + return + } + + if r.Method == "POST" { + email := r.FormValue("email") + password := r.FormValue("password") + + session := api.NewSession() + err := session.Login(email, password) + if err != nil { + if r.Header.Get("HX-Request") == "true" { + w.Write([]byte("
Login failed: " + err.Error() + "
")) + } else { + http.Error(w, "Login failed", http.StatusUnauthorized) + } + return + } + + cookieParts := strings.Split(session.Cookie, ";") + sessionID := strings.TrimPrefix(cookieParts[0], "PHPSESSID=") + + middleware.SessionStore.Set(sessionID, session) + + http.SetCookie(w, &http.Cookie{ + Name: "PHPSESSID", + Value: sessionID, + Path: "/", + HttpOnly: true, + Secure: r.TLS != nil, + SameSite: http.SameSiteLaxMode, + }) + + if r.Header.Get("HX-Request") == "true" { + w.Header().Set("HX-Redirect", "/") + w.WriteHeader(http.StatusOK) + w.Write([]byte("Login successful")) + } else { + http.Redirect(w, r, "/", http.StatusSeeOther) + } + } +} + +func LogoutHandler(w http.ResponseWriter, r *http.Request) { + cookie, err := r.Cookie("PHPSESSID") + if err != nil { + log.Printf("No session cookie found: %v", err) + redirectToLogin(w, r) + return + } + + sessionID := cookie.Value + session, exists := middleware.SessionStore.Get(sessionID) + if !exists { + log.Println("No session found in store") + redirectToLogin(w, r) + return + } + + err = session.Logout() + if err != nil { + log.Printf("Logout failed: %v", err) + http.Error(w, "Logout failed", http.StatusInternalServerError) + return + } + + middleware.SessionStore.Delete(sessionID) + + http.SetCookie(w, &http.Cookie{ + Name: "PHPSESSID", + Value: "", + Path: "/", + MaxAge: -1, + HttpOnly: true, + Secure: r.TLS != nil, + SameSite: http.SameSiteLaxMode, + }) + + log.Println("Logout successful, redirecting to login page") + redirectToLogin(w, r) +} + +func redirectToLogin(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("HX-Request") != "" { + w.Header().Set("HX-Redirect", "/login") + w.WriteHeader(http.StatusOK) + } else { + http.Redirect(w, r, "/login", http.StatusSeeOther) + } +} diff --git a/internal/handlers/web/notifications.go b/internal/handlers/web/notifications.go new file mode 100644 index 0000000..1e86255 --- /dev/null +++ b/internal/handlers/web/notifications.go @@ -0,0 +1,11 @@ +package handlers + +import ( + "html/template" + "net/http" +) + +func NotificationsHandler(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/notifications.html")) + tmpl.Execute(w, nil) +} diff --git a/internal/handlers/web/quotes.go b/internal/handlers/web/quotes.go new file mode 100644 index 0000000..2bb895c --- /dev/null +++ b/internal/handlers/web/quotes.go @@ -0,0 +1,11 @@ +package handlers + +import ( + "html/template" + "net/http" +) + +func QuotesHandler(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/quotes.html")) + tmpl.Execute(w, nil) +} diff --git a/internal/handlers/web/services.go b/internal/handlers/web/services.go new file mode 100644 index 0000000..785835a --- /dev/null +++ b/internal/handlers/web/services.go @@ -0,0 +1,11 @@ +package handlers + +import ( + "html/template" + "net/http" +) + +func ServicesHandler(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/services.html")) + tmpl.Execute(w, nil) +} diff --git a/internal/handlers/web/tags.go b/internal/handlers/web/tags.go new file mode 100644 index 0000000..d4b37eb --- /dev/null +++ b/internal/handlers/web/tags.go @@ -0,0 +1,11 @@ +package handlers + +import ( + "html/template" + "net/http" +) + +func TagsHandler(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/tags.html")) + tmpl.Execute(w, nil) +} diff --git a/internal/handlers/web/users.go b/internal/handlers/web/users.go new file mode 100644 index 0000000..f471a8c --- /dev/null +++ b/internal/handlers/web/users.go @@ -0,0 +1,11 @@ +package handlers + +import ( + "html/template" + "net/http" +) + +func UsersHandler(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/users.html")) + tmpl.Execute(w, nil) +} diff --git a/internal/menu/jobs.go b/internal/menu/jobs.go deleted file mode 100644 index 8413939..0000000 --- a/internal/menu/jobs.go +++ /dev/null @@ -1,189 +0,0 @@ -package menu - -import ( - "encoding/json" - "fmt" - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/ui" -) - -func HandleJobs(session *api.Session) { - jobsMenu := getJobsMenu() - for { - choice := DisplayMenuAndGetChoice(jobsMenu, "Jobs Menu") - if choice == len(jobsMenu)+1 { - return // Go back to main menu - } - jobsMenu[choice-1].Handler(session) - } -} - -func getJobsMenu() []MenuItem { - return []MenuItem{ - {"Search Job by ID", searchJobByID, nil}, - {"List Recent Jobs", listRecentJobs, nil}, - {"Create New Job", createNewJob, nil}, - {"Manage Job Attachments", manageJobAttachments, nil}, - {"View Deficiencies", viewDeficiencyById, nil}, - } -} - -func searchJobByID(session *api.Session) { - ui.ClearScreen() - ui.DisplayMessage("We will search a job ID here.") - ui.PressEnterToContinue() -} - -func listRecentJobs(session *api.Session) { - ui.ClearScreen() - ui.DisplayMessage("Listing recent jobs...") - ui.PressEnterToContinue() -} - -func createNewJob(session *api.Session) { - ui.ClearScreen() - ui.DisplayMessage("Creating a new job...") - ui.PressEnterToContinue() -} - -func manageJobAttachments(session *api.Session) { - ui.ClearScreen() - jobID := ui.PromptForInput("Enter Job ID: ") - - attachments, err := session.GetAttachmentsForJob(jobID) - if err != nil { - ui.DisplayError("Failed to retrieve attachments:", err) - return - } - - // Display attachments - ui.DisplayMessage("Attachments for Job " + 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 { - fmt.Printf("%d. %s\n", i+1, att["fileName"]) - } - } - } - } - - // Prompt for attachments to delete - toDelete := ui.PromptForInput("Enter the numbers of attachments to delete (comma-separated), or 'all' for all: ") - - var filesToDelete []string - if toDelete == "all" { - if dataMap, ok := attachments["data"].(map[string]interface{}); ok { - if attachmentsList, ok := dataMap["attachments"].([]interface{}); ok { - for _, attachment := range attachmentsList { - if att, ok := attachment.(map[string]interface{}); ok { - filesToDelete = append(filesToDelete, att["fileName"].(string)) - } - } - } - } - } else { - // Parse the input and get the corresponding filenames - // This part needs to be implemented - } - - // Generate delete endpoints - endpoints := generateDeleteEndpoints(attachments, filesToDelete) - - // Confirm deletion - ui.DisplayMessage(fmt.Sprintf("You are about to delete %d attachments. Are you sure? (y/n)", len(endpoints))) - confirm := ui.PromptForInput("") - if confirm != "y" { - ui.DisplayMessage("Deletion cancelled.") - return - } - - // Perform deletion - for _, endpoint := range endpoints { - err := session.DeleteAttachment(endpoint) - if err != nil { - ui.DisplayError(fmt.Sprintf("Failed to delete attachment %s:", endpoint), err) - } else { - ui.DisplayMessage(fmt.Sprintf("Successfully deleted attachment: %s", endpoint)) - } - } - - ui.DisplayMessage("Attachment management completed.") - ui.PressEnterToContinue() -} - -func viewDeficiencyById(session *api.Session) { - ui.ClearScreen() - deficiencyId := ui.PromptForInput("Enter Deficiency ID: ") - - ui.DisplayMessage(fmt.Sprintf("Fetching information for Deficiency %s...", deficiencyId)) - - result, err := session.GetDeficiencyById(deficiencyId) - if err != nil { - ui.DisplayError("Failed to retrieve deficiency information:", err) - ui.PressEnterToContinue() - return - } - - ui.ClearScreen() - ui.DisplayMessage(fmt.Sprintf("Information for Deficiency %s:", deficiencyId)) - - if data, ok := result["data"].(map[string]interface{}); ok { - if len(data) == 0 { - ui.DisplayMessage("No information found for this deficiency.") - } else { - fmt.Println("Deficiency Details:") - for key, value := range data { - fmt.Printf("- %s:\n", key) - if details, ok := value.(map[string]interface{}); ok { - for detailKey, detailValue := range details { - if detailValue != nil { - // Handle potential json.Number values - if num, ok := detailValue.(json.Number); ok { - fmt.Printf(" %s: %s\n", detailKey, num.String()) - } else { - fmt.Printf(" %s: %v\n", detailKey, detailValue) - } - } - } - } else if num, ok := value.(json.Number); ok { - fmt.Printf(" %s\n", num.String()) - } else { - fmt.Printf(" %v\n", value) - } - fmt.Println() - } - } - } else { - ui.DisplayMessage("Unexpected data structure in the API response.") - fmt.Printf("Response structure: %+v\n", result) - } - - ui.DisplayMessage("\nDeficiency information viewing completed.") - ui.PressEnterToContinue() -} - -// Helper function to generate delete endpoints -func generateDeleteEndpoints(data map[string]interface{}, filenames []string) []string { - var endpoints []string - - filenamesToDeleteMap := make(map[string]struct{}) - for _, name := range filenames { - filenamesToDeleteMap[name] = struct{}{} - } - - if dataMap, ok := data["data"].(map[string]interface{}); ok { - if attachments, ok := dataMap["attachments"].([]interface{}); ok { - for _, item := range attachments { - attachment := item.(map[string]interface{}) - if filename, ok := attachment["fileName"].(string); ok { - if _, exists := filenamesToDeleteMap[filename]; exists { - endpoints = append(endpoints, fmt.Sprintf("https://api.servicetrade.com/api/attachment/%d", int64(attachment["id"].(float64)))) - } - } - } - } - } - - return endpoints -} diff --git a/internal/menu/menu.go b/internal/menu/menu.go deleted file mode 100644 index 6a76011..0000000 --- a/internal/menu/menu.go +++ /dev/null @@ -1,129 +0,0 @@ -package menu - -import ( - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/ui" -) - -type MenuItem struct { - Name string - Handler func(*api.Session) - Submenu []MenuItem -} - -func GetMainMenu() []MenuItem { - return []MenuItem{ - {"Administration", HandleAdministration, nil}, - {"Assets", HandleAssets, nil}, - {"Companies", HandleCompanies, nil}, - {"Contacts", HandleContacts, nil}, - {"Contracts", HandleContracts, nil}, - {"Generic Tools", HandleGenericTools, nil}, - {"Invoices", HandleInvoices, nil}, - {"Jobs", HandleJobs, nil}, - {"Locations", HandleLocations, nil}, - {"Notifications", HandleNotifications, nil}, - {"Quotes", HandleQuotes, nil}, - {"Services", HandleServices, nil}, - {"Tags", HandleTags, nil}, - {"Users", HandleUsers, nil}, - } -} - -func DisplayMenuAndGetChoice(menuItems []MenuItem, title string) int { - for { - displayMenuItems := make([]string, len(menuItems)+1) - for i, item := range menuItems { - displayMenuItems[i] = item.Name - } - displayMenuItems[len(menuItems)] = "Back/Exit" - - ui.DisplayMenu(displayMenuItems, title) - - choice, err := ui.GetUserChoice(len(displayMenuItems)) - if err == nil { - return choice - } - ui.DisplayMessage("Invalid input. Please try again.") - ui.PressEnterToContinue() - } -} - -// Placeholder functions for other main menu items -func HandleAdministration(session *api.Session) { - ui.ClearScreen() - ui.DisplayMessage("Administration menu placeholder") - ui.PressEnterToContinue() -} - -func HandleAssets(session *api.Session) { - ui.ClearScreen() - ui.DisplayMessage("Assets menu placeholder") - ui.PressEnterToContinue() -} - -func HandleCompanies(session *api.Session) { - ui.ClearScreen() - ui.DisplayMessage("Companies menu placeholder") - ui.PressEnterToContinue() -} - -func HandleContacts(session *api.Session) { - ui.ClearScreen() - ui.DisplayMessage("Contacts menu placeholder") - ui.PressEnterToContinue() -} - -func HandleContracts(session *api.Session) { - ui.ClearScreen() - ui.DisplayMessage("Contracts menu placeholder") - ui.PressEnterToContinue() -} - -func HandleGenericTools(session *api.Session) { - ui.ClearScreen() - ui.DisplayMessage("Generic Tools menu placeholder") - ui.PressEnterToContinue() -} - -func HandleInvoices(session *api.Session) { - ui.ClearScreen() - ui.DisplayMessage("Invoices menu placeholder") - ui.PressEnterToContinue() -} - -func HandleLocations(session *api.Session) { - ui.ClearScreen() - ui.DisplayMessage("Locations menu placeholder") - ui.PressEnterToContinue() -} - -func HandleNotifications(session *api.Session) { - ui.ClearScreen() - ui.DisplayMessage("Notifications menu placeholder") - ui.PressEnterToContinue() -} - -func HandleQuotes(session *api.Session) { - ui.ClearScreen() - ui.DisplayMessage("Quotes menu placeholder") - ui.PressEnterToContinue() -} - -func HandleServices(session *api.Session) { - ui.ClearScreen() - ui.DisplayMessage("Services menu placeholder") - ui.PressEnterToContinue() -} - -func HandleTags(session *api.Session) { - ui.ClearScreen() - ui.DisplayMessage("Tags menu placeholder") - ui.PressEnterToContinue() -} - -func HandleUsers(session *api.Session) { - ui.ClearScreen() - ui.DisplayMessage("Users menu placeholder") - ui.PressEnterToContinue() -} diff --git a/internal/middleware/auth_middleware.go b/internal/middleware/auth_middleware.go deleted file mode 100644 index 461bc97..0000000 --- a/internal/middleware/auth_middleware.go +++ /dev/null @@ -1,36 +0,0 @@ -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)) - }) -} diff --git a/internal/ui/ui.go b/internal/utils/ui.go similarity index 100% rename from internal/ui/ui.go rename to internal/utils/ui.go