Skip to content

Commit df47210

Browse files
committed
New routes for character submissions and approvals, including admin functionalities for managing characters. Conversation management routes.
1 parent d1df85e commit df47210

File tree

4 files changed

+343
-1
lines changed

4 files changed

+343
-1
lines changed

backend/app/api/main.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
from fastapi import APIRouter
22

3-
from app.api.routes import items, login, private, users, utils
3+
from app.api.routes import items, login, private, users, utils, characters, admin_characters, conversations
44
from app.core.config import settings
55

66
api_router = APIRouter()
77
api_router.include_router(login.router)
88
api_router.include_router(users.router)
99
api_router.include_router(utils.router)
1010
api_router.include_router(items.router)
11+
api_router.include_router(characters.router)
12+
api_router.include_router(admin_characters.router)
13+
api_router.include_router(conversations.router)
1114

1215

1316
if settings.ENVIRONMENT == "local":
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Placeholder for admin character management routes
2+
3+
import uuid
4+
from typing import Any
5+
6+
from fastapi import APIRouter, HTTPException, Depends
7+
from sqlmodel import Session, select, func
8+
9+
from app.api.deps import SessionDep, CurrentUser, get_current_active_superuser
10+
from app import crud
11+
from app.models import (
12+
Character, CharacterUpdate, CharacterPublic, CharactersPublic, CharacterStatus, Message
13+
)
14+
15+
router = APIRouter(prefix="/admin/characters", tags=["admin-characters"],
16+
dependencies=[Depends(get_current_active_superuser)])
17+
18+
19+
@router.get("/", response_model=CharactersPublic)
20+
def list_all_characters(
21+
session: SessionDep, skip: int = 0, limit: int = 100, status: CharacterStatus | None = None
22+
) -> Any:
23+
"""
24+
Retrieve all characters (admin view).
25+
Optionally filter by status.
26+
"""
27+
count = crud.characters.get_characters_count(session=session, status=status)
28+
characters = crud.characters.get_characters(
29+
session=session, skip=skip, limit=limit, status=status
30+
)
31+
return CharactersPublic(data=characters, count=count)
32+
33+
34+
@router.get("/pending", response_model=CharactersPublic)
35+
def list_pending_characters(
36+
session: SessionDep, skip: int = 0, limit: int = 100
37+
) -> Any:
38+
"""
39+
Retrieve characters pending approval.
40+
"""
41+
count = crud.characters.get_characters_count(session=session, status=CharacterStatus.PENDING)
42+
characters = crud.characters.get_characters(
43+
session=session, skip=skip, limit=limit, status=CharacterStatus.PENDING
44+
)
45+
return CharactersPublic(data=characters, count=count)
46+
47+
48+
@router.patch("/{id}/approve", response_model=CharacterPublic)
49+
def approve_character(session: SessionDep, id: uuid.UUID) -> Any:
50+
"""
51+
Approve a character submission.
52+
"""
53+
db_character = crud.characters.get_character(session=session, character_id=id)
54+
if not db_character:
55+
raise HTTPException(status_code=404, detail="Character not found")
56+
if db_character.status != CharacterStatus.PENDING:
57+
raise HTTPException(status_code=400, detail="Character is not pending approval")
58+
59+
character_update = CharacterUpdate(status=CharacterStatus.APPROVED)
60+
character = crud.characters.update_character(
61+
session=session, db_character=db_character, character_in=character_update
62+
)
63+
return character
64+
65+
66+
@router.patch("/{id}/reject", response_model=CharacterPublic)
67+
def reject_character(session: SessionDep, id: uuid.UUID) -> Any:
68+
"""
69+
Reject a character submission.
70+
"""
71+
db_character = crud.characters.get_character(session=session, character_id=id)
72+
if not db_character:
73+
raise HTTPException(status_code=404, detail="Character not found")
74+
if db_character.status != CharacterStatus.PENDING:
75+
raise HTTPException(status_code=400, detail="Character is not pending approval")
76+
77+
character_update = CharacterUpdate(status=CharacterStatus.REJECTED)
78+
character = crud.characters.update_character(
79+
session=session, db_character=db_character, character_in=character_update
80+
)
81+
return character
82+
83+
84+
@router.put("/{id}", response_model=CharacterPublic)
85+
def update_character_admin(
86+
*, session: SessionDep, id: uuid.UUID, character_in: CharacterUpdate
87+
) -> Any:
88+
"""
89+
Update any character (admin only).
90+
"""
91+
db_character = crud.characters.get_character(session=session, character_id=id)
92+
if not db_character:
93+
raise HTTPException(status_code=404, detail="Character not found")
94+
95+
character = crud.characters.update_character(
96+
session=session, db_character=db_character, character_in=character_in
97+
)
98+
return character
99+
100+
101+
@router.delete("/{id}")
102+
def delete_character_admin(
103+
session: SessionDep, id: uuid.UUID
104+
) -> Message:
105+
"""
106+
Delete a character (admin only).
107+
"""
108+
db_character = crud.characters.get_character(session=session, character_id=id)
109+
if not db_character:
110+
raise HTTPException(status_code=404, detail="Character not found")
111+
112+
crud.characters.delete_character(session=session, db_character=db_character)
113+
return Message(message="Character deleted successfully")
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Placeholder for character submission and listing routes
2+
3+
import uuid
4+
from typing import Any
5+
6+
from fastapi import APIRouter, HTTPException, Depends
7+
from sqlmodel import Session, select, func
8+
9+
from app.api.deps import SessionDep, CurrentUser
10+
from app import crud
11+
from app.models import (
12+
Character, CharacterCreate, CharacterPublic, CharactersPublic, CharacterStatus, Message
13+
)
14+
15+
router = APIRouter(prefix="/characters", tags=["characters"])
16+
17+
18+
@router.get("/", response_model=CharactersPublic)
19+
def list_approved_characters(
20+
session: SessionDep, skip: int = 0, limit: int = 100
21+
) -> Any:
22+
"""
23+
Retrieve approved characters.
24+
"""
25+
count = crud.characters.get_characters_count(
26+
session=session, status=CharacterStatus.APPROVED
27+
)
28+
characters = crud.characters.get_characters(
29+
session=session, skip=skip, limit=limit, status=CharacterStatus.APPROVED
30+
)
31+
return CharactersPublic(data=characters, count=count)
32+
33+
34+
@router.get("/{id}", response_model=CharacterPublic)
35+
def get_approved_character(
36+
session: SessionDep, id: uuid.UUID
37+
) -> Any:
38+
"""
39+
Get a specific approved character by ID.
40+
"""
41+
character = crud.characters.get_character(session=session, character_id=id)
42+
if not character:
43+
raise HTTPException(status_code=404, detail="Character not found")
44+
if character.status != CharacterStatus.APPROVED:
45+
# Hide non-approved characters from this public endpoint
46+
raise HTTPException(status_code=404, detail="Character not found") # Or 403 Forbidden
47+
return character
48+
49+
50+
@router.post("/submit", response_model=CharacterPublic, status_code=201)
51+
def submit_character(
52+
*, session: SessionDep, current_user: CurrentUser, character_in: CharacterCreate
53+
) -> Any:
54+
"""
55+
Submit a new character for review.
56+
Status defaults to 'pending'.
57+
"""
58+
character = crud.characters.create_character(
59+
session=session, character_create=character_in, creator_id=current_user.id
60+
)
61+
return character
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Placeholder for conversation management routes
2+
3+
import uuid
4+
from typing import Any
5+
6+
from fastapi import APIRouter, HTTPException, Depends
7+
from sqlmodel import Session
8+
9+
from app.api.deps import SessionDep, CurrentUser
10+
from app import crud
11+
from app.models import (
12+
Conversation, ConversationCreate, ConversationPublic, ConversationsPublic,
13+
Message, MessageCreate, MessagePublic, MessagesPublic, MessageSender,
14+
CharacterStatus, Character # Import Character
15+
)
16+
# Import AI service
17+
from app.services import ai_service
18+
19+
router = APIRouter(prefix="/conversations", tags=["conversations"])
20+
21+
22+
@router.post("/", response_model=ConversationPublic, status_code=201)
23+
def start_conversation(
24+
*, session: SessionDep, current_user: CurrentUser, conversation_in: ConversationCreate
25+
) -> Any:
26+
"""
27+
Start a new conversation with an approved character.
28+
"""
29+
# Check if character exists and is approved
30+
character = crud.characters.get_character(
31+
session=session, character_id=conversation_in.character_id
32+
)
33+
if not character or character.status != CharacterStatus.APPROVED:
34+
raise HTTPException(status_code=404, detail="Approved character not found")
35+
36+
try:
37+
conversation = crud.conversations.create_conversation(
38+
session=session, conversation_create=conversation_in, user_id=current_user.id
39+
)
40+
except ValueError as e:
41+
# Catch potential errors from CRUD (like character not found again, just in case)
42+
raise HTTPException(status_code=404, detail=str(e))
43+
44+
# Optionally: Add the character's greeting message as the first AI message
45+
if character.greeting_message:
46+
crud.conversations.create_message(
47+
session=session,
48+
message_create=MessageCreate(content=character.greeting_message),
49+
conversation_id=conversation.id,
50+
sender=MessageSender.AI
51+
)
52+
session.refresh(conversation) # Refresh to potentially load the new message relationship
53+
54+
return conversation
55+
56+
57+
@router.get("/", response_model=ConversationsPublic)
58+
def list_my_conversations(
59+
session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100
60+
) -> Any:
61+
"""
62+
Retrieve conversations for the current user.
63+
"""
64+
count = crud.conversations.get_user_conversations_count(
65+
session=session, user_id=current_user.id
66+
)
67+
conversations = crud.conversations.get_user_conversations(
68+
session=session, user_id=current_user.id, skip=skip, limit=limit
69+
)
70+
return ConversationsPublic(data=conversations, count=count)
71+
72+
73+
@router.get("/{conversation_id}/messages", response_model=MessagesPublic)
74+
def get_conversation_messages_route(
75+
session: SessionDep, current_user: CurrentUser, conversation_id: uuid.UUID, skip: int = 0, limit: int = 100
76+
) -> Any:
77+
"""
78+
Retrieve messages for a specific conversation owned by the current user.
79+
"""
80+
conversation = crud.conversations.get_conversation(
81+
session=session, conversation_id=conversation_id
82+
)
83+
if not conversation:
84+
raise HTTPException(status_code=404, detail="Conversation not found")
85+
if conversation.user_id != current_user.id:
86+
raise HTTPException(status_code=403, detail="Not authorized to view these messages")
87+
88+
count = crud.conversations.get_conversation_messages_count(
89+
session=session, conversation_id=conversation_id
90+
)
91+
messages = crud.conversations.get_conversation_messages(
92+
session=session, conversation_id=conversation_id, skip=skip, limit=limit
93+
)
94+
return MessagesPublic(data=messages, count=count)
95+
96+
97+
@router.post("/{conversation_id}/messages", response_model=MessagePublic)
98+
def send_message(
99+
*, session: SessionDep, current_user: CurrentUser, conversation_id: uuid.UUID, message_in: MessageCreate
100+
) -> Any:
101+
"""
102+
Send a message from the user to a conversation and get an AI response.
103+
"""
104+
conversation = crud.conversations.get_conversation(
105+
session=session, conversation_id=conversation_id
106+
)
107+
if not conversation:
108+
raise HTTPException(status_code=404, detail="Conversation not found")
109+
if conversation.user_id != current_user.id:
110+
raise HTTPException(status_code=403, detail="Not authorized to send messages to this conversation")
111+
if not conversation.character: # Ensure character relationship is loaded or handle if None
112+
# This might require adjusting how conversations are fetched or created if lazy loading isn't setup
113+
# For now, assume it exists if conversation exists
114+
raise HTTPException(status_code=500, detail="Character details missing for conversation")
115+
116+
# 1. Save the user's message
117+
user_message = crud.conversations.create_message(
118+
session=session,
119+
message_create=message_in,
120+
conversation_id=conversation_id,
121+
sender=MessageSender.USER
122+
)
123+
124+
# 2. Prepare context and get AI response
125+
# Get recent messages (including the one just sent by the user)
126+
# Adjust limit as needed for AI context window
127+
message_history = crud.conversations.get_conversation_messages(
128+
session=session, conversation_id=conversation_id, limit=20
129+
)
130+
131+
ai_response_text = ai_service.get_ai_response(
132+
character=conversation.character,
133+
history=message_history
134+
)
135+
136+
# 3. Save the AI's message
137+
ai_message = crud.conversations.create_message(
138+
session=session,
139+
message_create=MessageCreate(content=ai_response_text),
140+
conversation_id=conversation_id,
141+
sender=MessageSender.AI
142+
)
143+
144+
# Return the AI's response message
145+
return ai_message
146+
147+
148+
@router.delete("/{conversation_id}", status_code=204)
149+
def delete_conversation_route(
150+
session: SessionDep, current_user: CurrentUser, conversation_id: uuid.UUID
151+
) -> None:
152+
"""
153+
Delete a conversation owned by the current user.
154+
"""
155+
conversation = crud.conversations.get_conversation(
156+
session=session, conversation_id=conversation_id
157+
)
158+
if not conversation:
159+
# Idempotent delete: if not found, act as if deleted
160+
return None
161+
if conversation.user_id != current_user.id:
162+
raise HTTPException(status_code=403, detail="Not authorized to delete this conversation")
163+
164+
crud.conversations.delete_conversation(session=session, db_conversation=conversation)
165+
return None # No content response

0 commit comments

Comments
 (0)