42 changed files with 367 additions and 493 deletions
@ -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 |
|
||||
} |
|
||||
@ -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) |
|
||||
} |
|
||||
@ -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) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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, |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
@ -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) |
||||
|
} |
||||
@ -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) |
||||
|
} |
||||
@ -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) |
||||
|
} |
||||
@ -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) |
||||
|
} |
||||
@ -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 |
||||
|
} |
||||
|
} |
||||
@ -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) |
||||
|
} |
||||
@ -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) |
||||
|
} |
||||
|
} |
||||
@ -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, |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
@ -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) |
||||
|
} |
||||
@ -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("<div class='error'>Login failed: " + err.Error() + "</div>")) |
||||
|
} 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) |
||||
|
} |
||||
|
} |
||||
@ -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) |
||||
|
} |
||||
@ -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) |
||||
|
} |
||||
@ -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) |
||||
|
} |
||||
@ -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) |
||||
|
} |
||||
@ -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) |
||||
|
} |
||||
@ -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 |
|
||||
} |
|
||||
@ -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() |
|
||||
} |
|
||||
@ -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)) |
|
||||
}) |
|
||||
} |
|
||||
Loading…
Reference in new issue