Refactor user profile update handler to support optional email field and dynamic query construction

This commit is contained in:
Leon Bösche
2026-01-27 08:47:21 +01:00
parent 8400e97d17
commit 425bfcb495
3 changed files with 78 additions and 60 deletions

View File

@@ -28,7 +28,6 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
// Profile fields // Profile fields
late TextEditingController _displayNameController; late TextEditingController _displayNameController;
late TextEditingController _emailController;
String? _avatarUrl; String? _avatarUrl;
String? _blurHash; String? _blurHash;
@@ -43,7 +42,6 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
void initState() { void initState() {
super.initState(); super.initState();
_displayNameController = TextEditingController(); _displayNameController = TextEditingController();
_emailController = TextEditingController();
_currentPasswordController = TextEditingController(); _currentPasswordController = TextEditingController();
_newPasswordController = TextEditingController(); _newPasswordController = TextEditingController();
_confirmPasswordController = TextEditingController(); _confirmPasswordController = TextEditingController();
@@ -53,7 +51,6 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
@override @override
void dispose() { void dispose() {
_displayNameController.dispose(); _displayNameController.dispose();
_emailController.dispose();
_currentPasswordController.dispose(); _currentPasswordController.dispose();
_newPasswordController.dispose(); _newPasswordController.dispose();
_confirmPasswordController.dispose(); _confirmPasswordController.dispose();
@@ -65,7 +62,6 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
if (authState is AuthAuthenticated) { if (authState is AuthAuthenticated) {
_currentUser = authState.user; _currentUser = authState.user;
_displayNameController.text = _currentUser?.displayName ?? ''; _displayNameController.text = _currentUser?.displayName ?? '';
_emailController.text = _currentUser?.email ?? '';
_avatarUrl = _currentUser?.avatarUrl; _avatarUrl = _currentUser?.avatarUrl;
_blurHash = _currentUser?.blurHash; _blurHash = _currentUser?.blurHash;
} }
@@ -135,7 +131,6 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
displayName: _displayNameController.text.isEmpty displayName: _displayNameController.text.isEmpty
? null ? null
: _displayNameController.text, : _displayNameController.text,
email: _emailController.text,
avatarUrl: _avatarUrl, avatarUrl: _avatarUrl,
blurHash: _blurHash, blurHash: _blurHash,
); );
@@ -144,7 +139,6 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
displayName: _displayNameController.text.isEmpty displayName: _displayNameController.text.isEmpty
? null ? null
: _displayNameController.text, : _displayNameController.text,
email: _emailController.text,
avatarUrl: _avatarUrl, avatarUrl: _avatarUrl,
blurHash: _blurHash, blurHash: _blurHash,
); );
@@ -340,6 +334,7 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
Center( Center(
child: Column( child: Column(
children: [ children: [
SizedBox(height: 16),
GestureDetector( GestureDetector(
onTap: _pickAndUploadAvatar, onTap: _pickAndUploadAvatar,
child: CircleAvatar( child: CircleAvatar(
@@ -397,41 +392,27 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
), ),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 24),
// Email // Save Button
Text( Center(
'Email', child: SizedBox(
style: TextStyle( width: 88,
color: AppTheme.primaryText, child: ModernGlassButton(
fontWeight: FontWeight.bold, onPressed: _updateProfile,
), child: const Text('Save Changes'),
),
const SizedBox(height: 8),
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
style: TextStyle(color: AppTheme.primaryText),
decoration: InputDecoration(
hintText: 'Enter email',
hintStyle: TextStyle(color: AppTheme.secondaryText),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: AppTheme.secondaryText),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: AppTheme.secondaryText),
), ),
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
// Save Button // Logout Button
Center( Center(
child: ModernGlassButton( child: IconButton(
onPressed: _updateProfile, onPressed: _logout,
child: const Text('Save Changes'), icon: Icon(Icons.logout, color: AppTheme.errorColor),
tooltip: 'Logout',
iconSize: 28,
), ),
), ),
], ],
@@ -530,19 +511,12 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
// Change Password Button // Change Password Button
Center( Center(
child: ModernGlassButton( child: SizedBox(
onPressed: _changePassword, width: 88,
child: const Text('Change Password'), child: ModernGlassButton(
), onPressed: _changePassword,
), child: const Text('Change Password'),
const SizedBox(height: 24), ),
// Logout Button
Center(
child: TextButton(
onPressed: _logout,
style: TextButton.styleFrom(foregroundColor: AppTheme.errorColor),
child: const Text('Logout'),
), ),
), ),
], ],

Binary file not shown.

View File

@@ -3735,7 +3735,7 @@ func updateUserProfileHandler(w http.ResponseWriter, r *http.Request, db *databa
var req struct { var req struct {
DisplayName *string `json:"displayName"` DisplayName *string `json:"displayName"`
Email string `json:"email"` Email *string `json:"email"`
AvatarURL *string `json:"avatarUrl"` AvatarURL *string `json:"avatarUrl"`
BlurHash *string `json:"blurHash"` BlurHash *string `json:"blurHash"`
} }
@@ -3745,11 +3745,44 @@ func updateUserProfileHandler(w http.ResponseWriter, r *http.Request, db *databa
return return
} }
// Update user // Build dynamic update query
_, err = db.ExecContext(r.Context(), var setParts []string
`UPDATE users SET display_name = $1, email = $2, avatar_url = $3, blur_hash = $4, updated_at = NOW() var args []interface{}
WHERE id = $5`, argIndex := 1
req.DisplayName, req.Email, req.AvatarURL, req.BlurHash, userID)
if req.DisplayName != nil {
setParts = append(setParts, fmt.Sprintf("display_name = $%d", argIndex))
args = append(args, *req.DisplayName)
argIndex++
}
if req.Email != nil {
setParts = append(setParts, fmt.Sprintf("email = $%d", argIndex))
args = append(args, *req.Email)
argIndex++
}
if req.AvatarURL != nil {
setParts = append(setParts, fmt.Sprintf("avatar_url = $%d", argIndex))
args = append(args, *req.AvatarURL)
argIndex++
}
if req.BlurHash != nil {
setParts = append(setParts, fmt.Sprintf("blur_hash = $%d", argIndex))
args = append(args, *req.BlurHash)
argIndex++
}
if len(setParts) == 0 {
// No fields to update
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"message": "No changes to update"})
return
}
setParts = append(setParts, "updated_at = NOW()")
query := fmt.Sprintf("UPDATE users SET %s WHERE id = $%d", strings.Join(setParts, ", "), argIndex)
args = append(args, userID)
_, err = db.ExecContext(r.Context(), query, args...)
if err != nil { if err != nil {
errors.LogError(r, err, "Failed to update user profile") errors.LogError(r, err, "Failed to update user profile")
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError) errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
@@ -3757,14 +3790,25 @@ func updateUserProfileHandler(w http.ResponseWriter, r *http.Request, db *databa
} }
// Audit log // Audit log
metadata := make(map[string]interface{})
if req.DisplayName != nil {
metadata["displayName"] = *req.DisplayName
}
if req.Email != nil {
metadata["email"] = *req.Email
}
if req.AvatarURL != nil {
metadata["avatarUrl"] = *req.AvatarURL
}
if req.BlurHash != nil {
metadata["blurHash"] = *req.BlurHash
}
auditLogger.Log(r.Context(), audit.Entry{ auditLogger.Log(r.Context(), audit.Entry{
UserID: &userID, UserID: &userID,
Action: "profile_update", Action: "profile_update",
Success: true, Success: true,
Metadata: map[string]interface{}{ Metadata: metadata,
"displayName": req.DisplayName,
"email": req.Email,
},
}) })
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)