added language setting to the users profile
This commit is contained in:
@@ -441,10 +441,11 @@ func GetUserProfile(c *gin.Context) {
|
||||
|
||||
logger.Debug("Benutzerprofil geladen für: %s (ID: %d)", user.Username, user.UserID)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"id": user.UserID,
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"role": user.Role,
|
||||
"id": user.UserID,
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"role": user.Role,
|
||||
"preferred_language": user.PreferredLanguage,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -554,3 +555,53 @@ func UpdatePassword(c *gin.Context) {
|
||||
"message": "Password updated successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateLanguage Handler to update user's preferred language
|
||||
func UpdateLanguage(c *gin.Context) {
|
||||
logger.Debug("Starte Sprach-Aktualisierung...")
|
||||
|
||||
// Get user ID from context
|
||||
userID, exists := c.Get("userID")
|
||||
if !exists {
|
||||
logger.Error("Benutzer-ID nicht im Context gefunden")
|
||||
respondWithError(c, http.StatusUnauthorized, "Unauthorized")
|
||||
return
|
||||
}
|
||||
|
||||
var input struct {
|
||||
Language string `json:"language" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
logger.Error("Fehler beim Parsen der Sprach-Daten: %s", err.Error())
|
||||
respondWithError(c, http.StatusBadRequest, "Language is required")
|
||||
return
|
||||
}
|
||||
|
||||
// Validate language (only de and en supported)
|
||||
if input.Language != "de" && input.Language != "en" {
|
||||
logger.Warn("Ungültige Sprache angefordert: %s", input.Language)
|
||||
respondWithError(c, http.StatusBadRequest, "Invalid language. Supported languages: de, en")
|
||||
return
|
||||
}
|
||||
|
||||
var user User
|
||||
if err := user.FirstId(userID.(uint)); err != nil {
|
||||
logger.Error("Benutzer mit ID %v nicht gefunden: %s", userID, err.Error())
|
||||
respondWithError(c, http.StatusNotFound, "User not found")
|
||||
return
|
||||
}
|
||||
|
||||
user.PreferredLanguage = input.Language
|
||||
if err := user.Save(); err != nil {
|
||||
logger.Error("Fehler beim Speichern der Sprache für Benutzer %s: %s", user.Username, err.Error())
|
||||
respondWithError(c, http.StatusInternalServerError, "Failed to update language")
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("Sprache erfolgreich aktualisiert für Benutzer: %s (ID: %d) - Neue Sprache: %s", user.Username, user.UserID, input.Language)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Language updated successfully",
|
||||
"language": user.PreferredLanguage,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -461,3 +461,195 @@ func TestUpdatePassword(t *testing.T) {
|
||||
assert.Equal(t, expectedHash, updatedUser.PasswordHash)
|
||||
})
|
||||
}
|
||||
|
||||
// TestUpdateLanguage tests the UpdateLanguage handler
|
||||
func TestUpdateLanguage(t *testing.T) {
|
||||
setupHandlerTestEnvironment(t)
|
||||
|
||||
t.Run("Success - Update language to en", func(t *testing.T) {
|
||||
testUser := createTestUser(t, "languser1", "password123", "languser1@test.com")
|
||||
|
||||
requestData := map[string]interface{}{
|
||||
"language": "en",
|
||||
}
|
||||
requestBody, _ := json.Marshal(requestData)
|
||||
|
||||
req, _ := http.NewRequest("PUT", "/api/user/language", bytes.NewBuffer(requestBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
c.Set("userID", testUser.UserID)
|
||||
|
||||
UpdateLanguage(c)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Language updated successfully", response["message"])
|
||||
assert.Equal(t, "en", response["language"])
|
||||
|
||||
// Verify language was actually updated in database
|
||||
var updatedUser User
|
||||
err = updatedUser.FirstId(testUser.UserID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "en", updatedUser.PreferredLanguage)
|
||||
})
|
||||
|
||||
t.Run("Success - Update language to de", func(t *testing.T) {
|
||||
testUser := createTestUser(t, "languser2", "password123", "languser2@test.com")
|
||||
|
||||
requestData := map[string]interface{}{
|
||||
"language": "de",
|
||||
}
|
||||
requestBody, _ := json.Marshal(requestData)
|
||||
|
||||
req, _ := http.NewRequest("PUT", "/api/user/language", bytes.NewBuffer(requestBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
c.Set("userID", testUser.UserID)
|
||||
|
||||
UpdateLanguage(c)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "de", response["language"])
|
||||
})
|
||||
|
||||
t.Run("Failure - Invalid language", func(t *testing.T) {
|
||||
testUser := createTestUser(t, "languser3", "password123", "languser3@test.com")
|
||||
|
||||
requestData := map[string]interface{}{
|
||||
"language": "fr",
|
||||
}
|
||||
requestBody, _ := json.Marshal(requestData)
|
||||
|
||||
req, _ := http.NewRequest("PUT", "/api/user/language", bytes.NewBuffer(requestBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
c.Set("userID", testUser.UserID)
|
||||
|
||||
UpdateLanguage(c)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Invalid language. Supported languages: de, en", response["error"])
|
||||
})
|
||||
|
||||
t.Run("Failure - No user ID in context", func(t *testing.T) {
|
||||
requestData := map[string]interface{}{
|
||||
"language": "en",
|
||||
}
|
||||
requestBody, _ := json.Marshal(requestData)
|
||||
|
||||
req, _ := http.NewRequest("PUT", "/api/user/language", bytes.NewBuffer(requestBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
|
||||
UpdateLanguage(c)
|
||||
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Unauthorized", response["error"])
|
||||
})
|
||||
|
||||
t.Run("Failure - Empty language", func(t *testing.T) {
|
||||
testUser := createTestUser(t, "languser4", "password123", "languser4@test.com")
|
||||
|
||||
requestData := map[string]interface{}{
|
||||
"language": "",
|
||||
}
|
||||
requestBody, _ := json.Marshal(requestData)
|
||||
|
||||
req, _ := http.NewRequest("PUT", "/api/user/language", bytes.NewBuffer(requestBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
c.Set("userID", testUser.UserID)
|
||||
|
||||
UpdateLanguage(c)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Language is required", response["error"])
|
||||
})
|
||||
}
|
||||
|
||||
// TestGetUserProfileWithLanguage tests that GetUserProfile returns the language field
|
||||
func TestGetUserProfileWithLanguage(t *testing.T) {
|
||||
setupHandlerTestEnvironment(t)
|
||||
|
||||
t.Run("Success - Profile includes preferred language", func(t *testing.T) {
|
||||
testUser := createTestUser(t, "langprofileuser", "password123", "langprofile@test.com")
|
||||
|
||||
// Set language to en
|
||||
testUser.PreferredLanguage = "en"
|
||||
err := testUser.Save()
|
||||
require.NoError(t, err)
|
||||
|
||||
req, _ := http.NewRequest("GET", "/api/user/profile", nil)
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
c.Set("userID", testUser.UserID)
|
||||
|
||||
GetUserProfile(c)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response map[string]interface{}
|
||||
err = json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "en", response["preferred_language"])
|
||||
})
|
||||
|
||||
t.Run("Success - New user has default language de", func(t *testing.T) {
|
||||
testUser := createTestUser(t, "newlanguser", "password123", "newlang@test.com")
|
||||
|
||||
req, _ := http.NewRequest("GET", "/api/user/profile", nil)
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
c.Set("userID", testUser.UserID)
|
||||
|
||||
GetUserProfile(c)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check that preferred_language is present and defaults to "de"
|
||||
lang, ok := response["preferred_language"]
|
||||
assert.True(t, ok, "preferred_language should be present in response")
|
||||
if lang == "" || lang == nil {
|
||||
// If empty, GORM default should be "de"
|
||||
assert.Equal(t, "de", testUser.PreferredLanguage)
|
||||
} else {
|
||||
assert.Equal(t, "de", lang)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ type User struct {
|
||||
PasswordHash string `json:"password"`
|
||||
Email string `gorm:"unique" json:"email"`
|
||||
Role string `gorm:"default:standard" json:"role"`
|
||||
PreferredLanguage string `gorm:"default:de" json:"preferred_language"`
|
||||
ResetPwHash *string `gorm:"index" json:"-"` // Hash für Password-Reset (wird nicht serialisiert)
|
||||
ResetPwHashExpires *time.Time `json:"-"` // Ablaufzeit für Password-Reset-Hash
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
|
||||
@@ -12,6 +12,7 @@ func RegisterRoutes(r *gin.RouterGroup) {
|
||||
userGroup.GET("/profile", GetUserProfile)
|
||||
userGroup.PUT("/email", UpdateEmail)
|
||||
userGroup.PUT("/password", UpdatePassword)
|
||||
userGroup.PUT("/language", UpdateLanguage)
|
||||
}
|
||||
|
||||
// Admin routes - require admin role
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Environment variables for Bamort production environment
|
||||
|
||||
# API Configuration
|
||||
API_URL=https://bamort-api.trokan.de
|
||||
VITE_API_URL=https://bamort-api.trokan.de
|
||||
|
||||
# Database Configuration Backend
|
||||
DATABASE_TYPE=mysql
|
||||
#DATABASE_URL=bamort:bG4)efozrc@tcp(mariadb:3306)/bamort?charset=utf8mb4&parseTime=True&loc=Local
|
||||
|
||||
# MariaDB Configuration
|
||||
MARIADB_ROOT_PASSWORD=moam-bmrt-2.6
|
||||
MARIADB_PASSWORD=bG4)efozrc
|
||||
MARIADB_DATABASE=bamort
|
||||
MARIADB_USER=bamort
|
||||
|
||||
API_PORT=8180
|
||||
BASE_URL=https://bamort.trokan.de
|
||||
TEMPLATES_DIR=./templates
|
||||
EXPORT_TEMP_DIR=./export_temp
|
||||
@@ -530,6 +530,12 @@ export default {
|
||||
username: 'Benutzername',
|
||||
currentEmail: 'Aktuelle E-Mail',
|
||||
role: 'Rolle',
|
||||
language: 'Sprache',
|
||||
changeLanguage: 'Sprache ändern',
|
||||
selectLanguage: 'Sprache auswählen',
|
||||
updateLanguage: 'Sprache aktualisieren',
|
||||
languageUpdateSuccess: 'Sprache erfolgreich aktualisiert',
|
||||
languageUpdateError: 'Fehler beim Aktualisieren der Sprache',
|
||||
changeEmail: 'E-Mail ändern',
|
||||
newEmail: 'Neue E-Mail',
|
||||
emailPlaceholder: 'ihre.email@example.com',
|
||||
|
||||
@@ -526,6 +526,12 @@ export default {
|
||||
username: 'Username',
|
||||
currentEmail: 'Current Email',
|
||||
role: 'Role',
|
||||
language: 'Language',
|
||||
changeLanguage: 'Change Language',
|
||||
selectLanguage: 'Select Language',
|
||||
updateLanguage: 'Update Language',
|
||||
languageUpdateSuccess: 'Language updated successfully',
|
||||
languageUpdateError: 'Failed to update language',
|
||||
changeEmail: 'Change Email',
|
||||
newEmail: 'New Email',
|
||||
emailPlaceholder: 'your.email@example.com',
|
||||
|
||||
@@ -25,6 +25,32 @@
|
||||
{{ $t(`userManagement.roles.${userProfile.role}`) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<label>{{ $t('profile.language') }}:</label>
|
||||
<span>{{ userProfile.preferred_language === 'de' ? 'Deutsch' : 'English' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Change Language Section -->
|
||||
<div class="profile-section">
|
||||
<h2>{{ $t('profile.changeLanguage') }}</h2>
|
||||
<form @submit.prevent="updateLanguage" class="profile-form">
|
||||
<div class="form-group">
|
||||
<label for="language">{{ $t('profile.selectLanguage') }}:</label>
|
||||
<select
|
||||
id="language"
|
||||
v-model="languageForm.selectedLanguage"
|
||||
required
|
||||
>
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="en">English</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" :disabled="isUpdating" class="btn-primary">
|
||||
<span v-if="!isUpdating">{{ $t('profile.updateLanguage') }}</span>
|
||||
<span v-else>{{ $t('profile.updating') }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Change Email Section -->
|
||||
@@ -206,7 +232,11 @@ export default {
|
||||
userProfile: {
|
||||
username: '',
|
||||
email: '',
|
||||
role: 'standard'
|
||||
role: 'standard',
|
||||
preferred_language: 'de'
|
||||
},
|
||||
languageForm: {
|
||||
selectedLanguage: 'de'
|
||||
},
|
||||
emailForm: {
|
||||
newEmail: ''
|
||||
@@ -228,6 +258,7 @@ export default {
|
||||
const response = await API.get('/api/user/profile')
|
||||
this.userProfile = response.data
|
||||
this.emailForm.newEmail = this.userProfile.email
|
||||
this.languageForm.selectedLanguage = this.userProfile.preferred_language || 'de'
|
||||
} catch (error) {
|
||||
console.error('Failed to load profile:', error)
|
||||
alert(this.$t('profile.loadError') + ': ' + (error.response?.data?.error || error.message))
|
||||
@@ -315,6 +346,36 @@ export default {
|
||||
} finally {
|
||||
this.isUpdating = false
|
||||
}
|
||||
},
|
||||
async updateLanguage() {
|
||||
if (!this.languageForm.selectedLanguage) {
|
||||
alert(this.$t('profile.selectLanguage'))
|
||||
return
|
||||
}
|
||||
|
||||
this.isUpdating = true
|
||||
try {
|
||||
const response = await API.put('/api/user/language', {
|
||||
language: this.languageForm.selectedLanguage
|
||||
})
|
||||
|
||||
this.userProfile.preferred_language = response.data.language
|
||||
|
||||
// Update i18n language
|
||||
this.$i18n.locale = response.data.language
|
||||
localStorage.setItem('language', response.data.language)
|
||||
|
||||
alert(this.$t('profile.languageUpdateSuccess'))
|
||||
} catch (error) {
|
||||
console.error('Failed to update language:', error)
|
||||
let errorMsg = this.$t('profile.languageUpdateError')
|
||||
if (error.response?.data?.error) {
|
||||
errorMsg += ': ' + error.response.data.error
|
||||
}
|
||||
alert(errorMsg)
|
||||
} finally {
|
||||
this.isUpdating = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user