diff --git a/.gitignore b/.gitignore index 378e58d..dfb45fe 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,10 @@ output/ .pytest_cache/ .coverage htmlcov/ + + +# API secret files +polygon.io.key +alpaca.key +alpaca.secret +alpaca.url diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 16da959..c569404 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -18,6 +18,24 @@ server { proxy_set_header X-Forwarded-Proto $scheme; } + # Proxy Symbol Registry (companies CRUD, sources, watchlists, aliases) + location /registry/ { + proxy_pass http://symbol-registry:8000/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Proxy Risk Engine + location /risk/ { + proxy_pass http://risk:8000/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + # Cache static assets location /assets/ { expires 1y; diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 564edbc..aa32d68 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -4,8 +4,8 @@ */ const QUERY_API_BASE = import.meta.env.VITE_QUERY_API_URL ?? ''; -const SYMBOL_REGISTRY_BASE = import.meta.env.VITE_SYMBOL_REGISTRY_URL ?? ''; -const RISK_ENGINE_BASE = import.meta.env.VITE_RISK_ENGINE_URL ?? ''; +const SYMBOL_REGISTRY_BASE = import.meta.env.VITE_SYMBOL_REGISTRY_URL ?? '/registry'; +const RISK_ENGINE_BASE = import.meta.env.VITE_RISK_ENGINE_URL ?? '/risk'; export type ApiBase = 'query' | 'registry' | 'risk'; diff --git a/frontend/src/pages/Companies.tsx b/frontend/src/pages/Companies.tsx index 88f62a8..c1294d7 100644 --- a/frontend/src/pages/Companies.tsx +++ b/frontend/src/pages/Companies.tsx @@ -1,7 +1,8 @@ +import { useState } from 'react'; import { useNavigate } from '@tanstack/react-router'; -import { useCompanies } from '../api/hooks'; +import { useCompanies, useCreateCompany } from '../api/hooks'; import { DataTable, type Column } from '../components/DataTable'; -import { StatusBadge, LoadingSpinner } from '../components/ui'; +import { StatusBadge, LoadingSpinner, Card } from '../components/ui'; import type { Company } from '../api/hooks'; const columns: Column[] = [ @@ -23,13 +24,25 @@ const columns: Column[] = [ export function CompaniesPage() { const navigate = useNavigate(); const { data, isLoading, error } = useCompanies(); + const [showAdd, setShowAdd] = useState(false); if (isLoading) return ; if (error) return
Failed to load companies
; return (
-

Companies

+
+

Companies

+ +
+ + {showAdd && setShowAdd(false)} />} + data={data ?? []} columns={columns} @@ -47,3 +60,61 @@ export function CompaniesPage() {
); } + +function AddCompanyForm({ onClose }: { onClose: () => void }) { + const [ticker, setTicker] = useState(''); + const [name, setName] = useState(''); + const [sector, setSector] = useState(''); + const [exchange, setExchange] = useState(''); + const mutation = useCreateCompany(); + + return ( + +
{ + e.preventDefault(); + if (ticker.trim() && name.trim()) { + mutation.mutate( + { + ticker: ticker.trim().toUpperCase(), + legal_name: name.trim(), + sector: sector || undefined, + exchange: exchange || undefined, + }, + { onSuccess: onClose }, + ); + } + }} + > +
+
+ + setTicker(e.target.value)} required placeholder="AAPL" className="w-full rounded-md border border-surface-700 bg-surface-900 px-2 py-1.5 text-sm text-gray-200 placeholder-gray-500 uppercase" /> +
+
+ + setName(e.target.value)} required placeholder="Apple Inc." className="w-full rounded-md border border-surface-700 bg-surface-900 px-2 py-1.5 text-sm text-gray-200 placeholder-gray-500" /> +
+
+ + setSector(e.target.value)} placeholder="Technology" className="w-full rounded-md border border-surface-700 bg-surface-900 px-2 py-1.5 text-sm text-gray-200 placeholder-gray-500" /> +
+
+ + setExchange(e.target.value)} placeholder="NASDAQ" className="w-full rounded-md border border-surface-700 bg-surface-900 px-2 py-1.5 text-sm text-gray-200 placeholder-gray-500" /> +
+
+ {mutation.error &&
Failed to create company
} +
+ + +
+
+
+ ); +} diff --git a/infra/helm/stonks-oracle/templates/network-policies.yaml b/infra/helm/stonks-oracle/templates/network-policies.yaml index 1cb98fb..f2c60ff 100644 --- a/infra/helm/stonks-oracle/templates/network-policies.yaml +++ b/infra/helm/stonks-oracle/templates/network-policies.yaml @@ -48,6 +48,9 @@ spec: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system + - podSelector: + matchLabels: + app: dashboard ports: - protocol: TCP port: 8000 @@ -71,6 +74,9 @@ spec: - podSelector: matchLabels: app: query-api + - podSelector: + matchLabels: + app: dashboard ports: - protocol: TCP port: 8000 diff --git a/runmefirst.sh b/runmefirst.sh index 76653ae..8efce55 100755 --- a/runmefirst.sh +++ b/runmefirst.sh @@ -12,6 +12,10 @@ MINIO_ACCESS_KEY="AKIA6V7J3N9B5P0D2YQH" MINIO_SECRET_KEY='8fG3!v2rJ7$wN@9mLpQ6zXbC4tKdPqW1' PG_PASSWORD='St0nks0racl3!' REDIS_PASSWORD='PSCh4ng3me!' +POLYGON_API_KEY=$(cat "$REPO_DIR/polygon.io.key" | tr -d '[:space:]') +ALPACA_API_KEY=$(cat "$REPO_DIR/alpaca.key" | tr -d '[:space:]') +ALPACA_API_SECRET=$(cat "$REPO_DIR/alpaca.secret" | tr -d '[:space:]') +ALPACA_BASE_URL=$(cat "$REPO_DIR/alpaca.url" | tr -d '[:space:]') echo "=== Stonks Oracle Deployment ===" @@ -66,7 +70,11 @@ helm upgrade --install stonks-oracle "$CHART_DIR" \ --set "secrets.core.POSTGRES_PASSWORD=$PG_PASSWORD" \ --set "secrets.core.MINIO_ACCESS_KEY=$MINIO_ACCESS_KEY" \ --set "secrets.core.MINIO_SECRET_KEY=$MINIO_SECRET_KEY" \ - --set "secrets.core.REDIS_PASSWORD=$REDIS_PASSWORD" + --set "secrets.core.REDIS_PASSWORD=$REDIS_PASSWORD" \ + --set "secrets.market.MARKET_DATA_API_KEY=$POLYGON_API_KEY" \ + --set "secrets.broker.BROKER_API_KEY=$ALPACA_API_KEY" \ + --set "secrets.broker.BROKER_API_SECRET=$ALPACA_API_SECRET" \ + --set "secrets.broker.BROKER_BASE_URL=$ALPACA_BASE_URL" # --- Rolling restart to pick up secrets --- echo "Rolling restart..."