diff --git a/openapi/user/invitation.go b/openapi/user/invitation.go index 6aedc3e9..e84f379d 100644 --- a/openapi/user/invitation.go +++ b/openapi/user/invitation.go @@ -194,28 +194,20 @@ func GinInvitationCreate(c *gin.Context) { "expiry": req.Expiry, } - // Add send_email setting from top-level field - if req.SendEmail != nil { - if invitationData["settings"] == nil { - invitationData["settings"] = make(map[string]interface{}) - } - if settings, ok := invitationData["settings"].(map[string]interface{}); ok { - settings["send_email"] = *req.SendEmail - } + // Prepare settings + settings := &InvitationSettings{} + if req.Settings != nil { + settings = req.Settings } - // Add other settings if provided - if req.Settings != nil { - if invitationData["settings"] == nil { - 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 send_email from top-level field (for backward compatibility) + if req.SendEmail != nil { + settings.SendEmail = *req.SendEmail + } + + // Add settings to invitation data + if settings.SendEmail || settings.Locale != "" { + invitationData["settings"] = settings } // Call business logic @@ -734,8 +726,11 @@ func invitationCreate(ctx context.Context, userID, teamID string, invitationData // Check send_email requirement early shouldSendEmail := false - if settings, ok := invitationData["settings"].(map[string]interface{}); ok { - shouldSendEmail = toBool(settings["send_email"]) + if settings, ok := invitationData["settings"].(*InvitationSettings); ok && settings != nil { + 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 @@ -987,8 +982,13 @@ func getInvitationExpiry(invitationData maps.MapStrAny) (time.Duration, error) { // Get team config expiry (from global config) // Try to get locale from invitation data settings locale := "en" - if settings, ok := invitationData["settings"].(map[string]interface{}); ok { - if loc := toString(settings["locale"]); loc != "" { + if settings, ok := invitationData["settings"].(*InvitationSettings); ok && settings != nil { + 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 } } @@ -1020,8 +1020,13 @@ func sendInvitationEmail(ctx context.Context, email, inviterName, teamName, toke // Get locale from invitation data settings locale := "en" - if settings, ok := invitationData["settings"].(map[string]interface{}); ok { - if loc := toString(settings["locale"]); loc != "" { + if settings, ok := invitationData["settings"].(*InvitationSettings); ok && settings != nil { + 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 } } @@ -1094,8 +1099,15 @@ func mapToInvitationResponse(data maps.MapStr) InvitationResponse { // Add settings if available if settings, ok := data["settings"]; ok { - if settingsMap, ok := settings.(map[string]interface{}); ok { - invitation.Settings = settingsMap + if invSettings, ok := settings.(*InvitationSettings); ok { + 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 } } diff --git a/openapi/user/member.go b/openapi/user/member.go index db04c7b9..f4afdd3f 100644 --- a/openapi/user/member.go +++ b/openapi/user/member.go @@ -797,8 +797,28 @@ func mapToMemberResponse(data maps.MapStr) MemberResponse { // Add settings if available if settings, ok := data["settings"]; ok { - if settingsMap, ok := settings.(map[string]interface{}); ok { - member.Settings = settingsMap + if memSettings, ok := settings.(*MemberSettings); ok { + 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 } } diff --git a/openapi/user/team.go b/openapi/user/team.go index 976ff7ef..0f8edb86 100644 --- a/openapi/user/team.go +++ b/openapi/user/team.go @@ -818,8 +818,15 @@ func mapToTeamDetailResponse(data maps.MapStr) TeamDetailResponse { // Add settings if available if settings, ok := data["settings"]; ok { - if settingsMap, ok := settings.(map[string]interface{}); ok { - team.Settings = settingsMap + if teamSettings, ok := settings.(*TeamSettings); ok { + 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 } } diff --git a/openapi/user/types.go b/openapi/user/types.go index aaba9baf..64f7b187 100644 --- a/openapi/user/types.go +++ b/openapi/user/types.go @@ -201,6 +201,26 @@ const ( 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 ==== // TeamResponse represents a team in API responses @@ -222,40 +242,40 @@ type TeamResponse struct { type TeamDetailResponse struct { TeamResponse // 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 type CreateTeamRequest struct { - Name string `json:"name" binding:"required"` - Description string `json:"description,omitempty"` - Settings map[string]interface{} `json:"settings,omitempty"` + Name string `json:"name" binding:"required"` + Description string `json:"description,omitempty"` + Settings *TeamSettings `json:"settings,omitempty"` } // UpdateTeamRequest represents the request to update a team type UpdateTeamRequest struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - Settings map[string]interface{} `json:"settings,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Settings *TeamSettings `json:"settings,omitempty"` } // ==== Member API Types ==== // MemberResponse represents a team member in API responses type MemberResponse struct { - ID int64 `json:"id"` - TeamID string `json:"team_id"` - UserID string `json:"user_id"` - MemberType string `json:"member_type"` - RoleID string `json:"role_id"` - Status string `json:"status"` - InvitedBy string `json:"invited_by,omitempty"` - InvitedAt string `json:"invited_at,omitempty"` - JoinedAt string `json:"joined_at,omitempty"` - LastActivity string `json:"last_activity,omitempty"` - Settings map[string]interface{} `json:"settings,omitempty"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` + ID int64 `json:"id"` + TeamID string `json:"team_id"` + UserID string `json:"user_id"` + MemberType string `json:"member_type"` + RoleID string `json:"role_id"` + Status string `json:"status"` + InvitedBy string `json:"invited_by,omitempty"` + InvitedAt string `json:"invited_at,omitempty"` + JoinedAt string `json:"joined_at,omitempty"` + LastActivity string `json:"last_activity,omitempty"` + Settings *MemberSettings `json:"settings,omitempty"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` } // MemberDetailResponse represents detailed member information @@ -267,38 +287,38 @@ type MemberDetailResponse struct { // CreateMemberRequest represents the request to add a member directly type CreateMemberRequest struct { - UserID string `json:"user_id" binding:"required"` - MemberType string `json:"member_type,omitempty"` // "user" or "robot" - RoleID string `json:"role_id" binding:"required"` - Settings map[string]interface{} `json:"settings,omitempty"` + UserID string `json:"user_id" binding:"required"` + MemberType string `json:"member_type,omitempty"` // "user" or "robot" + RoleID string `json:"role_id" binding:"required"` + Settings *MemberSettings `json:"settings,omitempty"` } // UpdateMemberRequest represents the request to update a member type UpdateMemberRequest struct { - RoleID string `json:"role_id,omitempty"` - Status string `json:"status,omitempty"` - Settings map[string]interface{} `json:"settings,omitempty"` - LastActivity string `json:"last_activity,omitempty"` + RoleID string `json:"role_id,omitempty"` + Status string `json:"status,omitempty"` + Settings *MemberSettings `json:"settings,omitempty"` + LastActivity string `json:"last_activity,omitempty"` } // ==== Invitation API Types ==== // InvitationResponse represents a team invitation in API responses type InvitationResponse struct { - ID int64 `json:"id"` - TeamID string `json:"team_id"` - UserID string `json:"user_id"` - MemberType string `json:"member_type"` - RoleID string `json:"role_id"` - Status string `json:"status"` - InvitedBy string `json:"invited_by"` - InvitedAt string `json:"invited_at"` - InvitationToken string `json:"invitation_token,omitempty"` - InvitationExpiresAt string `json:"invitation_expires_at,omitempty"` - Message string `json:"message,omitempty"` - Settings map[string]interface{} `json:"settings,omitempty"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` + ID int64 `json:"id"` + TeamID string `json:"team_id"` + UserID string `json:"user_id"` + MemberType string `json:"member_type"` + RoleID string `json:"role_id"` + Status string `json:"status"` + InvitedBy string `json:"invited_by"` + InvitedAt string `json:"invited_at"` + InvitationToken string `json:"invitation_token,omitempty"` + InvitationExpiresAt string `json:"invitation_expires_at,omitempty"` + Message string `json:"message,omitempty"` + Settings *InvitationSettings `json:"settings,omitempty"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` } // InvitationDetailResponse represents detailed invitation information @@ -311,14 +331,14 @@ type InvitationDetailResponse struct { // CreateInvitationRequest represents the request to send a team invitation type CreateInvitationRequest struct { - 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) - MemberType string `json:"member_type,omitempty"` // "user" or "robot" - RoleID string `json:"role_id" binding:"required"` - Message string `json:"message,omitempty"` - 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) - Settings map[string]interface{} `json:"settings,omitempty"` + 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) + MemberType string `json:"member_type,omitempty"` // "user" or "robot" + RoleID string `json:"role_id" binding:"required"` + Message string `json:"message,omitempty"` + 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) + Settings *InvitationSettings `json:"settings,omitempty"` } // ==== Team Configuration Types ====