aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2024-05-24 15:31:51 -0400
committerXe Iaso <me@xeiaso.net>2024-05-24 15:32:30 -0400
commitc3b8a7a5f9276b62cc3a93b1720f8c1c7d60c6eb (patch)
tree4a7fc94c5df7bd8545758144f1968c0192d78326 /cmd
parent042eb1851d06597ca91a83a9cf93d4135efe1096 (diff)
downloadx-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.go2
-rw-r--r--cmd/mi/models/dao.go7
-rw-r--r--cmd/mi/models/events.go60
-rw-r--r--cmd/mi/services/events/events.go70
-rw-r--r--cmd/x/main.go12
-rw-r--r--cmd/x/mi.go158
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
+}