changes :(
This commit is contained in:
parent
96f95b5cd1
commit
62da10b69c
5 changed files with 249 additions and 122 deletions
|
|
@ -1,60 +1,187 @@
|
|||
from logging import Logger
|
||||
import os
|
||||
import secrets
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Annotated
|
||||
from fastapi import FastAPI, Depends, HTTPException, Header
|
||||
from sqlmodel import select, Session
|
||||
from .db import get_session, init_db
|
||||
from .models import Player, World
|
||||
from secrets import token_urlsafe
|
||||
from argon2 import PasswordHasher
|
||||
from urllib.parse import urlencode
|
||||
from typing import Annotated, Any
|
||||
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from fastapi import Depends, FastAPI, Header, HTTPException, Request
|
||||
from fastapi.responses import RedirectResponse
|
||||
from slack_bolt.adapter.fastapi.async_handler import AsyncSlackRequestHandler
|
||||
from slack_bolt.async_app import AsyncAck, AsyncApp, AsyncRespond, AsyncSay
|
||||
from slack_bolt.response import BoltResponse
|
||||
from tortoise import Tortoise
|
||||
|
||||
from .models import User, UserCreate, UserResponse, UserUpdate
|
||||
|
||||
|
||||
if not load_dotenv():
|
||||
raise FileNotFoundError("Environment secrets not found!")
|
||||
|
||||
hasher = PasswordHasher()
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_: FastAPI):
|
||||
init_db()
|
||||
await Tortoise.init(
|
||||
db_url="sqlite://random_access.db", modules={"models": ["random_access.models"]}
|
||||
)
|
||||
await Tortoise.generate_schemas()
|
||||
yield
|
||||
await Tortoise.close_connections()
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
slack = AsyncApp(
|
||||
signing_secret=os.getenv("SLACK_SIGNING_SECRET")
|
||||
)
|
||||
slack_handler = AsyncSlackRequestHandler(slack)
|
||||
|
||||
|
||||
@app.post("/players/")
|
||||
def create_player(player: Player, session: Session = Depends(get_session)) -> Player:
|
||||
session.add(player)
|
||||
session.commit()
|
||||
session.refresh(player)
|
||||
return player
|
||||
@slack.event("app_mention") # pyright:ignore[reportUnknownMemberType]
|
||||
async def handle_app_mentions(body: BoltResponse, say: AsyncSay, logger: Logger):
|
||||
logger.info(body)
|
||||
_ = await say("What's up?")
|
||||
|
||||
|
||||
@app.get("/players/location")
|
||||
def read_player_location(
|
||||
api_token: Annotated[str | None, Header()], session: Session = Depends(get_session)
|
||||
@slack.event("message") # pyright:ignore[reportUnknownMemberType]
|
||||
async def handle_message():
|
||||
pass
|
||||
|
||||
@slack.command("/random-access") # pyright:ignore[reportUnknownMemberType]
|
||||
async def handle_command(ack: AsyncAck, body: BoltResponse, respond: AsyncRespond):
|
||||
await ack()
|
||||
subcommand = dict(body).get('text') # type: ignore
|
||||
print(subcommand)
|
||||
|
||||
await respond("hewowo")
|
||||
|
||||
app = FastAPI(title="Random Access API", version="0.1.0", lifespan=lifespan)
|
||||
|
||||
|
||||
@app.post("/slack/events")
|
||||
async def endpoint(req: Request):
|
||||
return await slack_handler.handle(req)
|
||||
|
||||
|
||||
# Authentication dependency
|
||||
async def get_current_user(x_api_key: Annotated[str, Header()]) -> User:
|
||||
if not x_api_key:
|
||||
raise HTTPException(status_code=401, detail="API key required")
|
||||
|
||||
user = await User.get_or_none(api_key=x_api_key)
|
||||
if not user:
|
||||
raise HTTPException(status_code=401, detail="Invalid API key")
|
||||
|
||||
return user
|
||||
|
||||
|
||||
# Public endpoints (no auth required)
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": "Random Access API is running"}
|
||||
|
||||
@app.get("/auth/start")
|
||||
async def auth_start(game_id: str):
|
||||
url = "https://slack.com/openid/connect/authorize/?"
|
||||
params = {"response_type": "code", "scope": "openid profile email", "client_id": os.environ.get("SLACK_CLIENT_ID"), "state": game_id, "redirect_uri": "https://random-access.prox.mra.sh/auth/callback"}
|
||||
return RedirectResponse(url + urlencode(params))
|
||||
|
||||
@app.get("/auth/callback")
|
||||
async def auth_callback(code: str, state: str):
|
||||
print(code, state)
|
||||
return "yay!"
|
||||
|
||||
@app.post("/register", response_model=UserResponse)
|
||||
async def register_user(user_data: UserCreate):
|
||||
# Check if username or email already exists
|
||||
existing_user = await User.get_or_none(slack_id=user_data.slack_id)
|
||||
if existing_user:
|
||||
raise HTTPException(status_code=400, detail="Username already exists")
|
||||
|
||||
existing_email = await User.get_or_none(email=user_data.email)
|
||||
if existing_email:
|
||||
raise HTTPException(status_code=400, detail="Email already exists")
|
||||
|
||||
# Generate API key
|
||||
api_key = secrets.token_urlsafe(32)
|
||||
|
||||
# Create user
|
||||
user = await User.create(
|
||||
slack_id=user_data.slack_id,
|
||||
email=user_data.email,
|
||||
display_name=user_data.display_name,
|
||||
api_key=api_key,
|
||||
)
|
||||
|
||||
return UserResponse(
|
||||
id=user.id,
|
||||
slack_id=user.slack_id,
|
||||
email=user.email,
|
||||
display_name=user.display_name,
|
||||
api_key=user.api_key,
|
||||
created_at=user.created_at.isoformat(),
|
||||
)
|
||||
|
||||
|
||||
# Protected endpoints (auth required)
|
||||
@app.get("/profile", response_model=UserResponse)
|
||||
async def get_profile(current_user: User = Depends(get_current_user)): # pyright:ignore[reportCallInDefaultInitializer]
|
||||
return UserResponse(
|
||||
id=current_user.id,
|
||||
slack_id=current_user.slack_id,
|
||||
email=current_user.email,
|
||||
display_name=current_user.display_name,
|
||||
api_key=current_user.api_key,
|
||||
created_at=current_user.created_at.isoformat(),
|
||||
)
|
||||
|
||||
|
||||
@app.put("/profile", response_model=UserResponse)
|
||||
async def update_profile(
|
||||
user_update: UserUpdate, current_user: User = Depends(get_current_user) # pyright:ignore[reportCallInDefaultInitializer]
|
||||
|
||||
):
|
||||
if not api_token:
|
||||
raise HTTPException(status_code=401, detail="No API token provided")
|
||||
hashed_token = hasher.hash(api_token)
|
||||
results = session.exec(select(Player).where(Player.api_token == hashed_token))
|
||||
if len(results.all()) != 1:
|
||||
raise HTTPException(status_code=500, detail="Critical error, please report this!")
|
||||
# Update fields if provided
|
||||
if user_update.email is not None:
|
||||
# Check if email is already taken by another user
|
||||
existing_email = await User.get_or_none(email=user_update.email)
|
||||
if existing_email and existing_email.id != current_user.id:
|
||||
raise HTTPException(status_code=400, detail="Email already exists")
|
||||
current_user.email = user_update.email
|
||||
|
||||
if user_update.display_name is not None:
|
||||
current_user.display_name = user_update.display_name
|
||||
|
||||
await current_user.save()
|
||||
|
||||
return UserResponse(
|
||||
id=current_user.id,
|
||||
slack_id=current_user.slack_id,
|
||||
email=current_user.email,
|
||||
display_name=current_user.display_name,
|
||||
api_key=current_user.api_key,
|
||||
created_at=current_user.created_at.isoformat(),
|
||||
)
|
||||
|
||||
|
||||
@app.get("/profile/data")
|
||||
async def get_profile_data(current_user: User = Depends(get_current_user)) -> dict[str, int | str | bool | float | dict[str, Any]]: # pyright:ignore[reportCallInDefaultInitializer,reportExplicitAny]
|
||||
|
||||
"""Get flexible profile data (for future passport features)"""
|
||||
|
||||
@app.get("/players/")
|
||||
def read_players(session: Session = Depends(get_session)) -> list[dict[str, int | str]]:
|
||||
players = [
|
||||
{
|
||||
"id": player.id,
|
||||
"username": player.in_game_name,
|
||||
"current_world_id": player.current_world_id,
|
||||
"level": player.level,
|
||||
}
|
||||
for player in list(session.exec(select(Player)).all())
|
||||
]
|
||||
return players
|
||||
return {"profile_data": current_user.profile_data} # pyright:ignore[reportUnknownMemberType]
|
||||
|
||||
|
||||
@app.get("/worlds")
|
||||
def read_worlds(session: Session = Depends(get_session)):
|
||||
worlds = list(session.exec(select(World)).all())
|
||||
return worlds
|
||||
@app.put("/profile/data")
|
||||
async def update_profile_data(
|
||||
data: dict[str, Any], current_user: User = Depends(get_current_user) # pyright:ignore[reportCallInDefaultInitializer,reportExplicitAny]
|
||||
|
||||
):
|
||||
"""Update flexible profile data (for future passport features)"""
|
||||
current_user.profile_data = data
|
||||
await current_user.save()
|
||||
return {
|
||||
"message": "Profile data updated",
|
||||
"profile_data": current_user.profile_data,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue