This commit is contained in:
@@ -35,10 +35,11 @@ def create_trade(session: Session, trade_data: Mapping) -> models.Trades:
|
||||
data = dict(trade_data)
|
||||
allowed = {c.name for c in models.Trades.__table__.columns}
|
||||
payload = {k: v for k, v in data.items() if k in allowed}
|
||||
cycle_id = payload.get("cycle_id")
|
||||
if "symbol" not in payload:
|
||||
raise ValueError("symbol is required")
|
||||
if "exchange" not in payload:
|
||||
raise ValueError("exchange is required")
|
||||
if "exchange_id" not in payload and cycle_id is None:
|
||||
raise ValueError("exchange_id is required when no cycle is attached")
|
||||
if "underlying_currency" not in payload:
|
||||
raise ValueError("underlying_currency is required")
|
||||
payload["underlying_currency"] = _check_enum(models.UnderlyingCurrency, payload["underlying_currency"], "underlying_currency")
|
||||
@@ -54,7 +55,6 @@ def create_trade(session: Session, trade_data: Mapping) -> models.Trades:
|
||||
payload["trade_time_utc"] = now
|
||||
if "trade_date" not in payload or payload.get("trade_date") is None:
|
||||
payload["trade_date"] = payload["trade_time_utc"].date()
|
||||
cycle_id = payload.get("cycle_id")
|
||||
user_id = payload.get("user_id")
|
||||
if "quantity" not in payload:
|
||||
raise ValueError("quantity is required")
|
||||
@@ -76,7 +76,7 @@ def create_trade(session: Session, trade_data: Mapping) -> models.Trades:
|
||||
c_payload = {
|
||||
"user_id": user_id,
|
||||
"symbol": payload["symbol"],
|
||||
"exchange": payload["exchange"],
|
||||
"exchange_id": payload["exchange_id"],
|
||||
"underlying_currency": payload["underlying_currency"],
|
||||
"friendly_name": "Auto-created Cycle by trade " + payload.get("friendly_name", ""),
|
||||
"status": models.CycleStatus.OPEN,
|
||||
@@ -89,8 +89,11 @@ def create_trade(session: Session, trade_data: Mapping) -> models.Trades:
|
||||
# If cycle_id provided, validate existence and ownership
|
||||
if cycle_id is not None:
|
||||
cycle = session.get(models.Cycles, cycle_id)
|
||||
|
||||
if cycle is None:
|
||||
raise ValueError("cycle_id does not exist")
|
||||
payload.pop("exchange_id", None) # ignore exchange_id if provided; use cycle's exchange_id
|
||||
payload["exchange_id"] = cycle.exchange_id
|
||||
if cycle.user_id != user_id:
|
||||
raise ValueError("cycle.user_id does not match trade.user_id")
|
||||
|
||||
@@ -187,8 +190,8 @@ def create_cycle(session: Session, cycle_data: Mapping) -> models.Cycles:
|
||||
raise ValueError("user_id is required")
|
||||
if "symbol" not in payload:
|
||||
raise ValueError("symbol is required")
|
||||
if "exchange" not in payload:
|
||||
raise ValueError("exchange is required")
|
||||
if "exchange_id" not in payload:
|
||||
raise ValueError("exchange_id is required")
|
||||
if "underlying_currency" not in payload:
|
||||
raise ValueError("underlying_currency is required")
|
||||
payload["underlying_currency"] = _check_enum(models.UnderlyingCurrency, payload["underlying_currency"], "underlying_currency")
|
||||
|
||||
@@ -58,7 +58,8 @@ class Database:
|
||||
event.listen(self._engine, "connect", _enable_sqlite_pragmas)
|
||||
|
||||
def init_db(self) -> None:
|
||||
db_migration.run_migrations(self._engine)
|
||||
# db_migration.run_migrations(self._engine)
|
||||
pass
|
||||
|
||||
def get_session(self) -> Generator[Session, None, None]:
|
||||
session = Session(self._engine)
|
||||
|
||||
@@ -14,8 +14,43 @@ class TradeBase(SQLModel):
|
||||
user_id: int
|
||||
friendly_name: str | None
|
||||
symbol: str
|
||||
exchange: str
|
||||
underlying_currency: UnderlyingCurrency
|
||||
trade_type: TradeType
|
||||
trade_strategy: TradeStrategy
|
||||
trade_date: date
|
||||
trade_time_utc: datetime
|
||||
quantity: int
|
||||
price_cents: int
|
||||
gross_cash_flow_cents: int
|
||||
commission_cents: int
|
||||
net_cash_flow_cents: int
|
||||
notes: str | None
|
||||
cycle_id: int | None = None
|
||||
|
||||
|
||||
class TradeCreate(TradeBase):
|
||||
expiry_date: date | None = None
|
||||
strike_price_cents: int | None = None
|
||||
is_invalidated: bool = False
|
||||
invalidated_at: datetime | None = None
|
||||
replaced_by_trade_id: int | None = None
|
||||
|
||||
|
||||
class TradeRead(TradeBase):
|
||||
id: int
|
||||
is_invalidated: bool
|
||||
invalidated_at: datetime | None
|
||||
|
||||
|
||||
class UserBase(SQLModel):
|
||||
username: str
|
||||
is_active: bool = True
|
||||
|
||||
|
||||
class UserCreate(UserBase):
|
||||
password: str
|
||||
|
||||
|
||||
class UserRead(UserBase):
|
||||
id: int
|
||||
|
||||
@@ -72,7 +72,8 @@ class Trades(SQLModel, table=True):
|
||||
# allow null while user may omit friendly_name; uniqueness enforced per-user by constraint
|
||||
friendly_name: str | None = Field(default=None, sa_column=Column(Text, nullable=True))
|
||||
symbol: str = Field(sa_column=Column(Text, nullable=False))
|
||||
exchange: str = Field(sa_column=Column(Text, nullable=False))
|
||||
exchange_id: int = Field(foreign_key="exchanges.id", nullable=False, index=True)
|
||||
exchange: "Exchanges" = Relationship(back_populates="trades")
|
||||
underlying_currency: UnderlyingCurrency = Field(sa_column=Column(Text, nullable=False))
|
||||
trade_type: TradeType = Field(sa_column=Column(Text, nullable=False))
|
||||
trade_strategy: TradeStrategy = Field(sa_column=Column(Text, nullable=False))
|
||||
@@ -101,7 +102,8 @@ class Cycles(SQLModel, table=True):
|
||||
user_id: int = Field(foreign_key="users.id", nullable=False, index=True)
|
||||
friendly_name: str | None = Field(default=None, sa_column=Column(Text, nullable=True))
|
||||
symbol: str = Field(sa_column=Column(Text, nullable=False))
|
||||
exchange: str = Field(sa_column=Column(Text, nullable=False))
|
||||
exchange_id: int = Field(foreign_key="exchanges.id", nullable=False, index=True)
|
||||
exchange: "Exchanges" = Relationship(back_populates="cycles")
|
||||
underlying_currency: UnderlyingCurrency = Field(sa_column=Column(Text, nullable=False))
|
||||
status: CycleStatus = Field(sa_column=Column(Text, nullable=False))
|
||||
funding_source: FundingSource = Field(sa_column=Column(Text, nullable=True))
|
||||
@@ -113,6 +115,15 @@ class Cycles(SQLModel, table=True):
|
||||
trades: list["Trades"] = Relationship(back_populates="cycle")
|
||||
|
||||
|
||||
class Exchanges(SQLModel, table=True):
|
||||
__tablename__ = "exchanges"
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str = Field(sa_column=Column(Text, nullable=False, unique=True))
|
||||
notes: str | None = Field(default=None, sa_column=Column(Text, nullable=True))
|
||||
trades: list["Trades"] = Relationship(back_populates="exchange")
|
||||
cycles: list["Cycles"] = Relationship(back_populates="exchange")
|
||||
|
||||
|
||||
class Users(SQLModel, table=True):
|
||||
__tablename__ = "users"
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
|
||||
@@ -72,7 +72,8 @@ class Trades(SQLModel, table=True):
|
||||
# allow null while user may omit friendly_name; uniqueness enforced per-user by constraint
|
||||
friendly_name: str | None = Field(default=None, sa_column=Column(Text, nullable=True))
|
||||
symbol: str = Field(sa_column=Column(Text, nullable=False))
|
||||
exchange: str = Field(sa_column=Column(Text, nullable=False))
|
||||
exchange_id: int = Field(foreign_key="exchanges.id", nullable=False, index=True)
|
||||
exchange: "Exchanges" = Relationship(back_populates="trades")
|
||||
underlying_currency: UnderlyingCurrency = Field(sa_column=Column(Text, nullable=False))
|
||||
trade_type: TradeType = Field(sa_column=Column(Text, nullable=False))
|
||||
trade_strategy: TradeStrategy = Field(sa_column=Column(Text, nullable=False))
|
||||
@@ -101,7 +102,8 @@ class Cycles(SQLModel, table=True):
|
||||
user_id: int = Field(foreign_key="users.id", nullable=False, index=True)
|
||||
friendly_name: str | None = Field(default=None, sa_column=Column(Text, nullable=True))
|
||||
symbol: str = Field(sa_column=Column(Text, nullable=False))
|
||||
exchange: str = Field(sa_column=Column(Text, nullable=False))
|
||||
exchange_id: int = Field(foreign_key="exchanges.id", nullable=False, index=True)
|
||||
exchange: "Exchanges" = Relationship(back_populates="cycles")
|
||||
underlying_currency: UnderlyingCurrency = Field(sa_column=Column(Text, nullable=False))
|
||||
status: CycleStatus = Field(sa_column=Column(Text, nullable=False))
|
||||
funding_source: FundingSource = Field(sa_column=Column(Text, nullable=True))
|
||||
@@ -113,6 +115,15 @@ class Cycles(SQLModel, table=True):
|
||||
trades: list["Trades"] = Relationship(back_populates="cycle")
|
||||
|
||||
|
||||
class Exchanges(SQLModel, table=True):
|
||||
__tablename__ = "exchanges"
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str = Field(sa_column=Column(Text, nullable=False, unique=True))
|
||||
notes: str | None = Field(default=None, sa_column=Column(Text, nullable=True))
|
||||
trades: list["Trades"] = Relationship(back_populates="exchange")
|
||||
cycles: list["Cycles"] = Relationship(back_populates="exchange")
|
||||
|
||||
|
||||
class Users(SQLModel, table=True):
|
||||
__tablename__ = "users"
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
|
||||
11
backend/trading_journal/security.py
Normal file
11
backend/trading_journal/security.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from passlib.context import CryptContext
|
||||
|
||||
pwd_ctx = CryptContext(schemes=["argon2"], deprecated="auto")
|
||||
|
||||
|
||||
def hash_password(plain: str) -> str:
|
||||
return pwd_ctx.hash(plain)
|
||||
|
||||
def verify_password(plain: str, hashed: str) -> bool:
|
||||
return pwd_ctx.verify(plain, hashed)
|
||||
|
||||
Reference in New Issue
Block a user