- 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.
232 lines
6.7 KiB
Go
232 lines
6.7 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/yaoapp/gou/model"
|
|
"github.com/yaoapp/kun/maps"
|
|
)
|
|
|
|
// OAuth Account Resource
|
|
|
|
// CreateOAuthAccount creates a new OAuth account association
|
|
func (u *DefaultUser) CreateOAuthAccount(ctx context.Context, userID string, oauthData maps.MapStrAny) (interface{}, error) {
|
|
// Set required fields
|
|
oauthData["user_id"] = userID
|
|
|
|
// Set default status if not provided
|
|
if _, exists := oauthData["is_active"]; !exists {
|
|
oauthData["is_active"] = true
|
|
}
|
|
|
|
// Set last login time if not provided
|
|
if _, exists := oauthData["last_login_at"]; !exists {
|
|
oauthData["last_login_at"] = time.Now()
|
|
}
|
|
|
|
m := model.Select(u.oauthAccountModel)
|
|
id, err := m.Create(oauthData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(ErrFailedToCreateOAuth, err)
|
|
}
|
|
|
|
return id, nil
|
|
}
|
|
|
|
// GetOAuthAccount retrieves OAuth account by provider and subject
|
|
func (u *DefaultUser) GetOAuthAccount(ctx context.Context, provider string, subject string) (maps.MapStrAny, error) {
|
|
m := model.Select(u.oauthAccountModel)
|
|
accounts, err := m.Get(model.QueryParam{
|
|
Select: u.oauthAccountFields,
|
|
Wheres: []model.QueryWhere{
|
|
{Column: "provider", Value: provider},
|
|
{Column: "sub", Value: subject},
|
|
},
|
|
Limit: 1,
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf(ErrFailedToGetOAuthAccount, err)
|
|
}
|
|
|
|
if len(accounts) == 0 {
|
|
return nil, fmt.Errorf("oauth account not found for provider %s with subject %s", provider, subject)
|
|
}
|
|
|
|
return accounts[0], nil
|
|
}
|
|
|
|
// OAuthAccountExists checks if an OAuth account exists by provider and subject (lightweight query)
|
|
func (u *DefaultUser) OAuthAccountExists(ctx context.Context, provider string, subject string) (bool, error) {
|
|
m := model.Select(u.oauthAccountModel)
|
|
accounts, err := m.Get(model.QueryParam{
|
|
Select: []interface{}{"id"}, // Only select ID for existence check
|
|
Wheres: []model.QueryWhere{
|
|
{Column: "provider", Value: provider},
|
|
{Column: "sub", Value: subject},
|
|
},
|
|
Limit: 1, // Only need to know if at least one exists
|
|
})
|
|
|
|
if err != nil {
|
|
return false, fmt.Errorf(ErrFailedToGetOAuthAccount, err)
|
|
}
|
|
|
|
return len(accounts) > 0, nil
|
|
}
|
|
|
|
// GetUserOAuthAccounts retrieves all OAuth accounts for a user
|
|
func (u *DefaultUser) GetUserOAuthAccounts(ctx context.Context, userID string) ([]maps.MapStrAny, error) {
|
|
m := model.Select(u.oauthAccountModel)
|
|
accounts, err := m.Get(model.QueryParam{
|
|
Select: u.oauthAccountFields,
|
|
Wheres: []model.QueryWhere{
|
|
{Column: "user_id", Value: userID},
|
|
},
|
|
Orders: []model.QueryOrder{
|
|
{Column: "last_login_at", Option: "desc"},
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf(ErrFailedToGetOAuthAccount, err)
|
|
}
|
|
|
|
return accounts, nil
|
|
}
|
|
|
|
// UpdateOAuthAccount updates OAuth account information
|
|
func (u *DefaultUser) UpdateOAuthAccount(ctx context.Context, provider string, subject string, oauthData maps.MapStrAny) error {
|
|
// Remove sensitive fields that should not be updated directly
|
|
sensitiveFields := []string{"id", "user_id", "provider", "sub", "created_at"}
|
|
for _, field := range sensitiveFields {
|
|
delete(oauthData, field)
|
|
}
|
|
|
|
// Skip update if no valid fields remain
|
|
if len(oauthData) == 0 {
|
|
return nil
|
|
}
|
|
|
|
m := model.Select(u.oauthAccountModel)
|
|
affected, err := m.UpdateWhere(model.QueryParam{
|
|
Wheres: []model.QueryWhere{
|
|
{Column: "provider", Value: provider},
|
|
{Column: "sub", Value: subject},
|
|
},
|
|
Limit: 1, // Safety: ensure only one record is updated
|
|
}, oauthData)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf(ErrFailedToUpdateOAuth, err)
|
|
}
|
|
|
|
if affected == 0 {
|
|
// Check if OAuth account exists
|
|
exists, checkErr := u.OAuthAccountExists(ctx, provider, subject)
|
|
if checkErr != nil {
|
|
return fmt.Errorf(ErrFailedToUpdateOAuth, checkErr)
|
|
}
|
|
if !exists {
|
|
return fmt.Errorf("oauth account not found for provider %s with subject %s", provider, subject)
|
|
}
|
|
// OAuth account exists but no changes were made
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeleteOAuthAccount removes an OAuth account association
|
|
func (u *DefaultUser) DeleteOAuthAccount(ctx context.Context, provider string, subject string) error {
|
|
m := model.Select(u.oauthAccountModel)
|
|
affected, err := m.DeleteWhere(model.QueryParam{
|
|
Wheres: []model.QueryWhere{
|
|
{Column: "provider", Value: provider},
|
|
{Column: "sub", Value: subject},
|
|
},
|
|
Limit: 1, // Safety: ensure only one record is deleted
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf(ErrFailedToDeleteOAuth, err)
|
|
}
|
|
|
|
if affected == 0 {
|
|
return fmt.Errorf("oauth account not found for provider %s with subject %s", provider, subject)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeleteUserOAuthAccounts removes all OAuth accounts for a specific user
|
|
func (u *DefaultUser) DeleteUserOAuthAccounts(ctx context.Context, userID string) error {
|
|
m := model.Select(u.oauthAccountModel)
|
|
|
|
// Use batch soft delete (the Gou library bug has been fixed)
|
|
_, err := m.DeleteWhere(model.QueryParam{
|
|
Wheres: []model.QueryWhere{
|
|
{Column: "user_id", Value: userID},
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf(ErrFailedToDeleteOAuth, err)
|
|
}
|
|
|
|
// Note: We don't check affected count here because it's valid for a user to have no OAuth accounts
|
|
// This method is typically called during user deletion as a cleanup operation
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetOAuthAccounts retrieves OAuth accounts by query parameters
|
|
func (u *DefaultUser) GetOAuthAccounts(ctx context.Context, param model.QueryParam) ([]maps.MapStr, error) {
|
|
// Set default select fields if not provided
|
|
if param.Select == nil {
|
|
param.Select = u.oauthAccountFields
|
|
}
|
|
|
|
m := model.Select(u.oauthAccountModel)
|
|
accounts, err := m.Get(param)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(ErrFailedToGetOAuthAccount, err)
|
|
}
|
|
|
|
return accounts, nil
|
|
}
|
|
|
|
// PaginateOAuthAccounts retrieves paginated list of OAuth accounts
|
|
func (u *DefaultUser) PaginateOAuthAccounts(ctx context.Context, param model.QueryParam, page int, pagesize int) (maps.MapStr, error) {
|
|
// Set default select fields if not provided
|
|
if param.Select == nil {
|
|
param.Select = u.oauthAccountFields
|
|
}
|
|
|
|
m := model.Select(u.oauthAccountModel)
|
|
result, err := m.Paginate(param, page, pagesize)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(ErrFailedToGetOAuthAccount, err)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// CountOAuthAccounts returns total count of OAuth accounts with optional filters
|
|
func (u *DefaultUser) CountOAuthAccounts(ctx context.Context, param model.QueryParam) (int64, error) {
|
|
// Use Paginate with a small page size to get the total count
|
|
// This is more reliable than manual COUNT(*) queries
|
|
m := model.Select(u.oauthAccountModel)
|
|
result, err := m.Paginate(param, 1, 1) // Get first page with 1 item to get total
|
|
if err != nil {
|
|
return 0, fmt.Errorf(ErrFailedToGetOAuthAccount, err)
|
|
}
|
|
|
|
// Extract total from pagination result using utility function
|
|
if totalInterface, ok := result["total"]; ok {
|
|
return parseIntFromDB(totalInterface)
|
|
}
|
|
|
|
return 0, fmt.Errorf("total not found in pagination result")
|
|
}
|