refactor: remove unused Pinia stores and related code

- Deleted the layout-store, profiles-store, and index.ts files from the Pinia store setup.
- Removed the zod-sync script as it is no longer needed.
- Cleaned up the tsconfig.json file by removing unnecessary references.
- Updated the login logic in the profiles package to use polling for login results instead of in-process waiters.
- Added validation methods for personal data and profile parameters.
- Introduced new tests for validation logic in profiles and registration packages.
- Enhanced email normalization and validation in the registration package.
This commit is contained in:
fabio
2026-09-19 12:03:16 +02:00
parent cd858b0d8c
commit a0b65a4506
72 changed files with 664 additions and 9885 deletions

View File

@@ -3,7 +3,6 @@ package profiles
import (
"context"
"errors"
"sync"
"time"
"encore.app/auth"
@@ -34,12 +33,6 @@ var loginRequests = pubsub.TopicRef[pubsub.Publisher[*auth.LoginRequested]](auth
// auth.LoginRequests) and waits for the matching auth.LoginCompleted reply
// on auth.LoginResults before responding to the caller.
//
// NOTE: the wait is implemented with an in-process registry keyed by request
// ID, so the instance that publishes the request must be the one that
// receives the reply. That holds for local development (a single instance)
// but not for a horizontally scaled deployment, which would need a shared
// mechanism instead (e.g. a results table polled with retry_until).
//
//encore:api public method=POST path=/auth/login
func Login(ctx context.Context, p *LoginParams) (*LoginResponse, error) {
var userID uuid.UUID
@@ -58,9 +51,6 @@ func Login(ctx context.Context, p *LoginParams) (*LoginResponse, error) {
return nil, errs.WrapCode(err, errs.Internal, "failed to generate request id")
}
wait := loginWaiters.register(requestID)
defer loginWaiters.cancel(requestID)
if _, err := loginRequests.Publish(ctx, &auth.LoginRequested{
RequestID: requestID,
UserID: userID,
@@ -69,64 +59,47 @@ func Login(ctx context.Context, p *LoginParams) (*LoginResponse, error) {
return nil, errs.WrapCode(err, errs.Internal, "failed to publish login request")
}
select {
case result := <-wait:
if !result.OK {
return nil, &errs.Error{Code: errs.Unauthenticated, Message: "invalid credentials"}
result, err := pollLoginResult(ctx, func(ctx context.Context) (*auth.LoginCompleted, error) {
var ok bool
var token uuid.UUID
var expiresAt time.Time
err := authDB.QueryRow(ctx, `
SELECT ok, token, expires_at FROM login_results WHERE request_id = $1
`, requestID).Scan(&ok, &token, &expiresAt)
if errors.Is(err, sqldb.ErrNoRows) {
return nil, sqldb.ErrNoRows
}
if err != nil {
return nil, errs.WrapCode(err, errs.Internal, "failed to read login result")
}
return &auth.LoginCompleted{RequestID: requestID, OK: ok, Token: token, ExpiresAt: expiresAt}, nil
}, loginTimeout, 50*time.Millisecond)
if err != nil {
return nil, err
}
if !result.OK {
return nil, &errs.Error{Code: errs.Unauthenticated, Message: "invalid credentials"}
}
return &LoginResponse{Token: result.Token, ExpiresAt: result.ExpiresAt}, nil
}
func pollLoginResult(ctx context.Context, fetch func(context.Context) (*auth.LoginCompleted, error), timeout, interval time.Duration) (*auth.LoginCompleted, error) {
deadline := time.Now().Add(timeout)
for {
result, err := fetch(ctx)
if err == nil {
return result, nil
}
if !errors.Is(err, sqldb.ErrNoRows) {
return nil, err
}
if time.Now().After(deadline) {
return nil, &errs.Error{Code: errs.DeadlineExceeded, Message: "login timed out"}
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(interval):
}
return &LoginResponse{Token: result.Token, ExpiresAt: result.ExpiresAt}, nil
case <-time.After(loginTimeout):
return nil, &errs.Error{Code: errs.DeadlineExceeded, Message: "login timed out"}
case <-ctx.Done():
return nil, ctx.Err()
}
}
var _ = pubsub.NewSubscription(
auth.LoginResults, "deliver-to-waiting-login",
pubsub.SubscriptionConfig[*auth.LoginCompleted]{
Handler: func(ctx context.Context, ev *auth.LoginCompleted) error {
loginWaiters.deliver(ev)
return nil
},
},
)
// loginWaiters lets the synchronous Login handler above wait for the
// asynchronous LoginCompleted reply that matches its RequestID.
var loginWaiters = newWaiterRegistry()
type waiterRegistry struct {
mu sync.Mutex
waiting map[uuid.UUID]chan *auth.LoginCompleted
}
func newWaiterRegistry() *waiterRegistry {
return &waiterRegistry{waiting: make(map[uuid.UUID]chan *auth.LoginCompleted)}
}
func (r *waiterRegistry) register(id uuid.UUID) <-chan *auth.LoginCompleted {
ch := make(chan *auth.LoginCompleted, 1)
r.mu.Lock()
r.waiting[id] = ch
r.mu.Unlock()
return ch
}
func (r *waiterRegistry) cancel(id uuid.UUID) {
r.mu.Lock()
delete(r.waiting, id)
r.mu.Unlock()
}
func (r *waiterRegistry) deliver(ev *auth.LoginCompleted) {
r.mu.Lock()
ch, ok := r.waiting[ev.RequestID]
if ok {
delete(r.waiting, ev.RequestID)
}
r.mu.Unlock()
if ok {
ch <- ev
}
}