diff --git a/.gitignore b/.gitignore
index 7be8fe0..eb17c0a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -89,3 +89,4 @@ instance/
# uv specific
.uvcache/
+/db_fixtures/
diff --git a/dev-server.sh b/dev-server.sh
index 2c8e0c2..7227bdc 100644
--- a/dev-server.sh
+++ b/dev-server.sh
@@ -1,4 +1,7 @@
if [ -z "${APS_username}" ]; then
export APS_username="testuser"
fi
+if [ -z "${APS_display_name}" ]; then
+ export APS_display_name="Dr. T. Estuser"
+fi
fastapi dev src/allmende_payment_system/app.py
\ No newline at end of file
diff --git a/justfile b/justfile
index edc12dc..0e6e74d 100644
--- a/justfile
+++ b/justfile
@@ -1,3 +1,8 @@
lint:
uv run isort test src
- uv run black test src
\ No newline at end of file
+ uv run black test src
+
+reset_db:
+ rm -f aps_db.db
+ uv run python -c "from allmende_payment_system.database import create_tables; create_tables()"
+ for file in db_fixtures/*.sql; do sqlite3 aps_db.db < "$file"; done
\ No newline at end of file
diff --git a/src/allmende_payment_system/app.py b/src/allmende_payment_system/app.py
index 62135b1..a854e34 100644
--- a/src/allmende_payment_system/app.py
+++ b/src/allmende_payment_system/app.py
@@ -4,12 +4,18 @@ from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
+from sqlalchemy.orm import Session
+
+from allmende_payment_system.database import SessionLocal, ensure_user
async def get_user(request: Request) -> dict:
if username := os.environ.get("APS_username", None):
- return {"username": username}
+ return {
+ "username": username,
+ "display_name": os.environ.get("APS_display_name", "Missing Display Name"),
+ }
if "ynh_user" not in request.headers:
raise HTTPException(status_code=401, detail="Missing ynh_user header")
return {"username": request.headers["ynh_user"]}
@@ -28,6 +34,21 @@ app.mount(
)
+def get_session() -> Session:
+ db = SessionLocal()
+ try:
+ yield db
+ finally:
+ db.close()
+
+
+SessionDep = Annotated[Session, Depends(get_session)]
+
+
@app.get("/")
-async def landing_page(request: Request):
- return templates.TemplateResponse("index.html.jinja", {"request": request})
+async def landing_page(request: Request, user_info: UserDep, session: SessionDep):
+ user = ensure_user(user_info, session)
+ print(f"User {user.username} ({user.display_name}) accessed landing page")
+ return templates.TemplateResponse(
+ "index.html.jinja", context={"request": request, "user": user}
+ )
diff --git a/src/allmende_payment_system/database.py b/src/allmende_payment_system/database.py
index 3139eec..2ed4cec 100644
--- a/src/allmende_payment_system/database.py
+++ b/src/allmende_payment_system/database.py
@@ -1,6 +1,29 @@
-from sqlalchemy import create_engine
-from sqlalchemy.orm import sessionmaker
+from sqlalchemy import create_engine, select
+from sqlalchemy.orm import Session, sessionmaker
+
+from allmende_payment_system.models import User
SQLALCHEMY_DATABASE_URL = "sqlite:///./aps_db.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+
+def create_tables():
+ from allmende_payment_system.models import Base
+
+ Base.metadata.create_all(bind=engine)
+
+
+def ensure_user(user_info: dict, session: Session) -> User:
+ statement = select(User).where(User.username == user_info["username"])
+
+ if user := session.scalars(statement).one_or_none():
+ return user
+
+ user = User(
+ username=user_info["username"], display_name=user_info.get("display_name")
+ )
+ session.add(user)
+ session.commit()
+
+ return user
diff --git a/src/allmende_payment_system/models.py b/src/allmende_payment_system/models.py
index 64d53aa..b276981 100644
--- a/src/allmende_payment_system/models.py
+++ b/src/allmende_payment_system/models.py
@@ -19,6 +19,10 @@ class Account(Base):
back_populates="accounts",
)
+ @property
+ def balance(self):
+ return 141.23
+
user_account_association = Table(
TABLE_PREFIX + "user_account_association",
diff --git a/src/allmende_payment_system/templates/index.html.jinja b/src/allmende_payment_system/templates/index.html.jinja
index 5045302..1bb5bb4 100644
--- a/src/allmende_payment_system/templates/index.html.jinja
+++ b/src/allmende_payment_system/templates/index.html.jinja
@@ -1,7 +1,75 @@
+
{% extends "base.html.jinja" %}
{% block content %}
-
-
Welcome to My App
-
This is your landing page content. Replace this with your actual content.
+
+
+
Meine Konten
+
+ {% if user.accounts|length > 0 %}
+ {% for account in user.accounts %}
+
+
+
+
{{ account.name }}
+
+ {{ '%.2f' | format(account.balance) }} €
+
+
+
+
+
+ {% endfor %}
+ {% else %}
+
+
+ Keine Konten verfügbar
+
+
+ {% endif %}
+
+
+
+
+
+
+
Latest Transactions
+
View All
+
+
+
+
+ {% if transactions and transactions|length > 0 %}
+ {% for transaction in transactions[:5] %}
+
+
+
{{ transaction.product_name | default('Transaction') }}
+
+ {{ transaction.date | default('') }}
+ {% if transaction.description %}
+ · {{ transaction.description }}
+ {% endif %}
+
+
+
+
+ {{ '%+.2f' | format(transaction.amount | default(0)) }} €
+
+ {% if transaction.quantity %}
+
{{ transaction.quantity }} {{ transaction.unit | default('pcs') }}
+ {% endif %}
+
+
+ {% endfor %}
+ {% else %}
+
+
No transactions yet
+
Your transactions will appear here
+
+ {% endif %}
+
+
{% endblock %}
\ No newline at end of file
diff --git a/test/conftest.py b/test/conftest.py
index 15dff24..95d8be6 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -1,30 +1,35 @@
-import pytest
-from fastapi import Request
-from fastapi.testclient import TestClient
+import os
-from allmende_payment_system.app import create_app
+import pytest
+from fastapi.testclient import TestClient
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+
+from allmende_payment_system.app import app
+from allmende_payment_system.models import Base
@pytest.fixture(scope="session")
def client():
- app = create_app()
-
- async def add_ynh_headers(request: Request, call_next):
- username = request.headers.get("APS-TEST-username", "test")
- # This seems to work although headers are immutable
- # If this ever turns out to be a problem, we can use request.state instead,
- # but will have to modify app.get_user
- request.headers._list.append((b"ynh_user", username.encode("utf-8")))
-
- response = await call_next(request)
- return response
-
- app.middleware("http")(add_ynh_headers)
-
+ os.environ["APS_username"] = "test"
return TestClient(app)
@pytest.fixture(scope="session")
def unauthorized_client():
- app = create_app()
+ os.environ.pop("APS_username", None)
return TestClient(app)
+
+
+@pytest.fixture
+def test_db():
+ engine = create_engine("sqlite:///:memory:")
+ Base.metadata.create_all(bind=engine) # Create tables
+ TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+ # Provide a session and the engine
+ db = TestingSessionLocal()
+ try:
+ yield db
+ finally:
+ db.close()
diff --git a/test/test_auth.py b/test/test_auth.py
index dae3e81..b5d9625 100644
--- a/test/test_auth.py
+++ b/test/test_auth.py
@@ -6,7 +6,6 @@ def test_unauthorized_access(unauthorized_client):
assert response.status_code == 401
-def test_unauthorized_access(unauthorized_client):
- response = unauthorized_client.get("/")
- print(response.text)
- assert response.status_code == 401
+def test_authorized_access(client):
+ response = client.get("/")
+ assert response.status_code == 200
diff --git a/test/test_database.py b/test/test_database.py
new file mode 100644
index 0000000..0edfdef
--- /dev/null
+++ b/test/test_database.py
@@ -0,0 +1,19 @@
+from sqlalchemy import func, select
+
+from allmende_payment_system.database import ensure_user
+from allmende_payment_system.models import User
+
+
+def test_ensure_user(test_db):
+
+ user_info = {"username": "test", "display_name": "Test User"}
+ user = ensure_user(user_info, test_db)
+
+ assert user.username == "test"
+
+ test_db.commit()
+
+ assert test_db.scalar(select(func.count()).select_from(User)) == 1
+
+ user = ensure_user(user_info, test_db)
+ assert test_db.scalar(select(func.count()).select_from(User)) == 1
diff --git a/test/test_models.py b/test/test_models.py
index 2431d44..d5825e2 100644
--- a/test/test_models.py
+++ b/test/test_models.py
@@ -1,35 +1,16 @@
-import pytest
-from sqlalchemy import create_engine
-from sqlalchemy.orm import sessionmaker
-
-from allmende_payment_system.models import Account, Base, User
+from allmende_payment_system.models import Account, User
-# Create an in-memory SQLite database
-@pytest.fixture
-def in_memory_db():
- engine = create_engine("sqlite:///:memory:")
- Base.metadata.create_all(bind=engine) # Create tables
- TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
-
- # Provide a session and the engine
- db = TestingSessionLocal()
- try:
- yield db
- finally:
- db.close()
-
-
-def test_user_model(in_memory_db):
+def test_user_model(test_db):
user = User(username="test", display_name="Test User")
- in_memory_db.add(user)
- in_memory_db.commit()
+ test_db.add(user)
+ test_db.commit()
assert user.id is not None
account = Account(name="Test Account")
account.users.append(user)
- in_memory_db.add(account)
- in_memory_db.commit()
+ test_db.add(account)
+ test_db.commit()
assert len(user.accounts) == 1