- Added functions to check the existence of invitation codes, members, OAuth accounts, roles, teams, and user types before performing updates, enhancing error handling and user feedback. - Updated relevant update functions to utilize these existence checks, ensuring accurate error messages when no changes are made or when entities do not exist. - Refactored tests to validate the new existence check logic, improving overall test coverage and reliability.
234 lines
5.9 KiB
Go
234 lines
5.9 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/yaoapp/gou/model"
|
|
"github.com/yaoapp/kun/maps"
|
|
)
|
|
|
|
// Invitation Code Resource (Official Platform Invitation Codes)
|
|
|
|
// invitationCodeExists checks if an invitation code exists by code
|
|
func (u *DefaultUser) invitationCodeExists(ctx context.Context, code string) (bool, error) {
|
|
m := model.Select(u.invitationModel)
|
|
invitations, err := m.Get(model.QueryParam{
|
|
Select: []interface{}{"id"},
|
|
Wheres: []model.QueryWhere{
|
|
{Column: "code", Value: code},
|
|
},
|
|
Limit: 1,
|
|
})
|
|
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to check invitation code existence: %w", err)
|
|
}
|
|
|
|
return len(invitations) > 0, nil
|
|
}
|
|
|
|
// CreateInvitationCodes creates invitation codes in batch
|
|
// Supports creating multiple invitation codes at once for efficiency
|
|
func (u *DefaultUser) CreateInvitationCodes(ctx context.Context, codeData []maps.MapStrAny) ([]string, error) {
|
|
if len(codeData) == 0 {
|
|
return []string{}, nil
|
|
}
|
|
|
|
codes := make([]string, 0, len(codeData))
|
|
m := model.Select(u.invitationModel)
|
|
|
|
// Validate and prepare data - collect all possible columns
|
|
columnsSet := make(map[string]bool)
|
|
columnsSet["code"] = true
|
|
columnsSet["status"] = true
|
|
columnsSet["is_published"] = true
|
|
columnsSet["code_type"] = true
|
|
|
|
for i := range codeData {
|
|
// Validate required fields
|
|
code, hasCode := codeData[i]["code"].(string)
|
|
if !hasCode || code == "" {
|
|
return nil, fmt.Errorf("code is required in codeData at index %d", i)
|
|
}
|
|
|
|
// Set default values if not provided
|
|
if _, exists := codeData[i]["status"]; !exists {
|
|
codeData[i]["status"] = "draft"
|
|
}
|
|
if _, exists := codeData[i]["is_published"]; !exists {
|
|
codeData[i]["is_published"] = false
|
|
}
|
|
if _, exists := codeData[i]["code_type"]; !exists {
|
|
codeData[i]["code_type"] = "official"
|
|
}
|
|
|
|
// Collect optional columns
|
|
for _, col := range []string{"owner_id", "description", "source", "expires_at", "metadata"} {
|
|
if _, exists := codeData[i][col]; exists {
|
|
columnsSet[col] = true
|
|
}
|
|
}
|
|
|
|
codes = append(codes, code)
|
|
}
|
|
|
|
// Build ordered column list
|
|
columns := []string{"code", "status", "is_published", "code_type"}
|
|
for _, col := range []string{"owner_id", "description", "source", "expires_at", "metadata"} {
|
|
if columnsSet[col] {
|
|
columns = append(columns, col)
|
|
}
|
|
}
|
|
|
|
// Build values matrix
|
|
values := make([][]interface{}, 0, len(codeData))
|
|
for i := range codeData {
|
|
row := make([]interface{}, len(columns))
|
|
for j, col := range columns {
|
|
if val, exists := codeData[i][col]; exists {
|
|
row[j] = val
|
|
} else {
|
|
row[j] = nil
|
|
}
|
|
}
|
|
values = append(values, row)
|
|
}
|
|
|
|
// Batch insert
|
|
err := m.Insert(columns, values)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(ErrFailedToCreateInvitationCode, err)
|
|
}
|
|
|
|
return codes, nil
|
|
}
|
|
|
|
// UseInvitationCode marks an invitation code as used (redemption)
|
|
// This is called when a user successfully uses an invitation code during registration
|
|
func (u *DefaultUser) UseInvitationCode(ctx context.Context, code string, userID string) error {
|
|
m := model.Select(u.invitationModel)
|
|
|
|
// First, get the invitation code to validate it
|
|
invitations, err := m.Get(model.QueryParam{
|
|
Select: []interface{}{"id", "code", "status", "is_published", "expires_at", "used_by"},
|
|
Wheres: []model.QueryWhere{
|
|
{Column: "code", Value: code},
|
|
},
|
|
Limit: 1,
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf(ErrFailedToUseInvitationCode, err)
|
|
}
|
|
|
|
if len(invitations) == 0 {
|
|
return fmt.Errorf(ErrInvitationCodeNotFound)
|
|
}
|
|
|
|
invitation := invitations[0]
|
|
|
|
// Check if already used
|
|
if usedBy := invitation["used_by"]; usedBy != nil && usedBy != "" {
|
|
return fmt.Errorf(ErrInvitationCodeAlreadyUsed)
|
|
}
|
|
|
|
// Check if published (handle both bool and int64 types from different databases)
|
|
isPublished := false
|
|
switch v := invitation["is_published"].(type) {
|
|
case bool:
|
|
isPublished = v
|
|
case int64:
|
|
isPublished = v != 0
|
|
case int:
|
|
isPublished = v != 0
|
|
}
|
|
if !isPublished {
|
|
return fmt.Errorf(ErrInvitationCodeNotPublished)
|
|
}
|
|
|
|
// Check status
|
|
status, ok := invitation["status"].(string)
|
|
if !ok || status != "active" {
|
|
return fmt.Errorf("invitation code status must be 'active' to use, current status: %s", status)
|
|
}
|
|
|
|
// Check if expired
|
|
if expiresAt := invitation["expires_at"]; expiresAt != nil {
|
|
if expired, err := checkTimeExpired(expiresAt); err == nil && expired {
|
|
return fmt.Errorf(ErrInvitationCodeExpired)
|
|
}
|
|
}
|
|
|
|
// Mark as used
|
|
updateData := maps.MapStrAny{
|
|
"used_by": userID,
|
|
"used_at": time.Now(),
|
|
"status": "used",
|
|
}
|
|
|
|
affected, err := m.UpdateWhere(model.QueryParam{
|
|
Wheres: []model.QueryWhere{
|
|
{Column: "code", Value: code},
|
|
},
|
|
Limit: 1,
|
|
}, updateData)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf(ErrFailedToUseInvitationCode, err)
|
|
}
|
|
|
|
if affected == 0 {
|
|
// Check if invitation code still exists
|
|
exists, checkErr := u.invitationCodeExists(ctx, code)
|
|
if checkErr != nil {
|
|
return fmt.Errorf(ErrFailedToUseInvitationCode, checkErr)
|
|
}
|
|
if !exists {
|
|
return fmt.Errorf(ErrInvitationCodeNotFound)
|
|
}
|
|
// Invitation code exists but no changes were made (already in this state)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeleteInvitationCode soft deletes an invitation code
|
|
func (u *DefaultUser) DeleteInvitationCode(ctx context.Context, code string) error {
|
|
// First check if invitation code exists
|
|
m := model.Select(u.invitationModel)
|
|
invitations, err := m.Get(model.QueryParam{
|
|
Select: []interface{}{"id", "code"},
|
|
Wheres: []model.QueryWhere{
|
|
{Column: "code", Value: code},
|
|
},
|
|
Limit: 1,
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf(ErrFailedToDeleteInvitationCode, err)
|
|
}
|
|
|
|
if len(invitations) == 0 {
|
|
return fmt.Errorf(ErrInvitationCodeNotFound)
|
|
}
|
|
|
|
// Proceed with soft delete
|
|
affected, err := m.DeleteWhere(model.QueryParam{
|
|
Wheres: []model.QueryWhere{
|
|
{Column: "code", Value: code},
|
|
},
|
|
Limit: 1,
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf(ErrFailedToDeleteInvitationCode, err)
|
|
}
|
|
|
|
if affected == 0 {
|
|
return fmt.Errorf(ErrInvitationCodeNotFound)
|
|
}
|
|
|
|
return nil
|
|
}
|