Merge pull request #1178 from trheyi/main

Refactor settings handling in user invitation, member, and team APIs
This commit is contained in:
Max 2025-10-08 16:46:56 +08:00 committed by GitHub
commit 110abd5161
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 141 additions and 82 deletions

View file

@ -194,28 +194,20 @@ func GinInvitationCreate(c *gin.Context) {
"expiry": req.Expiry, "expiry": req.Expiry,
} }
// Add send_email setting from top-level field // Prepare settings
if req.SendEmail != nil { settings := &InvitationSettings{}
if invitationData["settings"] == nil { if req.Settings != nil {
invitationData["settings"] = make(map[string]interface{}) settings = req.Settings
}
if settings, ok := invitationData["settings"].(map[string]interface{}); ok {
settings["send_email"] = *req.SendEmail
}
} }
// Add other settings if provided // Add send_email from top-level field (for backward compatibility)
if req.Settings != nil { if req.SendEmail != nil {
if invitationData["settings"] == nil { settings.SendEmail = *req.SendEmail
invitationData["settings"] = req.Settings
} else {
// Merge settings
if existingSettings, ok := invitationData["settings"].(map[string]interface{}); ok {
for k, v := range req.Settings {
existingSettings[k] = v
}
}
} }
// Add settings to invitation data
if settings.SendEmail || settings.Locale != "" {
invitationData["settings"] = settings
} }
// Call business logic // Call business logic
@ -734,8 +726,11 @@ func invitationCreate(ctx context.Context, userID, teamID string, invitationData
// Check send_email requirement early // Check send_email requirement early
shouldSendEmail := false shouldSendEmail := false
if settings, ok := invitationData["settings"].(map[string]interface{}); ok { if settings, ok := invitationData["settings"].(*InvitationSettings); ok && settings != nil {
shouldSendEmail = toBool(settings["send_email"]) shouldSendEmail = settings.SendEmail
} else if settingsMap, ok := invitationData["settings"].(map[string]interface{}); ok {
// Fallback for map format (for backward compatibility)
shouldSendEmail = toBool(settingsMap["send_email"])
} }
// If send_email is true, email must be provided // If send_email is true, email must be provided
@ -987,8 +982,13 @@ func getInvitationExpiry(invitationData maps.MapStrAny) (time.Duration, error) {
// Get team config expiry (from global config) // Get team config expiry (from global config)
// Try to get locale from invitation data settings // Try to get locale from invitation data settings
locale := "en" locale := "en"
if settings, ok := invitationData["settings"].(map[string]interface{}); ok { if settings, ok := invitationData["settings"].(*InvitationSettings); ok && settings != nil {
if loc := toString(settings["locale"]); loc != "" { if settings.Locale != "" {
locale = settings.Locale
}
} else if settingsMap, ok := invitationData["settings"].(map[string]interface{}); ok {
// Fallback for map format (for backward compatibility)
if loc := toString(settingsMap["locale"]); loc != "" {
locale = loc locale = loc
} }
} }
@ -1020,8 +1020,13 @@ func sendInvitationEmail(ctx context.Context, email, inviterName, teamName, toke
// Get locale from invitation data settings // Get locale from invitation data settings
locale := "en" locale := "en"
if settings, ok := invitationData["settings"].(map[string]interface{}); ok { if settings, ok := invitationData["settings"].(*InvitationSettings); ok && settings != nil {
if loc := toString(settings["locale"]); loc != "" { if settings.Locale != "" {
locale = settings.Locale
}
} else if settingsMap, ok := invitationData["settings"].(map[string]interface{}); ok {
// Fallback for map format (for backward compatibility)
if loc := toString(settingsMap["locale"]); loc != "" {
locale = loc locale = loc
} }
} }
@ -1094,8 +1099,15 @@ func mapToInvitationResponse(data maps.MapStr) InvitationResponse {
// Add settings if available // Add settings if available
if settings, ok := data["settings"]; ok { if settings, ok := data["settings"]; ok {
if settingsMap, ok := settings.(map[string]interface{}); ok { if invSettings, ok := settings.(*InvitationSettings); ok {
invitation.Settings = settingsMap invitation.Settings = invSettings
} else if settingsMap, ok := settings.(map[string]interface{}); ok {
// Convert map to InvitationSettings
invSettings := &InvitationSettings{
SendEmail: toBool(settingsMap["send_email"]),
Locale: toString(settingsMap["locale"]),
}
invitation.Settings = invSettings
} }
} }

View file

@ -797,8 +797,28 @@ func mapToMemberResponse(data maps.MapStr) MemberResponse {
// Add settings if available // Add settings if available
if settings, ok := data["settings"]; ok { if settings, ok := data["settings"]; ok {
if settingsMap, ok := settings.(map[string]interface{}); ok { if memSettings, ok := settings.(*MemberSettings); ok {
member.Settings = settingsMap member.Settings = memSettings
} else if settingsMap, ok := settings.(map[string]interface{}); ok {
// Convert map to MemberSettings (for backward compatibility)
memSettings := &MemberSettings{
Notifications: toBool(settingsMap["notifications"]),
}
// Handle permissions array
if perms, ok := settingsMap["permissions"]; ok {
if permsSlice, ok := perms.([]interface{}); ok {
permissions := make([]string, 0, len(permsSlice))
for _, p := range permsSlice {
if permStr, ok := p.(string); ok {
permissions = append(permissions, permStr)
}
}
memSettings.Permissions = permissions
} else if permsStrSlice, ok := perms.([]string); ok {
memSettings.Permissions = permsStrSlice
}
}
member.Settings = memSettings
} }
} }

View file

@ -818,8 +818,15 @@ func mapToTeamDetailResponse(data maps.MapStr) TeamDetailResponse {
// Add settings if available // Add settings if available
if settings, ok := data["settings"]; ok { if settings, ok := data["settings"]; ok {
if settingsMap, ok := settings.(map[string]interface{}); ok { if teamSettings, ok := settings.(*TeamSettings); ok {
team.Settings = settingsMap team.Settings = teamSettings
} else if settingsMap, ok := settings.(map[string]interface{}); ok {
// Convert map to TeamSettings (for backward compatibility)
teamSettings := &TeamSettings{
Theme: toString(settingsMap["theme"]),
Visibility: toString(settingsMap["visibility"]),
}
team.Settings = teamSettings
} }
} }

View file

@ -201,6 +201,26 @@ const (
UserInfoSourceAccessToken = "access_token" // Extract user info from access token response UserInfoSourceAccessToken = "access_token" // Extract user info from access token response
) )
// ==== Settings Types ====
// TeamSettings represents team-specific settings
type TeamSettings struct {
Theme string `json:"theme,omitempty"` // Team UI theme (e.g., "light", "dark")
Visibility string `json:"visibility,omitempty"` // Team visibility (e.g., "public", "private")
}
// MemberSettings represents member-specific settings
type MemberSettings struct {
Notifications bool `json:"notifications,omitempty"` // Whether to receive notifications
Permissions []string `json:"permissions,omitempty"` // Custom permissions (e.g., ["read", "write"])
}
// InvitationSettings represents invitation-specific settings
type InvitationSettings struct {
SendEmail bool `json:"send_email,omitempty"` // Whether to send invitation email
Locale string `json:"locale,omitempty"` // Locale for email template
}
// ==== Team API Types ==== // ==== Team API Types ====
// TeamResponse represents a team in API responses // TeamResponse represents a team in API responses
@ -222,21 +242,21 @@ type TeamResponse struct {
type TeamDetailResponse struct { type TeamDetailResponse struct {
TeamResponse TeamResponse
// Add additional fields that are only included in detailed responses // Add additional fields that are only included in detailed responses
Settings map[string]interface{} `json:"settings,omitempty"` Settings *TeamSettings `json:"settings,omitempty"`
} }
// CreateTeamRequest represents the request to create a team // CreateTeamRequest represents the request to create a team
type CreateTeamRequest struct { type CreateTeamRequest struct {
Name string `json:"name" binding:"required"` Name string `json:"name" binding:"required"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
Settings map[string]interface{} `json:"settings,omitempty"` Settings *TeamSettings `json:"settings,omitempty"`
} }
// UpdateTeamRequest represents the request to update a team // UpdateTeamRequest represents the request to update a team
type UpdateTeamRequest struct { type UpdateTeamRequest struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
Settings map[string]interface{} `json:"settings,omitempty"` Settings *TeamSettings `json:"settings,omitempty"`
} }
// ==== Member API Types ==== // ==== Member API Types ====
@ -253,7 +273,7 @@ type MemberResponse struct {
InvitedAt string `json:"invited_at,omitempty"` InvitedAt string `json:"invited_at,omitempty"`
JoinedAt string `json:"joined_at,omitempty"` JoinedAt string `json:"joined_at,omitempty"`
LastActivity string `json:"last_activity,omitempty"` LastActivity string `json:"last_activity,omitempty"`
Settings map[string]interface{} `json:"settings,omitempty"` Settings *MemberSettings `json:"settings,omitempty"`
CreatedAt string `json:"created_at"` CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"` UpdatedAt string `json:"updated_at"`
} }
@ -270,14 +290,14 @@ type CreateMemberRequest struct {
UserID string `json:"user_id" binding:"required"` UserID string `json:"user_id" binding:"required"`
MemberType string `json:"member_type,omitempty"` // "user" or "robot" MemberType string `json:"member_type,omitempty"` // "user" or "robot"
RoleID string `json:"role_id" binding:"required"` RoleID string `json:"role_id" binding:"required"`
Settings map[string]interface{} `json:"settings,omitempty"` Settings *MemberSettings `json:"settings,omitempty"`
} }
// UpdateMemberRequest represents the request to update a member // UpdateMemberRequest represents the request to update a member
type UpdateMemberRequest struct { type UpdateMemberRequest struct {
RoleID string `json:"role_id,omitempty"` RoleID string `json:"role_id,omitempty"`
Status string `json:"status,omitempty"` Status string `json:"status,omitempty"`
Settings map[string]interface{} `json:"settings,omitempty"` Settings *MemberSettings `json:"settings,omitempty"`
LastActivity string `json:"last_activity,omitempty"` LastActivity string `json:"last_activity,omitempty"`
} }
@ -296,7 +316,7 @@ type InvitationResponse struct {
InvitationToken string `json:"invitation_token,omitempty"` InvitationToken string `json:"invitation_token,omitempty"`
InvitationExpiresAt string `json:"invitation_expires_at,omitempty"` InvitationExpiresAt string `json:"invitation_expires_at,omitempty"`
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
Settings map[string]interface{} `json:"settings,omitempty"` Settings *InvitationSettings `json:"settings,omitempty"`
CreatedAt string `json:"created_at"` CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"` UpdatedAt string `json:"updated_at"`
} }
@ -318,7 +338,7 @@ type CreateInvitationRequest struct {
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
Expiry string `json:"expiry,omitempty"` // Custom expiry duration (e.g., "1d", "8h"), defaults to team config Expiry string `json:"expiry,omitempty"` // Custom expiry duration (e.g., "1d", "8h"), defaults to team config
SendEmail *bool `json:"send_email,omitempty"` // Whether to send email (defaults to false) SendEmail *bool `json:"send_email,omitempty"` // Whether to send email (defaults to false)
Settings map[string]interface{} `json:"settings,omitempty"` Settings *InvitationSettings `json:"settings,omitempty"`
} }
// ==== Team Configuration Types ==== // ==== Team Configuration Types ====