Bunch of fixes for new database model.
This commit is contained in:
@@ -8,7 +8,7 @@ import time
|
||||
from persistent.mapping import PersistentMapping
|
||||
from persistent.list import PersistentList
|
||||
from packetserver.common.util import is_valid_ax25_callsign
|
||||
from .database import ConnectionDependency
|
||||
from .database import DbDependency
|
||||
|
||||
ph = PasswordHasher()
|
||||
|
||||
@@ -52,23 +52,24 @@ class HttpUser(Persistent):
|
||||
# rf enabled checks..
|
||||
#
|
||||
|
||||
def is_rf_enabled(self, conn: ConnectionDependency) -> bool:
|
||||
def is_rf_enabled(self, db: DbDependency) -> bool:
|
||||
"""
|
||||
Check if RF gateway is enabled (i.e., callsign NOT in global blacklist).
|
||||
Requires an open ZODB connection.
|
||||
"""
|
||||
with db.transaction() as conn:
|
||||
root = conn.root()
|
||||
blacklist = root.get('config', {}).get('blacklist', [])
|
||||
return self.username not in blacklist
|
||||
|
||||
def set_rf_enabled(self, conn: ConnectionDependency, allow: bool):
|
||||
def set_rf_enabled(self, db: DbDependency, allow: bool):
|
||||
"""
|
||||
Enable/disable RF gateway by adding/removing from global blacklist.
|
||||
Requires an open ZODB connection (inside a transaction).
|
||||
Only allows enabling if the username is a valid AX.25 callsign.
|
||||
"""
|
||||
from packetserver.common.util import is_valid_ax25_callsign # our validator
|
||||
|
||||
with db.transaction() as conn:
|
||||
root = conn.root()
|
||||
config = root.setdefault('config', PersistentMapping())
|
||||
blacklist = config.setdefault('blacklist', PersistentList())
|
||||
|
||||
@@ -46,16 +46,16 @@ def get_db() -> ZODB.DB:
|
||||
raise RuntimeError("Database not initialized – call init_db() on startup")
|
||||
return _db
|
||||
|
||||
def get_connection() -> Generator[Connection, None, None]:
|
||||
"""Per-request dependency: yields an open Connection, closes on exit."""
|
||||
db = get_db()
|
||||
conn = db.open()
|
||||
try:
|
||||
yield conn
|
||||
finally:
|
||||
#print("not closing connection")
|
||||
#conn.close()
|
||||
pass
|
||||
#def get_connection() -> Generator[Connection, None, None]:
|
||||
# """Per-request dependency: yields an open Connection, closes on exit."""
|
||||
# db = get_db()
|
||||
# conn = db.open()
|
||||
# try:
|
||||
# yield conn
|
||||
# finally:
|
||||
# #print("not closing connection")
|
||||
# #conn.close()
|
||||
# pass
|
||||
|
||||
# Optional: per-request transaction (if you want automatic commit/abort)
|
||||
def get_transaction_manager():
|
||||
@@ -63,4 +63,4 @@ def get_transaction_manager():
|
||||
|
||||
# Annotated dependencies for routers
|
||||
DbDependency = Annotated[ZODB.DB, Depends(get_db)]
|
||||
ConnectionDependency = Annotated[Connection, Depends(get_connection)]
|
||||
#ConnectionDependency = Annotated[Connection, Depends(get_connection)]
|
||||
@@ -3,17 +3,17 @@ from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
||||
|
||||
from .auth import HttpUser
|
||||
from .database import ConnectionDependency
|
||||
from .database import DbDependency
|
||||
|
||||
security = HTTPBasic()
|
||||
|
||||
|
||||
async def get_current_http_user(conn: ConnectionDependency, credentials: HTTPBasicCredentials = Depends(security)):
|
||||
async def get_current_http_user(db: DbDependency, credentials: HTTPBasicCredentials = Depends(security)):
|
||||
"""
|
||||
Authenticate via Basic Auth using HttpUser from ZODB.
|
||||
Injected by the standalone runner (get_db_connection available).
|
||||
"""
|
||||
|
||||
with db.transaction() as conn:
|
||||
root = conn.root()
|
||||
|
||||
http_users = root.get("httpUsers")
|
||||
|
||||
@@ -7,7 +7,7 @@ import transaction
|
||||
from persistent.list import PersistentList
|
||||
from ZODB.Connection import Connection
|
||||
|
||||
from packetserver.http.database import DbDependency, ConnectionDependency, get_db
|
||||
from packetserver.http.database import DbDependency
|
||||
from ..dependencies import get_current_http_user
|
||||
from ..auth import HttpUser
|
||||
from ..server import templates
|
||||
|
||||
@@ -5,7 +5,7 @@ from fastapi.responses import HTMLResponse
|
||||
from packetserver.http.dependencies import get_current_http_user
|
||||
from packetserver.http.auth import HttpUser
|
||||
from packetserver.http.server import templates
|
||||
from packetserver.http.database import ConnectionDependency
|
||||
from packetserver.http.database import DbDependency
|
||||
|
||||
router = APIRouter(tags=["dashboard"])
|
||||
|
||||
@@ -16,18 +16,20 @@ from .bulletins import list_bulletins
|
||||
|
||||
@router.get("/dashboard", response_class=HTMLResponse)
|
||||
async def dashboard(
|
||||
conn: ConnectionDependency,
|
||||
db: DbDependency,
|
||||
request: Request,
|
||||
current_user: HttpUser = Depends(get_current_http_user)
|
||||
):
|
||||
# Internal call – pass explicit defaults to avoid Query object injection
|
||||
messages_resp = await api_get_messages(
|
||||
conn,
|
||||
db,
|
||||
current_user=current_user,
|
||||
type="all",
|
||||
limit=100,
|
||||
since=None # prevents Query wrapper
|
||||
)
|
||||
with db.transaction() as conn:
|
||||
# Internal call – pass explicit defaults to avoid Query object injection
|
||||
|
||||
messages = messages_resp["messages"]
|
||||
|
||||
bulletins_resp = await list_bulletins(conn, limit=10, since=None)
|
||||
@@ -45,12 +47,12 @@ async def dashboard(
|
||||
|
||||
@router.get("/dashboard/profile", response_class=HTMLResponse)
|
||||
async def profile_page(
|
||||
conn: ConnectionDependency,
|
||||
db: DbDependency,
|
||||
request: Request,
|
||||
current_user: HttpUser = Depends(get_current_http_user)
|
||||
):
|
||||
from packetserver.http.routers.profile import profile as api_profile
|
||||
profile_data = await api_profile(conn, current_user=current_user)
|
||||
profile_data = await api_profile(db, current_user=current_user)
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"profile.html",
|
||||
|
||||
@@ -4,11 +4,13 @@ from fastapi.responses import HTMLResponse
|
||||
from packetserver.http.dependencies import get_current_http_user
|
||||
from packetserver.http.auth import HttpUser
|
||||
from packetserver.http.server import templates
|
||||
from packetserver.http.database import DbDependency
|
||||
|
||||
router = APIRouter(tags=["message-detail"])
|
||||
|
||||
@router.get("/dashboard/message/{msg_id}", response_class=HTMLResponse)
|
||||
async def message_detail_page(
|
||||
db: DbDependency,
|
||||
request: Request,
|
||||
msg_id: str = Path(..., description="Message UUID as string"),
|
||||
current_user: HttpUser = Depends(get_current_http_user)
|
||||
@@ -18,6 +20,7 @@ async def message_detail_page(
|
||||
|
||||
# Call with mark_retrieved=True to auto-mark as read on view (optional—remove if you prefer manual)
|
||||
message_data = await api_get_message(
|
||||
db,
|
||||
msg_id=msg_id,
|
||||
mark_retrieved=True,
|
||||
current_user=current_user
|
||||
|
||||
@@ -10,7 +10,7 @@ from pydantic import BaseModel, Field, validator
|
||||
|
||||
from packetserver.http.dependencies import get_current_http_user
|
||||
from packetserver.http.auth import HttpUser
|
||||
from packetserver.http.database import ConnectionDependency
|
||||
from packetserver.http.database import DbDependency
|
||||
|
||||
|
||||
html_router = APIRouter(tags=["messages-html"])
|
||||
@@ -29,7 +29,7 @@ class MarkRetrievedRequest(BaseModel):
|
||||
|
||||
@router.get("/messages")
|
||||
async def get_messages(
|
||||
conn: ConnectionDependency,
|
||||
db: DbDependency,
|
||||
current_user: HttpUser = Depends(get_current_http_user),
|
||||
type: str = Query("received", description="received, sent, or all"),
|
||||
limit: Optional[int] = Query(20, le=100, description="Max messages to return (default 20, max 100)"),
|
||||
@@ -40,6 +40,7 @@ async def get_messages(
|
||||
limit = 20
|
||||
|
||||
username = current_user.username
|
||||
with db.transaction() as conn:
|
||||
root = conn.root()
|
||||
|
||||
if 'messages' not in root:
|
||||
@@ -81,11 +82,12 @@ async def get_messages(
|
||||
|
||||
@router.get("/messages/{msg_id}")
|
||||
async def get_message(
|
||||
conn: ConnectionDependency,
|
||||
db: DbDependency,
|
||||
msg_id: str = Path(..., description="UUID of the message (as string)"),
|
||||
mark_retrieved: bool = Query(False, description="If true, mark message as retrieved/read"),
|
||||
current_user: HttpUser = Depends(get_current_http_user)
|
||||
):
|
||||
with db.transaction() as conn:
|
||||
root = conn.root()
|
||||
|
||||
username = current_user.username
|
||||
@@ -126,11 +128,12 @@ async def get_message(
|
||||
|
||||
@router.patch("/messages/{msg_id}")
|
||||
async def mark_message_retrieved(
|
||||
conn: ConnectionDependency,
|
||||
db: DbDependency,
|
||||
msg_id: str = Path(..., description="Message UUID as string"),
|
||||
payload: MarkRetrievedRequest = None,
|
||||
current_user: HttpUser = Depends(get_current_http_user)
|
||||
):
|
||||
with db.transaction() as conn:
|
||||
root = conn.root()
|
||||
|
||||
username = current_user.username
|
||||
@@ -161,7 +164,7 @@ async def mark_message_retrieved(
|
||||
|
||||
@html_router.get("/messages", response_class=HTMLResponse)
|
||||
async def message_list_page(
|
||||
conn: ConnectionDependency,
|
||||
db: DbDependency,
|
||||
request: Request,
|
||||
type: str = Query("received", alias="msg_type"), # matches your filter links
|
||||
limit: Optional[int] = Query(50, le=100),
|
||||
@@ -169,7 +172,7 @@ async def message_list_page(
|
||||
):
|
||||
from packetserver.http.server import templates
|
||||
# Directly call the existing API endpoint function
|
||||
api_resp = await get_messages(conn, current_user=current_user, type=type, limit=limit, since=None)
|
||||
api_resp = await get_messages(db, current_user=current_user, type=type, limit=limit, since=None)
|
||||
messages = api_resp["messages"]
|
||||
|
||||
return templates.TemplateResponse(
|
||||
|
||||
@@ -6,7 +6,7 @@ import mimetypes
|
||||
|
||||
from packetserver.http.dependencies import get_current_http_user
|
||||
from packetserver.http.auth import HttpUser
|
||||
from packetserver.http.database import DbDependency, ConnectionDependency
|
||||
from packetserver.http.database import DbDependency
|
||||
from packetserver.server.objects import Object
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@@ -3,21 +3,23 @@ from fastapi import APIRouter, Depends
|
||||
|
||||
from packetserver.http.dependencies import get_current_http_user
|
||||
from packetserver.http.auth import HttpUser
|
||||
from packetserver.http.database import ConnectionDependency
|
||||
from packetserver.http.database import DbDependency
|
||||
|
||||
router = APIRouter(prefix="/api/v1", tags=["auth"])
|
||||
|
||||
|
||||
@router.get("/profile")
|
||||
async def profile(conn: ConnectionDependency,current_user: HttpUser = Depends(get_current_http_user)):
|
||||
async def profile(db: DbDependency, current_user: HttpUser = Depends(get_current_http_user)):
|
||||
username = current_user.username
|
||||
root = conn.root()
|
||||
rf_enabled = current_user.is_rf_enabled(db)
|
||||
|
||||
# Get main BBS User and safe dict
|
||||
with db.transaction() as conn:
|
||||
root = conn.root()
|
||||
main_users = root.get('users', {})
|
||||
bbs_user = main_users.get(username)
|
||||
safe_profile = bbs_user.to_safe_dict() if bbs_user else {}
|
||||
rf_enabled = current_user.is_rf_enabled(conn)
|
||||
|
||||
|
||||
return {
|
||||
**safe_profile,
|
||||
|
||||
@@ -11,7 +11,7 @@ from packetserver.http.dependencies import get_current_http_user
|
||||
from packetserver.http.auth import HttpUser
|
||||
from packetserver.server.messages import Message
|
||||
from packetserver.common.util import is_valid_ax25_callsign
|
||||
from packetserver.http.database import ConnectionDependency
|
||||
from packetserver.http.database import DbDependency
|
||||
|
||||
router = APIRouter(prefix="/api/v1", tags=["messages"])
|
||||
|
||||
@@ -40,13 +40,15 @@ class SendMessageRequest(BaseModel):
|
||||
|
||||
@router.post("/messages")
|
||||
async def send_message(
|
||||
conn: ConnectionDependency,
|
||||
db: DbDependency,
|
||||
payload: SendMessageRequest,
|
||||
current_user: HttpUser = Depends(get_current_http_user)
|
||||
):
|
||||
is_rf_enabled = current_user.is_rf_enabled(db)
|
||||
with db.transaction() as conn:
|
||||
root = conn.root()
|
||||
|
||||
if not current_user.is_rf_enabled(conn):
|
||||
if not is_rf_enabled:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="RF gateway access required to send messages"
|
||||
|
||||
Reference in New Issue
Block a user