aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2024-05-20 08:31:18 -0400
committerXe Iaso <me@xeiaso.net>2024-05-20 08:31:26 -0400
commit62fe9bdef194158ca9bb9d57e56872d2d73d121b (patch)
tree88a66b33e576a542a60d0ee60fe07596a40d7ef0 /cmd
parent1d407f9d6170b535d1174ca73dfaa022d2e3d0c5 (diff)
downloadx-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.go21
-rw-r--r--cmd/mi/main.go3
-rw-r--r--cmd/mi/manifest.yaml37
-rw-r--r--cmd/mi/models/dao.go112
-rw-r--r--cmd/mi/server.go123
-rw-r--r--cmd/mi/yeetfile.js1
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");