feat(router): add routes for consolidated user management
Add DELETE /api/auth/users/:id, PUT /api/auth/users/:id/password, and PUT /api/auth/users/:id routes. Remove individual profile update routes in favor of consolidated endpoints.
This commit is contained in:
@@ -21,7 +21,6 @@ func registerAuthRoutes(cfg *Config, rateLimitMiddleware echo.MiddlewareFunc) {
|
||||
|
||||
// Protected auth routes
|
||||
protected.GET("/auth/profile", cfg.AuthHandler.GetProfile)
|
||||
protected.PUT("/auth/profile", cfg.AuthHandler.UpdateProfile)
|
||||
|
||||
// Refresh token endpoint (no authentication required - uses refresh token from body)
|
||||
e.POST("/api/auth/refresh", cfg.AuthHandler.RefreshAccessToken)
|
||||
@@ -31,13 +30,18 @@ func registerAuthRoutes(cfg *Config, rateLimitMiddleware echo.MiddlewareFunc) {
|
||||
|
||||
// Auth update routes
|
||||
authGroup := e.Group("/api/auth", createJWTMiddleware(cfg))
|
||||
authGroup.PUT("/email", cfg.AuthHandler.UpdateEmail)
|
||||
authGroup.PUT("/username", cfg.AuthHandler.UpdateUsername)
|
||||
authGroup.PUT("/password", cfg.AuthHandler.UpdatePassword)
|
||||
authGroup.PUT("/theme", cfg.AuthHandler.UpdateTheme)
|
||||
|
||||
// Admin-only routes for user management
|
||||
// Profile management (combined handlers - self-edit)
|
||||
authGroup.PUT("/profile", cfg.AuthHandler.UpdateProfile)
|
||||
authGroup.DELETE("/profile", cfg.AuthHandler.DeleteUser)
|
||||
|
||||
// Admin-only routes (same handlers with URL param)
|
||||
admin := protected.Group("/auth", handlers.AdminMiddleware)
|
||||
admin.GET("/users", cfg.AuthHandler.ListUsers)
|
||||
admin.PUT("/users/:id/max-devices", cfg.AuthHandler.UpdateUserMaxDevices)
|
||||
admin.PUT("/profile/:id", cfg.AuthHandler.UpdateProfile)
|
||||
admin.PUT("/password/:id", cfg.AuthHandler.UpdatePassword)
|
||||
admin.DELETE("/profile/:id", cfg.AuthHandler.DeleteUser)
|
||||
}
|
||||
|
||||
+97
-14
@@ -53,7 +53,8 @@ func registerFrontendRoutes(cfg *Config) {
|
||||
e.GET("/login", func(c echo.Context) error {
|
||||
var buf bytes.Buffer
|
||||
sessionExpired := c.QueryParam("session") == "expired"
|
||||
err := templates.Login(sessionExpired).Render(c.Request().Context(), &buf)
|
||||
deleted := c.QueryParam("deleted") == "true"
|
||||
err := templates.Login(sessionExpired, deleted).Render(c.Request().Context(), &buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -361,6 +362,21 @@ func registerFrontendRoutes(cfg *Config) {
|
||||
return c.HTML(http.StatusOK, buf.String())
|
||||
})
|
||||
|
||||
// Profile page (all users)
|
||||
frontendProtected.GET("/profile", func(c echo.Context) error {
|
||||
user, err := getTemplateUserWithTheme(c, cfg)
|
||||
if err != nil {
|
||||
return renderErrorPage(c, "Error loading user", "user_load_error")
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = templates.Profile(user).Render(c.Request().Context(), &buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.HTML(http.StatusOK, buf.String())
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// ADMIN FRONTEND ROUTES
|
||||
// ============================================================================
|
||||
@@ -392,19 +408,6 @@ func registerFrontendRoutes(cfg *Config) {
|
||||
return c.HTML(http.StatusOK, buf.String())
|
||||
}))
|
||||
|
||||
frontendProtected.GET("/admin/profile", handlers.AdminMiddleware(func(c echo.Context) error {
|
||||
user, err := getTemplateUserWithTheme(c, cfg)
|
||||
if err != nil {
|
||||
return renderErrorPage(c, "Error loading user", "user_load_error")
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err = templates.AdminProfile(user).Render(c.Request().Context(), &buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.HTML(http.StatusOK, buf.String())
|
||||
}))
|
||||
|
||||
frontendProtected.GET("/admin/library", handlers.AdminMiddleware(func(c echo.Context) error {
|
||||
user, err := getTemplateUserWithTheme(c, cfg)
|
||||
if err != nil {
|
||||
@@ -418,6 +421,86 @@ func registerFrontendRoutes(cfg *Config) {
|
||||
return c.HTML(http.StatusOK, buf.String())
|
||||
}))
|
||||
|
||||
// Admin users page
|
||||
frontendProtected.GET("/admin/users", handlers.AdminMiddleware(func(c echo.Context) error {
|
||||
user, err := getTemplateUserWithTheme(c, cfg)
|
||||
if err != nil {
|
||||
return renderErrorPage(c, "Error loading user", "user_load_error")
|
||||
}
|
||||
|
||||
// Fetch all users
|
||||
users, err := cfg.Queries.ListUsers(c.Request().Context())
|
||||
if err != nil {
|
||||
return renderErrorPage(c, "Error loading users", "users_load_error")
|
||||
}
|
||||
|
||||
// Count admins for UI protection
|
||||
adminCount := 0
|
||||
for _, u := range users {
|
||||
if u.Role == "admin" {
|
||||
adminCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to template users
|
||||
templateUsers := make([]templates.User, len(users))
|
||||
for i, u := range users {
|
||||
templateUsers[i] = templates.User{
|
||||
ID: uuid.UUID(u.ID.Bytes).String(),
|
||||
Username: u.Username,
|
||||
Email: u.Email,
|
||||
Role: u.Role,
|
||||
Theme: getText(u.Theme),
|
||||
FirstName: getText(u.FirstName),
|
||||
LastName: getText(u.LastName),
|
||||
CreatedAt: u.CreatedAt.Time,
|
||||
}
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = templates.AdminUsers(templateUsers, adminCount, user.ID).Render(c.Request().Context(), &buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.HTML(http.StatusOK, buf.String())
|
||||
}))
|
||||
|
||||
// Admin: Get profile modal for editing user
|
||||
frontendProtected.GET("/admin/users/:id/profile-modal", handlers.AdminMiddleware(func(c echo.Context) error {
|
||||
// Get target user ID from URL
|
||||
targetUserID := c.Param("id")
|
||||
parsedUUID, err := uuid.Parse(targetUserID)
|
||||
if err != nil {
|
||||
return c.HTML(http.StatusBadRequest, "<div>Invalid user ID</div>")
|
||||
}
|
||||
|
||||
// Fetch target user
|
||||
targetUser, err := cfg.Queries.GetUser(c.Request().Context(), uuidToPGType(parsedUUID))
|
||||
if err != nil {
|
||||
return c.HTML(http.StatusNotFound, "<div>User not found</div>")
|
||||
}
|
||||
|
||||
// Convert to template user
|
||||
templateUser := templates.User{
|
||||
ID: uuid.UUID(targetUser.ID.Bytes).String(),
|
||||
Username: targetUser.Username,
|
||||
Email: targetUser.Email,
|
||||
Role: targetUser.Role,
|
||||
Theme: getText(targetUser.Theme),
|
||||
FirstName: getText(targetUser.FirstName),
|
||||
LastName: getText(targetUser.LastName),
|
||||
CreatedAt: targetUser.CreatedAt.Time,
|
||||
}
|
||||
|
||||
// Render modal
|
||||
var buf bytes.Buffer
|
||||
err = templates.ProfileModal(templateUser).Render(c.Request().Context(), &buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.HTML(http.StatusOK, buf.String())
|
||||
}))
|
||||
|
||||
// ============================================================================
|
||||
// LEGACY API ROUTES (for backward compatibility)
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user