196 lines
4.8 KiB
Go
196 lines
4.8 KiB
Go
package events
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/jinzhu/gorm"
|
|
"github.com/kirsle/blog/models/contacts"
|
|
"github.com/kirsle/golog"
|
|
)
|
|
|
|
// DB is a reference to the parent app's gorm DB.
|
|
var DB *gorm.DB
|
|
|
|
// UseDB registers the DB from the root app.
|
|
func UseDB(db *gorm.DB) {
|
|
DB = db
|
|
DB.AutoMigrate(&Event{}, &RSVP{})
|
|
DB.Model(&Event{}).Related(&RSVP{})
|
|
DB.Model(&RSVP{}).Related(&contacts.Contact{})
|
|
}
|
|
|
|
var log *golog.Logger
|
|
|
|
func init() {
|
|
log = golog.GetLogger("blog")
|
|
}
|
|
|
|
// Event holds information about events.
|
|
type Event struct {
|
|
ID int `json:"id"`
|
|
Title string `json:"title"`
|
|
Fragment string `json:"fragment"`
|
|
Description string `json:"description"`
|
|
Location string `json:"location"`
|
|
CoverPhoto string `json:"coverPhoto"`
|
|
StartTime time.Time `json:"startTime"`
|
|
EndTime time.Time `json:"endTime"`
|
|
AllDay bool `json:"allDay"`
|
|
OpenSignup bool `json:"openSignup"`
|
|
RSVP []RSVP `json:"rsvp"`
|
|
Created time.Time `json:"created"`
|
|
Updated time.Time `json:"updated"`
|
|
}
|
|
|
|
// New creates a blank event with sensible defaults.
|
|
func New() *Event {
|
|
return &Event{
|
|
StartTime: time.Now().UTC(),
|
|
EndTime: time.Now().UTC(),
|
|
}
|
|
}
|
|
|
|
// All returns all the events.
|
|
func All() ([]*Event, error) {
|
|
result := []*Event{}
|
|
err := DB.Order("start_time desc").Find(&result).Error
|
|
return result, err
|
|
}
|
|
|
|
// ParseForm populates the event from form values.
|
|
func (ev *Event) ParseForm(r *http.Request) {
|
|
id, _ := strconv.Atoi(r.FormValue("id"))
|
|
|
|
ev.ID = id
|
|
ev.Title = r.FormValue("title")
|
|
ev.Fragment = r.FormValue("fragment")
|
|
ev.Description = r.FormValue("description")
|
|
ev.Location = r.FormValue("location")
|
|
ev.AllDay = r.FormValue("all_day") == "true"
|
|
ev.OpenSignup = r.FormValue("open_signup") == "true"
|
|
|
|
startTime, err := parseDateTime(r, "start_date", "start_time")
|
|
ev.StartTime = startTime
|
|
if err != nil {
|
|
log.Error("startTime parse error: %s", err)
|
|
}
|
|
|
|
endTime, err := parseDateTime(r, "end_date", "end_time")
|
|
ev.EndTime = endTime
|
|
if err != nil {
|
|
log.Error("endTime parse error: %s", err)
|
|
}
|
|
}
|
|
|
|
// parseDateTime parses separate date + time fields into a single time.Time.
|
|
func parseDateTime(r *http.Request, dateField, timeField string) (time.Time, error) {
|
|
dateValue := r.FormValue(dateField)
|
|
timeValue := r.FormValue(timeField)
|
|
|
|
if dateValue != "" && timeValue != "" {
|
|
datetime, err := time.Parse("2006-01-02 15:04", dateValue+" "+timeValue)
|
|
return datetime, err
|
|
} else if dateValue != "" {
|
|
datetime, err := time.Parse("2006-01-02", dateValue)
|
|
return datetime, err
|
|
} else {
|
|
return time.Time{}, errors.New("no date/times given")
|
|
}
|
|
}
|
|
|
|
// Validate makes sure the required fields are all present.
|
|
func (ev *Event) Validate() error {
|
|
if ev.Title == "" {
|
|
return errors.New("title is required")
|
|
} else if ev.Description == "" {
|
|
return errors.New("description is required")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// joinedLoad loads the Event with its RSVPs and their Contacts.
|
|
func joinedLoad() *gorm.DB {
|
|
return DB.Preload("RSVP").Preload("RSVP.Contact")
|
|
}
|
|
|
|
// Load an event by its ID.
|
|
func Load(id int) (*Event, error) {
|
|
ev := &Event{}
|
|
err := joinedLoad().First(ev, id).Error
|
|
return ev, err
|
|
}
|
|
|
|
// LoadFragment loads an event by its URL fragment.
|
|
func LoadFragment(fragment string) (*Event, error) {
|
|
ev := &Event{}
|
|
err := joinedLoad().Where("fragment = ?", fragment).First(ev).Error
|
|
return ev, err
|
|
}
|
|
|
|
// Save the event.
|
|
func (ev *Event) Save() error {
|
|
// Generate a URL fragment if needed.
|
|
if ev.Fragment == "" {
|
|
fragment := strings.ToLower(ev.Title)
|
|
fragment = regexp.MustCompile(`[^A-Za-z0-9]+`).ReplaceAllString(fragment, "-")
|
|
if strings.Contains(fragment, "--") {
|
|
log.Error("Generated event fragment '%s' contains double dashes still!", fragment)
|
|
}
|
|
ev.Fragment = strings.Trim(fragment, "-")
|
|
|
|
// If still no fragment, make one based on the post ID.
|
|
if ev.Fragment == "" {
|
|
ev.Fragment = fmt.Sprintf("event-%d", ev.ID)
|
|
}
|
|
}
|
|
|
|
// Make sure the URL fragment is unique!
|
|
if len(ev.Fragment) > 0 {
|
|
if exist, err := LoadFragment(ev.Fragment); err == nil && exist.ID != ev.ID {
|
|
var resolved bool
|
|
for i := 1; i <= 100; i++ {
|
|
fragment := fmt.Sprintf("%s-%d", ev.Fragment, i)
|
|
_, err := LoadFragment(fragment)
|
|
if err == nil {
|
|
continue
|
|
}
|
|
|
|
ev.Fragment = fragment
|
|
resolved = true
|
|
break
|
|
}
|
|
|
|
if !resolved {
|
|
return fmt.Errorf("failed to generate a unique URL fragment for '%s' after 100 attempts", ev.Fragment)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Dates & times.
|
|
if ev.Created.IsZero() {
|
|
ev.Created = time.Now().UTC()
|
|
}
|
|
if ev.Updated.IsZero() {
|
|
ev.Updated = ev.Created
|
|
}
|
|
|
|
// Write the event.
|
|
return DB.Save(&ev).Error
|
|
}
|
|
|
|
// Delete an event.
|
|
func (ev *Event) Delete() error {
|
|
if ev.ID == 0 {
|
|
return errors.New("event has no ID")
|
|
}
|
|
|
|
// Delete the DB files.
|
|
return DB.Delete(ev).Error
|
|
}
|