diff options
| author | Xe Iaso <me@xeiaso.net> | 2024-05-24 15:31:51 -0400 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2024-05-24 15:32:30 -0400 |
| commit | c3b8a7a5f9276b62cc3a93b1720f8c1c7d60c6eb (patch) | |
| tree | 4a7fc94c5df7bd8545758144f1968c0192d78326 /cmd | |
| parent | 042eb1851d06597ca91a83a9cf93d4135efe1096 (diff) | |
| download | x-c3b8a7a5f9276b62cc3a93b1720f8c1c7d60c6eb.tar.xz x-c3b8a7a5f9276b62cc3a93b1720f8c1c7d60c6eb.zip | |
cmd/mi: add event tracking
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/mi/main.go | 2 | ||||
| -rw-r--r-- | cmd/mi/models/dao.go | 7 | ||||
| -rw-r--r-- | cmd/mi/models/events.go | 60 | ||||
| -rw-r--r-- | cmd/mi/services/events/events.go | 70 | ||||
| -rw-r--r-- | cmd/x/main.go | 12 | ||||
| -rw-r--r-- | cmd/x/mi.go | 158 |
6 files changed, 305 insertions, 4 deletions
diff --git a/cmd/mi/main.go b/cmd/mi/main.go index 1be0734..42d8d70 100644 --- a/cmd/mi/main.go +++ b/cmd/mi/main.go @@ -11,6 +11,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" "golang.org/x/sync/errgroup" "within.website/x/cmd/mi/models" + "within.website/x/cmd/mi/services/events" "within.website/x/cmd/mi/services/homefrontshim" "within.website/x/cmd/mi/services/importer" "within.website/x/cmd/mi/services/posse" @@ -74,6 +75,7 @@ func main() { mux.Handle(announce.AnnouncePathPrefix, announce.NewAnnounceServer(ann)) mux.Handle(pb.SwitchTrackerPathPrefix, pb.NewSwitchTrackerServer(switchtracker.New(dao))) + mux.Handle(pb.EventsPathPrefix, pb.NewEventsServer(events.New(dao))) mux.Handle("/front", homefrontshim.New(dao)) i := importer.New(dao) diff --git a/cmd/mi/models/dao.go b/cmd/mi/models/dao.go index e747a0d..3241d5e 100644 --- a/cmd/mi/models/dao.go +++ b/cmd/mi/models/dao.go @@ -46,7 +46,12 @@ func New(dbLoc string) (*DAO, error) { return nil, fmt.Errorf("failed to connect to database: %w", err) } - if err := db.AutoMigrate(&Member{}, &Switch{}, &Blogpost{}); err != nil { + if err := db.AutoMigrate( + &Member{}, + &Switch{}, + &Blogpost{}, + &Event{}, + ); err != nil { return nil, fmt.Errorf("failed to migrate schema: %w", err) } diff --git a/cmd/mi/models/events.go b/cmd/mi/models/events.go new file mode 100644 index 0000000..c4239b2 --- /dev/null +++ b/cmd/mi/models/events.go @@ -0,0 +1,60 @@ +package models + +import ( + "context" + "time" + + "google.golang.org/protobuf/types/known/timestamppb" + "gorm.io/gorm" + pb "within.website/x/proto/mi" +) + +// Event represents an event that members of DevRel will be attending. +type Event struct { + gorm.Model + ID int `gorm:"primaryKey"` + Name string + URL string + StartDate time.Time + EndDate time.Time + Location string `gorm:"index"` + Description string +} + +func (e *Event) AsProto() *pb.Event { + return &pb.Event{ + Id: int32(e.ID), + Name: e.Name, + Url: e.URL, + StartDate: timestamppb.New(e.StartDate), + EndDate: timestamppb.New(e.EndDate), + Location: e.Location, + Description: e.Description, + } +} + +func (d *DAO) CreateEvent(ctx context.Context, event *Event) (*Event, error) { + return event, d.db.WithContext(ctx).Create(event).Error +} + +func (d *DAO) GetEvent(ctx context.Context, id int) (*Event, error) { + var event Event + if err := d.db.WithContext(ctx).Where("id = ?", id).First(&event).Error; err != nil { + return nil, err + } + + return &event, nil +} + +func (d *DAO) UpcomingEvents(ctx context.Context, count int) ([]Event, error) { + var events []Event + if err := d.db. + WithContext(ctx). + Where("end_date >= ?", time.Now()). + Limit(count). + Find(&events).Error; err != nil { + return nil, err + } + + return events, nil +} diff --git a/cmd/mi/services/events/events.go b/cmd/mi/services/events/events.go new file mode 100644 index 0000000..bbc1015 --- /dev/null +++ b/cmd/mi/services/events/events.go @@ -0,0 +1,70 @@ +package events + +import ( + "context" + "errors" + "log/slog" + + "github.com/twitchtv/twirp" + "google.golang.org/protobuf/types/known/emptypb" + "gorm.io/gorm" + "within.website/x/cmd/mi/models" + pb "within.website/x/proto/mi" +) + +type Events struct { + dao *models.DAO +} + +var _ pb.Events = &Events{} + +// New creates a new Events service. +func New(dao *models.DAO) *Events { + return &Events{dao: dao} +} + +// Get fetches upcoming events. +func (e *Events) Get(ctx context.Context, _ *emptypb.Empty) (*pb.EventFeed, error) { + events, err := e.dao.UpcomingEvents(ctx, 10) + if err != nil { + slog.Error("can't fetch upcoming events", "err", err) + switch { + case errors.Is(err, gorm.ErrRecordNotFound): + return nil, twirp.NotFoundError("can't find any events") + default: + return nil, twirp.InternalErrorWith(err) + } + } + + if len(events) == 0 { + return nil, twirp.NotFoundError("can't find any events") + } + + var pbEvents []*pb.Event + for _, event := range events { + pbEvents = append(pbEvents, event.AsProto()) + } + + return &pb.EventFeed{Events: pbEvents}, nil +} + +// Add adds a new event to the database. +func (e *Events) Add(ctx context.Context, ev *pb.Event) (*emptypb.Empty, error) { + event := &models.Event{ + Name: ev.Name, + URL: ev.Url, + StartDate: ev.StartDate.AsTime(), + EndDate: ev.EndDate.AsTime(), + Location: ev.Location, + Description: ev.Description, + } + + _, err := e.dao.CreateEvent(ctx, event) + if err != nil { + return nil, err + } + + slog.Info("tracking new event", "event", event) + + return &emptypb.Empty{}, nil +} diff --git a/cmd/x/main.go b/cmd/x/main.go index 3977a4d..71a6670 100644 --- a/cmd/x/main.go +++ b/cmd/x/main.go @@ -13,10 +13,16 @@ func main() { subcommands.Register(subcommands.FlagsCommand(), "") subcommands.Register(subcommands.CommandsCommand(), "") - subcommands.Register(&miListSwitches{}, "mi") - subcommands.Register(&miSwitch{}, "mi") - subcommands.Register(&miWhoIsFront{}, "mi") + // Switch tracker + subcommands.Register(&miListSwitches{}, "switch-tracker") + subcommands.Register(&miSwitch{}, "switch-tracker") + subcommands.Register(&miWhoIsFront{}, "switch-tracker") + // Events + subcommands.Register(&miListEvents{}, "events") + subcommands.Register(&miAddEvent{}, "events") + + // Sanguisuga subcommands.Register(&sanguisugaAnimeList{}, "sanguisuga") subcommands.Register(&sanguisugaAnimeTrack{}, "sanguisuga") subcommands.Register(&sanguisugaTVList{}, "sanguisuga") diff --git a/cmd/x/mi.go b/cmd/x/mi.go index 08b5d8c..46a8447 100644 --- a/cmd/x/mi.go +++ b/cmd/x/mi.go @@ -4,11 +4,15 @@ import ( "context" "flag" "fmt" + "log/slog" "net/http" + "time" + "github.com/c-bata/go-prompt" "github.com/google/subcommands" "github.com/rodaine/table" "google.golang.org/protobuf/types/known/emptypb" + "google.golang.org/protobuf/types/known/timestamppb" "within.website/x/proto/mi" ) @@ -109,3 +113,157 @@ func (ls *miListSwitches) Execute(ctx context.Context, f *flag.FlagSet, _ ...int return subcommands.ExitSuccess } + +type miListEvents struct{} + +func (*miListEvents) Name() string { return "list-events" } +func (*miListEvents) Synopsis() string { return "List events to be attended." } +func (*miListEvents) Usage() string { + return `list-events: +List events to be attended. +` +} +func (*miListEvents) SetFlags(f *flag.FlagSet) {} + +func (le *miListEvents) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { + cli := mi.NewEventsProtobufClient(*miURL, http.DefaultClient) + + resp, err := cli.Get(ctx, &emptypb.Empty{}) + if err != nil { + fmt.Printf("error: %v\n", err) + return subcommands.ExitFailure + } + + tbl := table.New("Name", "Start Date", "End Date", "Location") + for _, ev := range resp.Events { + tbl.AddRow(ev.Name, ev.StartDate.AsTime().Format("2006-01-02"), ev.EndDate.AsTime().Format("2006-01-02"), ev.Location) + } + + tbl.Print() + + return subcommands.ExitSuccess +} + +type miAddEvent struct { + name string + url string + startDate string + endDate string + location string + description string +} + +func (*miAddEvent) Name() string { return "add-event" } +func (*miAddEvent) Synopsis() string { return "Add an event to be attended." } +func (*miAddEvent) Usage() string { + return `add-event [--name] [--url] [--start-date] [--end-date] [--location] [--description]: +Add an event to be attended. +` +} +func (ae *miAddEvent) SetFlags(f *flag.FlagSet) { + f.StringVar(&ae.name, "name", "", "Name of the event.") + f.StringVar(&ae.url, "url", "", "URL of the event.") + f.StringVar(&ae.startDate, "start-date", "", "Start date of the event.") + f.StringVar(&ae.endDate, "end-date", "", "End date of the event.") + f.StringVar(&ae.location, "location", "", "Location of the event.") + f.StringVar(&ae.description, "description", "", "Description of the event.") +} + +func (ae *miAddEvent) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { + if ae.name == "" { + ae.name = prompt.Input("Event name: ", func(d prompt.Document) []prompt.Suggest { + return nil + }) + } + + if ae.url == "" { + ae.url = prompt.Input("Event URL: ", func(d prompt.Document) []prompt.Suggest { + return nil + }) + } + + if ae.startDate == "" { + for { + ae.startDate = prompt.Input("Event start date (YYYY-MM-DD): ", func(d prompt.Document) []prompt.Suggest { + return nil + }) + + _, err := time.Parse("2006-01-02", ae.startDate) + if err != nil { + fmt.Printf("error parsing date: %v\n", err) + continue + } + + break + } + } + + if ae.endDate == "" { + for { + ae.endDate = prompt.Input("Event end date (YYYY-MM-DD): ", func(d prompt.Document) []prompt.Suggest { + return nil + }) + + _, err := time.Parse("2006-01-02", ae.endDate) + if err != nil { + fmt.Printf("error parsing date: %v\n", err) + continue + } + + break + } + } + + if ae.location == "" { + ae.location = prompt.Input("Event location: ", func(d prompt.Document) []prompt.Suggest { + s := []prompt.Suggest{ + {Text: "Remote", Description: "Remote event"}, + {Text: "San Francisco", Description: "San Francisco, CA, USA"}, + {Text: "New York", Description: "New York, NY, USA"}, + {Text: "Ottawa", Description: "Ottawa, ON, Canada"}, + {Text: "Montreal", Description: "Montreal, QC, Canada"}, + {Text: "Toronto", Description: "Toronto, ON, Canada"}, + } + return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true) + }) + } + + if ae.description == "" { + ae.description = prompt.Input("What will you be doing there? (first person): ", func(d prompt.Document) []prompt.Suggest { + return nil + }) + } + + cli := mi.NewEventsProtobufClient(*miURL, http.DefaultClient) + + startDate, err := time.Parse("2006-01-02", ae.startDate) + if err != nil { + fmt.Printf("error parsing start date: %v\n", err) + return subcommands.ExitFailure + } + + endDate, err := time.Parse("2006-01-02", ae.endDate) + if err != nil { + fmt.Printf("error parsing end date: %v\n", err) + return subcommands.ExitFailure + } + + ev := &mi.Event{ + Name: ae.name, + Url: ae.url, + StartDate: timestamppb.New(startDate), + EndDate: timestamppb.New(endDate), + Location: ae.location, + Description: ae.description, + } + + slog.Info("adding event", "event", ev) + + _, err = cli.Add(ctx, ev) + if err != nil { + fmt.Printf("error: %v\n", err) + return subcommands.ExitFailure + } + + return subcommands.ExitSuccess +} |
