feat(users): add user_preferences migration with appearance fields
This commit is contained in:
parent
93666a3390
commit
6f4c9c50ff
4 changed files with 83 additions and 24 deletions
|
|
@ -3,6 +3,7 @@ package user
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -398,6 +399,10 @@ func (s *Service) GetUserPreferences(userID uuid.UUID) (*UserPreferencesResponse
|
|||
COALESCE(notifications, '{}') as notifications,
|
||||
COALESCE(privacy, '{}') as privacy,
|
||||
COALESCE(audio, '{}') as audio,
|
||||
COALESCE(contrast, 'normal') as contrast,
|
||||
COALESCE(density, 'comfortable') as density,
|
||||
COALESCE(accent_hue, 220) as accent_hue,
|
||||
COALESCE(font_size, 16) as font_size,
|
||||
updated_at
|
||||
FROM user_preferences
|
||||
WHERE user_id = $1
|
||||
|
|
@ -409,7 +414,8 @@ func (s *Service) GetUserPreferences(userID uuid.UUID) (*UserPreferencesResponse
|
|||
err := s.db.QueryRow(query, userID).Scan(
|
||||
&preferences.UserID, &preferences.Theme, &preferences.Language,
|
||||
&preferences.Timezone, ¬ificationsJSON, &privacyJSON,
|
||||
&audioJSON, &preferences.UpdatedAt,
|
||||
&audioJSON, &preferences.Contrast, &preferences.Density,
|
||||
&preferences.AccentHue, &preferences.FontSize, &preferences.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -432,24 +438,34 @@ func (s *Service) GetUserPreferences(userID uuid.UUID) (*UserPreferencesResponse
|
|||
Audio: AudioSettings{
|
||||
AutoPlay: true, Quality: "high", Volume: 0.8, Crossfade: 5,
|
||||
},
|
||||
Contrast: "normal",
|
||||
Density: "comfortable",
|
||||
AccentHue: 220,
|
||||
FontSize: 16,
|
||||
UpdatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get user preferences: %w", err)
|
||||
}
|
||||
|
||||
// TODO: Parse JSON strings to structs (simplified for now)
|
||||
preferences.Notifications = NotificationSettings{
|
||||
Email: true, Push: true, Desktop: true,
|
||||
NewFollowers: true, TrackComments: true,
|
||||
DirectMessages: true, Mentions: true, Likes: false,
|
||||
// Parse JSON strings to structs
|
||||
if err := json.Unmarshal([]byte(notificationsJSON), &preferences.Notifications); err != nil {
|
||||
preferences.Notifications = NotificationSettings{
|
||||
Email: true, Push: true, Desktop: true,
|
||||
NewFollowers: true, TrackComments: true,
|
||||
DirectMessages: true, Mentions: true, Likes: false,
|
||||
}
|
||||
}
|
||||
preferences.Privacy = PrivacySettings{
|
||||
ShowEmail: false, ShowActivity: true, AllowDM: true,
|
||||
TrackVisibility: "public", ProfileVisibility: "public",
|
||||
if err := json.Unmarshal([]byte(privacyJSON), &preferences.Privacy); err != nil {
|
||||
preferences.Privacy = PrivacySettings{
|
||||
ShowEmail: false, ShowActivity: true, AllowDM: true,
|
||||
TrackVisibility: "public", ProfileVisibility: "public",
|
||||
}
|
||||
}
|
||||
preferences.Audio = AudioSettings{
|
||||
AutoPlay: true, Quality: "high", Volume: 0.8, Crossfade: 5,
|
||||
if err := json.Unmarshal([]byte(audioJSON), &preferences.Audio); err != nil {
|
||||
preferences.Audio = AudioSettings{
|
||||
AutoPlay: true, Quality: "high", Volume: 0.8, Crossfade: 5,
|
||||
}
|
||||
}
|
||||
|
||||
return &preferences, nil
|
||||
|
|
@ -482,13 +498,30 @@ func (s *Service) UpdateUserPreferences(userID uuid.UUID, req UserPreferencesReq
|
|||
if req.Audio != nil {
|
||||
current.Audio = *req.Audio
|
||||
}
|
||||
if req.Contrast != nil {
|
||||
current.Contrast = *req.Contrast
|
||||
}
|
||||
if req.Density != nil {
|
||||
current.Density = *req.Density
|
||||
}
|
||||
if req.AccentHue != nil {
|
||||
current.AccentHue = *req.AccentHue
|
||||
}
|
||||
if req.FontSize != nil {
|
||||
current.FontSize = *req.FontSize
|
||||
}
|
||||
|
||||
current.UpdatedAt = time.Now()
|
||||
|
||||
// Serialize structs to JSON
|
||||
notificationsJSON, _ := json.Marshal(current.Notifications)
|
||||
privacyJSON, _ := json.Marshal(current.Privacy)
|
||||
audioJSON, _ := json.Marshal(current.Audio)
|
||||
|
||||
// Sauvegarder en base (upsert)
|
||||
query := `
|
||||
INSERT INTO user_preferences (user_id, theme, language, timezone, notifications, privacy, audio, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
INSERT INTO user_preferences (user_id, theme, language, timezone, notifications, privacy, audio, contrast, density, accent_hue, font_size, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||
ON CONFLICT (user_id) DO UPDATE SET
|
||||
theme = EXCLUDED.theme,
|
||||
language = EXCLUDED.language,
|
||||
|
|
@ -496,16 +529,17 @@ func (s *Service) UpdateUserPreferences(userID uuid.UUID, req UserPreferencesReq
|
|||
notifications = EXCLUDED.notifications,
|
||||
privacy = EXCLUDED.privacy,
|
||||
audio = EXCLUDED.audio,
|
||||
contrast = EXCLUDED.contrast,
|
||||
density = EXCLUDED.density,
|
||||
accent_hue = EXCLUDED.accent_hue,
|
||||
font_size = EXCLUDED.font_size,
|
||||
updated_at = EXCLUDED.updated_at
|
||||
`
|
||||
|
||||
// TODO: Serialize structs to JSON (simplified for now)
|
||||
notificationsJSON := "{}"
|
||||
privacyJSON := "{}"
|
||||
audioJSON := "{}"
|
||||
|
||||
_, err = s.db.Exec(query, userID, current.Theme, current.Language, current.Timezone,
|
||||
notificationsJSON, privacyJSON, audioJSON, current.UpdatedAt)
|
||||
string(notificationsJSON), string(privacyJSON), string(audioJSON),
|
||||
current.Contrast, current.Density, current.AccentHue, current.FontSize,
|
||||
current.UpdatedAt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update user preferences: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -249,8 +249,8 @@ func TestService_UpdateUserPreferences_Success(t *testing.T) {
|
|||
userID := uuid.New()
|
||||
|
||||
// Expect GetUserPreferences first
|
||||
prefRows := sqlmock.NewRows([]string{"user_id", "theme", "language", "timezone", "notifications", "privacy", "audio", "updated_at"}).
|
||||
AddRow(userID, "light", "en", "UTC", "{}", "{}", "{}", time.Now())
|
||||
prefRows := sqlmock.NewRows([]string{"user_id", "theme", "language", "timezone", "notifications", "privacy", "audio", "contrast", "density", "accent_hue", "font_size", "updated_at"}).
|
||||
AddRow(userID, "light", "en", "UTC", "{}", "{}", "{}", "normal", "comfortable", 220, 16, time.Now())
|
||||
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT user_id`)).
|
||||
WithArgs(userID).
|
||||
|
|
@ -263,7 +263,7 @@ func TestService_UpdateUserPreferences_Success(t *testing.T) {
|
|||
}
|
||||
|
||||
mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO user_preferences`)).
|
||||
WithArgs(userID, "dark", "en", "UTC", "{}", "{}", "{}", sqlmock.AnyArg()).
|
||||
WithArgs(userID, "dark", "en", "UTC", sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), "normal", "comfortable", 220, 16, sqlmock.AnyArg()).
|
||||
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
|
||||
pref, err := service.UpdateUserPreferences(userID, req)
|
||||
|
|
@ -373,8 +373,8 @@ func TestService_ExportUserData_Success(t *testing.T) {
|
|||
WillReturnRows(userRows)
|
||||
|
||||
// 2. GetUserPreferences
|
||||
prefRows := sqlmock.NewRows([]string{"user_id", "theme", "language", "timezone", "notifications", "privacy", "audio", "updated_at"}).
|
||||
AddRow(userID, "light", "en", "UTC", "{}", "{}", "{}", time.Now())
|
||||
prefRows := sqlmock.NewRows([]string{"user_id", "theme", "language", "timezone", "notifications", "privacy", "audio", "contrast", "density", "accent_hue", "font_size", "updated_at"}).
|
||||
AddRow(userID, "light", "en", "UTC", "{}", "{}", "{}", "normal", "comfortable", 220, 16, time.Now())
|
||||
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT user_id`)).
|
||||
WithArgs(userID).
|
||||
|
|
|
|||
|
|
@ -73,6 +73,10 @@ type UserPreferencesRequest struct {
|
|||
Notifications *NotificationSettings `json:"notifications,omitempty"`
|
||||
Privacy *PrivacySettings `json:"privacy,omitempty"`
|
||||
Audio *AudioSettings `json:"audio,omitempty"`
|
||||
Contrast *string `json:"contrast,omitempty"`
|
||||
Density *string `json:"density,omitempty"`
|
||||
AccentHue *int `json:"accentHue,omitempty"`
|
||||
FontSize *int `json:"fontSize,omitempty"`
|
||||
}
|
||||
|
||||
// UserPreferencesResponse représente les préférences utilisateur
|
||||
|
|
@ -84,6 +88,10 @@ type UserPreferencesResponse struct {
|
|||
Notifications NotificationSettings `json:"notifications"`
|
||||
Privacy PrivacySettings `json:"privacy"`
|
||||
Audio AudioSettings `json:"audio"`
|
||||
Contrast string `json:"contrast"`
|
||||
Density string `json:"density"`
|
||||
AccentHue int `json:"accentHue"`
|
||||
FontSize int `json:"fontSize"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
|
|
|
|||
17
veza-backend-api/migrations/118_user_preferences.sql
Normal file
17
veza-backend-api/migrations/118_user_preferences.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
-- v0.801: User preferences table with appearance fields (contrast, density, accent_hue, font_size)
|
||||
CREATE TABLE IF NOT EXISTS user_preferences (
|
||||
user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
||||
theme VARCHAR(20) NOT NULL DEFAULT 'light',
|
||||
language VARCHAR(10) NOT NULL DEFAULT 'en',
|
||||
timezone VARCHAR(50) NOT NULL DEFAULT 'UTC',
|
||||
notifications JSONB NOT NULL DEFAULT '{}',
|
||||
privacy JSONB NOT NULL DEFAULT '{}',
|
||||
audio JSONB NOT NULL DEFAULT '{}',
|
||||
contrast VARCHAR(10) NOT NULL DEFAULT 'normal',
|
||||
density VARCHAR(12) NOT NULL DEFAULT 'comfortable',
|
||||
accent_hue INT NOT NULL DEFAULT 220,
|
||||
font_size INT NOT NULL DEFAULT 16,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_preferences_user_id ON user_preferences(user_id);
|
||||
Loading…
Reference in a new issue