feat: spaces redesign, model selector, auth fixes
Spaces: - Perplexity-like UI with collaboration features - Space detail page with threads/members tabs - Invite members via email, role management - New space creation with icon/color picker Model selector: - Added Ollama client for free Auto model - GooSeek 1.0 via Timeweb (tariff-based) - Frontend model dropdown in ChatInput Auth & Infrastructure: - Fixed auth-svc missing from Dockerfile.all - Removed duplicate ratelimit_tiered.go (conflict) - Added Redis to api-gateway for rate limiting - Fixed Next.js proxy for local development UI improvements: - Redesigned login button in sidebar (gradient) - Settings page with tabs (account/billing/prefs) - Auth pages visual refresh Made-with: Cursor
This commit is contained in:
@@ -38,6 +38,7 @@ func (r *Repository) RunMigrations(ctx context.Context) error {
|
||||
avatar TEXT,
|
||||
role VARCHAR(50) DEFAULT 'user',
|
||||
tier VARCHAR(50) DEFAULT 'free',
|
||||
balance DECIMAL(12,2) DEFAULT 0,
|
||||
email_verified BOOLEAN DEFAULT FALSE,
|
||||
provider VARCHAR(50) DEFAULT 'local',
|
||||
provider_id VARCHAR(255),
|
||||
@@ -69,6 +70,8 @@ func (r *Repository) RunMigrations(ctx context.Context) error {
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_password_reset_tokens ON password_reset_tokens(token)`,
|
||||
|
||||
`ALTER TABLE auth_users ADD COLUMN IF NOT EXISTS balance DECIMAL(12,2) DEFAULT 0`,
|
||||
}
|
||||
|
||||
for _, m := range migrations {
|
||||
@@ -125,18 +128,19 @@ func (r *Repository) CreateUser(ctx context.Context, email, password, name strin
|
||||
|
||||
func (r *Repository) GetUserByEmail(ctx context.Context, email string) (*User, error) {
|
||||
query := `
|
||||
SELECT id, email, password_hash, name, avatar, role, tier, email_verified,
|
||||
SELECT id, email, password_hash, name, avatar, role, tier, balance, email_verified,
|
||||
provider, provider_id, last_login_at, created_at, updated_at
|
||||
FROM auth_users WHERE email = $1
|
||||
`
|
||||
|
||||
user := &User{}
|
||||
var lastLogin, avatar, providerID sql.NullString
|
||||
var avatar, providerID sql.NullString
|
||||
var lastLoginTime sql.NullTime
|
||||
var balance sql.NullFloat64
|
||||
|
||||
err := r.db.QueryRowContext(ctx, query, email).Scan(
|
||||
&user.ID, &user.Email, &user.PasswordHash, &user.Name, &avatar,
|
||||
&user.Role, &user.Tier, &user.EmailVerified, &user.Provider,
|
||||
&user.Role, &user.Tier, &balance, &user.EmailVerified, &user.Provider,
|
||||
&providerID, &lastLoginTime, &user.CreatedAt, &user.UpdatedAt,
|
||||
)
|
||||
|
||||
@@ -156,14 +160,16 @@ func (r *Repository) GetUserByEmail(ctx context.Context, email string) (*User, e
|
||||
if lastLoginTime.Valid {
|
||||
user.LastLoginAt = lastLoginTime.Time
|
||||
}
|
||||
_ = lastLogin
|
||||
if balance.Valid {
|
||||
user.Balance = balance.Float64
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetUserByID(ctx context.Context, id string) (*User, error) {
|
||||
query := `
|
||||
SELECT id, email, password_hash, name, avatar, role, tier, email_verified,
|
||||
SELECT id, email, password_hash, name, avatar, role, tier, balance, email_verified,
|
||||
provider, provider_id, last_login_at, created_at, updated_at
|
||||
FROM auth_users WHERE id = $1
|
||||
`
|
||||
@@ -171,10 +177,11 @@ func (r *Repository) GetUserByID(ctx context.Context, id string) (*User, error)
|
||||
user := &User{}
|
||||
var avatar, providerID sql.NullString
|
||||
var lastLoginTime sql.NullTime
|
||||
var balance sql.NullFloat64
|
||||
|
||||
err := r.db.QueryRowContext(ctx, query, id).Scan(
|
||||
&user.ID, &user.Email, &user.PasswordHash, &user.Name, &avatar,
|
||||
&user.Role, &user.Tier, &user.EmailVerified, &user.Provider,
|
||||
&user.Role, &user.Tier, &balance, &user.EmailVerified, &user.Provider,
|
||||
&providerID, &lastLoginTime, &user.CreatedAt, &user.UpdatedAt,
|
||||
)
|
||||
|
||||
@@ -194,6 +201,9 @@ func (r *Repository) GetUserByID(ctx context.Context, id string) (*User, error)
|
||||
if lastLoginTime.Valid {
|
||||
user.LastLoginAt = lastLoginTime.Time
|
||||
}
|
||||
if balance.Valid {
|
||||
user.Balance = balance.Float64
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
@@ -254,6 +264,22 @@ func (r *Repository) UpdateRole(ctx context.Context, userID string, role UserRol
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Repository) UpdateBalance(ctx context.Context, userID string, amount float64) error {
|
||||
_, err := r.db.ExecContext(ctx,
|
||||
"UPDATE auth_users SET balance = balance + $2, updated_at = NOW() WHERE id = $1",
|
||||
userID, amount,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Repository) SetBalance(ctx context.Context, userID string, balance float64) error {
|
||||
_, err := r.db.ExecContext(ctx,
|
||||
"UPDATE auth_users SET balance = $2, updated_at = NOW() WHERE id = $1",
|
||||
userID, balance,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Repository) CreateRefreshToken(ctx context.Context, userID, userAgent, ip string, duration time.Duration) (*RefreshToken, error) {
|
||||
token := generateSecureToken(32)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ type User struct {
|
||||
Avatar string `json:"avatar,omitempty"`
|
||||
Role string `json:"role"`
|
||||
Tier string `json:"tier"`
|
||||
Balance float64 `json:"balance"`
|
||||
EmailVerified bool `json:"emailVerified"`
|
||||
Provider string `json:"provider"`
|
||||
ProviderID string `json:"providerId,omitempty"`
|
||||
|
||||
Reference in New Issue
Block a user