Skip to content

Commit 473de3e

Browse files
fix(onboard): fix styling and update route
1 parent 4c9f08b commit 473de3e

10 files changed

Lines changed: 132 additions & 210 deletions

File tree

apps/api/internal/api/api.go

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,47 +58,60 @@ func (api *API) setupRoutes(mw *mw.Middleware) {
5858
api.Router.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
5959
api.Logger.Trace().Str("method", r.Method).Str("path", r.URL.Path).Msg("Received ping.")
6060
w.Header().Set("Content-Type", "text/plain")
61-
w.Header().Set("Content-Length", "6") // "pong!\n" is 6 bytes
61+
w.Header().Set("Content-Length", "6")
6262
if _, err := w.Write([]byte("pong!\n")); err != nil {
6363
log.Err(err)
6464
}
6565
})
6666

67-
// Auth routes
67+
// --- Auth routes ---
6868
api.Router.Route("/auth", func(r chi.Router) {
6969
r.Get("/callback", api.Handlers.Auth.OAuthCallback)
7070

71+
// Protected auth routes
7172
r.Group(func(r chi.Router) {
7273
r.Use(mw.Auth.RequireAuth)
7374
r.Get("/me", api.Handlers.Auth.GetMe)
7475
r.Post("/logout", api.Handlers.Auth.Logout)
7576
})
7677
})
7778

78-
// User routes
79+
// --- User routes ---
7980
api.Router.Route("/users", func(r chi.Router) {
8081
r.Use(mw.Auth.RequireAuth)
8182
r.Get("/me", api.Handlers.User.GetProfile)
82-
r.Patch("/me", api.Handlers.User.UpdateProfile)
83-
r.Patch("/me/onboarded", api.Handlers.User.UpdateOnboarded)
83+
r.Patch("/me/onboarding", api.Handlers.User.CompleteOnboarding)
8484
})
8585

86-
// Event routes
86+
// --- Event routes ---
8787
api.Router.Route("/events", func(r chi.Router) {
88+
89+
// Superuser-only
8890
r.With(mw.Auth.RequireAuth, ensureSuperuser).Post("/", api.Handlers.Event.CreateEvent)
91+
92+
// Authenticated
8993
r.With(mw.Auth.RequireAuth).Get("/", api.Handlers.Event.GetEvents)
94+
95+
// Event-specific routes
9096
r.Route("/{eventId}", func(r chi.Router) {
91-
r.With(mw.Auth.RequireAuth, ensureEventAdmin).Patch("/", api.Handlers.Event.UpdateEventById)
92-
r.With(mw.Auth.RequireAuth, ensureSuperuser).Delete("/", api.Handlers.Event.DeleteEventById)
93-
r.With(mw.Auth.RequireAuth, ensureEventAdmin).Get("/staff", api.Handlers.Event.GetEventStaffUsers)
94-
r.With(mw.Auth.RequireAuth, ensureEventAdmin).Post("/roles", api.Handlers.Event.AssignEventRole)
95-
r.With(mw.Auth.RequireAuth).Get("/role", api.Handlers.Event.GetEventRole)
97+
r.Use(mw.Auth.RequireAuth)
98+
99+
// General access
96100
r.Get("/", api.Handlers.Event.GetEventByID)
97101
r.Post("/interest", api.Handlers.EventInterest.AddEmailToEvent)
98102

103+
// Admin-only
104+
r.With(ensureEventAdmin).Patch("/", api.Handlers.Event.UpdateEventById)
105+
r.With(ensureEventAdmin).Get("/staff", api.Handlers.Event.GetEventStaffUsers)
106+
r.With(ensureEventAdmin).Post("/roles", api.Handlers.Event.AssignEventRole)
107+
r.With(ensureEventAdmin).Get("/role", api.Handlers.Event.GetEventRole)
108+
109+
// Superuser-only
110+
r.With(ensureSuperuser).Delete("/", api.Handlers.Event.DeleteEventById)
111+
112+
// Application routes
99113
r.Route("/application", func(r chi.Router) {
100114
r.Use(mw.Auth.RequireAuth)
101-
102115
r.Get("/", api.Handlers.Application.GetApplicationByUserAndEventID)
103116
r.Post("/submit", api.Handlers.Application.SubmitApplication)
104117
r.Post("/save", api.Handlers.Application.SaveApplication)

apps/api/internal/api/handlers/user.go

Lines changed: 15 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"github.com/rs/zerolog"
88
res "github.com/swamphacks/core/apps/api/internal/api/response"
99
"github.com/swamphacks/core/apps/api/internal/ctxutils"
10-
"github.com/swamphacks/core/apps/api/internal/db/sqlc"
1110
"github.com/swamphacks/core/apps/api/internal/email"
1211
"github.com/swamphacks/core/apps/api/internal/services"
1312
)
@@ -24,7 +23,6 @@ func NewUserHandler(userService *services.UserService, logger zerolog.Logger) *U
2423
}
2524
}
2625

27-
// GetProfile returns the current user's profile information
2826
func (h *UserHandler) GetProfile(w http.ResponseWriter, r *http.Request) {
2927
userId := ctxutils.GetUserIdFromCtx(r.Context())
3028
if userId == nil {
@@ -46,120 +44,46 @@ func (h *UserHandler) GetProfile(w http.ResponseWriter, r *http.Request) {
4644
res.Send(w, http.StatusOK, user)
4745
}
4846

49-
// UpdateProfileRequest represents the request body for updating user profile
50-
type UpdateProfileRequest struct {
51-
Name *string `json:"name,omitempty"`
52-
Email *string `json:"email,omitempty"`
47+
type CompleteOnboardingRequest struct {
48+
Name string `json:"name"`
49+
PreferredEmail string `json:"preferred_email"`
5350
}
5451

55-
// UpdateProfile updates the current user's profile information
56-
func (h *UserHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) {
52+
func (h *UserHandler) CompleteOnboarding(w http.ResponseWriter, r *http.Request) {
5753
userId := ctxutils.GetUserIdFromCtx(r.Context())
5854
if userId == nil {
5955
res.SendError(w, http.StatusUnauthorized, res.NewError("unauthorized", "User not authenticated"))
6056
return
6157
}
6258

63-
var req UpdateProfileRequest
59+
var req CompleteOnboardingRequest
6460
decoder := json.NewDecoder(r.Body)
6561
decoder.DisallowUnknownFields() // Prevents requests with extraneous fields
6662
if err := decoder.Decode(&req); err != nil {
6763
res.SendError(w, http.StatusBadRequest, res.NewError("invalid_request", "Invalid request body"))
6864
return
6965
}
7066

71-
// Validate that at least one field is provided
72-
if req.Name == nil && req.Email == nil {
73-
res.SendError(w, http.StatusBadRequest, res.NewError("invalid_request", "At least one field (name or email) must be provided"))
67+
// Validate required fields
68+
if req.Name == "" || req.PreferredEmail == "" {
69+
res.SendError(w, http.StatusBadRequest, res.NewError("invalid_request", "Name and preferred email are required"))
7470
return
7571
}
7672

77-
// Validate email format if provided
78-
if req.Email != nil && *req.Email != "" {
79-
// Basic email validation - you might want to use a more robust validation library
80-
if !email.IsValidEmail(*req.Email) {
81-
res.SendError(w, http.StatusBadRequest, res.NewError("invalid_email", "Invalid email format"))
82-
return
83-
}
73+
// Validate email format
74+
if !email.IsValidEmail(req.PreferredEmail) {
75+
res.SendError(w, http.StatusBadRequest, res.NewError("invalid_email", "Invalid email format"))
76+
return
8477
}
8578

86-
err := h.userService.UpdateUserProfile(r.Context(), *userId, req.Name, req.Email)
79+
err := h.userService.CompleteOnboarding(r.Context(), *userId, req.Name, req.PreferredEmail)
8780
if err != nil {
88-
h.logger.Err(err).Msg("failed to update user profile")
81+
h.logger.Err(err).Msg("failed to complete onboarding")
8982
if err == services.ErrUserNotFound {
9083
res.SendError(w, http.StatusNotFound, res.NewError("user_not_found", "User not found"))
9184
} else {
92-
res.SendError(w, http.StatusInternalServerError, res.NewError("update_failed", "Failed to update user profile"))
85+
res.SendError(w, http.StatusInternalServerError, res.NewError("update_failed", "Failed to complete onboarding"))
9386
}
9487
return
9588
}
96-
97-
// Return the updated user profile
98-
user, err := h.userService.GetUser(r.Context(), *userId)
99-
if err != nil {
100-
h.logger.Err(err).Msg("failed to get updated user profile")
101-
res.SendError(w, http.StatusInternalServerError, res.NewError("internal_err", "Profile updated but failed to retrieve updated data"))
102-
return
103-
}
104-
105-
res.Send(w, http.StatusOK, user)
106-
}
107-
108-
type UpdateOnboardedRequest struct {
109-
Onboarded bool `json:"onboarded"`
110-
}
111-
112-
func (h *UserHandler) UpdateOnboarded(w http.ResponseWriter, r *http.Request) {
113-
userId := ctxutils.GetUserIdFromCtx(r.Context())
114-
if userId == nil {
115-
res.SendError(w, http.StatusUnauthorized, res.NewError("unauthorized", "User not authenticated"))
116-
return
117-
}
118-
119-
var req UpdateOnboardedRequest
120-
decoder := json.NewDecoder(r.Body)
121-
decoder.DisallowUnknownFields() // Prevents requests with extraneous fields
122-
if err := decoder.Decode(&req); err != nil {
123-
res.SendError(w, http.StatusBadRequest, res.NewError("invalid_request", "Invalid request body"))
124-
return
125-
}
126-
127-
if req.Onboarded {
128-
err := h.userService.UpdateUserOnboarded(r.Context(), *userId)
129-
if err != nil {
130-
h.logger.Err(err).Msg("failed to update user onboarded status")
131-
if err == services.ErrUserNotFound {
132-
res.SendError(w, http.StatusNotFound, res.NewError("user_not_found", "User not found"))
133-
} else {
134-
res.SendError(w, http.StatusInternalServerError, res.NewError("update_failed", "Failed to update onboarded status"))
135-
}
136-
return
137-
}
138-
} else {
139-
params := sqlc.UpdateUserParams{
140-
ID: *userId,
141-
OnboardedDoUpdate: true,
142-
Onboarded: false,
143-
}
144-
err := h.userService.UpdateUser(r.Context(), *userId, params)
145-
if err != nil {
146-
h.logger.Err(err).Msg("failed to update user onboarded status")
147-
if err == services.ErrUserNotFound {
148-
res.SendError(w, http.StatusNotFound, res.NewError("user_not_found", "User not found"))
149-
} else {
150-
res.SendError(w, http.StatusInternalServerError, res.NewError("update_failed", "Failed to update onboarded status"))
151-
}
152-
return
153-
}
154-
}
155-
156-
// Return the updated user profile
157-
user, err := h.userService.GetUser(r.Context(), *userId)
158-
if err != nil {
159-
h.logger.Err(err).Msg("failed to get updated user profile")
160-
res.SendError(w, http.StatusInternalServerError, res.NewError("internal_err", "Status updated but failed to retrieve updated data"))
161-
return
162-
}
163-
164-
res.Send(w, http.StatusOK, user)
16589
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- +goose Up
2+
-- +goose StatementBegin
3+
ALTER TABLE auth.users
4+
ADD COLUMN preferred_email TEXT;
5+
-- +goose StatementEnd
6+
7+
-- +goose Down
8+
-- +goose StatementBegin
9+
ALTER TABLE auth.users
10+
DROP COLUMN preferred_email;
11+
-- +goose StatementEnd

apps/api/internal/db/queries/users.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ SET
2222
name = CASE WHEN @name_do_update::boolean THEN @name ELSE name END,
2323
email = CASE WHEN @email_do_update::boolean THEN @email ELSE email END,
2424
email_verified = CASE WHEN @email_verified_do_update::boolean THEN @email_verified ELSE email_verified END,
25+
preferred_email = CASE WHEN @preferred_email_do_update::boolean THEN @preferred_email ELSE preferred_email END,
2526
onboarded = CASE WHEN @onboarded_do_update::boolean THEN @onboarded ELSE onboarded END,
2627
image = CASE WHEN @image_do_update::boolean THEN @image ELSE image END,
2728
updated_at = NOW()

apps/api/internal/db/repository/users.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,3 @@ func (r *UserRepository) UpdateUser(ctx context.Context, params sqlc.UpdateUserP
7474
}
7575
return err
7676
}
77-
78-
// Why does SQLC have a function specifically for onboarded?
79-
func (r *UserRepository) UpdateUserOnboarded(ctx context.Context, id uuid.UUID) error {
80-
err := r.db.Query.UpdateUserOnboarded(ctx, id)
81-
if err != nil {
82-
if err == pgx.ErrNoRows {
83-
return ErrUserNotFound
84-
}
85-
}
86-
return err
87-
}

apps/api/internal/db/sqlc/event_roles.sql.go

Lines changed: 13 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/api/internal/db/sqlc/models.go

Lines changed: 10 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)