Skip to content

Commit 3987d99

Browse files
committed
Created scripts, code and documentation for client and server.
1 parent efdd681 commit 3987d99

File tree

69 files changed

+391
-355
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+391
-355
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/.env_oauth
1+
client/.env_oauth
22
/infra/terraform.tfvars
33
/.idea/.gitignore
44
/infra/.terraform.lock.hcl

README.md

+221-22
Large diffs are not rendered by default.

client/__init__.py

-1
This file was deleted.

client/config/__init__.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# client/config/__init__.py
2-
31
from .oauth import oauth_settings
42

53
__all__ = ["oauth_settings"]

client/config/oauth.py

+13-12
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,42 @@
1-
# client/config/oauth.py
2-
31
from dotenv import load_dotenv
42
from fastapi import HTTPException
53
from pydantic_settings import BaseSettings
6-
from client import logger
4+
from client.logger import logger
75

86
load_dotenv()
97

8+
109
class OAuthSettings(BaseSettings):
1110
AZURE_CLIENT_ID: str
1211
AZURE_CLIENT_SECRET: str
1312
AZURE_TENANT_ID: str
14-
API_SCOPE: str
1513
REDIRECT_URI: str
14+
SCOPES: str
1615

1716
class Config:
18-
env_file = ".env_oauth"
17+
env_file = "client/.env_oauth"
1918

2019

2120
def initialize_oauth_settings():
2221
try:
2322
# Create an instance of OAuthSettings
24-
internal_oauth_settings = OAuthSettings()
23+
settings = OAuthSettings()
2524

2625
# Check if the required OAuth fields are set
27-
if not internal_oauth_settings.AZURE_CLIENT_ID or not internal_oauth_settings.AZURE_CLIENT_SECRET or not internal_oauth_settings.AZURE_TENANT_ID or not internal_oauth_settings.API_SCOPE:
28-
logger.logger.error("One or more required OAuth environment variables are missing.")
26+
if not settings.AZURE_CLIENT_ID or not settings.AZURE_CLIENT_SECRET \
27+
or not settings.AZURE_TENANT_ID or not settings.REDIRECT_URI\
28+
or not settings.SCOPES:
29+
logger.error("One or more required OAuth environment variables are missing.")
2930
raise HTTPException(status_code=500,
3031
detail="Configuration error: Required OAuth environment variables are missing.")
3132

32-
logger.logger.info("OAuth settings loaded successfully.")
33-
return internal_oauth_settings
33+
logger.info("OAuth settings loaded successfully.")
34+
return settings
3435
except FileNotFoundError:
35-
logger.logger.critical(".env file not found.")
36+
logger.critical(".env file not found.")
3637
raise HTTPException(status_code=500, detail="Configuration error: .env file not found.")
3738
except Exception as e:
38-
logger.logger.critical(f"Error loading OAuth settings: {e}")
39+
logger.critical(f"Error loading OAuth settings: {e}")
3940
raise HTTPException(status_code=500,
4041
detail="Configuration error: An error occurred while loading OAuth settings.")
4142

client/logger.py

-12
This file was deleted.

client/logger/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .logger import logger
2+
3+
__all__ = ["logger"]

client/logger/logger.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import logging
2+
3+
logging.basicConfig(
4+
level=logging.INFO,
5+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
6+
)
7+
8+
logger = logging.getLogger("logger")

client/main.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
from client.routers import auth, heroes
55

66
app = FastAPI(
7-
title="Hero API",
8-
description="An API to manage heroes secure by OAuth 2.0 auth code flow",
7+
title="Hvalfangst Client",
8+
description="Client accessing our server deployed on Azure Web Apps secured by OAuth 2.0 authorization code flow with OIDC",
99
version="1.0.0"
1010
)
1111

client/models/__init__.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# client/models/__init__.py
1+
from .hero import Hero
22

3-
from .dnd_hero import DnDHero, AbilityScores, SkillProficiencies, Equipment, Spell
4-
5-
__all__ = ["DnDHero", "AbilityScores", "SkillProficiencies", "Equipment", "Spell"]
3+
__all__ = ["Hero"]

client/models/ability_scores.py

-12
This file was deleted.

client/models/dnd_hero.py

-34
This file was deleted.

client/models/equipment.py

-10
This file was deleted.

client/models/hero.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from typing import Optional
2+
from pydantic import BaseModel
3+
4+
5+
class Hero(BaseModel):
6+
id: str
7+
name: str
8+
race: str
9+
class_: str # Avoids conflict with the Python `class` keyword
10+
level: int
11+
background: Optional[str] = None
12+
alignment: Optional[str] = None
13+
hit_points: int
14+
armor_class: int
15+
speed: int
16+
personality_traits: Optional[str] = None
17+
ideals: Optional[str] = None
18+
bonds: Optional[str] = None
19+
flaws: Optional[str] = None

client/models/skill_proficiencies.py

-24
This file was deleted.

client/models/spell.py

-13
This file was deleted.

client/requirements.txt

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
fastapi==0.115.2 # FastAPI framework for building APIs
2-
uvicorn==0.32.0 # ASGI server for running FastAPI apps
3-
pydantic==2.9.2 # Data validation and parsing for FastAPI models
1+
fastapi==0.115.2
2+
uvicorn==0.32.0
3+
pydantic==2.9.2
44
config~=0.5.1
55
dotenv~=0.0.5
66
python-dotenv==1.0.1
77
httpx==0.27.2
8-
jwt==1.3.1
8+
pyjwt==2.9.0
99
pydantic_settings==2.6.0

client/routers/__init__.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
1-
# client/routers/__init__.py
2-
31
__all__ = ["heroes", "auth"]

client/routers/auth.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# client/routers/auth.py
2-
31
from http.client import HTTPException
42
from fastapi import APIRouter, HTTPException, Request
53
from client.logger import logger
@@ -8,7 +6,6 @@
86
router = APIRouter()
97

108

11-
# Example usage in the callback route
129
@router.get("/callback")
1310
async def auth_callback(request: Request):
1411
"""Callback handler for OpenID Connect flow."""
@@ -24,9 +21,9 @@ async def auth_callback(request: Request):
2421
# Call the OpenID Connect handler function
2522
try:
2623
logger.info("Initiating OpenID Connect flow handling")
27-
result = await handle_openid_connect_flow(code)
24+
decoded_tokens = await handle_openid_connect_flow(code)
2825
logger.info("OpenID Connect flow completed successfully")
29-
return result
26+
return decoded_tokens
3027
except Exception as e:
3128
logger.error(f"An error occurred during OpenID Connect flow: {str(e)}")
32-
raise HTTPException(status_code=500, detail="Internal server error during OpenID Connect flow")
29+
raise HTTPException(status_code=500, detail="Internal server error during OpenID Connect flow")

client/routers/heroes.py

+44-31
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,66 @@
1-
# client/routers/heroes.py
2-
1+
import os
32
from http.client import HTTPException
43
from typing import List
54

6-
from fastapi import APIRouter
7-
from fastapi import HTTPException
5+
import httpx
6+
from fastapi import APIRouter, HTTPException
87

9-
from client.models.dnd_hero import DnDHero
10-
from client.services.auth_service import verify_scope
11-
from client.services.hero_service import HeroService
8+
from client.logger import logger
9+
from client.models import Hero
10+
from client.services.token_storage import get_stored_token # Import get_stored_token function
1211

1312
router = APIRouter()
14-
hero_service = HeroService()
13+
14+
# Set values based on environment variable or hard-coded URL
15+
BACKEND_API_BASE_URL = os.getenv("HVALFANGST_API_URL", "https://hvalfangstlinuxwebapp.azurewebsites.net/api")
16+
17+
18+
# Helper function to make HTTP requests to the backend API
19+
async def request_backend(method: str, endpoint: str, json=None):
20+
url = f"{BACKEND_API_BASE_URL}{endpoint}"
21+
22+
# Retrieve the access token from token storage
23+
token_data = get_stored_token()
24+
headers = {"Authorization": f"Bearer {token_data}"} if token_data else {}
25+
26+
# Log the request details
27+
logger.info(f"Preparing {method} request to URL: {url}")
28+
logger.info(f"Headers: {headers}")
29+
logger.info(f"Payload: {json}")
30+
31+
try:
32+
async with httpx.AsyncClient() as client:
33+
response = await client.request(method, url, json=json, headers=headers)
34+
response.raise_for_status()
35+
logger.info(f"Request to {url} completed successfully with status code {response.status_code}")
36+
return response.json()
37+
except httpx.HTTPStatusError as e:
38+
logger.error(f"HTTP error occurred for {method} request to {url}: {e.response.status_code} - {e.response.text}")
39+
raise HTTPException(status_code=e.response.status_code, detail=e.response.text)
40+
except Exception as e:
41+
logger.error(f"An unexpected error occurred: {e}")
42+
raise HTTPException(status_code=500, detail="An unexpected error occurred")
1543

1644

1745
# POST: Create a new Hero
18-
@router.post("/heroes/", response_model=DnDHero)
19-
async def create_hero(hero: DnDHero):
20-
return await hero_service.create_hero(hero)
46+
@router.post("/heroes/", response_model=Hero)
47+
async def create_hero(hero: Hero):
48+
return await request_backend("POST", "/heroes/", json=hero.dict())
2149

2250

2351
# GET: Retrieve a hero by ID
24-
@router.get("/heroes/{hero_id}", response_model=DnDHero)
52+
@router.get("/heroes/{hero_id}", response_model=Hero)
2553
async def read_hero(hero_id: str):
26-
hero = await hero_service.get_hero(hero_id)
27-
if hero:
28-
return hero
29-
else:
30-
raise HTTPException(status_code=404, detail="Hero not found")
54+
return await request_backend("GET", f"/heroes/{hero_id}")
3155

3256

3357
# GET: Retrieve all heroes
34-
@router.get("/heroes/", response_model=List[DnDHero])
58+
@router.get("/heroes/", response_model=List[Hero])
3559
async def read_heroes():
36-
await verify_scope(["Heroes.Read"])
37-
return await hero_service.list_heroes()
60+
return await request_backend("GET", "/heroes/")
3861

3962

4063
# DELETE: Delete a hero by ID
4164
@router.delete("/heroes/{hero_id}", response_model=dict)
4265
async def delete_hero(hero_id: str):
43-
success = await hero_service.delete_hero(hero_id)
44-
if success:
45-
return {"message": f"Hero with id '{hero_id}' deleted successfully"}
46-
else:
47-
raise HTTPException(status_code=404, detail="Hero not found")
48-
49-
50-
# GET: Custom query to retrieve heroes with Fireball spell and AC < 20
51-
@router.get("/heroes-fireball-low-ac", response_model=List[DnDHero])
52-
async def get_fireball_heroes_with_low_ac():
53-
return await hero_service.query_heroes_fireball_low_ac()
66+
return await request_backend("DELETE", f"/heroes/{hero_id}")

0 commit comments

Comments
 (0)