Merge pull request #1147 from trheyi/main
Refactor invitation handling and data parsing in user provider
This commit is contained in:
commit
f78af80014
8 changed files with 106 additions and 127 deletions
|
|
@ -228,16 +228,15 @@ func (u *DefaultUser) AcceptInvitation(ctx context.Context, invitationToken stri
|
||||||
member := members[0]
|
member := members[0]
|
||||||
|
|
||||||
// Check if invitation has expired
|
// Check if invitation has expired
|
||||||
if expiresAt, ok := member["invitation_expires_at"]; ok {
|
if expired, err := checkTimeExpired(member["invitation_expires_at"]); err == nil && expired {
|
||||||
if expiryTime, ok := expiresAt.(time.Time); ok {
|
return fmt.Errorf("invitation has expired")
|
||||||
if time.Now().After(expiryTime) {
|
|
||||||
return fmt.Errorf("invitation has expired")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update member status to active
|
// Update member status to active
|
||||||
memberID := member["id"].(int64)
|
memberID, err := parseIntFromDB(member["id"])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid member ID: %w", err)
|
||||||
|
}
|
||||||
updateData := maps.MapStrAny{
|
updateData := maps.MapStrAny{
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"joined_at": time.Now(),
|
"joined_at": time.Now(),
|
||||||
|
|
@ -489,15 +488,10 @@ func (u *DefaultUser) UpdateMemberLastActivity(ctx context.Context, teamID strin
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
loginCount := 0
|
loginCount := int64(0)
|
||||||
if count := member["login_count"]; count != nil {
|
if count := member["login_count"]; count != nil {
|
||||||
switch v := count.(type) {
|
if parsedCount, err := parseIntFromDB(count); err == nil {
|
||||||
case int:
|
loginCount = parsedCount
|
||||||
loginCount = v
|
|
||||||
case int64:
|
|
||||||
loginCount = int(v)
|
|
||||||
case int32:
|
|
||||||
loginCount = int(v)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateData["login_count"] = loginCount + 1
|
updateData["login_count"] = loginCount + 1
|
||||||
|
|
|
||||||
|
|
@ -722,6 +722,7 @@ func TestMemberInvitationExpiry(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// Create member with expired invitation
|
// Create member with expired invitation
|
||||||
|
expiredTime := time.Now().Add(-2 * time.Hour) // Expired 2 hours ago to be safe
|
||||||
memberData := maps.MapStrAny{
|
memberData := maps.MapStrAny{
|
||||||
"team_id": teamID,
|
"team_id": teamID,
|
||||||
"user_id": inviteeUser,
|
"user_id": inviteeUser,
|
||||||
|
|
@ -729,9 +730,9 @@ func TestMemberInvitationExpiry(t *testing.T) {
|
||||||
"role_id": "user",
|
"role_id": "user",
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"invited_by": ownerUser,
|
"invited_by": ownerUser,
|
||||||
"invited_at": time.Now(),
|
"invited_at": expiredTime.Add(-1 * time.Hour), // Invited 3 hours ago
|
||||||
"invitation_token": "expired-token-" + testUUID,
|
"invitation_token": "expired-token-" + testUUID,
|
||||||
"invitation_expires_at": time.Now().Add(-1 * time.Hour), // Expired 1 hour ago
|
"invitation_expires_at": expiredTime, // Expired 2 hours ago
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = testProvider.CreateMember(ctx, memberData)
|
_, err = testProvider.CreateMember(ctx, memberData)
|
||||||
|
|
|
||||||
|
|
@ -215,29 +215,9 @@ func (u *DefaultUser) CountOAuthAccounts(ctx context.Context, param model.QueryP
|
||||||
return 0, fmt.Errorf(ErrFailedToGetOAuthAccount, err)
|
return 0, fmt.Errorf(ErrFailedToGetOAuthAccount, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract total from pagination result
|
// Extract total from pagination result using utility function
|
||||||
if total, ok := result["total"].(int64); ok {
|
|
||||||
return total, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle different total types returned by Paginate
|
|
||||||
if totalInterface, ok := result["total"]; ok {
|
if totalInterface, ok := result["total"]; ok {
|
||||||
switch v := totalInterface.(type) {
|
return parseIntFromDB(totalInterface)
|
||||||
case int:
|
|
||||||
return int64(v), nil
|
|
||||||
case int32:
|
|
||||||
return int64(v), nil
|
|
||||||
case int64:
|
|
||||||
return v, nil
|
|
||||||
case uint:
|
|
||||||
return int64(v), nil
|
|
||||||
case uint32:
|
|
||||||
return int64(v), nil
|
|
||||||
case uint64:
|
|
||||||
return int64(v), nil
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("unexpected total type: %T", totalInterface)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, fmt.Errorf("total not found in pagination result")
|
return 0, fmt.Errorf("total not found in pagination result")
|
||||||
|
|
|
||||||
|
|
@ -212,29 +212,9 @@ func (u *DefaultUser) CountRoles(ctx context.Context, param model.QueryParam) (i
|
||||||
return 0, fmt.Errorf(ErrFailedToGetRole, err)
|
return 0, fmt.Errorf(ErrFailedToGetRole, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract total from pagination result
|
// Extract total from pagination result using utility function
|
||||||
if total, ok := result["total"].(int64); ok {
|
|
||||||
return total, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle different total types returned by Paginate
|
|
||||||
if totalInterface, ok := result["total"]; ok {
|
if totalInterface, ok := result["total"]; ok {
|
||||||
switch v := totalInterface.(type) {
|
return parseIntFromDB(totalInterface)
|
||||||
case int:
|
|
||||||
return int64(v), nil
|
|
||||||
case int32:
|
|
||||||
return int64(v), nil
|
|
||||||
case int64:
|
|
||||||
return v, nil
|
|
||||||
case uint:
|
|
||||||
return int64(v), nil
|
|
||||||
case uint32:
|
|
||||||
return int64(v), nil
|
|
||||||
case uint64:
|
|
||||||
return int64(v), nil
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("unexpected total type: %T", totalInterface)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, fmt.Errorf("total not found in pagination result")
|
return 0, fmt.Errorf("total not found in pagination result")
|
||||||
|
|
|
||||||
|
|
@ -228,29 +228,9 @@ func (u *DefaultUser) CountTeams(ctx context.Context, param model.QueryParam) (i
|
||||||
return 0, fmt.Errorf(ErrFailedToGetTeam, err)
|
return 0, fmt.Errorf(ErrFailedToGetTeam, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract total from pagination result
|
// Extract total from pagination result using utility function
|
||||||
if total, ok := result["total"].(int64); ok {
|
|
||||||
return total, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle different total types returned by Paginate
|
|
||||||
if totalInterface, ok := result["total"]; ok {
|
if totalInterface, ok := result["total"]; ok {
|
||||||
switch v := totalInterface.(type) {
|
return parseIntFromDB(totalInterface)
|
||||||
case int:
|
|
||||||
return int64(v), nil
|
|
||||||
case int32:
|
|
||||||
return int64(v), nil
|
|
||||||
case int64:
|
|
||||||
return v, nil
|
|
||||||
case uint:
|
|
||||||
return int64(v), nil
|
|
||||||
case uint32:
|
|
||||||
return int64(v), nil
|
|
||||||
case uint64:
|
|
||||||
return int64(v), nil
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("unexpected total type: %T", totalInterface)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, fmt.Errorf("total not found in pagination result")
|
return 0, fmt.Errorf("total not found in pagination result")
|
||||||
|
|
|
||||||
|
|
@ -202,29 +202,9 @@ func (u *DefaultUser) CountTypes(ctx context.Context, param model.QueryParam) (i
|
||||||
return 0, fmt.Errorf(ErrFailedToGetType, err)
|
return 0, fmt.Errorf(ErrFailedToGetType, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract total from pagination result
|
// Extract total from pagination result using utility function
|
||||||
if total, ok := result["total"].(int64); ok {
|
|
||||||
return total, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle different total types returned by Paginate
|
|
||||||
if totalInterface, ok := result["total"]; ok {
|
if totalInterface, ok := result["total"]; ok {
|
||||||
switch v := totalInterface.(type) {
|
return parseIntFromDB(totalInterface)
|
||||||
case int:
|
|
||||||
return int64(v), nil
|
|
||||||
case int32:
|
|
||||||
return int64(v), nil
|
|
||||||
case int64:
|
|
||||||
return v, nil
|
|
||||||
case uint:
|
|
||||||
return int64(v), nil
|
|
||||||
case uint32:
|
|
||||||
return int64(v), nil
|
|
||||||
case uint64:
|
|
||||||
return int64(v), nil
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("unexpected total type: %T", totalInterface)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, fmt.Errorf("total not found in pagination result")
|
return 0, fmt.Errorf("total not found in pagination result")
|
||||||
|
|
|
||||||
|
|
@ -52,29 +52,9 @@ func (u *DefaultUser) CountUsers(ctx context.Context, param model.QueryParam) (i
|
||||||
return 0, fmt.Errorf(ErrFailedToGetUser, err)
|
return 0, fmt.Errorf(ErrFailedToGetUser, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract total from pagination result
|
// Extract total from pagination result using utility function
|
||||||
if total, ok := result["total"].(int64); ok {
|
|
||||||
return total, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle different total types returned by Paginate
|
|
||||||
if totalInterface, ok := result["total"]; ok {
|
if totalInterface, ok := result["total"]; ok {
|
||||||
switch v := totalInterface.(type) {
|
return parseIntFromDB(totalInterface)
|
||||||
case int:
|
|
||||||
return int64(v), nil
|
|
||||||
case int32:
|
|
||||||
return int64(v), nil
|
|
||||||
case int64:
|
|
||||||
return v, nil
|
|
||||||
case uint:
|
|
||||||
return int64(v), nil
|
|
||||||
case uint32:
|
|
||||||
return int64(v), nil
|
|
||||||
case uint64:
|
|
||||||
return int64(v), nil
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("unexpected total type: %T", totalInterface)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, fmt.Errorf("total not found in pagination result")
|
return 0, fmt.Errorf("total not found in pagination result")
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||||
|
|
@ -166,3 +167,86 @@ func generateRandomPassword(length int) (string, error) {
|
||||||
|
|
||||||
return string(bytes), nil
|
return string(bytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseTimeFromDB parses time values from database fields, handling different formats and types
|
||||||
|
func parseTimeFromDB(value interface{}) (*time.Time, error) {
|
||||||
|
if value == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case time.Time:
|
||||||
|
return &v, nil
|
||||||
|
case string:
|
||||||
|
if v == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// Try parsing common time formats - assume local timezone for database timestamps
|
||||||
|
if parsedTime, err := time.ParseInLocation("2006-01-02 15:04:05", v, time.Local); err == nil {
|
||||||
|
return &parsedTime, nil
|
||||||
|
}
|
||||||
|
if parsedTime, err := time.Parse(time.RFC3339, v); err == nil {
|
||||||
|
return &parsedTime, nil
|
||||||
|
}
|
||||||
|
if parsedTime, err := time.ParseInLocation("2006-01-02T15:04:05", v, time.Local); err == nil {
|
||||||
|
return &parsedTime, nil
|
||||||
|
}
|
||||||
|
if parsedTime, err := time.ParseInLocation("2006-01-02 15:04:05.000000", v, time.Local); err == nil {
|
||||||
|
return &parsedTime, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unable to parse time format: %s", v)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported time type: %T", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseIntFromDB parses integer values from database fields, handling different integer types
|
||||||
|
func parseIntFromDB(value interface{}) (int64, error) {
|
||||||
|
if value == nil {
|
||||||
|
return 0, fmt.Errorf("value is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case int64:
|
||||||
|
return v, nil
|
||||||
|
case int:
|
||||||
|
return int64(v), nil
|
||||||
|
case int32:
|
||||||
|
return int64(v), nil
|
||||||
|
case uint:
|
||||||
|
return int64(v), nil
|
||||||
|
case uint32:
|
||||||
|
return int64(v), nil
|
||||||
|
case uint64:
|
||||||
|
// Check for overflow
|
||||||
|
if v > 9223372036854775807 { // max int64
|
||||||
|
return 0, fmt.Errorf("value too large for int64: %d", v)
|
||||||
|
}
|
||||||
|
return int64(v), nil
|
||||||
|
case float64:
|
||||||
|
// Handle cases where database returns numbers as floats
|
||||||
|
return int64(v), nil
|
||||||
|
case string:
|
||||||
|
// Try to parse string as integer
|
||||||
|
if parsed, err := fmt.Sscanf(v, "%d", new(int64)); err == nil && parsed == 1 {
|
||||||
|
var result int64
|
||||||
|
fmt.Sscanf(v, "%d", &result)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("unable to parse string as integer: %s", v)
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unsupported integer type: %T", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkTimeExpired checks if a time field from database indicates expiration
|
||||||
|
func checkTimeExpired(value interface{}) (bool, error) {
|
||||||
|
parsedTime, err := parseTimeFromDB(value)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if parsedTime == nil {
|
||||||
|
return false, nil // No expiry time set
|
||||||
|
}
|
||||||
|
return time.Now().After(*parsedTime), nil
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue