Enhance user profile handling by fetching full profile data and updating avatar URLs in AuthBloc and related components
This commit is contained in:
@@ -6,6 +6,7 @@ import 'auth_event.dart';
|
|||||||
import 'auth_state.dart';
|
import 'auth_state.dart';
|
||||||
import '../../services/api_client.dart';
|
import '../../services/api_client.dart';
|
||||||
import '../../models/api_error.dart';
|
import '../../models/api_error.dart';
|
||||||
|
import '../../models/user.dart';
|
||||||
|
|
||||||
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||||
final ApiClient apiClient;
|
final ApiClient apiClient;
|
||||||
@@ -109,6 +110,20 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
|
|
||||||
sessionBloc.add(SessionStarted(token));
|
sessionBloc.add(SessionStarted(token));
|
||||||
|
|
||||||
|
// Fetch full profile and include it in state when possible
|
||||||
|
try {
|
||||||
|
final profile = await apiClient.getUserProfile();
|
||||||
|
final fullUser = profile.isNotEmpty ? User.fromJson(profile) : null;
|
||||||
|
emit(
|
||||||
|
AuthAuthenticated(
|
||||||
|
token: token,
|
||||||
|
userId: user['id'],
|
||||||
|
username: user['username'],
|
||||||
|
email: user['email'],
|
||||||
|
user: fullUser,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
emit(
|
emit(
|
||||||
AuthAuthenticated(
|
AuthAuthenticated(
|
||||||
token: token,
|
token: token,
|
||||||
@@ -117,6 +132,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
email: user['email'],
|
email: user['email'],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
final errorMessage = _extractErrorMessage(e);
|
final errorMessage = _extractErrorMessage(e);
|
||||||
emit(AuthFailure(errorMessage));
|
emit(AuthFailure(errorMessage));
|
||||||
@@ -189,6 +205,20 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
|
|
||||||
sessionBloc.add(SessionStarted(token));
|
sessionBloc.add(SessionStarted(token));
|
||||||
|
|
||||||
|
// Fetch full profile and include it in state when possible
|
||||||
|
try {
|
||||||
|
final profile = await apiClient.getUserProfile();
|
||||||
|
final fullUser = profile.isNotEmpty ? User.fromJson(profile) : null;
|
||||||
|
emit(
|
||||||
|
AuthAuthenticated(
|
||||||
|
token: token,
|
||||||
|
userId: user['id'],
|
||||||
|
username: user['username'],
|
||||||
|
email: user['email'],
|
||||||
|
user: fullUser,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
emit(
|
emit(
|
||||||
AuthAuthenticated(
|
AuthAuthenticated(
|
||||||
token: token,
|
token: token,
|
||||||
@@ -197,6 +227,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
email: user['email'],
|
email: user['email'],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
final errorMessage = _extractErrorMessage(e);
|
final errorMessage = _extractErrorMessage(e);
|
||||||
emit(AuthFailure(errorMessage));
|
emit(AuthFailure(errorMessage));
|
||||||
@@ -229,6 +260,21 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
final user = response['user'];
|
final user = response['user'];
|
||||||
|
|
||||||
sessionBloc.add(SessionStarted(token));
|
sessionBloc.add(SessionStarted(token));
|
||||||
|
|
||||||
|
// Fetch full profile and include it in state when possible
|
||||||
|
try {
|
||||||
|
final profile = await apiClient.getUserProfile();
|
||||||
|
final fullUser = profile.isNotEmpty ? User.fromJson(profile) : null;
|
||||||
|
emit(
|
||||||
|
AuthAuthenticated(
|
||||||
|
token: token,
|
||||||
|
userId: user['id'],
|
||||||
|
username: user['username'],
|
||||||
|
email: user['email'],
|
||||||
|
user: fullUser,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
emit(
|
emit(
|
||||||
AuthAuthenticated(
|
AuthAuthenticated(
|
||||||
token: token,
|
token: token,
|
||||||
@@ -237,6 +283,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
email: user['email'],
|
email: user['email'],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
final errorMessage = _extractErrorMessage(e);
|
final errorMessage = _extractErrorMessage(e);
|
||||||
emit(AuthFailure(errorMessage));
|
emit(AuthFailure(errorMessage));
|
||||||
@@ -258,8 +305,21 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
final sessionState = sessionBloc.state;
|
final sessionState = sessionBloc.state;
|
||||||
|
|
||||||
if (sessionState is SessionActive) {
|
if (sessionState is SessionActive) {
|
||||||
// Session already active - emit authenticated state with minimal info
|
// Try to fetch full profile immediately so UI can show avatar/displayName
|
||||||
// The full user info will be fetched when needed
|
try {
|
||||||
|
final profile = await apiClient.getUserProfile();
|
||||||
|
final fullUser = profile.isNotEmpty ? User.fromJson(profile) : null;
|
||||||
|
emit(
|
||||||
|
AuthAuthenticated(
|
||||||
|
token: sessionState.token,
|
||||||
|
userId: fullUser?.id ?? '',
|
||||||
|
username: fullUser?.username ?? '',
|
||||||
|
email: fullUser?.email ?? '',
|
||||||
|
user: fullUser,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// Fall back to minimal authenticated state if profile fetch fails
|
||||||
emit(
|
emit(
|
||||||
AuthAuthenticated(
|
AuthAuthenticated(
|
||||||
token: sessionState.token,
|
token: sessionState.token,
|
||||||
@@ -268,6 +328,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
email: '',
|
email: '',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
emit(AuthUnauthenticated());
|
emit(AuthUnauthenticated());
|
||||||
}
|
}
|
||||||
@@ -277,9 +338,25 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
UpdateUserProfile event,
|
UpdateUserProfile event,
|
||||||
Emitter<AuthState> emit,
|
Emitter<AuthState> emit,
|
||||||
) async {
|
) async {
|
||||||
// For now, just update the local state - in real app, call API
|
|
||||||
if (state is AuthAuthenticated) {
|
if (state is AuthAuthenticated) {
|
||||||
final currentState = state as AuthAuthenticated;
|
final currentState = state as AuthAuthenticated;
|
||||||
|
// Try to reload profile from backend to ensure we have canonical avatar URL (with token+version)
|
||||||
|
try {
|
||||||
|
final profile = await apiClient.getUserProfile();
|
||||||
|
final fullUser = profile.isNotEmpty
|
||||||
|
? User.fromJson(profile)
|
||||||
|
: event.updatedUser;
|
||||||
|
emit(
|
||||||
|
AuthAuthenticated(
|
||||||
|
token: currentState.token,
|
||||||
|
userId: fullUser.id,
|
||||||
|
username: fullUser.username,
|
||||||
|
email: fullUser.email,
|
||||||
|
user: fullUser,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// Fallback to using the provided updatedUser
|
||||||
emit(
|
emit(
|
||||||
AuthAuthenticated(
|
AuthAuthenticated(
|
||||||
token: currentState.token,
|
token: currentState.token,
|
||||||
@@ -291,4 +368,5 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -400,9 +400,21 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: isAvatar
|
child: isAvatar
|
||||||
? CircleAvatar(
|
? BlocBuilder<AuthBloc, AuthState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state is AuthAuthenticated &&
|
||||||
|
state.user?.avatarUrl != null) {
|
||||||
|
return CircleAvatar(
|
||||||
|
radius: 20,
|
||||||
|
backgroundImage: NetworkImage(state.user!.avatarUrl!),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return CircleAvatar(
|
||||||
backgroundColor: isSelected ? highlightColor : defaultColor,
|
backgroundColor: isSelected ? highlightColor : defaultColor,
|
||||||
child: Icon(icon, color: AppTheme.primaryBackground),
|
child: Icon(icon, color: AppTheme.primaryBackground),
|
||||||
|
);
|
||||||
|
},
|
||||||
)
|
)
|
||||||
: Column(
|
: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|||||||
@@ -109,25 +109,40 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_avatarUrl = response['avatarUrl'] as String?;
|
|
||||||
_isUploadingAvatar = false;
|
_isUploadingAvatar = false;
|
||||||
_avatarUploadProgress = 0.0;
|
_avatarUploadProgress = 0.0;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (_avatarUrl != null) {
|
// Fetch latest profile from backend so we get the canonical avatar URL and version
|
||||||
|
try {
|
||||||
|
final apiClient = GetIt.I<ApiClient>();
|
||||||
|
final profile = await apiClient.getUserProfile();
|
||||||
|
final newAvatar = profile['avatarUrl'] as String?;
|
||||||
|
|
||||||
|
setState(() => _avatarUrl = newAvatar);
|
||||||
|
|
||||||
|
if (profile.isNotEmpty && mounted) {
|
||||||
|
final updatedUser = User.fromJson(profile);
|
||||||
|
context.read<AuthBloc>().add(UpdateUserProfile(updatedUser));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Fall back to response avatar URL if profile fetch fails
|
||||||
|
setState(() {
|
||||||
|
_avatarUrl = response['avatarUrl'] as String?;
|
||||||
final authState = context.read<AuthBloc>().state;
|
final authState = context.read<AuthBloc>().state;
|
||||||
if (authState is AuthAuthenticated) {
|
if (authState is AuthAuthenticated && _avatarUrl != null) {
|
||||||
_avatarUrl = "$_avatarUrl&token=${authState.token}";
|
_avatarUrl = "$_avatarUrl&token=${authState.token}";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Update auth state with new avatar
|
|
||||||
if (_currentUser != null && _avatarUrl != null) {
|
if (_currentUser != null && _avatarUrl != null) {
|
||||||
final updatedUser = _currentUser!.copyWith(avatarUrl: _avatarUrl);
|
final updatedUser = _currentUser!.copyWith(
|
||||||
|
avatarUrl: _avatarUrl,
|
||||||
|
);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
context.read<AuthBloc>().add(UpdateUserProfile(updatedUser));
|
context.read<AuthBloc>().add(UpdateUserProfile(updatedUser));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
@@ -168,14 +183,13 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
|||||||
setState(() => _isLoading = true);
|
setState(() => _isLoading = true);
|
||||||
try {
|
try {
|
||||||
final apiClient = GetIt.I<ApiClient>();
|
final apiClient = GetIt.I<ApiClient>();
|
||||||
await apiClient.updateUserProfile(
|
final response = await apiClient.updateUserProfile(
|
||||||
displayName: _displayNameController.text,
|
displayName: _displayNameController.text,
|
||||||
);
|
);
|
||||||
|
|
||||||
final updatedUser = _currentUser!.copyWith(
|
// Use returned profile to update auth state (ensures avatarUrl + version are present)
|
||||||
displayName: _displayNameController.text,
|
final updatedProfile = response;
|
||||||
avatarUrl: _avatarUrl,
|
final updatedUser = User.fromJson(updatedProfile);
|
||||||
);
|
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
// Update auth state
|
// Update auth state
|
||||||
|
|||||||
@@ -3830,12 +3830,13 @@ func getUserProfileHandler(w http.ResponseWriter, r *http.Request, db *database.
|
|||||||
AvatarURL *string `json:"avatarUrl"`
|
AvatarURL *string `json:"avatarUrl"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
LastLoginAt *time.Time `json:"lastLoginAt"`
|
LastLoginAt *time.Time `json:"lastLoginAt"`
|
||||||
|
UpdatedAt *time.Time `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.QueryRowContext(r.Context(),
|
err = db.QueryRowContext(r.Context(),
|
||||||
`SELECT id, username, email, display_name, avatar_url, created_at, last_login_at
|
`SELECT id, username, email, display_name, avatar_url, created_at, last_login_at, updated_at
|
||||||
FROM users WHERE id = $1`, userID).
|
FROM users WHERE id = $1`, userID).
|
||||||
Scan(&user.ID, &user.Username, &user.Email, &user.DisplayName, &user.AvatarURL, &user.CreatedAt, &user.LastLoginAt)
|
Scan(&user.ID, &user.Username, &user.Email, &user.DisplayName, &user.AvatarURL, &user.CreatedAt, &user.LastLoginAt, &user.UpdatedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
errors.WriteError(w, errors.CodeNotFound, "User not found", http.StatusNotFound)
|
errors.WriteError(w, errors.CodeNotFound, "User not found", http.StatusNotFound)
|
||||||
@@ -3848,7 +3849,20 @@ func getUserProfileHandler(w http.ResponseWriter, r *http.Request, db *database.
|
|||||||
|
|
||||||
// If avatar exists, return the backend URL instead of the internal WebDAV URL
|
// If avatar exists, return the backend URL instead of the internal WebDAV URL
|
||||||
if user.AvatarURL != nil && *user.AvatarURL != "" {
|
if user.AvatarURL != nil && *user.AvatarURL != "" {
|
||||||
user.AvatarURL = &[]string{fmt.Sprintf("https://go.b0esche.cloud/user/avatar?v=%d", time.Now().Unix())}[0]
|
// Use updated_at for versioning if available to allow cache busting when avatar changes
|
||||||
|
var v int64
|
||||||
|
if user.UpdatedAt != nil {
|
||||||
|
v = user.UpdatedAt.Unix()
|
||||||
|
} else {
|
||||||
|
v = time.Now().Unix()
|
||||||
|
}
|
||||||
|
// Include token in the avatar URL so frontends that cannot set headers (Image.network) can fetch it
|
||||||
|
token, ok := middleware.GetToken(r.Context())
|
||||||
|
if ok && token != "" {
|
||||||
|
user.AvatarURL = &[]string{fmt.Sprintf("https://go.b0esche.cloud/user/avatar?v=%d&token=%s", v, token)}[0]
|
||||||
|
} else {
|
||||||
|
user.AvatarURL = &[]string{fmt.Sprintf("https://go.b0esche.cloud/user/avatar?v=%d", v)}[0]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
@@ -3929,8 +3943,44 @@ func updateUserProfileHandler(w http.ResponseWriter, r *http.Request, db *databa
|
|||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Return updated profile JSON
|
||||||
|
var updatedUser struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
DisplayName *string `json:"displayName"`
|
||||||
|
AvatarURL *string `json:"avatarUrl"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
LastLoginAt *time.Time `json:"lastLoginAt"`
|
||||||
|
UpdatedAt *time.Time `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.QueryRowContext(r.Context(),
|
||||||
|
`SELECT id, username, email, display_name, avatar_url, created_at, last_login_at, updated_at
|
||||||
|
FROM users WHERE id = $1`, userID).
|
||||||
|
Scan(&updatedUser.ID, &updatedUser.Username, &updatedUser.Email, &updatedUser.DisplayName, &updatedUser.AvatarURL, &updatedUser.CreatedAt, &updatedUser.LastLoginAt, &updatedUser.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogError(r, err, "Failed to fetch updated user profile")
|
||||||
|
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if updatedUser.AvatarURL != nil && *updatedUser.AvatarURL != "" {
|
||||||
|
var v int64
|
||||||
|
if updatedUser.UpdatedAt != nil {
|
||||||
|
v = updatedUser.UpdatedAt.Unix()
|
||||||
|
} else {
|
||||||
|
v = time.Now().Unix()
|
||||||
|
}
|
||||||
|
if token, ok := middleware.GetToken(r.Context()); ok && token != "" {
|
||||||
|
updatedUser.AvatarURL = &[]string{fmt.Sprintf("https://go.b0esche.cloud/user/avatar?v=%d&token=%s", v, token)}[0]
|
||||||
|
} else {
|
||||||
|
updatedUser.AvatarURL = &[]string{fmt.Sprintf("https://go.b0esche.cloud/user/avatar?v=%d", v)}[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "Profile updated"})
|
json.NewEncoder(w).Encode(updatedUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
// changePasswordHandler changes the current user's password
|
// changePasswordHandler changes the current user's password
|
||||||
@@ -4020,14 +4070,14 @@ func uploadUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *databas
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate unique filename
|
// Generate deterministic filename based on user ID
|
||||||
ext := filepath.Ext(header.Filename)
|
ext := filepath.Ext(header.Filename)
|
||||||
if ext == "" {
|
if ext == "" {
|
||||||
ext = ".jpg" // default extension
|
ext = ".png" // default extension
|
||||||
}
|
}
|
||||||
filename := fmt.Sprintf("%s%s", uuid.New().String(), ext)
|
filename := fmt.Sprintf("%s%s", userID.String(), ext)
|
||||||
|
|
||||||
// Upload to Nextcloud
|
// Upload to Nextcloud at .avatars/<user-id>.<ext>
|
||||||
client := storage.NewWebDAVClient(cfg)
|
client := storage.NewWebDAVClient(cfg)
|
||||||
avatarPath := fmt.Sprintf(".avatars/%s", filename)
|
avatarPath := fmt.Sprintf(".avatars/%s", filename)
|
||||||
err = client.Upload(r.Context(), avatarPath, bytes.NewReader(fileBytes), header.Size)
|
err = client.Upload(r.Context(), avatarPath, bytes.NewReader(fileBytes), header.Size)
|
||||||
@@ -4037,12 +4087,9 @@ func uploadUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *databas
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get public URL - for now, construct it manually since Nextcloud doesn't provide direct public URLs
|
|
||||||
// In a real setup, you'd configure Nextcloud to serve public URLs or use a CDN
|
|
||||||
publicURL := fmt.Sprintf("https://go.b0esche.cloud/user/avatar?v=%d", time.Now().Unix())
|
|
||||||
webdavURL := fmt.Sprintf("%s/%s", client.BaseURL, avatarPath)
|
webdavURL := fmt.Sprintf("%s/%s", client.BaseURL, avatarPath)
|
||||||
|
|
||||||
// Update user profile with avatar URL
|
// Update user profile with avatar URL and updated_at
|
||||||
_, err = db.ExecContext(r.Context(),
|
_, err = db.ExecContext(r.Context(),
|
||||||
`UPDATE users SET avatar_url = $1, updated_at = NOW() WHERE id = $2`,
|
`UPDATE users SET avatar_url = $1, updated_at = NOW() WHERE id = $2`,
|
||||||
webdavURL, userID)
|
webdavURL, userID)
|
||||||
@@ -4063,6 +4110,19 @@ func uploadUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *databas
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Build public URL including version based on updated_at and include token if available
|
||||||
|
var version int64 = time.Now().Unix()
|
||||||
|
// Try to use updated_at from DB to be more accurate
|
||||||
|
var updatedAt time.Time
|
||||||
|
err = db.QueryRowContext(r.Context(), `SELECT updated_at FROM users WHERE id = $1`, userID).Scan(&updatedAt)
|
||||||
|
if err == nil {
|
||||||
|
version = updatedAt.Unix()
|
||||||
|
}
|
||||||
|
publicURL := fmt.Sprintf("https://go.b0esche.cloud/user/avatar?v=%d", version)
|
||||||
|
if token, ok := middleware.GetToken(r.Context()); ok && token != "" {
|
||||||
|
publicURL = fmt.Sprintf("%s&token=%s", publicURL, token)
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
"message": "Avatar uploaded successfully",
|
"message": "Avatar uploaded successfully",
|
||||||
@@ -4072,7 +4132,14 @@ func uploadUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *databas
|
|||||||
|
|
||||||
// getUserAvatarHandler serves the user's avatar image
|
// getUserAvatarHandler serves the user's avatar image
|
||||||
func getUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *database.DB, jwtManager *jwt.Manager, cfg *config.Config) {
|
func getUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *database.DB, jwtManager *jwt.Manager, cfg *config.Config) {
|
||||||
|
// Accept token via query param or Authorization header (Bearer)
|
||||||
tokenString := r.URL.Query().Get("token")
|
tokenString := r.URL.Query().Get("token")
|
||||||
|
if tokenString == "" {
|
||||||
|
authHeader := r.Header.Get("Authorization")
|
||||||
|
if strings.HasPrefix(authHeader, "Bearer ") {
|
||||||
|
tokenString = strings.TrimPrefix(authHeader, "Bearer ")
|
||||||
|
}
|
||||||
|
}
|
||||||
if tokenString == "" {
|
if tokenString == "" {
|
||||||
errors.WriteError(w, errors.CodeUnauthenticated, "Unauthorized", http.StatusUnauthorized)
|
errors.WriteError(w, errors.CodeUnauthenticated, "Unauthorized", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user