Refactor updateUserProfile method to require displayName and simplify data construction in ApiClient
Add GET route for user avatar retrieval and update CORS settings in routes.go Implement getUserAvatarHandler to serve user avatars from storage
This commit is contained in:
@@ -183,12 +183,11 @@ class ApiClient {
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> updateUserProfile({
|
||||
String? displayName,
|
||||
required String displayName,
|
||||
String? email,
|
||||
String? avatarUrl,
|
||||
}) async {
|
||||
final data = <String, dynamic>{};
|
||||
if (displayName != null) data['displayName'] = displayName;
|
||||
final data = <String, dynamic>{'displayName': displayName};
|
||||
if (email != null) data['email'] = email;
|
||||
if (avatarUrl != null) data['avatarUrl'] = avatarUrl;
|
||||
|
||||
|
||||
@@ -139,16 +139,12 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
try {
|
||||
final apiClient = GetIt.I<ApiClient>();
|
||||
await apiClient.updateUserProfile(
|
||||
displayName: _displayNameController.text.isEmpty
|
||||
? null
|
||||
: _displayNameController.text,
|
||||
displayName: _displayNameController.text,
|
||||
avatarUrl: _avatarUrl,
|
||||
);
|
||||
|
||||
final updatedUser = _currentUser!.copyWith(
|
||||
displayName: _displayNameController.text.isEmpty
|
||||
? null
|
||||
: _displayNameController.text,
|
||||
displayName: _displayNameController.text,
|
||||
avatarUrl: _avatarUrl,
|
||||
);
|
||||
|
||||
|
||||
BIN
go_cloud/api
BIN
go_cloud/api
Binary file not shown.
@@ -259,9 +259,12 @@ func NewRouter(cfg *config.Config, db *database.DB, jwtManager *jwt.Manager, aut
|
||||
r.Post("/user/avatar", func(w http.ResponseWriter, req *http.Request) {
|
||||
uploadUserAvatarHandler(w, req, db, auditLogger, cfg)
|
||||
})
|
||||
r.Get("/user/avatar", func(w http.ResponseWriter, req *http.Request) {
|
||||
getUserAvatarHandler(w, req, db, cfg)
|
||||
})
|
||||
r.Options("/user/avatar", func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
@@ -3843,6 +3846,11 @@ func getUserProfileHandler(w http.ResponseWriter, r *http.Request, db *database.
|
||||
return
|
||||
}
|
||||
|
||||
// If avatar exists, return the backend URL instead of the internal WebDAV URL
|
||||
if user.AvatarURL != nil && *user.AvatarURL != "" {
|
||||
user.AvatarURL = &[]string{"/user/avatar"}[0]
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(user)
|
||||
}
|
||||
@@ -4030,7 +4038,7 @@ func uploadUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *databas
|
||||
|
||||
// Upload to Nextcloud
|
||||
client := storage.NewWebDAVClient(cfg)
|
||||
avatarPath := fmt.Sprintf("avatars/%s", filename)
|
||||
avatarPath := fmt.Sprintf("remote.php/dav/files/b0esche/avatars/%s", filename)
|
||||
err = client.Upload(r.Context(), avatarPath, bytes.NewReader(fileBytes), header.Size)
|
||||
if err != nil {
|
||||
errors.LogError(r, err, "Failed to upload avatar")
|
||||
@@ -4040,11 +4048,7 @@ func uploadUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *databas
|
||||
|
||||
// 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
|
||||
baseURL := cfg.NextcloudURL
|
||||
if !strings.HasSuffix(baseURL, "/") {
|
||||
baseURL += "/"
|
||||
}
|
||||
publicURL := fmt.Sprintf("%sindex.php/apps/files_sharing/public.php/webdav/avatars/%s", baseURL, filename)
|
||||
publicURL := "/user/avatar"
|
||||
|
||||
// Update user profile with avatar URL
|
||||
_, err = db.ExecContext(r.Context(),
|
||||
@@ -4074,6 +4078,66 @@ func uploadUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *databas
|
||||
})
|
||||
}
|
||||
|
||||
// getUserAvatarHandler serves the user's avatar image
|
||||
func getUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *database.DB, cfg *config.Config) {
|
||||
userIDStr, ok := middleware.GetUserID(r.Context())
|
||||
if !ok {
|
||||
errors.WriteError(w, errors.CodeUnauthenticated, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := uuid.Parse(userIDStr)
|
||||
if err != nil {
|
||||
errors.WriteError(w, errors.CodeInvalidArgument, "Invalid user ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var avatarURL *string
|
||||
err = db.QueryRowContext(r.Context(),
|
||||
`SELECT avatar_url FROM users WHERE id = $1`, userID).
|
||||
Scan(&avatarURL)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
errors.WriteError(w, errors.CodeNotFound, "User not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
errors.LogError(r, err, "Failed to get user avatar")
|
||||
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if avatarURL == nil || *avatarURL == "" {
|
||||
// No avatar, return 404
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the avatar URL to get the remote path
|
||||
// Assuming avatarURL is like https://storage.b0esche.cloud/remote.php/dav/files/b0esche/avatars/filename
|
||||
baseURL := cfg.NextcloudURL
|
||||
if !strings.HasSuffix(baseURL, "/") {
|
||||
baseURL += "/"
|
||||
}
|
||||
remotePath := strings.TrimPrefix(*avatarURL, baseURL)
|
||||
|
||||
// Download from WebDAV
|
||||
client := storage.NewWebDAVClient(cfg)
|
||||
resp, err := client.Download(r.Context(), remotePath, "")
|
||||
if err != nil {
|
||||
errors.LogError(r, err, "Failed to download avatar")
|
||||
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Copy headers
|
||||
for k, v := range resp.Header {
|
||||
w.Header()[k] = v
|
||||
}
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
io.Copy(w, resp.Body)
|
||||
}
|
||||
|
||||
// deleteUserAccountHandler handles user account deletion
|
||||
func deleteUserAccountHandler(w http.ResponseWriter, r *http.Request, db *database.DB, auditLogger *audit.Logger, cfg *config.Config) {
|
||||
userIDStr, ok := middleware.GetUserID(r.Context())
|
||||
|
||||
Reference in New Issue
Block a user