mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-05-04 21:20:51 +08:00
feat(auth): reclaim stale identities and refresh profile UI
This commit is contained in:
@@ -12,7 +12,9 @@ import (
|
||||
dbent "github.com/Wei-Shaw/sub2api/ent"
|
||||
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||
"github.com/Wei-Shaw/sub2api/ent/authidentity"
|
||||
"github.com/Wei-Shaw/sub2api/ent/authidentitychannel"
|
||||
dbgroup "github.com/Wei-Shaw/sub2api/ent/group"
|
||||
"github.com/Wei-Shaw/sub2api/ent/identityadoptiondecision"
|
||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||
dbuser "github.com/Wei-Shaw/sub2api/ent/user"
|
||||
"github.com/Wei-Shaw/sub2api/ent/userallowedgroup"
|
||||
@@ -292,13 +294,57 @@ func normalizeEmailAuthIdentitySubject(email string) string {
|
||||
}
|
||||
|
||||
func (r *userRepository) Delete(ctx context.Context, id int64) error {
|
||||
affected, err := r.client.User.Delete().Where(dbuser.IDEQ(id)).Exec(ctx)
|
||||
tx, err := r.client.Tx(ctx)
|
||||
if err != nil && !errors.Is(err, dbent.ErrTxStarted) {
|
||||
return translatePersistenceError(err, service.ErrUserNotFound, nil)
|
||||
}
|
||||
|
||||
var txClient *dbent.Client
|
||||
if err == nil {
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
txClient = tx.Client()
|
||||
} else {
|
||||
txClient = r.client
|
||||
}
|
||||
|
||||
identityIDs, err := txClient.AuthIdentity.Query().
|
||||
Where(authidentity.UserIDEQ(id)).
|
||||
IDs(ctx)
|
||||
if err != nil {
|
||||
return translatePersistenceError(err, service.ErrUserNotFound, nil)
|
||||
}
|
||||
if len(identityIDs) > 0 {
|
||||
if _, err := txClient.IdentityAdoptionDecision.Update().
|
||||
Where(identityadoptiondecision.IdentityIDIn(identityIDs...)).
|
||||
ClearIdentityID().
|
||||
Save(ctx); err != nil {
|
||||
return translatePersistenceError(err, service.ErrUserNotFound, nil)
|
||||
}
|
||||
if _, err := txClient.AuthIdentityChannel.Delete().
|
||||
Where(authidentitychannel.IdentityIDIn(identityIDs...)).
|
||||
Exec(ctx); err != nil {
|
||||
return translatePersistenceError(err, service.ErrUserNotFound, nil)
|
||||
}
|
||||
if _, err := txClient.AuthIdentity.Delete().
|
||||
Where(authidentity.UserIDEQ(id)).
|
||||
Exec(ctx); err != nil {
|
||||
return translatePersistenceError(err, service.ErrUserNotFound, nil)
|
||||
}
|
||||
}
|
||||
|
||||
affected, err := txClient.User.Delete().Where(dbuser.IDEQ(id)).Exec(ctx)
|
||||
if err != nil {
|
||||
return translatePersistenceError(err, service.ErrUserNotFound, nil)
|
||||
}
|
||||
if affected == 0 {
|
||||
return service.ErrUserNotFound
|
||||
}
|
||||
|
||||
if tx != nil {
|
||||
if err := tx.Commit(); err != nil {
|
||||
return translatePersistenceError(err, service.ErrUserNotFound, nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -645,15 +691,17 @@ func (r *userRepository) ExistsByEmail(ctx context.Context, email string) (bool,
|
||||
}
|
||||
|
||||
func userEmailLookupPredicate(email string) predicate.User {
|
||||
normalized := strings.TrimSpace(email)
|
||||
normalized := strings.ToLower(strings.TrimSpace(email))
|
||||
if normalized == "" {
|
||||
return dbuser.EmailEQ(email)
|
||||
}
|
||||
return predicate.User(func(s *entsql.Selector) {
|
||||
s.Where(entsql.ExprP(
|
||||
fmt.Sprintf("LOWER(TRIM(%s)) = LOWER(TRIM(?))", s.C(dbuser.FieldEmail)),
|
||||
normalized,
|
||||
))
|
||||
s.Where(entsql.P(func(b *entsql.Builder) {
|
||||
b.WriteString("LOWER(TRIM(").
|
||||
Ident(s.C(dbuser.FieldEmail)).
|
||||
WriteString(")) = ").
|
||||
Arg(normalized)
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"time"
|
||||
|
||||
dbent "github.com/Wei-Shaw/sub2api/ent"
|
||||
"github.com/Wei-Shaw/sub2api/ent/authidentity"
|
||||
"github.com/Wei-Shaw/sub2api/ent/authidentitychannel"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
"github.com/stretchr/testify/suite"
|
||||
@@ -124,11 +126,27 @@ func (s *UserRepoSuite) TestGetByEmail() {
|
||||
s.Require().Equal(user.ID, got.ID)
|
||||
}
|
||||
|
||||
func (s *UserRepoSuite) TestGetByEmail_NormalizesSpacingAndCaseOnPostgres() {
|
||||
user := s.mustCreateUser(&service.User{Email: " Legacy@Example.com "})
|
||||
|
||||
got, err := s.repo.GetByEmail(s.ctx, " legacy@example.com ")
|
||||
s.Require().NoError(err, "GetByEmail normalized lookup")
|
||||
s.Require().Equal(user.ID, got.ID)
|
||||
}
|
||||
|
||||
func (s *UserRepoSuite) TestGetByEmail_NotFound() {
|
||||
_, err := s.repo.GetByEmail(s.ctx, "nonexistent@test.com")
|
||||
s.Require().Error(err, "expected error for non-existent email")
|
||||
}
|
||||
|
||||
func (s *UserRepoSuite) TestExistsByEmail_NormalizesSpacingAndCaseOnPostgres() {
|
||||
s.mustCreateUser(&service.User{Email: " Legacy@Example.com "})
|
||||
|
||||
exists, err := s.repo.ExistsByEmail(s.ctx, " LEGACY@example.com ")
|
||||
s.Require().NoError(err, "ExistsByEmail normalized lookup")
|
||||
s.Require().True(exists)
|
||||
}
|
||||
|
||||
func (s *UserRepoSuite) TestUpdate() {
|
||||
user := s.mustCreateUser(&service.User{Email: "update@test.com", Username: "original"})
|
||||
|
||||
@@ -152,6 +170,39 @@ func (s *UserRepoSuite) TestDelete() {
|
||||
s.Require().Error(err, "expected error after delete")
|
||||
}
|
||||
|
||||
func (s *UserRepoSuite) TestDeleteRemovesAuthIdentitiesAndChannels() {
|
||||
user := s.mustCreateUser(&service.User{Email: "delete-oauth@test.com"})
|
||||
|
||||
identity, err := s.client.AuthIdentity.Create().
|
||||
SetUserID(user.ID).
|
||||
SetProviderType("linuxdo").
|
||||
SetProviderKey("linuxdo").
|
||||
SetProviderSubject("delete-oauth-subject").
|
||||
Save(s.ctx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
_, err = s.client.AuthIdentityChannel.Create().
|
||||
SetIdentityID(identity.ID).
|
||||
SetProviderType("wechat").
|
||||
SetProviderKey("wechat").
|
||||
SetChannel("open").
|
||||
SetChannelAppID("app-id").
|
||||
SetChannelSubject("openid-123").
|
||||
Save(s.ctx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.repo.Delete(s.ctx, user.ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
identityCount, err := s.client.AuthIdentity.Query().Where(authidentity.UserIDEQ(user.ID)).Count(s.ctx)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Zero(identityCount)
|
||||
|
||||
channelCount, err := s.client.AuthIdentityChannel.Query().Where(authidentitychannel.IdentityIDEQ(identity.ID)).Count(s.ctx)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Zero(channelCount)
|
||||
}
|
||||
|
||||
// --- List / ListWithFilters ---
|
||||
|
||||
func (s *UserRepoSuite) TestList() {
|
||||
|
||||
Reference in New Issue
Block a user