yao/openapi/oauth/providers/user/oauth_account.go
Max f71ce2ac9a Implement existence checks for various entities in update functions
- 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.
2025-10-26 11:09:07 +08:00

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")
}