package main import ( "bufio" "fmt" "os" "path/filepath" "strconv" "strings" "wedding-server/internal/store" ) // runCLI handles admin subcommands (listing/removing bad RSVPs or photo // uploads) so fixing a mistake doesn't require touching the database by hand. // Returns the process exit code. func runCLI(dataDir string, st *store.Store, args []string) int { if len(args) == 0 { printCLIUsage() return 1 } switch args[0] { case "rsvp": return runRSVPCLI(st, args[1:]) case "photos": return runPhotosCLI(dataDir, st, args[1:]) case "help", "-h", "--help": printCLIUsage() return 0 default: fmt.Fprintf(os.Stderr, "unknown command %q\n\n", args[0]) printCLIUsage() return 1 } } func printCLIUsage() { fmt.Println(`Usage: wedding-server start the web server wedding-server rsvp list list all RSVPs wedding-server rsvp delete delete an RSVP and its guests wedding-server photos list list all photo uploads wedding-server photos delete delete a photo upload and its files`) } func runRSVPCLI(st *store.Store, args []string) int { if len(args) == 0 { printCLIUsage() return 1 } switch args[0] { case "list": rsvps, err := st.ListRSVPs() if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) return 1 } if len(rsvps) == 0 { fmt.Println("no RSVPs yet") return 0 } for _, r := range rsvps { names := make([]string, len(r.Guests)) for i, g := range r.Guests { names[i] = g.Name if g.IsChild { names[i] += " (child)" } } fmt.Printf("#%d %s %s\n", r.ID, r.CreatedAt.Local().Format("2006-01-02 15:04"), strings.Join(names, ", ")) if r.Message != "" { fmt.Printf(" message: %s\n", r.Message) } } return 0 case "delete": if len(args) < 2 { fmt.Fprintln(os.Stderr, "usage: wedding-server rsvp delete ") return 1 } id, err := strconv.ParseInt(args[1], 10, 64) if err != nil { fmt.Fprintf(os.Stderr, "invalid id %q\n", args[1]) return 1 } if !confirm(fmt.Sprintf("Delete RSVP #%d? This cannot be undone.", id)) { fmt.Println("cancelled") return 0 } if err := st.DeleteRSVP(id); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) return 1 } fmt.Printf("deleted RSVP #%d\n", id) return 0 default: printCLIUsage() return 1 } } func runPhotosCLI(dataDir string, st *store.Store, args []string) int { if len(args) == 0 { printCLIUsage() return 1 } switch args[0] { case "list": uploads, err := st.ListPhotoUploads() if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) return 1 } if len(uploads) == 0 { fmt.Println("no photo uploads yet") return 0 } for _, u := range uploads { fmt.Printf("#%d %s %s (%d photo(s))\n", u.ID, u.CreatedAt.Local().Format("2006-01-02 15:04"), u.Name, len(u.Files)) for _, f := range u.Files { fmt.Printf(" %s\n", f.URL) } } return 0 case "delete": if len(args) < 2 { fmt.Fprintln(os.Stderr, "usage: wedding-server photos delete ") return 1 } id, err := strconv.ParseInt(args[1], 10, 64) if err != nil { fmt.Fprintf(os.Stderr, "invalid id %q\n", args[1]) return 1 } if !confirm(fmt.Sprintf("Delete photo upload #%d and its files? This cannot be undone.", id)) { fmt.Println("cancelled") return 0 } files, err := st.DeletePhotoUpload(id) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) return 1 } for _, f := range files { if err := os.Remove(f.Path); err != nil && !os.IsNotExist(err) { fmt.Fprintf(os.Stderr, "warning: could not remove %s: %v\n", f.Path, err) } } // Best-effort: only removes the per-upload directory if now empty. _ = os.Remove(filepath.Join(dataDir, "photos", strconv.FormatInt(id, 10))) fmt.Printf("deleted photo upload #%d (%d file(s))\n", id, len(files)) return 0 default: printCLIUsage() return 1 } } func confirm(prompt string) bool { fmt.Printf("%s [y/N]: ", prompt) line, _ := bufio.NewReader(os.Stdin).ReadString('\n') line = strings.ToLower(strings.TrimSpace(line)) return line == "y" || line == "yes" }