diff --git a/backend/internal/channel/channel.go b/backend/internal/channel/channel.go deleted file mode 100644 index 302e518..0000000 --- a/backend/internal/channel/channel.go +++ /dev/null @@ -1,8 +0,0 @@ -package channel - -type Channel struct { -} - -func CreateChannel(frequency string, description string) *Channel { - return nil -} diff --git a/backend/internal/channel/handler.go b/backend/internal/channel/handler.go index c1c0e46..65e5d07 100644 --- a/backend/internal/channel/handler.go +++ b/backend/internal/channel/handler.go @@ -7,34 +7,33 @@ import ( "connectrpc.com/connect" radiov1 "echsen.club/radio/gen/radio/v1" - "echsen.club/radio/internal/db" + "echsen.club/radio/internal/user" ) -type StateProvider interface { - GetUserByID(ctx context.Context, id string) (*db.User, error) - UserFromContext(ctx context.Context) (*db.User, bool) -} - type ChannelHandler struct { - state StateProvider + store *Store } -func NewHandler(s StateProvider) *ChannelHandler { +func NewHandler(store *Store) *ChannelHandler { return &ChannelHandler{ - state: s, + store: store, } } func (handler *ChannelHandler) CreateChannel(ctx context.Context, req *radiov1.CreateChannelRequest) (*radiov1.CreateChannelResponse, error) { - _, ok := handler.state.UserFromContext(ctx) + _, ok := user.FromContext(ctx) if !ok { slog.Error("User missing from context in protected route") return nil, connect.NewError(connect.CodeUnauthenticated, fmt.Errorf("User not authenticated")) } - channel := &radiov1.Channel{Id: "1", Frequency: "97.1", Description: ""} - return &radiov1.CreateChannelResponse{Channel: channel}, nil + // Use the store to create channel + // Note: We ignore the request frequency for this dummy implementation as per original code + c, _ := handler.store.CreateChannel("97.1", "") + + channelProto := &radiov1.Channel{Id: c.ID, Frequency: c.Frequency, Description: c.Description} + return &radiov1.CreateChannelResponse{Channel: channelProto}, nil } func (handler *ChannelHandler) DeleteChannel(ctx context.Context, req *radiov1.DeleteChannelRequest) (*radiov1.DeleteChannelResponse, error) { diff --git a/backend/internal/channel/model.go b/backend/internal/channel/model.go new file mode 100644 index 0000000..43feb7f --- /dev/null +++ b/backend/internal/channel/model.go @@ -0,0 +1,7 @@ +package channel + +type Channel struct { + ID string + Frequency string + Description string +} diff --git a/backend/internal/channel/store.go b/backend/internal/channel/store.go new file mode 100644 index 0000000..6b63238 --- /dev/null +++ b/backend/internal/channel/store.go @@ -0,0 +1,21 @@ +package channel + +import "database/sql" + +type Store struct { + db *sql.DB +} + +func NewStore(db *sql.DB) *Store { + return &Store{db: db} +} + +// CreateChannel is a placeholder for DB logic +func (s *Store) CreateChannel(frequency, description string) (*Channel, error) { + // TODO: Implement actual DB insert + return &Channel{ + ID: "1", + Frequency: "97.1", + Description: "", + }, nil +} diff --git a/backend/internal/state/middleware.go b/backend/internal/state/middleware.go index e1d44c9..c3dad4a 100644 --- a/backend/internal/state/middleware.go +++ b/backend/internal/state/middleware.go @@ -1,12 +1,12 @@ package state import ( - "context" "log/slog" "net/http" "strings" "echsen.club/radio/internal/oauth" + "echsen.club/radio/internal/user" ) func (state *State) AuthMiddleware(next http.Handler) http.Handler { @@ -50,21 +50,21 @@ func (state *State) AuthMiddleware(next http.Handler) http.Handler { return } - user, err := state.GetUserByID(r.Context(), userID) + u, err := state.userStore.GetByID(r.Context(), userID) if err != nil { slog.Warn("Session user not found in DB", "user_id", userID) unauthorized() return } - ctx := context.WithValue(r.Context(), userKey, user) + ctx := user.NewContext(r.Context(), u) next.ServeHTTP(w, r.WithContext(ctx)) }) } func (state *State) handleAuthSuccess(w http.ResponseWriter, r *http.Request, claims oauth.UserClaims) { - userID, err := state.UpsertUser(r.Context(), claims.Subject, claims.Email, claims.Name) + userID, err := state.userStore.Upsert(r.Context(), claims.Subject, claims.Email, claims.Name) // upserting on every endpoint may be a little wasteful but this app is made for a versy specific and tiny // audience so it shouldnt matter. If it does end up mattering at some point you know what to change :) if err != nil { diff --git a/backend/internal/state/state.go b/backend/internal/state/state.go index f474d94..32829f0 100644 --- a/backend/internal/state/state.go +++ b/backend/internal/state/state.go @@ -11,6 +11,7 @@ import ( "echsen.club/radio/internal/channel" "echsen.club/radio/internal/config" "echsen.club/radio/internal/oauth" + "echsen.club/radio/internal/user" ) type State struct { @@ -18,6 +19,8 @@ type State struct { oauth *oauth.Oauth mux *http.ServeMux channelHandler *channel.ChannelHandler + userStore *user.Store + channelStore *channel.Store } func New(db *sql.DB, config config.Config) *State { @@ -26,14 +29,19 @@ func New(db *sql.DB, config config.Config) *State { log.Fatalf("Failed to initialize OAuth: %v", err) } + userStore := user.NewStore(db) + channelStore := channel.NewStore(db) + channelHandler := channel.NewHandler(channelStore) + state := &State{ - db: db, - oauth: oauth, - mux: http.NewServeMux(), + db: db, + oauth: oauth, + mux: http.NewServeMux(), + userStore: userStore, + channelStore: channelStore, + channelHandler: channelHandler, } - state.channelHandler = channel.NewHandler(state) - state.RegisterHandlers() return state diff --git a/backend/internal/user/context.go b/backend/internal/user/context.go new file mode 100644 index 0000000..91dd9cb --- /dev/null +++ b/backend/internal/user/context.go @@ -0,0 +1,16 @@ +package user + +import "context" + +type contextKey string + +const userKey contextKey = "user" + +func FromContext(ctx context.Context) (*User, bool) { + u, ok := ctx.Value(userKey).(*User) + return u, ok +} + +func NewContext(ctx context.Context, u *User) context.Context { + return context.WithValue(ctx, userKey, u) +} diff --git a/backend/internal/db/user.go b/backend/internal/user/model.go similarity index 91% rename from backend/internal/db/user.go rename to backend/internal/user/model.go index 44c826e..5b1c3cf 100644 --- a/backend/internal/db/user.go +++ b/backend/internal/user/model.go @@ -1,4 +1,4 @@ -package db +package user import ( "github.com/google/uuid" diff --git a/backend/internal/state/user.go b/backend/internal/user/store.go similarity index 54% rename from backend/internal/state/user.go rename to backend/internal/user/store.go index 84de725..3c4109c 100644 --- a/backend/internal/state/user.go +++ b/backend/internal/user/store.go @@ -1,34 +1,33 @@ -package state +package user import ( "context" + "database/sql" "fmt" - "echsen.club/radio/internal/db" "github.com/google/uuid" ) -type contextKey string +type Store struct { + db *sql.DB +} -const userKey contextKey = "user" +func NewStore(db *sql.DB) *Store { + return &Store{db: db} +} -func (state *State) GetUserByID(ctx context.Context, id string) (*db.User, error) { - var u db.User +func (s *Store) GetByID(ctx context.Context, id string) (*User, error) { + var u User query := `SELECT id, subject_id, email, display_name FROM users WHERE id = $1` - err := state.db.QueryRowContext(ctx, query, id).Scan(&u.ID, &u.SubjectID, &u.Email, &u.DisplayName) + err := s.db.QueryRowContext(ctx, query, id).Scan(&u.ID, &u.SubjectID, &u.Email, &u.DisplayName) if err != nil { return nil, err } return &u, nil } -func (state *State) UserFromContext(ctx context.Context) (*db.User, bool) { - u, ok := ctx.Value(userKey).(*db.User) - return u, ok -} - -func (state *State) UpsertUser(ctx context.Context, subjectID, email, name string) (uuid.UUID, error) { +func (s *Store) Upsert(ctx context.Context, subjectID, email, name string) (uuid.UUID, error) { var id uuid.UUID // ON CONFLICT (subject_id) tells Postgres: "If this OIDC user already exists, @@ -43,7 +42,7 @@ func (state *State) UpsertUser(ctx context.Context, subjectID, email, name strin RETURNING id; ` - err := state.db.QueryRowContext(ctx, query, subjectID, email, name).Scan(&id) + err := s.db.QueryRowContext(ctx, query, subjectID, email, name).Scan(&id) if err != nil { return uuid.Nil, fmt.Errorf("failed to upsert user: %w", err) }