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,
}
// 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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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 ====