feat(cli): add CLI for managing RSVPs and photo uploads
* implement runCLI function to handle subcommands * add runRSVPCLI and runPhotosCLI for RSVP and photo management * include usage instructions for CLI commands * integrate CLI execution in main function
This commit is contained in:
@@ -102,6 +102,76 @@ func (s *Store) ListGuests() ([]Guest, error) {
|
||||
return guests, rows.Err()
|
||||
}
|
||||
|
||||
type RSVPRecord struct {
|
||||
ID int64
|
||||
Message string
|
||||
CreatedAt time.Time
|
||||
Guests []Guest
|
||||
}
|
||||
|
||||
// ListRSVPs returns every RSVP submission (not flattened per-guest), oldest first.
|
||||
func (s *Store) ListRSVPs() ([]RSVPRecord, error) {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT r.id, r.message, r.created_at, g.name, g.is_child
|
||||
FROM rsvps r
|
||||
LEFT JOIN rsvp_guests g ON g.rsvp_id = r.id
|
||||
ORDER BY r.created_at ASC, r.id ASC, g.id ASC`,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var out []RSVPRecord
|
||||
index := map[int64]int{}
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
var message string
|
||||
var createdAt time.Time
|
||||
var name sql.NullString
|
||||
var isChild sql.NullBool
|
||||
if err := rows.Scan(&id, &message, &createdAt, &name, &isChild); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i, ok := index[id]
|
||||
if !ok {
|
||||
out = append(out, RSVPRecord{ID: id, Message: message, CreatedAt: createdAt})
|
||||
i = len(out) - 1
|
||||
index[id] = i
|
||||
}
|
||||
if name.Valid {
|
||||
out[i].Guests = append(out[i].Guests, Guest{Name: name.String, IsChild: isChild.Bool})
|
||||
}
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// DeleteRSVP removes an RSVP submission and its guests.
|
||||
func (s *Store) DeleteRSVP(id int64) error {
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if _, err := tx.Exec(`DELETE FROM rsvp_guests WHERE rsvp_id = ?`, id); err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := tx.Exec(`DELETE FROM rsvps WHERE id = ?`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return fmt.Errorf("rsvp %d not found", id)
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (s *Store) InsertRSVP(r RSVP) error {
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
@@ -189,6 +259,98 @@ func (s *Store) ListPhotoFiles() ([]PhotoFileRecord, error) {
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
type PhotoUploadRecord struct {
|
||||
ID int64
|
||||
Name string
|
||||
Message string
|
||||
CreatedAt time.Time
|
||||
Files []PhotoFile
|
||||
}
|
||||
|
||||
// ListPhotoUploads returns every upload submission (not flattened per-file), oldest first.
|
||||
func (s *Store) ListPhotoUploads() ([]PhotoUploadRecord, error) {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT u.id, u.name, u.message, u.created_at, f.filename, f.path, f.url
|
||||
FROM photo_uploads u
|
||||
LEFT JOIN photo_files f ON f.upload_id = u.id
|
||||
ORDER BY u.created_at ASC, u.id ASC, f.id ASC`,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var out []PhotoUploadRecord
|
||||
index := map[int64]int{}
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
var name, message string
|
||||
var createdAt time.Time
|
||||
var filename, path, url sql.NullString
|
||||
if err := rows.Scan(&id, &name, &message, &createdAt, &filename, &path, &url); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i, ok := index[id]
|
||||
if !ok {
|
||||
out = append(out, PhotoUploadRecord{ID: id, Name: name, Message: message, CreatedAt: createdAt})
|
||||
i = len(out) - 1
|
||||
index[id] = i
|
||||
}
|
||||
if filename.Valid {
|
||||
out[i].Files = append(out[i].Files, PhotoFile{Filename: filename.String, Path: path.String, URL: url.String})
|
||||
}
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// DeletePhotoUpload removes an upload's DB rows and returns the files it had,
|
||||
// so the caller can also remove them from disk.
|
||||
func (s *Store) DeletePhotoUpload(id int64) ([]PhotoFile, error) {
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
rows, err := tx.Query(`SELECT filename, path, url FROM photo_files WHERE upload_id = ?`, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var files []PhotoFile
|
||||
for rows.Next() {
|
||||
var f PhotoFile
|
||||
if err := rows.Scan(&f.Filename, &f.Path, &f.URL); err != nil {
|
||||
rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
rows.Close()
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`DELETE FROM photo_files WHERE upload_id = ?`, id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := tx.Exec(`DELETE FROM photo_uploads WHERE id = ?`, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
return nil, fmt.Errorf("photo upload %d not found", id)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (s *Store) AddPhotoFiles(uploadID int64, files []PhotoFile) error {
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user