feat(ui): add wedding theme styles and layout components
* Introduced wedding-themed CSS variables for styling. * Created layout component for the wedding site with navigation and countdown. * Added RSVP and photo upload pages with form handling. * Implemented QR code page for wedding site access. * Configured TypeScript and Vite for the project. * Added robots.txt for search engine crawling.
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
const schema = `
|
||||
CREATE TABLE IF NOT EXISTS rsvps (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
message TEXT,
|
||||
created_at DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rsvp_guests (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
rsvp_id INTEGER NOT NULL REFERENCES rsvps(id),
|
||||
name TEXT NOT NULL,
|
||||
is_child INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS photo_uploads (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
message TEXT,
|
||||
email TEXT,
|
||||
phone TEXT,
|
||||
created_at DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS photo_files (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
upload_id INTEGER NOT NULL REFERENCES photo_uploads(id),
|
||||
filename TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
created_at DATETIME NOT NULL
|
||||
);
|
||||
`
|
||||
|
||||
func Open(dataDir string) (*Store, error) {
|
||||
dbPath := filepath.Join(dataDir, "rsvp.db")
|
||||
db, err := sql.Open("sqlite", dbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("opening database: %w", err)
|
||||
}
|
||||
// SQLite only supports one writer at a time; a single connection avoids
|
||||
// SQLITE_BUSY errors under concurrent requests instead of retry logic.
|
||||
db.SetMaxOpenConns(1)
|
||||
|
||||
if _, err := db.Exec(schema); err != nil {
|
||||
db.Close()
|
||||
return nil, fmt.Errorf("migrating schema: %w", err)
|
||||
}
|
||||
|
||||
return &Store{db: db}, nil
|
||||
}
|
||||
|
||||
func (s *Store) Close() error {
|
||||
return s.db.Close()
|
||||
}
|
||||
|
||||
type Guest struct {
|
||||
Name string
|
||||
IsChild bool
|
||||
}
|
||||
|
||||
type RSVP struct {
|
||||
Guests []Guest
|
||||
Message string
|
||||
}
|
||||
|
||||
func (s *Store) InsertRSVP(r RSVP) error {
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
res, err := tx.Exec(
|
||||
`INSERT INTO rsvps (message, created_at) VALUES (?, ?)`,
|
||||
r.Message, time.Now().UTC(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rsvpID, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, guest := range r.Guests {
|
||||
if _, err := tx.Exec(
|
||||
`INSERT INTO rsvp_guests (rsvp_id, name, is_child) VALUES (?, ?, ?)`,
|
||||
rsvpID, guest.Name, guest.IsChild,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
type PhotoFile struct {
|
||||
Filename string
|
||||
Path string
|
||||
URL string
|
||||
}
|
||||
|
||||
type PhotoUpload struct {
|
||||
Name string
|
||||
Message string
|
||||
Email string
|
||||
Phone string
|
||||
}
|
||||
|
||||
// CreatePhotoUpload inserts the upload record and returns its id, which the
|
||||
// caller uses as the on-disk directory name before attaching files.
|
||||
func (s *Store) CreatePhotoUpload(u PhotoUpload) (int64, error) {
|
||||
res, err := s.db.Exec(
|
||||
`INSERT INTO photo_uploads (name, message, email, phone, created_at) VALUES (?, ?, ?, ?, ?)`,
|
||||
u.Name, u.Message, u.Email, u.Phone, time.Now().UTC(),
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res.LastInsertId()
|
||||
}
|
||||
|
||||
func (s *Store) AddPhotoFiles(uploadID int64, files []PhotoFile) error {
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
now := time.Now().UTC()
|
||||
for _, f := range files {
|
||||
if _, err := tx.Exec(
|
||||
`INSERT INTO photo_files (upload_id, filename, path, url, created_at) VALUES (?, ?, ?, ?, ?)`,
|
||||
uploadID, f.Filename, f.Path, f.URL, now,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
Reference in New Issue
Block a user