diff options
| author | Xe Iaso <me@xeiaso.net> | 2024-05-20 08:31:18 -0400 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2024-05-20 08:31:26 -0400 |
| commit | 62fe9bdef194158ca9bb9d57e56872d2d73d121b (patch) | |
| tree | 88a66b33e576a542a60d0ee60fe07596a40d7ef0 /cmd | |
| parent | 1d407f9d6170b535d1174ca73dfaa022d2e3d0c5 (diff) | |
| download | x-62fe9bdef194158ca9bb9d57e56872d2d73d121b.tar.xz x-62fe9bdef194158ca9bb9d57e56872d2d73d121b.zip | |
cmd/mi: move front tracking to a shim
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/mi/home_front_shim.go | 21 | ||||
| -rw-r--r-- | cmd/mi/main.go | 3 | ||||
| -rw-r--r-- | cmd/mi/manifest.yaml | 37 | ||||
| -rw-r--r-- | cmd/mi/models/dao.go | 112 | ||||
| -rw-r--r-- | cmd/mi/server.go | 123 | ||||
| -rw-r--r-- | cmd/mi/yeetfile.js | 1 |
6 files changed, 233 insertions, 64 deletions
diff --git a/cmd/mi/home_front_shim.go b/cmd/mi/home_front_shim.go new file mode 100644 index 0000000..23b7b48 --- /dev/null +++ b/cmd/mi/home_front_shim.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + "net/http" + + "within.website/x/cmd/mi/models" +) + +type HomeFrontShim struct { + dao *models.DAO +} + +func (hfs *HomeFrontShim) ServeHTTP(w http.ResponseWriter, r *http.Request) { + sw, err := hfs.dao.WhoIsFront(r.Context()) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + fmt.Fprint(w, sw.Member.Name) +} diff --git a/cmd/mi/main.go b/cmd/mi/main.go index b7ed2ce..142dbab 100644 --- a/cmd/mi/main.go +++ b/cmd/mi/main.go @@ -40,9 +40,12 @@ func main() { os.Exit(1) } + dao := models.New(db) + mux := http.NewServeMux() mux.Handle(pb.SwitchTrackerPathPrefix, pb.NewSwitchTrackerServer(NewSwitchTracker(db))) + mux.Handle("/front", &HomeFrontShim{dao: dao}) i := &Importer{db: db} i.Mount(http.DefaultServeMux) diff --git a/cmd/mi/manifest.yaml b/cmd/mi/manifest.yaml index ca8ee8d..e203fce 100644 --- a/cmd/mi/manifest.yaml +++ b/cmd/mi/manifest.yaml @@ -152,6 +152,43 @@ spec: apiVersion: networking.k8s.io/v1 kind: Ingress metadata: + name: mi-front-public + namespace: mi + labels: + app.kubernetes.io/name: mi + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/custom-headers: "mi-front-public-headers" + nginx.ingress.kubernetes.io/ssl-redirect: "true" +spec: + ingressClassName: nginx + tls: + - hosts: + - home.cetacean.club + secretName: home-cetacean-club-tls + rules: + - host: home.cetacean.club + http: + paths: + - pathType: Prefix + path: "/front" + backend: + service: + name: mi + port: + name: http +--- +apiVersion: v1 +data: + Server: "within.website/x/cmd/mi + within.website/x/cmd/ingressd" +kind: ConfigMap +metadata: + name: mi-front-public-headers + namespace: ingress-nginx +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: name: mi-tango-sierra namespace: mi labels: diff --git a/cmd/mi/models/dao.go b/cmd/mi/models/dao.go new file mode 100644 index 0000000..9830bfe --- /dev/null +++ b/cmd/mi/models/dao.go @@ -0,0 +1,112 @@ +package models + +import ( + "context" + "crypto/rand" + "errors" + "time" + + "github.com/oklog/ulid/v2" + "gorm.io/gorm" +) + +var ( + ErrCantSwitchToYourself = errors.New("models: you can't switch to yourself") +) + +type DAO struct { + db *gorm.DB +} + +func New(db *gorm.DB) *DAO { + return &DAO{db: db} +} + +func (d *DAO) Members(ctx context.Context) ([]Member, error) { + var result []Member + if err := d.db.WithContext(ctx).Find(&result).Error; err != nil { + return nil, err + } + + return result, nil +} + +func (d *DAO) WhoIsFront(ctx context.Context) (*Switch, error) { + var sw Switch + if err := d.db.Joins("Member").Order("created_at DESC").First(&sw).Error; err != nil { + return nil, err + } + + return &sw, nil +} + +func (d *DAO) SwitchFront(ctx context.Context, memberName string) (*Switch, *Switch, error) { + var old Switch + tx := d.db.Begin() + + if err := tx.WithContext(ctx).Joins("Member").Where("ended_at IS NULL").First(&old).Error; err != nil { + tx.Rollback() + return nil, nil, err + } + + if old.Member.Name == memberName { + tx.WithContext(ctx).Rollback() + return nil, nil, ErrCantSwitchToYourself + } + + now := time.Now() + old.EndedAt = &now + if err := tx.WithContext(ctx).Save(&old).Error; err != nil { + tx.Rollback() + return nil, nil, err + } + + var newMember Member + if err := tx.WithContext(ctx).Where("name = ?", memberName).First(&newMember).Error; err != nil { + tx.Rollback() + return nil, nil, err + } + + new := Switch{ + ID: ulid.MustNew(ulid.Now(), rand.Reader).String(), + MemberID: newMember.ID, + } + + if err := tx.WithContext(ctx).Create(&new).Error; err != nil { + tx.Rollback() + return nil, nil, err + } + + if err := tx.WithContext(ctx).Commit().Error; err != nil { + tx.Rollback() + return nil, nil, err + } + + return &old, &new, nil +} + +func (d *DAO) GetSwitch(ctx context.Context, id string) (*Switch, error) { + var sw Switch + if err := d.db.WithContext(ctx). + Joins("Member"). + Where("id = ?", id). + First(&sw).Error; err != nil { + return nil, err + } + + return &sw, nil +} + +func (d *DAO) ListSwitches(ctx context.Context, count, page int) ([]Switch, error) { + var switches []Switch + if err := d.db.WithContext(ctx). + Joins("Member"). + Order("rowid DESC"). + Limit(count). + Offset(count * page). + Find(&switches).Error; err != nil { + return nil, err + } + + return switches, nil +} diff --git a/cmd/mi/server.go b/cmd/mi/server.go index 81da15d..fd43385 100644 --- a/cmd/mi/server.go +++ b/cmd/mi/server.go @@ -2,10 +2,9 @@ package main import ( "context" + "errors" "log/slog" - "time" - "github.com/oklog/ulid/v2" "github.com/twitchtv/twirp" "google.golang.org/protobuf/types/known/emptypb" "gorm.io/gorm" @@ -13,22 +12,22 @@ import ( pb "within.website/x/proto/mi" ) -func p[T any](v T) *T { - return &v -} - type SwitchTracker struct { - db *gorm.DB + db *gorm.DB + dao *models.DAO } func NewSwitchTracker(db *gorm.DB) *SwitchTracker { - return &SwitchTracker{db: db} + return &SwitchTracker{ + db: db, + dao: models.New(db), + } } func (s *SwitchTracker) Members(ctx context.Context, _ *emptypb.Empty) (*pb.MembersResp, error) { - var members []models.Member - if err := s.db.Find(&members).Error; err != nil { - return nil, err + members, err := s.dao.Members(ctx) + if err != nil { + return nil, twirp.InternalErrorWith(err) } var resp pb.MembersResp @@ -40,67 +39,45 @@ func (s *SwitchTracker) Members(ctx context.Context, _ *emptypb.Empty) (*pb.Memb } func (s *SwitchTracker) WhoIsFront(ctx context.Context, _ *emptypb.Empty) (*pb.FrontChange, error) { - var sw models.Switch - if err := s.db.Joins("Member").Order("created_at DESC").First(&sw).Error; err != nil { - return nil, twirp.InternalErrorWith(err) + sw, err := s.dao.WhoIsFront(ctx) + if err != nil { + slog.Error("can't find who is front", "err", err) + switch { + case errors.Is(err, gorm.ErrRecordNotFound): + return nil, twirp.NotFoundError("can't find current switch") + default: + return nil, twirp.InternalErrorWith(err) + } } + slog.Info("current front", "sw", sw) + return sw.AsFrontChange(), nil } func (s *SwitchTracker) Switch(ctx context.Context, req *pb.SwitchReq) (*pb.SwitchResp, error) { if err := req.Valid(); err != nil { - slog.Error("can't switch", "req", req, "err", err) + slog.Error("can't switch without a member", "req", req, "err", err) return nil, twirp.InvalidArgumentError("member_name", err.Error()) } - var sw models.Switch - - tx := s.db.Begin() - - if err := tx.Joins("Member").Where("ended_at IS NULL").First(&sw).Error; err != nil { - tx.Rollback() - return nil, twirp.InternalErrorf("failed to find current switch: %w", err) - } - - if sw.Member.Name == req.MemberName { - tx.Rollback() - return nil, twirp.InvalidArgumentError("member_name", "cannot switch to the same member"). - WithMeta("member_name", req.MemberName). - WithMeta("current_member", sw.Member.Name) - } - - sw.EndedAt = p(time.Now()) - if err := tx.Save(&sw).Error; err != nil { - tx.Rollback() - return nil, twirp.InternalErrorf("failed to save current switch: %w", err) + old, new, err := s.dao.SwitchFront(ctx, req.GetMemberName()) + if err != nil { + slog.Error("can't switch front", "req", req, "err", err) + switch { + case errors.Is(err, models.ErrCantSwitchToYourself): + twirp.InvalidArgumentError("member_name", "cannot switch to yourself"). + WithMeta("member_name", req.GetMemberName()) + case errors.Is(err, gorm.ErrRecordNotFound): + return nil, twirp.NotFoundError("can't find current switch") + default: + return nil, twirp.InternalErrorWith(err) + } } - var newMember models.Member - if err := tx.Where("name = ?", req.MemberName).First(&newMember).Error; err != nil { - tx.Rollback() - return nil, twirp.NotFoundError("member not found").WithMeta("member_name", req.MemberName) - } - - newSwitch := models.Switch{ - ID: ulid.MustNew(ulid.Now(), nil).String(), - MemberID: newMember.ID, - } - - if err := tx.Create(&newSwitch).Error; err != nil { - tx.Rollback() - return nil, twirp.InternalErrorf("failed to create new switch: %w", err) - } - - if err := tx.Commit().Error; err != nil { - return nil, twirp.InternalErrorf("failed to commit transaction: %w", err) - } - - slog.Info("switched", "from", sw.AsProto(), "to", newSwitch.AsProto()) - return &pb.SwitchResp{ - Old: sw.AsProto(), - Current: newSwitch.AsProto(), + Old: old.AsProto(), + Current: new.AsProto(), }, nil } @@ -110,9 +87,16 @@ func (s *SwitchTracker) GetSwitch(ctx context.Context, req *pb.GetSwitchReq) (*p return nil, twirp.InvalidArgumentError("id", err.Error()) } - var sw models.Switch - if err := s.db.Joins("Member").Where("id = ?", req.Id).First(&sw).Error; err != nil { - return nil, twirp.NotFoundError("switch not found").WithMeta("id", req.Id) + sw, err := s.dao.GetSwitch(ctx, req.GetId()) + if err != nil { + slog.Error("can't get switch", "req", req, "err", err) + switch { + case errors.Is(err, gorm.ErrRecordNotFound): + return nil, twirp.NotFoundError("can't find switch"). + WithMeta("id", req.GetId()) + default: + return nil, twirp.InternalErrorWith(err) + } } return sw.AsFrontChange(), nil @@ -125,8 +109,19 @@ func (s *SwitchTracker) ListSwitches(ctx context.Context, req *pb.ListSwitchesRe req.Count = 30 } - if err := s.db.Joins("Member").Order("rowid DESC").Limit(int(req.GetCount())).Offset(int(req.GetCount() * req.GetPage())).Find(&switches).Error; err != nil { - return nil, err + switches, err := s.dao.ListSwitches(ctx, int(req.GetCount()), int(req.GetPage())) + if err != nil { + slog.Error("can't get switches", "req", req, "err", err) + switch { + case errors.Is(err, gorm.ErrRecordNotFound): + return nil, twirp.NotFoundError("can't find switch info") + default: + return nil, twirp.InternalErrorWith(err) + } + } + + if len(switches) == 0 { + return nil, twirp.NotFoundError("no switches returned") } var resp pb.ListSwitchesResp diff --git a/cmd/mi/yeetfile.js b/cmd/mi/yeetfile.js index a26b3ea..6711e04 100644 --- a/cmd/mi/yeetfile.js +++ b/cmd/mi/yeetfile.js @@ -2,3 +2,4 @@ nix.build(".#docker.mi"); docker.load("./result"); docker.push(`ghcr.io/xe/x/mi`); yeet.run("kubectl", "apply", "-f=manifest.yaml"); +yeet.run("sh", "-c", "kubectl rollout restart -n mi deployments/mi"); |
