Refactor user profile update handler to support optional email field and dynamic query construction
This commit is contained in:
@@ -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,20 +511,13 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
|||||||
|
|
||||||
// Change Password Button
|
// Change Password Button
|
||||||
Center(
|
Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 88,
|
||||||
child: ModernGlassButton(
|
child: ModernGlassButton(
|
||||||
onPressed: _changePassword,
|
onPressed: _changePassword,
|
||||||
child: const Text('Change Password'),
|
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'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
BIN
go_cloud/api
BIN
go_cloud/api
Binary file not shown.
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user