Merge pull request #1182 from trheyi/main
Remove invitation management handlers and related business logic
This commit is contained in:
commit
7bdcf2acce
6 changed files with 335 additions and 106 deletions
|
|
@ -87,7 +87,7 @@ func GinMemberList(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Return the paginated result
|
||||
c.JSON(http.StatusOK, result)
|
||||
response.RespondWithSuccess(c, http.StatusOK, result)
|
||||
}
|
||||
|
||||
// GinMemberGet handles GET /teams/:team_id/members/:member_id - Get team member details
|
||||
|
|
@ -143,7 +143,7 @@ func GinMemberGet(c *gin.Context) {
|
|||
|
||||
// Convert to response format
|
||||
member := mapToMemberDetailResponse(memberData)
|
||||
c.JSON(http.StatusOK, member)
|
||||
response.RespondWithSuccess(c, http.StatusOK, member)
|
||||
}
|
||||
|
||||
// GinMemberCreateDirect handles POST /teams/:team_id/members - Add member directly to team
|
||||
|
|
@ -226,7 +226,7 @@ func GinMemberCreateDirect(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Return created member ID
|
||||
c.JSON(http.StatusCreated, gin.H{"member_id": memberID})
|
||||
response.RespondWithSuccess(c, http.StatusCreated, gin.H{"member_id": memberID})
|
||||
}
|
||||
|
||||
// GinMemberUpdate handles PUT /teams/:team_id/members/:member_id - Update team member
|
||||
|
|
@ -307,7 +307,7 @@ func GinMemberUpdate(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Member updated successfully"})
|
||||
response.RespondWithSuccess(c, http.StatusOK, gin.H{"message": "Member updated successfully"})
|
||||
}
|
||||
|
||||
// GinMemberDelete handles DELETE /teams/:team_id/members/:member_id - Remove team member
|
||||
|
|
@ -361,7 +361,7 @@ func GinMemberDelete(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Member removed successfully"})
|
||||
response.RespondWithSuccess(c, http.StatusOK, gin.H{"message": "Member removed successfully"})
|
||||
}
|
||||
|
||||
// Yao Process Handlers (for Yao application calls)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import (
|
|||
|
||||
// Team Management Handlers
|
||||
|
||||
// Public Team Configuration Endpoint
|
||||
// GinTeamConfig handles GET /teams/config - Get team configuration (public)
|
||||
func GinTeamConfig(c *gin.Context) {
|
||||
locale := c.Query("locale")
|
||||
|
|
@ -35,11 +34,15 @@ func GinTeamConfig(c *gin.Context) {
|
|||
|
||||
config := GetTeamConfig(locale)
|
||||
if config == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Team configuration not found"})
|
||||
errorResp := &response.ErrorResponse{
|
||||
Code: response.ErrInvalidRequest.Code,
|
||||
ErrorDescription: "Team configuration not found",
|
||||
}
|
||||
response.RespondWithError(c, response.StatusNotFound, errorResp)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, config)
|
||||
response.RespondWithSuccess(c, http.StatusOK, config)
|
||||
}
|
||||
|
||||
// GinTeamList handles GET /teams - Get user teams
|
||||
|
|
@ -123,7 +126,7 @@ func GinTeamList(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Return the paginated result directly (consistent with other modules)
|
||||
c.JSON(http.StatusOK, result)
|
||||
response.RespondWithSuccess(c, http.StatusOK, result)
|
||||
}
|
||||
|
||||
// GinTeamGet handles GET /teams/:id - Get user team details
|
||||
|
|
@ -195,7 +198,7 @@ func GinTeamGet(c *gin.Context) {
|
|||
|
||||
// Convert to response format
|
||||
team := mapToTeamDetailResponse(teamData)
|
||||
c.JSON(http.StatusOK, team)
|
||||
response.RespondWithSuccess(c, http.StatusOK, team)
|
||||
}
|
||||
|
||||
// GinTeamCreate handles POST /teams - Create user team
|
||||
|
|
@ -250,7 +253,7 @@ func GinTeamCreate(c *gin.Context) {
|
|||
if err != nil {
|
||||
log.Error("Failed to get user provider: %v", err)
|
||||
// Return basic response if we can't get details
|
||||
c.JSON(http.StatusCreated, gin.H{"team_id": teamID})
|
||||
response.RespondWithSuccess(c, http.StatusCreated, gin.H{"team_id": teamID})
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -258,13 +261,13 @@ func GinTeamCreate(c *gin.Context) {
|
|||
if err != nil {
|
||||
log.Error("Failed to get created team details: %v", err)
|
||||
// Return basic response if we can't get details
|
||||
c.JSON(http.StatusCreated, gin.H{"team_id": teamID})
|
||||
response.RespondWithSuccess(c, http.StatusCreated, gin.H{"team_id": teamID})
|
||||
return
|
||||
}
|
||||
|
||||
// Convert to response format
|
||||
team := mapToTeamDetailResponse(createdTeam)
|
||||
c.JSON(http.StatusCreated, team)
|
||||
response.RespondWithSuccess(c, http.StatusCreated, team)
|
||||
}
|
||||
|
||||
// GinTeamUpdate handles PUT /teams/:id - Update user team
|
||||
|
|
@ -345,20 +348,20 @@ func GinTeamUpdate(c *gin.Context) {
|
|||
provider, err := getUserProvider()
|
||||
if err != nil {
|
||||
log.Error("Failed to get user provider: %v", err)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Team updated successfully"})
|
||||
response.RespondWithSuccess(c, http.StatusOK, gin.H{"message": "Team updated successfully"})
|
||||
return
|
||||
}
|
||||
|
||||
updatedTeam, err := provider.GetTeamDetail(c.Request.Context(), teamID)
|
||||
if err != nil {
|
||||
log.Error("Failed to get updated team details: %v", err)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Team updated successfully"})
|
||||
response.RespondWithSuccess(c, http.StatusOK, gin.H{"message": "Team updated successfully"})
|
||||
return
|
||||
}
|
||||
|
||||
// Convert to response format
|
||||
team := mapToTeamDetailResponse(updatedTeam)
|
||||
c.JSON(http.StatusOK, team)
|
||||
response.RespondWithSuccess(c, http.StatusOK, team)
|
||||
}
|
||||
|
||||
// GinTeamDelete handles DELETE /teams/:id - Delete user team
|
||||
|
|
@ -411,7 +414,7 @@ func GinTeamDelete(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Team deleted successfully"})
|
||||
response.RespondWithSuccess(c, http.StatusOK, gin.H{"message": "Team deleted successfully"})
|
||||
}
|
||||
|
||||
// Yao Process Handlers (for Yao application calls)
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@ import (
|
|||
"github.com/yaoapp/yao/share"
|
||||
)
|
||||
|
||||
// Team Invitation Management Handlers
|
||||
// User Team Invitation Management Handlers
|
||||
|
||||
// GinInvitationList handles GET /teams/:team_id/invitations - Get team invitations
|
||||
func GinInvitationList(c *gin.Context) {
|
||||
// GinTeamInvitationList handles GET /teams/:team_id/invitations - Get team invitations
|
||||
func GinTeamInvitationList(c *gin.Context) {
|
||||
// Get authorized user info
|
||||
authInfo := oauth.GetAuthorizedInfo(c)
|
||||
if authInfo == nil || authInfo.UserID == "" {
|
||||
|
|
@ -65,7 +65,7 @@ func GinInvitationList(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Call business logic
|
||||
result, err := invitationList(c.Request.Context(), authInfo.UserID, teamID, page, pagesize, c.Query("status"))
|
||||
result, err := teamInvitationList(c.Request.Context(), authInfo.UserID, teamID, page, pagesize, c.Query("status"))
|
||||
if err != nil {
|
||||
log.Error("Failed to get team invitations: %v", err)
|
||||
// Check error type for appropriate response
|
||||
|
|
@ -92,11 +92,70 @@ func GinInvitationList(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Return the paginated result
|
||||
c.JSON(http.StatusOK, result)
|
||||
response.RespondWithSuccess(c, http.StatusOK, result)
|
||||
}
|
||||
|
||||
// GinInvitationGet handles GET /teams/:team_id/invitations/:invitation_id - Get invitation details
|
||||
func GinInvitationGet(c *gin.Context) {
|
||||
// GinTeamInvitationGetPublic handles GET /user/teams/invitations/:invitation_id - Get invitation details (public)
|
||||
// This is a public endpoint that doesn't require authentication
|
||||
// Supports ?locale=zh-CN query parameter for internationalization
|
||||
func GinTeamInvitationGetPublic(c *gin.Context) {
|
||||
invitationID := c.Param("invitation_id")
|
||||
if invitationID == "" {
|
||||
errorResp := &response.ErrorResponse{
|
||||
Code: response.ErrInvalidRequest.Code,
|
||||
ErrorDescription: "Invitation ID is required",
|
||||
}
|
||||
response.RespondWithError(c, response.StatusBadRequest, errorResp)
|
||||
return
|
||||
}
|
||||
|
||||
// Get locale from query parameter or Accept-Language header
|
||||
locale := c.Query("locale")
|
||||
if locale == "" {
|
||||
// Try to get from Accept-Language header
|
||||
acceptLang := c.GetHeader("Accept-Language")
|
||||
if acceptLang != "" {
|
||||
// Parse Accept-Language header (e.g., "zh-CN,zh;q=0.9,en;q=0.8")
|
||||
parts := strings.Split(acceptLang, ",")
|
||||
if len(parts) > 0 {
|
||||
langParts := strings.Split(parts[0], ";")
|
||||
if len(langParts) > 0 {
|
||||
locale = strings.TrimSpace(langParts[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Default to "en" if no locale specified
|
||||
if locale == "" {
|
||||
locale = "en"
|
||||
}
|
||||
|
||||
// Call business logic (no user authentication required for public access)
|
||||
publicInvitation, err := teamInvitationGetPublic(c.Request.Context(), invitationID, locale)
|
||||
if err != nil {
|
||||
log.Error("Failed to get invitation details: %v", err)
|
||||
// Check error type for appropriate response
|
||||
if strings.Contains(err.Error(), "not found") || strings.Contains(err.Error(), "expired") {
|
||||
errorResp := &response.ErrorResponse{
|
||||
Code: response.ErrInvalidRequest.Code,
|
||||
ErrorDescription: "Invitation not found or expired",
|
||||
}
|
||||
response.RespondWithError(c, response.StatusNotFound, errorResp)
|
||||
} else {
|
||||
errorResp := &response.ErrorResponse{
|
||||
Code: response.ErrServerError.Code,
|
||||
ErrorDescription: "Failed to retrieve invitation details",
|
||||
}
|
||||
response.RespondWithError(c, response.StatusInternalServerError, errorResp)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
response.RespondWithSuccess(c, http.StatusOK, publicInvitation)
|
||||
}
|
||||
|
||||
// GinTeamInvitationGet handles GET /teams/:team_id/invitations/:invitation_id - Get invitation details (admin only)
|
||||
func GinTeamInvitationGet(c *gin.Context) {
|
||||
// Get authorized user info
|
||||
authInfo := oauth.GetAuthorizedInfo(c)
|
||||
if authInfo == nil || authInfo.UserID == "" {
|
||||
|
|
@ -123,7 +182,7 @@ func GinInvitationGet(c *gin.Context) {
|
|||
requestBaseURL := getRequestBaseURL(c)
|
||||
|
||||
// Call business logic
|
||||
invitationData, err := invitationGet(c.Request.Context(), authInfo.UserID, teamID, invitationID)
|
||||
invitationData, err := teamInvitationGet(c.Request.Context(), authInfo.UserID, teamID, invitationID)
|
||||
if err != nil {
|
||||
log.Error("Failed to get invitation details: %v", err)
|
||||
// Check error type for appropriate response
|
||||
|
|
@ -150,12 +209,12 @@ func GinInvitationGet(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Convert to response format (with requestBaseURL for building full invitation link)
|
||||
invitation := mapToInvitationDetailResponse(invitationData, requestBaseURL)
|
||||
c.JSON(http.StatusOK, invitation)
|
||||
invitation := mapToTeamInvitationDetailResponse(invitationData, requestBaseURL)
|
||||
response.RespondWithSuccess(c, http.StatusOK, invitation)
|
||||
}
|
||||
|
||||
// GinInvitationCreate handles POST /teams/:team_id/invitations - Send team invitation
|
||||
func GinInvitationCreate(c *gin.Context) {
|
||||
// GinTeamInvitationCreate handles POST /teams/:team_id/invitations - Send team invitation
|
||||
func GinTeamInvitationCreate(c *gin.Context) {
|
||||
// Get authorized user info
|
||||
authInfo := oauth.GetAuthorizedInfo(c)
|
||||
if authInfo == nil || authInfo.UserID == "" {
|
||||
|
|
@ -224,7 +283,7 @@ func GinInvitationCreate(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Call business logic
|
||||
invitationID, err := invitationCreate(c.Request.Context(), authInfo.UserID, teamID, invitationData)
|
||||
invitationID, err := teamInvitationCreate(c.Request.Context(), authInfo.UserID, teamID, invitationData)
|
||||
if err != nil {
|
||||
log.Error("Failed to create invitation: %v", err)
|
||||
// Check error type for appropriate response
|
||||
|
|
@ -263,23 +322,23 @@ func GinInvitationCreate(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Get the created invitation to return complete data
|
||||
invitation, err := invitationGet(c.Request.Context(), authInfo.UserID, teamID, invitationID)
|
||||
invitation, err := teamInvitationGet(c.Request.Context(), authInfo.UserID, teamID, invitationID)
|
||||
if err != nil {
|
||||
log.Error("Failed to retrieve created invitation: %v", err)
|
||||
// Fallback to returning just the ID if retrieval fails
|
||||
c.JSON(http.StatusCreated, gin.H{"invitation_id": invitationID})
|
||||
response.RespondWithSuccess(c, http.StatusCreated, gin.H{"invitation_id": invitationID})
|
||||
return
|
||||
}
|
||||
|
||||
// Convert to InvitationResponse (with requestBaseURL for building full invitation link)
|
||||
invitationResp := convertToInvitationResponse(invitation, requestBaseURL)
|
||||
invitationResp := convertToTeamInvitationResponse(invitation, requestBaseURL)
|
||||
|
||||
// Return created invitation with full details (including token)
|
||||
c.JSON(http.StatusCreated, invitationResp)
|
||||
response.RespondWithSuccess(c, http.StatusCreated, invitationResp)
|
||||
}
|
||||
|
||||
// GinInvitationResend handles PUT /teams/:team_id/invitations/:invitation_id/resend - Resend invitation
|
||||
func GinInvitationResend(c *gin.Context) {
|
||||
// GinTeamInvitationResend handles PUT /teams/:team_id/invitations/:invitation_id/resend - Resend invitation
|
||||
func GinTeamInvitationResend(c *gin.Context) {
|
||||
// Get authorized user info
|
||||
authInfo := oauth.GetAuthorizedInfo(c)
|
||||
if authInfo == nil || authInfo.UserID == "" {
|
||||
|
|
@ -303,7 +362,7 @@ func GinInvitationResend(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Call business logic
|
||||
err := invitationResend(c.Request.Context(), authInfo.UserID, teamID, invitationID, getRequestBaseURL(c))
|
||||
err := teamInvitationResend(c.Request.Context(), authInfo.UserID, teamID, invitationID, getRequestBaseURL(c))
|
||||
if err != nil {
|
||||
log.Error("Failed to resend invitation: %v", err)
|
||||
// Check error type for appropriate response
|
||||
|
|
@ -335,11 +394,11 @@ func GinInvitationResend(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Invitation resent successfully"})
|
||||
response.RespondWithSuccess(c, http.StatusOK, gin.H{"message": "Invitation resent successfully"})
|
||||
}
|
||||
|
||||
// GinInvitationDelete handles DELETE /teams/:team_id/invitations/:invitation_id - Cancel invitation
|
||||
func GinInvitationDelete(c *gin.Context) {
|
||||
// GinTeamInvitationDelete handles DELETE /teams/:team_id/invitations/:invitation_id - Cancel invitation
|
||||
func GinTeamInvitationDelete(c *gin.Context) {
|
||||
// Get authorized user info
|
||||
authInfo := oauth.GetAuthorizedInfo(c)
|
||||
if authInfo == nil || authInfo.UserID == "" {
|
||||
|
|
@ -363,7 +422,7 @@ func GinInvitationDelete(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Call business logic
|
||||
err := invitationDelete(c.Request.Context(), authInfo.UserID, teamID, invitationID)
|
||||
err := teamInvitationDelete(c.Request.Context(), authInfo.UserID, teamID, invitationID)
|
||||
if err != nil {
|
||||
log.Error("Failed to cancel invitation: %v", err)
|
||||
// Check error type for appropriate response
|
||||
|
|
@ -389,16 +448,16 @@ func GinInvitationDelete(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Invitation cancelled successfully"})
|
||||
response.RespondWithSuccess(c, http.StatusOK, gin.H{"message": "Invitation cancelled successfully"})
|
||||
}
|
||||
|
||||
// Yao Process Handlers (for Yao application calls)
|
||||
|
||||
// ProcessInvitationList user.invitation.list Invitation list processor
|
||||
// ProcessTeamInvitationList user.team.invitation.list Team invitation list processor
|
||||
// Args[0] string: team_id
|
||||
// Args[1] map: Query parameters {"status": "pending", "page": 1, "pagesize": 20}
|
||||
// Return: map: Paginated invitation list
|
||||
func ProcessInvitationList(process *process.Process) interface{} {
|
||||
func ProcessTeamInvitationList(process *process.Process) interface{} {
|
||||
process.ValidateArgNums(2)
|
||||
|
||||
// Get user_id from session
|
||||
|
|
@ -434,19 +493,19 @@ func ProcessInvitationList(process *process.Process) interface{} {
|
|||
}
|
||||
|
||||
// Call business logic
|
||||
result, err := invitationList(ctx, userIDStr, teamID, page, pagesize, status)
|
||||
result, err := teamInvitationList(ctx, userIDStr, teamID, page, pagesize, status)
|
||||
if err != nil {
|
||||
exception.New("failed to list invitations: %s", 500, err.Error()).Throw()
|
||||
exception.New("failed to list team invitations: %s", 500, err.Error()).Throw()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ProcessInvitationGet user.invitation.get Invitation get processor
|
||||
// ProcessTeamInvitationGet user.team.invitation.get Team invitation get processor
|
||||
// Args[0] string: team_id
|
||||
// Args[1] string: invitation_id
|
||||
// Return: map: Invitation details
|
||||
func ProcessInvitationGet(process *process.Process) interface{} {
|
||||
func ProcessTeamInvitationGet(process *process.Process) interface{} {
|
||||
process.ValidateArgNums(2)
|
||||
|
||||
// Get user_id from session
|
||||
|
|
@ -466,19 +525,19 @@ func ProcessInvitationGet(process *process.Process) interface{} {
|
|||
}
|
||||
|
||||
// Call business logic
|
||||
result, err := invitationGet(ctx, userIDStr, teamID, invitationID)
|
||||
result, err := teamInvitationGet(ctx, userIDStr, teamID, invitationID)
|
||||
if err != nil {
|
||||
exception.New("failed to get invitation: %s", 500, err.Error()).Throw()
|
||||
exception.New("failed to get team invitation: %s", 500, err.Error()).Throw()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ProcessInvitationCreate user.invitation.create Invitation create processor
|
||||
// ProcessTeamInvitationCreate user.team.invitation.create Team invitation create processor
|
||||
// Args[0] string: team_id
|
||||
// Args[1] map: Invitation data {"user_id": "user123", "member_type": "user", "role_id": "member", "message": "...", "settings": {...}}
|
||||
// Return: map: {"invitation_id": "created_invitation_id"}
|
||||
func ProcessInvitationCreate(process *process.Process) interface{} {
|
||||
func ProcessTeamInvitationCreate(process *process.Process) interface{} {
|
||||
process.ValidateArgNums(2)
|
||||
|
||||
// Get user_id from session
|
||||
|
|
@ -506,9 +565,9 @@ func ProcessInvitationCreate(process *process.Process) interface{} {
|
|||
}
|
||||
|
||||
// Call business logic
|
||||
invitationID, err := invitationCreate(ctx, userIDStr, teamID, invitationData)
|
||||
invitationID, err := teamInvitationCreate(ctx, userIDStr, teamID, invitationData)
|
||||
if err != nil {
|
||||
exception.New("failed to create invitation: %s", 500, err.Error()).Throw()
|
||||
exception.New("failed to create team invitation: %s", 500, err.Error()).Throw()
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
|
|
@ -516,11 +575,11 @@ func ProcessInvitationCreate(process *process.Process) interface{} {
|
|||
}
|
||||
}
|
||||
|
||||
// ProcessInvitationResend user.invitation.resend Invitation resend processor
|
||||
// ProcessTeamInvitationResend user.team.invitation.resend Team invitation resend processor
|
||||
// Args[0] string: team_id
|
||||
// Args[1] string: invitation_id
|
||||
// Return: map: {"message": "success"}
|
||||
func ProcessInvitationResend(process *process.Process) interface{} {
|
||||
func ProcessTeamInvitationResend(process *process.Process) interface{} {
|
||||
process.ValidateArgNums(2)
|
||||
|
||||
// Get user_id from session
|
||||
|
|
@ -540,9 +599,9 @@ func ProcessInvitationResend(process *process.Process) interface{} {
|
|||
}
|
||||
|
||||
// Call business logic (no requestBaseURL available in process context)
|
||||
err := invitationResend(ctx, userIDStr, teamID, invitationID, "")
|
||||
err := teamInvitationResend(ctx, userIDStr, teamID, invitationID, "")
|
||||
if err != nil {
|
||||
exception.New("failed to resend invitation: %s", 500, err.Error()).Throw()
|
||||
exception.New("failed to resend team invitation: %s", 500, err.Error()).Throw()
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
|
|
@ -550,11 +609,11 @@ func ProcessInvitationResend(process *process.Process) interface{} {
|
|||
}
|
||||
}
|
||||
|
||||
// ProcessInvitationDelete user.invitation.delete Invitation delete processor
|
||||
// ProcessTeamInvitationDelete user.team.invitation.delete Team invitation delete processor
|
||||
// Args[0] string: team_id
|
||||
// Args[1] string: invitation_id
|
||||
// Return: map: {"message": "success"}
|
||||
func ProcessInvitationDelete(process *process.Process) interface{} {
|
||||
func ProcessTeamInvitationDelete(process *process.Process) interface{} {
|
||||
process.ValidateArgNums(2)
|
||||
|
||||
// Get user_id from session
|
||||
|
|
@ -574,9 +633,9 @@ func ProcessInvitationDelete(process *process.Process) interface{} {
|
|||
}
|
||||
|
||||
// Call business logic
|
||||
err := invitationDelete(ctx, userIDStr, teamID, invitationID)
|
||||
err := teamInvitationDelete(ctx, userIDStr, teamID, invitationID)
|
||||
if err != nil {
|
||||
exception.New("failed to delete invitation: %s", 500, err.Error()).Throw()
|
||||
exception.New("failed to delete team invitation: %s", 500, err.Error()).Throw()
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
|
|
@ -622,13 +681,13 @@ func getRequestBaseURL(c *gin.Context) string {
|
|||
return fmt.Sprintf("%s://%s", scheme, host)
|
||||
}
|
||||
|
||||
// buildInvitationLink constructs a full invitation link from invitation_id, token and team configuration
|
||||
// buildTeamInvitationLink constructs a full invitation link from invitation_id, token and team configuration
|
||||
// This is a centralized function to ensure consistency across email sending and link generation
|
||||
// Format:
|
||||
// - With team config baseURL: {base_url}/{invitation_id}/{token}
|
||||
// - With requestBaseURL (from HTTP request): {scheme}://{host}{AdminRoot}team/invite/{invitation_id}/{token}
|
||||
// - Without any baseURL (fallback): {AdminRoot}team/invite/{invitation_id}/{token}
|
||||
func buildInvitationLink(invitationID, token string, teamConfig *TeamConfig, requestBaseURL string) string {
|
||||
func buildTeamInvitationLink(invitationID, token string, teamConfig *TeamConfig, requestBaseURL string) string {
|
||||
// Priority 1: Use team config baseURL if specified
|
||||
if teamConfig != nil && teamConfig.Invite != nil && teamConfig.Invite.BaseURL != "" {
|
||||
baseURL := teamConfig.Invite.BaseURL
|
||||
|
|
@ -655,8 +714,8 @@ func buildInvitationLink(invitationID, token string, teamConfig *TeamConfig, req
|
|||
return fmt.Sprintf("%s/team/invite/%s/%s", adminRoot, invitationID, token)
|
||||
}
|
||||
|
||||
// invitationList handles the business logic for listing team invitations
|
||||
func invitationList(ctx context.Context, userID, teamID string, page, pagesize int, status string) (maps.MapStr, error) {
|
||||
// teamInvitationList handles the business logic for listing team invitations
|
||||
func teamInvitationList(ctx context.Context, userID, teamID string, page, pagesize int, status string) (maps.MapStr, error) {
|
||||
// Check if user has access to the team (read permission: owner or member)
|
||||
isOwner, isMember, err := checkTeamAccess(ctx, teamID, userID)
|
||||
if err != nil {
|
||||
|
|
@ -704,8 +763,102 @@ func invitationList(ctx context.Context, userID, teamID string, page, pagesize i
|
|||
return result, nil
|
||||
}
|
||||
|
||||
// invitationGet handles the business logic for getting a specific team invitation
|
||||
func invitationGet(ctx context.Context, userID, teamID, invitationID string) (maps.MapStrAny, error) {
|
||||
// teamInvitationGetPublic handles the business logic for getting a specific team invitation (public access)
|
||||
// This function doesn't require authentication and is used for invitation recipients
|
||||
// locale parameter is used to get localized role labels
|
||||
func teamInvitationGetPublic(ctx context.Context, invitationID, locale string) (*PublicInvitationResponse, error) {
|
||||
// Get user provider instance
|
||||
provider, err := getUserProvider()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get user provider: %w", err)
|
||||
}
|
||||
|
||||
// Get invitation details using invitation_id (business key)
|
||||
invitationData, err := provider.GetMemberByInvitationID(ctx, invitationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invitation not found: %w", err)
|
||||
}
|
||||
|
||||
// Only return if it's a pending invitation
|
||||
if toString(invitationData["status"]) != "pending" {
|
||||
return nil, fmt.Errorf("invitation not found or no longer pending")
|
||||
}
|
||||
|
||||
// Check if invitation has expired
|
||||
expiresAt := invitationData["invitation_expires_at"]
|
||||
if expiresAt != nil {
|
||||
var expiryTime time.Time
|
||||
switch v := expiresAt.(type) {
|
||||
case time.Time:
|
||||
expiryTime = v
|
||||
case string:
|
||||
parsed, err := time.Parse(time.RFC3339, v)
|
||||
if err == nil {
|
||||
expiryTime = parsed
|
||||
}
|
||||
}
|
||||
if !expiryTime.IsZero() && time.Now().After(expiryTime) {
|
||||
return nil, fmt.Errorf("invitation has expired")
|
||||
}
|
||||
}
|
||||
|
||||
// Get team information
|
||||
teamID := toString(invitationData["team_id"])
|
||||
team, err := provider.GetTeam(ctx, teamID)
|
||||
if err != nil {
|
||||
log.Warn("Failed to get team information: %v", err)
|
||||
team = maps.MapStrAny{"name": "Team"}
|
||||
}
|
||||
|
||||
// Get inviter information
|
||||
inviterID := toString(invitationData["invited_by"])
|
||||
var inviterInfo *InviterInfo
|
||||
if inviterID != "" {
|
||||
inviter, err := provider.GetUser(ctx, inviterID)
|
||||
if err == nil {
|
||||
inviterInfo = &InviterInfo{
|
||||
Name: toString(inviter["name"]),
|
||||
Picture: toString(inviter["picture"]),
|
||||
}
|
||||
// Fallback to masked email if name is empty (for privacy protection)
|
||||
if inviterInfo.Name == "" {
|
||||
inviterInfo.Name = maskEmail(toString(inviter["email"]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get role label from team config using provided locale
|
||||
roleID := toString(invitationData["role_id"])
|
||||
roleLabel := ""
|
||||
teamConfig := GetTeamConfig(locale)
|
||||
if teamConfig != nil && teamConfig.Roles != nil {
|
||||
for _, role := range teamConfig.Roles {
|
||||
if role.RoleID == roleID {
|
||||
roleLabel = role.Label
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build public response (exclude sensitive data like IDs)
|
||||
publicResponse := &PublicInvitationResponse{
|
||||
InvitationID: toString(invitationData["invitation_id"]),
|
||||
TeamName: toString(team["name"]),
|
||||
TeamLogo: toString(team["logo"]),
|
||||
TeamDescription: toString(team["description"]),
|
||||
RoleLabel: roleLabel,
|
||||
Status: toString(invitationData["status"]),
|
||||
InvitedAt: toTimeString(invitationData["invited_at"]),
|
||||
InvitationExpiresAt: toTimeString(invitationData["invitation_expires_at"]),
|
||||
Message: toString(invitationData["message"]),
|
||||
InviterInfo: inviterInfo,
|
||||
}
|
||||
|
||||
return publicResponse, nil
|
||||
}
|
||||
|
||||
// teamInvitationGet handles the business logic for getting a specific team invitation (admin access)
|
||||
func teamInvitationGet(ctx context.Context, userID, teamID, invitationID string) (maps.MapStrAny, error) {
|
||||
// Check if user has access to the team (read permission: owner or member)
|
||||
isOwner, isMember, err := checkTeamAccess(ctx, teamID, userID)
|
||||
if err != nil {
|
||||
|
|
@ -742,11 +895,11 @@ func invitationGet(ctx context.Context, userID, teamID, invitationID string) (ma
|
|||
return invitationData, nil
|
||||
}
|
||||
|
||||
// invitationCreate handles the business logic for creating a team invitation
|
||||
// teamInvitationCreate handles the business logic for creating a team invitation
|
||||
// Supports two scenarios:
|
||||
// 1. Email invitation: provide email and role, send invitation link via email
|
||||
// 2. Link invitation: create invitation link for display in frontend, customizable expiry
|
||||
func invitationCreate(ctx context.Context, userID, teamID string, invitationData maps.MapStrAny) (string, error) {
|
||||
func teamInvitationCreate(ctx context.Context, userID, teamID string, invitationData maps.MapStrAny) (string, error) {
|
||||
// Check if user has access to the team (write permission: owner only)
|
||||
isOwner, _, err := checkTeamAccess(ctx, teamID, userID)
|
||||
if err != nil {
|
||||
|
|
@ -833,13 +986,13 @@ func invitationCreate(ctx context.Context, userID, teamID string, invitationData
|
|||
}
|
||||
|
||||
// Generate invitation token
|
||||
token, err := generateInvitationToken()
|
||||
token, err := generateTeamInvitationToken()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate invitation token: %w", err)
|
||||
}
|
||||
|
||||
// Calculate expiry duration
|
||||
expiryDuration, err := getInvitationExpiry(invitationData)
|
||||
expiryDuration, err := getTeamInvitationExpiry(invitationData)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse expiry duration: %w", err)
|
||||
}
|
||||
|
|
@ -892,7 +1045,7 @@ func invitationCreate(ctx context.Context, userID, teamID string, invitationData
|
|||
emailData["request_base_url"] = requestBaseURL
|
||||
emailData["settings"] = savedSettings // Restore settings
|
||||
|
||||
err := sendInvitationEmail(bgCtx, inviteeEmail, inviterName, teamName, token, invitationID, emailData)
|
||||
err := sendTeamInvitationEmail(bgCtx, inviteeEmail, inviterName, teamName, token, invitationID, emailData)
|
||||
if err != nil {
|
||||
log.Error("Failed to send invitation email: %v", err)
|
||||
} else {
|
||||
|
|
@ -904,8 +1057,8 @@ func invitationCreate(ctx context.Context, userID, teamID string, invitationData
|
|||
return invitationID, nil
|
||||
}
|
||||
|
||||
// invitationResend handles the business logic for resending a team invitation
|
||||
func invitationResend(ctx context.Context, userID, teamID, invitationID, requestBaseURL string) error {
|
||||
// teamInvitationResend handles the business logic for resending a team invitation
|
||||
func teamInvitationResend(ctx context.Context, userID, teamID, invitationID, requestBaseURL string) error {
|
||||
// Check if user has access to the team (write permission: owner only)
|
||||
isOwner, _, err := checkTeamAccess(ctx, teamID, userID)
|
||||
if err != nil {
|
||||
|
|
@ -958,13 +1111,13 @@ func invitationResend(ctx context.Context, userID, teamID, invitationID, request
|
|||
}
|
||||
|
||||
// Generate new invitation token
|
||||
newToken, err := generateInvitationToken()
|
||||
newToken, err := generateTeamInvitationToken()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate new invitation token: %w", err)
|
||||
}
|
||||
|
||||
// Calculate expiry duration (use existing expiry from original invitation data)
|
||||
expiryDuration, err := getInvitationExpiry(invitationData)
|
||||
expiryDuration, err := getTeamInvitationExpiry(invitationData)
|
||||
if err != nil {
|
||||
log.Warn("Failed to parse expiry duration: %v, using default", err)
|
||||
expiryDuration = 7 * 24 * time.Hour
|
||||
|
|
@ -1007,7 +1160,7 @@ func invitationResend(ctx context.Context, userID, teamID, invitationID, request
|
|||
go func() {
|
||||
// Use background context for async operation
|
||||
bgCtx := context.Background()
|
||||
err := sendInvitationEmail(bgCtx, inviteeEmail, inviterName, teamName, newToken, invitationID, invitationData)
|
||||
err := sendTeamInvitationEmail(bgCtx, inviteeEmail, inviterName, teamName, newToken, invitationID, invitationData)
|
||||
if err != nil {
|
||||
log.Error("Failed to resend invitation email: %v", err)
|
||||
} else {
|
||||
|
|
@ -1019,8 +1172,8 @@ func invitationResend(ctx context.Context, userID, teamID, invitationID, request
|
|||
return nil
|
||||
}
|
||||
|
||||
// invitationDelete handles the business logic for cancelling a team invitation
|
||||
func invitationDelete(ctx context.Context, userID, teamID, invitationID string) error {
|
||||
// teamInvitationDelete handles the business logic for cancelling a team invitation
|
||||
func teamInvitationDelete(ctx context.Context, userID, teamID, invitationID string) error {
|
||||
// Check if user has access to the team (write permission: owner only)
|
||||
isOwner, _, err := checkTeamAccess(ctx, teamID, userID)
|
||||
if err != nil {
|
||||
|
|
@ -1065,8 +1218,8 @@ func invitationDelete(ctx context.Context, userID, teamID, invitationID string)
|
|||
|
||||
// Private Helper Functions (internal use only)
|
||||
|
||||
// generateInvitationToken generates a secure random token for invitations
|
||||
func generateInvitationToken() (string, error) {
|
||||
// generateTeamInvitationToken generates a secure random token for invitations
|
||||
func generateTeamInvitationToken() (string, error) {
|
||||
bytes := make([]byte, 32) // 32 bytes = 256 bits
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
|
|
@ -1076,9 +1229,9 @@ func generateInvitationToken() (string, error) {
|
|||
return strings.TrimRight(base64.URLEncoding.EncodeToString(bytes), "="), nil
|
||||
}
|
||||
|
||||
// getInvitationExpiry calculates the expiry duration for an invitation
|
||||
// getTeamInvitationExpiry calculates the expiry duration for an invitation
|
||||
// Priority: 1. Request expiry parameter, 2. Team config, 3. Default (7 days)
|
||||
func getInvitationExpiry(invitationData maps.MapStrAny) (time.Duration, error) {
|
||||
func getTeamInvitationExpiry(invitationData maps.MapStrAny) (time.Duration, error) {
|
||||
// Default expiry: 7 days
|
||||
defaultExpiry := 7 * 24 * time.Hour
|
||||
|
||||
|
|
@ -1128,8 +1281,8 @@ func getInvitationExpiry(invitationData maps.MapStrAny) (time.Duration, error) {
|
|||
return defaultExpiry, nil
|
||||
}
|
||||
|
||||
// sendInvitationEmail sends an invitation email using messenger service
|
||||
func sendInvitationEmail(ctx context.Context, email, inviterName, teamName, token, invitationID string, invitationData maps.MapStrAny) error {
|
||||
// sendTeamInvitationEmail sends an invitation email using messenger service
|
||||
func sendTeamInvitationEmail(ctx context.Context, email, inviterName, teamName, token, invitationID string, invitationData maps.MapStrAny) error {
|
||||
// Check if messenger is available
|
||||
if messenger.Instance == nil {
|
||||
return fmt.Errorf("messenger service not available")
|
||||
|
|
@ -1179,7 +1332,7 @@ func sendInvitationEmail(ctx context.Context, email, inviterName, teamName, toke
|
|||
requestBaseURL := toString(invitationData["request_base_url"])
|
||||
|
||||
// Build invitation link using centralized helper function
|
||||
invitationLink := buildInvitationLink(invitationID, token, teamConfig, requestBaseURL)
|
||||
invitationLink := buildTeamInvitationLink(invitationID, token, teamConfig, requestBaseURL)
|
||||
|
||||
// Prepare template data for messenger
|
||||
templateData := messengertypes.TemplateData{
|
||||
|
|
@ -1203,13 +1356,13 @@ func sendInvitationEmail(ctx context.Context, email, inviterName, teamName, toke
|
|||
return nil
|
||||
}
|
||||
|
||||
// convertToInvitationResponse converts a map to InvitationResponse (alias for mapToInvitationResponse)
|
||||
func convertToInvitationResponse(data maps.MapStrAny, requestBaseURL string) InvitationResponse {
|
||||
return mapToInvitationResponse(maps.MapStr(data), requestBaseURL)
|
||||
// convertToTeamInvitationResponse converts a map to InvitationResponse (alias for mapToTeamInvitationResponse)
|
||||
func convertToTeamInvitationResponse(data maps.MapStrAny, requestBaseURL string) InvitationResponse {
|
||||
return mapToTeamInvitationResponse(maps.MapStr(data), requestBaseURL)
|
||||
}
|
||||
|
||||
// mapToInvitationResponse converts a map to InvitationResponse
|
||||
func mapToInvitationResponse(data maps.MapStr, requestBaseURL string) InvitationResponse {
|
||||
// mapToTeamInvitationResponse converts a map to InvitationResponse
|
||||
func mapToTeamInvitationResponse(data maps.MapStr, requestBaseURL string) InvitationResponse {
|
||||
invitation := InvitationResponse{
|
||||
ID: toInt64(data["id"]),
|
||||
InvitationID: toString(data["invitation_id"]),
|
||||
|
|
@ -1251,16 +1404,16 @@ func mapToInvitationResponse(data maps.MapStr, requestBaseURL string) Invitation
|
|||
// Build invitation link if token is available
|
||||
if invitation.InvitationToken != "" && invitation.InvitationID != "" {
|
||||
teamConfig := GetTeamConfig(locale)
|
||||
invitation.InvitationLink = buildInvitationLink(invitation.InvitationID, invitation.InvitationToken, teamConfig, requestBaseURL)
|
||||
invitation.InvitationLink = buildTeamInvitationLink(invitation.InvitationID, invitation.InvitationToken, teamConfig, requestBaseURL)
|
||||
}
|
||||
|
||||
return invitation
|
||||
}
|
||||
|
||||
// mapToInvitationDetailResponse converts a map to InvitationDetailResponse
|
||||
func mapToInvitationDetailResponse(data maps.MapStr, requestBaseURL string) InvitationDetailResponse {
|
||||
// mapToTeamInvitationDetailResponse converts a map to InvitationDetailResponse
|
||||
func mapToTeamInvitationDetailResponse(data maps.MapStr, requestBaseURL string) InvitationDetailResponse {
|
||||
invitation := InvitationDetailResponse{
|
||||
InvitationResponse: mapToInvitationResponse(data, requestBaseURL),
|
||||
InvitationResponse: mapToTeamInvitationResponse(data, requestBaseURL),
|
||||
}
|
||||
|
||||
// Add user info if available (could be joined from user table)
|
||||
|
|
@ -331,6 +331,27 @@ type InvitationDetailResponse struct {
|
|||
TeamInfo map[string]interface{} `json:"team_info,omitempty"`
|
||||
}
|
||||
|
||||
// PublicInvitationResponse represents a public team invitation (for invitation recipients)
|
||||
// This type excludes sensitive information like tokens, database IDs, and timestamps
|
||||
type PublicInvitationResponse struct {
|
||||
InvitationID string `json:"invitation_id"`
|
||||
TeamName string `json:"team_name"`
|
||||
TeamLogo string `json:"team_logo"` // Always return, empty string if not set
|
||||
TeamDescription string `json:"team_description"` // Always return, empty string if not set
|
||||
RoleLabel string `json:"role_label,omitempty"`
|
||||
Status string `json:"status"`
|
||||
InvitedAt string `json:"invited_at"`
|
||||
InvitationExpiresAt string `json:"invitation_expires_at,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
InviterInfo *InviterInfo `json:"inviter_info,omitempty"` // Inviter's public info
|
||||
}
|
||||
|
||||
// InviterInfo represents public information about the person who sent the invitation
|
||||
type InviterInfo struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Picture string `json:"picture"` // Always return, empty string if not set
|
||||
}
|
||||
|
||||
// CreateInvitationRequest represents the request to send a team invitation
|
||||
type CreateInvitationRequest struct {
|
||||
UserID string `json:"user_id,omitempty"` // Optional for unregistered users
|
||||
|
|
|
|||
|
|
@ -11,11 +11,16 @@ import (
|
|||
func init() {
|
||||
// Register user process handlers
|
||||
process.RegisterGroup("user", map[string]process.Handler{
|
||||
"team.list": ProcessTeamList,
|
||||
"team.get": ProcessTeamGet,
|
||||
"team.create": ProcessTeamCreate,
|
||||
"team.update": ProcessTeamUpdate,
|
||||
"team.delete": ProcessTeamDelete,
|
||||
"team.list": ProcessTeamList,
|
||||
"team.get": ProcessTeamGet,
|
||||
"team.create": ProcessTeamCreate,
|
||||
"team.update": ProcessTeamUpdate,
|
||||
"team.delete": ProcessTeamDelete,
|
||||
"team.invitation.list": ProcessTeamInvitationList,
|
||||
"team.invitation.get": ProcessTeamInvitationGet,
|
||||
"team.invitation.create": ProcessTeamInvitationCreate,
|
||||
"team.invitation.resend": ProcessTeamInvitationResend,
|
||||
"team.invitation.delete": ProcessTeamInvitationDelete,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -51,6 +56,10 @@ func Attach(group *gin.RouterGroup, oauth types.OAuth) {
|
|||
|
||||
// User Team Management
|
||||
func attachTeam(group *gin.RouterGroup, oauth types.OAuth) {
|
||||
// Public endpoint for viewing team invitations (no auth required)
|
||||
// Must be registered BEFORE the team group with auth guard
|
||||
group.GET("/teams/invitations/:invitation_id", GinTeamInvitationGetPublic) // GET /user/teams/invitations/:invitation_id - Get invitation details (public)
|
||||
|
||||
team := group.Group("/teams")
|
||||
|
||||
// Protected endpoints (authentication required)
|
||||
|
|
@ -74,11 +83,11 @@ func attachTeam(group *gin.RouterGroup, oauth types.OAuth) {
|
|||
team.DELETE("/:id/members/:member_id", GinMemberDelete) // DELETE /teams/:id/members/:member_id - Remove member
|
||||
|
||||
// Team Invitations - Nested resource endpoints
|
||||
team.GET("/:id/invitations", GinInvitationList) // GET /teams/:id/invitations - List invitations
|
||||
team.POST("/:id/invitations", GinInvitationCreate) // POST /teams/:id/invitations - Send invitation
|
||||
team.GET("/:id/invitations/:invitation_id", GinInvitationGet) // GET /teams/:id/invitations/:invitation_id - Get invitation
|
||||
team.PUT("/:id/invitations/:invitation_id/resend", GinInvitationResend) // PUT /teams/:id/invitations/:invitation_id/resend - Resend invitation
|
||||
team.DELETE("/:id/invitations/:invitation_id", GinInvitationDelete) // DELETE /teams/:id/invitations/:invitation_id - Cancel invitation
|
||||
team.GET("/:id/invitations", GinTeamInvitationList) // GET /teams/:id/invitations - List invitations
|
||||
team.POST("/:id/invitations", GinTeamInvitationCreate) // POST /teams/:id/invitations - Send invitation
|
||||
team.GET("/:id/invitations/:invitation_id", GinTeamInvitationGet) // GET /teams/:id/invitations/:invitation_id - Get invitation (admin)
|
||||
team.PUT("/:id/invitations/:invitation_id/resend", GinTeamInvitationResend) // PUT /teams/:id/invitations/:invitation_id/resend - Resend invitation
|
||||
team.DELETE("/:id/invitations/:invitation_id", GinTeamInvitationDelete) // DELETE /teams/:id/invitations/:invitation_id - Cancel invitation
|
||||
}
|
||||
|
||||
// Invitation Response Management (Cross-module invitation handling)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package user
|
|||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/yaoapp/gou/process"
|
||||
|
|
@ -147,3 +148,45 @@ func toTimeString(v interface{}) string {
|
|||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Security Utilities
|
||||
|
||||
// maskEmail masks an email address for privacy protection
|
||||
// Keeps the first and last character of the local part, masks the middle with ***
|
||||
// Examples:
|
||||
// - "john.doe@example.com" -> "j***e@example.com"
|
||||
// - "a@example.com" -> "a***@example.com"
|
||||
// - "ab@example.com" -> "a***b@example.com"
|
||||
//
|
||||
// Returns empty string for invalid email or empty input
|
||||
func maskEmail(email string) string {
|
||||
if email == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Split email into local and domain parts
|
||||
parts := strings.Split(email, "@")
|
||||
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
||||
return "" // Invalid email format
|
||||
}
|
||||
|
||||
local := parts[0]
|
||||
domain := parts[1]
|
||||
|
||||
// Mask the local part
|
||||
var masked string
|
||||
localLen := len(local)
|
||||
switch localLen {
|
||||
case 1:
|
||||
// Single character: show it with ***
|
||||
masked = local + "***"
|
||||
case 2:
|
||||
// Two characters: show first + *** + last
|
||||
masked = string(local[0]) + "***" + string(local[1])
|
||||
default:
|
||||
// Three or more characters: show first + *** + last
|
||||
masked = string(local[0]) + "***" + string(local[localLen-1])
|
||||
}
|
||||
|
||||
return masked + "@" + domain
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue