143 lines
4.7 KiB
Python
143 lines
4.7 KiB
Python
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel
|
|
from pathlib import Path
|
|
import os
|
|
from typing import Optional
|
|
from modules.logger_tool import initialise_logger
|
|
|
|
# Initialize logger
|
|
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL", "info"), os.getenv("LOG_PATH", "/logs"))
|
|
|
|
# Create router
|
|
router = APIRouter()
|
|
|
|
# === CONFIG ===
|
|
SOLID_BASE_URL = os.getenv("SOLID_BASE_URL", "http://solid.classroomcopilot.test")
|
|
SOLID_STORAGE_PATH = os.getenv("SOLID_STORAGE_PATH", "/data/users") # Path relative to Solid server data directory
|
|
KEYCLOAK_ISSUER = os.getenv("KEYCLOAK_ISSUER", "http://keycloak.classroomcopilot.test/realms/ClassroomCopilot")
|
|
|
|
# === DATA MODEL ===
|
|
class UserCreateRequest(BaseModel):
|
|
username: str
|
|
full_name: str
|
|
email: Optional[str] = None
|
|
|
|
# === UTILITIES ===
|
|
def create_profile_card(username: str, full_name: str, email: Optional[str] = None) -> str:
|
|
webid = f"{SOLID_BASE_URL}/users/{username}/profile/card#me"
|
|
profile_content = f"""@prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
|
@prefix solid: <http://www.w3.org/ns/solid/terms#> .
|
|
@prefix vcard: <http://www.w3.org/2006/vcard/ns#> .
|
|
|
|
<#me> a foaf:Person ;
|
|
foaf:name "{full_name}" ;
|
|
solid:oidcIssuer <{KEYCLOAK_ISSUER}> ."""
|
|
|
|
if email:
|
|
profile_content += f"""
|
|
vcard:hasEmail <mailto:{email}> ."""
|
|
|
|
return profile_content
|
|
|
|
def create_acl_file(username: str) -> str:
|
|
return f"""@prefix acl: <http://www.w3.org/ns/acl#> .
|
|
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
|
|
|
<#authorization>
|
|
a acl:Authorization ;
|
|
acl:agent <{SOLID_BASE_URL}/users/{username}/profile/card#me> ;
|
|
acl:accessTo <./> ;
|
|
acl:default <./> ;
|
|
acl:mode acl:Read, acl:Write, acl:Control ;
|
|
acl:agentClass foaf:Agent ."""
|
|
|
|
def create_public_acl() -> str:
|
|
return """@prefix acl: <http://www.w3.org/ns/acl#> .
|
|
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
|
|
|
<#authorization>
|
|
a acl:Authorization ;
|
|
acl:agentClass foaf:Agent ;
|
|
acl:accessTo <./> ;
|
|
acl:default <./> ;
|
|
acl:mode acl:Read ."""
|
|
|
|
# === ENDPOINTS ===
|
|
@router.post("/provision")
|
|
async def provision_user(data: UserCreateRequest):
|
|
"""
|
|
Create a new Solid pod for a user with their profile card and ACL files.
|
|
"""
|
|
try:
|
|
# Create user directory structure
|
|
user_base = Path(SOLID_STORAGE_PATH) / data.username
|
|
profile_dir = user_base / "profile"
|
|
public_dir = user_base / "public"
|
|
private_dir = user_base / "private"
|
|
|
|
# Create directories
|
|
for dir_path in [profile_dir, public_dir, private_dir]:
|
|
dir_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Create profile card
|
|
profile_path = profile_dir / "card.ttl"
|
|
profile_content = create_profile_card(data.username, data.full_name, data.email)
|
|
profile_path.write_text(profile_content)
|
|
|
|
# Create ACL files
|
|
profile_acl_path = profile_dir / ".acl"
|
|
profile_acl_content = create_acl_file(data.username)
|
|
profile_acl_path.write_text(profile_acl_content)
|
|
|
|
public_acl_path = public_dir / ".acl"
|
|
public_acl_content = create_public_acl()
|
|
public_acl_path.write_text(public_acl_content)
|
|
|
|
private_acl_path = private_dir / ".acl"
|
|
private_acl_content = create_acl_file(data.username)
|
|
private_acl_path.write_text(private_acl_content)
|
|
|
|
webid = f"{SOLID_BASE_URL}/users/{data.username}/profile/card#me"
|
|
|
|
logger.info(f"Successfully provisioned Solid pod for user {data.username}")
|
|
|
|
return {
|
|
"message": "User pod created successfully",
|
|
"webid": webid,
|
|
"profile_path": str(profile_path),
|
|
"pod_structure": {
|
|
"profile": str(profile_dir),
|
|
"public": str(public_dir),
|
|
"private": str(private_dir)
|
|
}
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error provisioning Solid pod for user {data.username}: {str(e)}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.get("/status/{username}")
|
|
async def check_pod_status(username: str):
|
|
"""
|
|
Check if a user's Solid pod exists and return its status.
|
|
"""
|
|
try:
|
|
user_base = Path(SOLID_STORAGE_PATH) / username
|
|
profile_path = user_base / "profile" / "card.ttl"
|
|
|
|
if not profile_path.exists():
|
|
return {
|
|
"exists": False,
|
|
"message": "Pod not found"
|
|
}
|
|
|
|
return {
|
|
"exists": True,
|
|
"webid": f"{SOLID_BASE_URL}/users/{username}/profile/card#me",
|
|
"profile_path": str(profile_path)
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error checking pod status for user {username}: {str(e)}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|