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 // Add settings to invitation data
if existingSettings, ok := invitationData["settings"].(map[string]interface{}); ok { if settings.SendEmail || settings.Locale != "" {
for k, v := range req.Settings { invitationData["settings"] = settings
existingSettings[k] = v
}
}
}
} }
// 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,40 +242,40 @@ 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 ====
// MemberResponse represents a team member in API responses // MemberResponse represents a team member in API responses
type MemberResponse struct { type MemberResponse struct {
ID int64 `json:"id"` ID int64 `json:"id"`
TeamID string `json:"team_id"` TeamID string `json:"team_id"`
UserID string `json:"user_id"` UserID string `json:"user_id"`
MemberType string `json:"member_type"` MemberType string `json:"member_type"`
RoleID string `json:"role_id"` RoleID string `json:"role_id"`
Status string `json:"status"` Status string `json:"status"`
InvitedBy string `json:"invited_by,omitempty"` InvitedBy string `json:"invited_by,omitempty"`
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"`
} }
// MemberDetailResponse represents detailed member information // MemberDetailResponse represents detailed member information
@ -267,38 +287,38 @@ type MemberDetailResponse struct {
// CreateMemberRequest represents the request to add a member directly // CreateMemberRequest represents the request to add a member directly
type CreateMemberRequest struct { 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"`
} }
// ==== Invitation API Types ==== // ==== Invitation API Types ====
// InvitationResponse represents a team invitation in API responses // InvitationResponse represents a team invitation in API responses
type InvitationResponse struct { type InvitationResponse struct {
ID int64 `json:"id"` ID int64 `json:"id"`
TeamID string `json:"team_id"` TeamID string `json:"team_id"`
UserID string `json:"user_id"` UserID string `json:"user_id"`
MemberType string `json:"member_type"` MemberType string `json:"member_type"`
RoleID string `json:"role_id"` RoleID string `json:"role_id"`
Status string `json:"status"` Status string `json:"status"`
InvitedBy string `json:"invited_by"` InvitedBy string `json:"invited_by"`
InvitedAt string `json:"invited_at"` InvitedAt string `json:"invited_at"`
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"`
} }
// InvitationDetailResponse represents detailed invitation information // InvitationDetailResponse represents detailed invitation information
@ -311,14 +331,14 @@ type InvitationDetailResponse struct {
// CreateInvitationRequest represents the request to send a team invitation // CreateInvitationRequest represents the request to send a team invitation
type CreateInvitationRequest struct { type CreateInvitationRequest struct {
UserID string `json:"user_id,omitempty"` // Optional for unregistered users UserID string `json:"user_id,omitempty"` // Optional for unregistered users
Email string `json:"email,omitempty"` // Email address (if not provided, will be read from user profile when user_id is provided) Email string `json:"email,omitempty"` // Email address (if not provided, will be read from user profile when user_id is provided)
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"`
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 ====