@marcos_feitoza/personal-finance-backen-trades-assets 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [1.0.4](https://github.com/MarcosOps/personal-finance-backend-trades-assets/compare/v1.0.3...v1.0.4) (2025-11-21)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* update trades and assets to support current user ([7a976a5](https://github.com/MarcosOps/personal-finance-backend-trades-assets/commit/7a976a576eea4ab9758e5fa20d5b1d314ae72912))
|
|
7
|
+
|
|
8
|
+
## [1.0.3](https://github.com/MarcosOps/personal-finance-backend-trades-assets/compare/v1.0.2...v1.0.3) (2025-11-11)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* crypto symbol working ([9c61bf9](https://github.com/MarcosOps/personal-finance-backend-trades-assets/commit/9c61bf91b382fc7563985c1973fc6a37e9e361c1))
|
|
14
|
+
|
|
1
15
|
## [1.0.2](https://github.com/MarcosOps/personal-finance-backend-trades-assets/compare/v1.0.1...v1.0.2) (2025-10-17)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -4,6 +4,8 @@ from sqlalchemy import distinct
|
|
|
4
4
|
from personal_finance_shared import models, schemas
|
|
5
5
|
from personal_finance_shared.database import get_db
|
|
6
6
|
import logging
|
|
7
|
+
from personal_finance_shared.dependencies import get_current_user
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
logging.basicConfig(level=logging.INFO)
|
|
9
11
|
logger = logging.getLogger(__name__)
|
|
@@ -14,32 +16,50 @@ router = APIRouter(
|
|
|
14
16
|
)
|
|
15
17
|
|
|
16
18
|
@router.post("/", response_model=schemas.AssetResponse)
|
|
17
|
-
def create_asset(
|
|
19
|
+
def create_asset(
|
|
20
|
+
asset: schemas.AssetCreate,
|
|
21
|
+
db: Session = Depends(get_db),
|
|
22
|
+
current_user: models.User = Depends(get_current_user) # Keep for authorization if needed
|
|
23
|
+
):
|
|
24
|
+
# Assets are global now, check for symbol existence globally
|
|
18
25
|
db_asset = db.query(models.Asset).filter(models.Asset.symbol == asset.symbol).first()
|
|
19
26
|
if db_asset:
|
|
20
27
|
raise HTTPException(status_code=400, detail="Asset with this symbol already exists")
|
|
21
|
-
|
|
28
|
+
|
|
29
|
+
# user_id is removed from Asset model
|
|
30
|
+
db_asset = models.Asset(
|
|
31
|
+
symbol=asset.symbol,
|
|
32
|
+
name=asset.name,
|
|
33
|
+
asset_type=asset.asset_type,
|
|
34
|
+
industry=asset.industry,
|
|
35
|
+
id_crypto=asset.id_crypto,
|
|
36
|
+
)
|
|
22
37
|
db.add(db_asset)
|
|
23
38
|
db.commit()
|
|
24
39
|
db.refresh(db_asset)
|
|
25
40
|
return db_asset
|
|
26
41
|
|
|
27
42
|
@router.get("/", response_model=list[schemas.AssetResponse])
|
|
28
|
-
def read_assets(
|
|
29
|
-
|
|
30
|
-
|
|
43
|
+
def read_assets(
|
|
44
|
+
investment_account: str = None,
|
|
45
|
+
skip: int = 0,
|
|
46
|
+
limit: int = 100,
|
|
47
|
+
db: Session = Depends(get_db),
|
|
48
|
+
current_user: models.User = Depends(get_current_user)
|
|
49
|
+
):
|
|
50
|
+
logger.info(f"Reading assets for user {current_user.id}, investment account: {investment_account}")
|
|
51
|
+
|
|
52
|
+
# The query should join Trades and Assets to find assets owned by the user.
|
|
53
|
+
query = db.query(models.Asset).join(models.Trade).filter(
|
|
54
|
+
models.Trade.user_id == current_user.id
|
|
55
|
+
)
|
|
31
56
|
|
|
32
57
|
if investment_account:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# Extract just the IDs from the list of tuples
|
|
38
|
-
asset_ids = [id for (id,) in asset_ids_in_account]
|
|
39
|
-
|
|
40
|
-
# Filter assets by these IDs
|
|
41
|
-
query = query.filter(models.Asset.id.in_(asset_ids))
|
|
58
|
+
query = query.filter(models.Trade.investment_account == investment_account)
|
|
59
|
+
|
|
60
|
+
# Use distinct to avoid duplicate assets if a user traded the same asset multiple times
|
|
61
|
+
query = query.distinct()
|
|
42
62
|
|
|
43
63
|
assets = query.offset(skip).limit(limit).all()
|
|
44
|
-
logger.info(f"Found {len(assets)} assets")
|
|
64
|
+
logger.info(f"Found {len(assets)} assets for user {current_user.id}")
|
|
45
65
|
return assets
|
|
@@ -6,6 +6,8 @@ import logging
|
|
|
6
6
|
import requests
|
|
7
7
|
import os
|
|
8
8
|
import httpx
|
|
9
|
+
from personal_finance_shared.dependencies import get_current_user, get_current_token
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
logging.basicConfig(level=logging.INFO)
|
|
11
13
|
logger = logging.getLogger(__name__)
|
|
@@ -31,65 +33,66 @@ def _get_asset_details_from_service(symbol: str) -> dict:
|
|
|
31
33
|
return {}
|
|
32
34
|
|
|
33
35
|
@router.post("/", response_model=schemas.TradeResponse)
|
|
34
|
-
async def create_trade(
|
|
36
|
+
async def create_trade(
|
|
37
|
+
trade: schemas.TradeCreate,
|
|
38
|
+
db: Session = Depends(get_db),
|
|
39
|
+
current_user: models.User = Depends(get_current_user),
|
|
40
|
+
token: str = Depends(get_current_token)
|
|
41
|
+
):
|
|
35
42
|
trade_symbol = trade.symbol.upper()
|
|
36
|
-
logger.info(f"Processing trade for symbol: {trade_symbol}")
|
|
43
|
+
logger.info(f"Processing trade for user {current_user.id}, symbol: {trade_symbol}")
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
# Search for the asset globally, not by user_id
|
|
46
|
+
db_asset = db.query(models.Asset).filter(
|
|
47
|
+
models.Asset.symbol == trade_symbol
|
|
48
|
+
).first()
|
|
41
49
|
|
|
42
50
|
if not db_asset:
|
|
43
|
-
logger.info(f"Asset with symbol {trade_symbol} not found.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
db.add(db_asset)
|
|
57
|
-
db.commit()
|
|
58
|
-
db.refresh(db_asset)
|
|
59
|
-
logger.info(f"Created new asset with ID: {db_asset.id}")
|
|
60
|
-
else:
|
|
61
|
-
logger.info(f"Found existing asset with canonical symbol: {canonical_symbol}")
|
|
51
|
+
logger.info(f"Asset with symbol {trade_symbol} not found globally. Creating new asset...")
|
|
52
|
+
db_asset = models.Asset(
|
|
53
|
+
symbol=trade_symbol,
|
|
54
|
+
name=trade.name,
|
|
55
|
+
asset_type=trade.asset_type,
|
|
56
|
+
industry=trade.industry,
|
|
57
|
+
id_crypto=trade.id_crypto if hasattr(trade, 'id_crypto') else None,
|
|
58
|
+
# user_id is no longer part of Asset model
|
|
59
|
+
)
|
|
60
|
+
db.add(db_asset)
|
|
61
|
+
db.commit()
|
|
62
|
+
db.refresh(db_asset)
|
|
63
|
+
logger.info(f"Created new global asset with ID: {db_asset.id}")
|
|
62
64
|
else:
|
|
63
|
-
logger.info(f"Found existing asset with ID: {db_asset.id}")
|
|
65
|
+
logger.info(f"Found existing global asset with ID: {db_asset.id}")
|
|
64
66
|
|
|
65
67
|
if trade.trade_type == 'buy':
|
|
66
68
|
investment_account_name = trade.investment_account
|
|
67
69
|
trade_cost = trade.shares * trade.price
|
|
68
70
|
current_balance = 0
|
|
71
|
+
headers = {"Authorization": f"Bearer {token}"}
|
|
69
72
|
try:
|
|
70
73
|
async with httpx.AsyncClient() as client:
|
|
71
|
-
response = await client.get(f"{BALANCE_SERVICE_URL}/api/balance/{investment_account_name}")
|
|
74
|
+
response = await client.get(f"{BALANCE_SERVICE_URL}/api/balance/{investment_account_name}", headers=headers)
|
|
72
75
|
response.raise_for_status()
|
|
73
76
|
data = response.json()
|
|
74
77
|
current_balance = data['balance']
|
|
75
78
|
logger.info(f"Calculated cash balance for '{investment_account_name}' is {current_balance}")
|
|
76
79
|
except Exception as e:
|
|
77
|
-
logger.error(f"Could not retrieve balance for validation: {e}")
|
|
80
|
+
logger.error(f"Could not retrieve balance for validation for user {current_user.id}: {e}")
|
|
78
81
|
|
|
79
82
|
if float(current_balance) < float(trade_cost):
|
|
80
|
-
logger.warning(f"Insufficient funds in '{investment_account_name}'. Balance: {current_balance}, Required: {trade_cost}")
|
|
83
|
+
logger.warning(f"Insufficient funds in '{investment_account_name}' for user {current_user.id}. Balance: {current_balance}, Required: {trade_cost}")
|
|
81
84
|
raise HTTPException(
|
|
82
85
|
status_code=400,
|
|
83
86
|
detail=f"Insufficient cash balance in '{investment_account_name}' to complete trade."
|
|
84
87
|
)
|
|
85
88
|
|
|
86
|
-
trade_data = trade.dict(exclude={'symbol', 'name', 'asset_type', 'industry'})
|
|
87
|
-
db_trade = models.Trade(**trade_data, asset_id=db_asset.id)
|
|
89
|
+
trade_data = trade.dict(exclude={'symbol', 'name', 'asset_type', 'industry', 'id_crypto'})
|
|
90
|
+
db_trade = models.Trade(**trade_data, asset_id=db_asset.id, user_id=current_user.id)
|
|
88
91
|
|
|
89
92
|
if db_trade.trade_type == 'buy':
|
|
90
|
-
logger.info(f"STOCK PURCHASE: Bought {db_trade.shares} shares of {trade_symbol} at ${db_trade.price:.2f} each.")
|
|
93
|
+
logger.info(f"STOCK PURCHASE for user {current_user.id}: Bought {db_trade.shares} shares of {trade_symbol} at ${db_trade.price:.2f} each.")
|
|
91
94
|
elif db_trade.trade_type == 'sell':
|
|
92
|
-
logger.info(f"STOCK SALE: Sold {db_trade.shares} shares of {trade_symbol} at ${db_trade.price:.2f} each.")
|
|
95
|
+
logger.info(f"STOCK SALE for user {current_user.id}: Sold {db_trade.shares} shares of {trade_symbol} at ${db_trade.price:.2f} each.")
|
|
93
96
|
|
|
94
97
|
db.add(db_trade)
|
|
95
98
|
db.commit()
|
|
@@ -101,13 +104,14 @@ def read_trades(
|
|
|
101
104
|
investment_account: str = None,
|
|
102
105
|
skip: int = 0,
|
|
103
106
|
limit: int = 100,
|
|
104
|
-
db: Session = Depends(get_db)
|
|
107
|
+
db: Session = Depends(get_db),
|
|
108
|
+
current_user: models.User = Depends(get_current_user)
|
|
105
109
|
):
|
|
106
|
-
logger.info(f"Reading trades for account: {investment_account}")
|
|
107
|
-
query = db.query(models.Trade)
|
|
110
|
+
logger.info(f"Reading trades for user {current_user.id}, account: {investment_account}")
|
|
111
|
+
query = db.query(models.Trade).filter(models.Trade.user_id == current_user.id)
|
|
108
112
|
if investment_account:
|
|
109
113
|
query = query.filter(models.Trade.investment_account == investment_account)
|
|
110
114
|
|
|
111
115
|
trades = query.order_by(models.Trade.date.desc()).offset(skip).limit(limit).all()
|
|
112
|
-
logger.info(f"Found {len(trades)} trades")
|
|
116
|
+
logger.info(f"Found {len(trades)} trades for user {current_user.id}")
|
|
113
117
|
return trades
|