Skip to content

Commit 1517f85

Browse files
committed
reg
1 parent a611c80 commit 1517f85

6 files changed

Lines changed: 327 additions & 31 deletions

File tree

backend/core/schemas.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,27 @@ class SignalCreate(BaseModel):
170170
rationale: Optional[str] = Field(None, description="Justificación breve")
171171
source: str = Field(..., description="Origen de la señal", max_length=50)
172172
extra: Optional[Dict[str, Any]] = Field(None, description="Metadatos adicionales")
173+
174+
175+
class UserCreate(BaseModel):
176+
"""
177+
Schema for user registration request.
178+
"""
179+
email: str = Field(..., description="User email (unique)")
180+
password: str = Field(..., description="Password (min 8 chars)", min_length=8)
181+
name: str = Field("Trader", description="User display name")
182+
183+
184+
class UserResponse(BaseModel):
185+
"""
186+
Schema for user response (safe DTO, no password).
187+
"""
188+
id: int
189+
email: str
190+
name: str
191+
role: str
192+
plan: str
193+
created_at: datetime
194+
195+
class Config:
196+
from_attributes = True

backend/routers/auth.py

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import timedelta
1+
from datetime import timedelta, datetime
22
from typing import Annotated
33
from fastapi import APIRouter, Depends, HTTPException, status, Request
44
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
@@ -7,6 +7,7 @@
77
from sqlalchemy.exc import IntegrityError
88
from database import get_db
99
from models_db import User
10+
from core.schemas import UserCreate, UserResponse
1011
from core.security import verify_password, create_access_token, get_password_hash, ACCESS_TOKEN_EXPIRE_MINUTES, SECRET_KEY, ALGORITHM
1112

1213
router = APIRouter(prefix="/auth", tags=["Auth"])
@@ -84,42 +85,64 @@ async def login_for_access_token(
8485
}
8586
}
8687

87-
@router.post("/register")
88+
@router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
8889
async def register_user(
89-
email: str,
90-
password: str,
91-
name: str = "Trader",
90+
user_data: UserCreate,
9291
db: Session = Depends(get_db)
9392
):
94-
"""(Dev Helper) Registra un usuario inicial."""
95-
# Verificar si existe
96-
result = db.execute(select(User).where(User.email == email))
93+
"""
94+
Registra un nuevo usuario en la plataforma.
95+
96+
- Valida email único (409 Conflict)
97+
- Hashing seguro de contraseña (bcrypt)
98+
- Retorna el usuario creado (DTO seguro sin password)
99+
"""
100+
# 1. Normalizar email
101+
email_norm = user_data.email.strip().lower()
102+
103+
# 2. Verificar duplicado
104+
result = db.execute(select(User).where(User.email == email_norm))
97105
existing_user = result.scalars().first()
98106

99107
if existing_user:
100-
raise HTTPException(status_code=400, detail="Email already registered")
108+
raise HTTPException(
109+
status_code=status.HTTP_409_CONFLICT,
110+
detail="Email already registered"
111+
)
101112

102-
hashed_pwd = get_password_hash(password)
103-
new_user = User(email=email, hashed_password=hashed_pwd, name=name, role="user")
113+
# 3. Hash Password
114+
hashed_pwd = get_password_hash(user_data.password)
115+
116+
# 4. Crear Usuario
117+
new_user = User(
118+
email=email_norm,
119+
hashed_password=hashed_pwd,
120+
name=user_data.name,
121+
role="user",
122+
plan="free",
123+
plan_status="active",
124+
created_at=datetime.utcnow() # Ensure model has this or defaults
125+
)
104126

105-
db.add(new_user)
106127
try:
128+
db.add(new_user)
107129
db.commit()
108130
db.refresh(new_user)
109-
return {"id": new_user.id, "email": new_user.email, "msg": "User created"}
131+
return new_user
132+
110133
except IntegrityError:
111134
db.rollback()
112-
raise HTTPException(status_code=400, detail="Error creating user")
113-
114-
return {
115-
"id": current_user.id,
116-
"email": current_user.email,
117-
"name": current_user.name,
118-
"role": current_user.role,
119-
"plan": current_user.plan,
120-
"plan_status": current_user.plan_status,
121-
"avatar_url": f"https://ui-avatars.com/api/?name={current_user.name}&background=10b981&color=fff"
122-
}
135+
raise HTTPException(
136+
status_code=status.HTTP_400_BAD_REQUEST,
137+
detail="Error creating user (Integrity)"
138+
)
139+
except Exception as e:
140+
db.rollback()
141+
print(f"[AUTH ERROR] Register failed: {e}")
142+
raise HTTPException(
143+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
144+
detail="Internal server error during registration"
145+
)
123146

124147
# Entitlements Endpoint (Sync DB required for core logic)
125148
from database import SessionLocal

web/src/App.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { PricingPage } from './pages/PricingPage';
1313
import { AdminPage } from './pages/AdminPage';
1414
import { SettingsPage } from './pages/SettingsPage';
1515
import { LoginPage } from './pages/LoginPage';
16+
import { RegisterPage } from './pages/RegisterPage';
1617
import { AuthProvider, useAuth } from './context/AuthContext';
1718
import { ThemeProvider } from './context/ThemeContext';
1819
import { ToastContainer } from './components/Toast';
@@ -46,6 +47,7 @@ const App: React.FC = () => {
4647
<ToastContainer />
4748
<Routes>
4849
<Route path="/login" element={<LoginPage />} />
50+
<Route path="/register" element={<RegisterPage />} />
4951

5052
{/* Protected Routes */}
5153
<Route
@@ -74,10 +76,10 @@ const App: React.FC = () => {
7476
<Route path="*" element={<Navigate to="/" replace />} />
7577
</Routes>
7678

77-
7879

79-
</Router>
80-
</ThemeProvider>
80+
81+
</Router>
82+
</ThemeProvider>
8183
</AuthProvider>
8284
);
8385
};

web/src/pages/LoginPage.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
CheckCircle2,
1515
ChevronRight
1616
} from 'lucide-react';
17-
import { useNavigate } from 'react-router-dom';
17+
import { useNavigate, Link } from 'react-router-dom';
1818

1919
export const LoginPage: React.FC = () => {
2020
const [email, setEmail] = useState('demo@tradercopilot.com');
@@ -180,6 +180,12 @@ export const LoginPage: React.FC = () => {
180180
>
181181
{loading ? <Loader2 className="animate-spin" size={20} /> : <>Initialize Session <ChevronRight size={18} className="group-hover/btn:translate-x-0.5 transition-transform" /></>}
182182
</button>
183+
184+
<div className="text-center pt-2">
185+
<Link to="/register" className="text-xs text-slate-400 hover:text-emerald-400 transition-colors">
186+
Don't have an account? <span className="font-bold text-slate-300">Register Access</span>
187+
</Link>
188+
</div>
183189
</form>
184190

185191

0 commit comments

Comments
 (0)