Skip to content

Commit d27f19b

Browse files
committed
lots of changes routes, apis, idk
1 parent ec89517 commit d27f19b

File tree

11 files changed

+443
-1
lines changed

11 files changed

+443
-1
lines changed

backend/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,5 @@ COPY ./app /app/app
4040
RUN --mount=type=cache,target=/root/.cache/uv \
4141
uv sync
4242

43-
CMD ["fastapi", "run", "--workers", "4", "app/main.py"]
43+
# Use Uvicorn directly for better compatibility with Render's $PORT
44+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "${PORT:-8000}", "--workers", "4"]

backend/app/alembic/env.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
import os
2+
import sys
3+
from pathlib import Path
24
from logging.config import fileConfig
35

46
from alembic import context
57
from sqlalchemy import engine_from_config, pool
68

9+
# Add project root to sys.path
10+
# Assumes env.py is in backend/app/alembic/
11+
project_root = Path(__file__).parents[2]
12+
sys.path.insert(0, str(project_root))
13+
714
# this is the Alembic Config object, which provides
815
# access to the values within the .ini file in use.
916
config = context.config
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""Add ON DELETE CASCADE to message.conversation_id
2+
3+
Revision ID: 47b3823eff09
4+
Revises: cba8d126b9ac
5+
Create Date: 2025-04-19 06:25:27.717138
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
12+
13+
# revision identifiers, used by Alembic.
14+
revision = '47b3823eff09'
15+
down_revision = 'cba8d126b9ac'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
# Manually added to apply ON DELETE CASCADE
23+
op.drop_constraint(
24+
'message_conversation_id_fkey', # Default constraint name might vary, check DB if needed
25+
'message',
26+
type_='foreignkey'
27+
)
28+
op.create_foreign_key(
29+
'message_conversation_id_fkey',
30+
'message',
31+
'conversation',
32+
['conversation_id'],
33+
['id'],
34+
ondelete='CASCADE'
35+
)
36+
# ### end Alembic commands ###
37+
38+
39+
def downgrade():
40+
# ### commands auto generated by Alembic - please adjust! ###
41+
# Manually added to revert ON DELETE CASCADE
42+
op.drop_constraint(
43+
'message_conversation_id_fkey',
44+
'message',
45+
type_='foreignkey'
46+
)
47+
op.create_foreign_key(
48+
'message_conversation_id_fkey',
49+
'message',
50+
'conversation',
51+
['conversation_id'],
52+
['id']
53+
# No ondelete here
54+
)
55+
# ### end Alembic commands ###
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""Add Character, Conversation, Message models
2+
3+
Revision ID: cba8d126b9ac
4+
Revises: 1a31ce608336
5+
Create Date: 2025-04-19 06:13:38.566914
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
12+
13+
# revision identifiers, used by Alembic.
14+
revision = 'cba8d126b9ac'
15+
down_revision = '1a31ce608336'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
op.create_table('character',
23+
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(length=100), nullable=False),
24+
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(length=1000), nullable=True),
25+
sa.Column('image_url', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
26+
sa.Column('greeting_message', sqlmodel.sql.sqltypes.AutoString(length=1000), nullable=True),
27+
sa.Column('id', sa.Uuid(), nullable=False),
28+
sa.Column('status', sa.Enum('PENDING', 'APPROVED', 'REJECTED', name='characterstatus'), nullable=False),
29+
sa.Column('creator_id', sa.Uuid(), nullable=False),
30+
sa.ForeignKeyConstraint(['creator_id'], ['user.id'], ),
31+
sa.PrimaryKeyConstraint('id')
32+
)
33+
op.create_index(op.f('ix_character_name'), 'character', ['name'], unique=False)
34+
op.create_table('conversation',
35+
sa.Column('id', sa.Uuid(), nullable=False),
36+
sa.Column('user_id', sa.Uuid(), nullable=False),
37+
sa.Column('character_id', sa.Uuid(), nullable=False),
38+
sa.Column('created_at', sa.DateTime(), nullable=False),
39+
sa.ForeignKeyConstraint(['character_id'], ['character.id'], ),
40+
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
41+
sa.PrimaryKeyConstraint('id')
42+
)
43+
op.create_table('message',
44+
sa.Column('content', sqlmodel.sql.sqltypes.AutoString(length=5000), nullable=False),
45+
sa.Column('id', sa.Uuid(), nullable=False),
46+
sa.Column('conversation_id', sa.Uuid(), nullable=False),
47+
sa.Column('sender', sa.Enum('USER', 'AI', name='messagesender'), nullable=False),
48+
sa.Column('timestamp', sa.DateTime(), nullable=False),
49+
sa.ForeignKeyConstraint(['conversation_id'], ['conversation.id'], ),
50+
sa.PrimaryKeyConstraint('id')
51+
)
52+
# ### end Alembic commands ###
53+
54+
55+
def downgrade():
56+
# ### commands auto generated by Alembic - please adjust! ###
57+
op.drop_table('message')
58+
op.drop_table('conversation')
59+
op.drop_index(op.f('ix_character_name'), table_name='character')
60+
op.drop_table('character')
61+
# ### end Alembic commands ###

backend/app/api/routes/characters.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,23 @@ def list_approved_characters(
3131
return CharactersPublic(data=characters, count=count)
3232

3333

34+
@router.get("/my-submissions", response_model=CharactersPublic)
35+
def list_my_character_submissions(
36+
session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100
37+
) -> Any:
38+
"""
39+
Retrieve characters submitted by the current user.
40+
Includes characters with any status (pending, approved, rejected).
41+
"""
42+
count = crud.characters.get_characters_count(
43+
session=session, creator_id=current_user.id
44+
)
45+
characters = crud.characters.get_characters(
46+
session=session, creator_id=current_user.id, skip=skip, limit=limit
47+
)
48+
return CharactersPublic(data=characters, count=count)
49+
50+
3451
@router.get("/{id}", response_model=CharacterPublic)
3552
def get_approved_character(
3653
session: SessionDep, id: uuid.UUID

backend/app/crud/__init__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from . import characters
2+
from . import conversations
3+
4+
# Import functions from the original crud file (now named base.py)
5+
from .base import (
6+
create_user,
7+
update_user,
8+
get_user_by_email,
9+
authenticate,
10+
create_item,
11+
)
12+
13+
# Now, when other modules do 'from app import crud',
14+
# they can access crud.create_user, crud.authenticate,
15+
# crud.characters.create_character, etc.
16+
17+
# You can also import specific functions if preferred, e.g.:
18+
# from .users import get_user_by_email, create_user, authenticate, update_user # Assuming users crud is also needed
19+
# from .items import create_item # Assuming items crud is also needed
20+
21+
# We need to import the existing user and item crud functions as well
22+
# Check where user/item crud functions are defined (likely crud.py initially)
23+
# Let's assume they are in a top-level crud.py that needs to be refactored or imported here
24+
# For now, just importing the new modules:

backend/app/crud/base.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import uuid
2+
from typing import Any
3+
4+
from sqlmodel import Session, select
5+
6+
from app.core.security import get_password_hash, verify_password
7+
from app.models import Item, ItemCreate, User, UserCreate, UserUpdate
8+
9+
10+
def create_user(*, session: Session, user_create: UserCreate) -> User:
11+
db_obj = User.model_validate(
12+
user_create, update={"hashed_password": get_password_hash(user_create.password)}
13+
)
14+
session.add(db_obj)
15+
session.commit()
16+
session.refresh(db_obj)
17+
return db_obj
18+
19+
20+
def update_user(*, session: Session, db_user: User, user_in: UserUpdate) -> Any:
21+
user_data = user_in.model_dump(exclude_unset=True)
22+
extra_data = {}
23+
if "password" in user_data:
24+
password = user_data["password"]
25+
hashed_password = get_password_hash(password)
26+
extra_data["hashed_password"] = hashed_password
27+
db_user.sqlmodel_update(user_data, update=extra_data)
28+
session.add(db_user)
29+
session.commit()
30+
session.refresh(db_user)
31+
return db_user
32+
33+
34+
def get_user_by_email(*, session: Session, email: str) -> User | None:
35+
statement = select(User).where(User.email == email)
36+
session_user = session.exec(statement).first()
37+
return session_user
38+
39+
40+
def authenticate(*, session: Session, email: str, password: str) -> User | None:
41+
db_user = get_user_by_email(session=session, email=email)
42+
if not db_user:
43+
return None
44+
if not verify_password(password, db_user.hashed_password):
45+
return None
46+
return db_user
47+
48+
49+
def create_item(*, session: Session, item_in: ItemCreate, owner_id: uuid.UUID) -> Item:
50+
db_item = Item.model_validate(item_in, update={"owner_id": owner_id})
51+
session.add(db_item)
52+
session.commit()
53+
session.refresh(db_item)
54+
return db_item

backend/app/crud/characters.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Placeholder for character CRUD operations
2+
3+
import uuid
4+
from typing import Sequence
5+
6+
from sqlmodel import Session, select, col, func
7+
8+
from app.models import Character, CharacterCreate, CharacterUpdate, CharacterStatus, User
9+
10+
11+
def create_character(
12+
*, session: Session, character_create: CharacterCreate, creator_id: uuid.UUID
13+
) -> Character:
14+
"""Creates a new character with status 'pending'."""
15+
db_obj = Character.model_validate(
16+
character_create, update={"creator_id": creator_id, "status": CharacterStatus.PENDING}
17+
)
18+
session.add(db_obj)
19+
session.commit()
20+
session.refresh(db_obj)
21+
return db_obj
22+
23+
24+
def get_character(*, session: Session, character_id: uuid.UUID) -> Character | None:
25+
"""Gets a single character by its ID."""
26+
return session.get(Character, character_id)
27+
28+
29+
def get_characters(
30+
*,
31+
session: Session,
32+
skip: int = 0,
33+
limit: int = 100,
34+
status: CharacterStatus | None = None,
35+
creator_id: uuid.UUID | None = None,
36+
) -> Sequence[Character]:
37+
"""Gets a list of characters with optional filters."""
38+
statement = select(Character).offset(skip).limit(limit)
39+
if status is not None:
40+
statement = statement.where(Character.status == status)
41+
if creator_id is not None:
42+
statement = statement.where(Character.creator_id == creator_id)
43+
44+
characters = session.exec(statement).all()
45+
return characters
46+
47+
48+
def get_characters_count(
49+
*,
50+
session: Session,
51+
status: CharacterStatus | None = None,
52+
creator_id: uuid.UUID | None = None,
53+
) -> int:
54+
"""Gets the total count of characters with optional filters."""
55+
statement = select(func.count(Character.id))
56+
if status is not None:
57+
statement = statement.where(Character.status == status)
58+
if creator_id is not None:
59+
statement = statement.where(Character.creator_id == creator_id)
60+
61+
count = session.exec(statement).one()
62+
return count
63+
64+
65+
def update_character(
66+
*, session: Session, db_character: Character, character_in: CharacterUpdate
67+
) -> Character:
68+
"""Updates an existing character."""
69+
update_data = character_in.model_dump(exclude_unset=True)
70+
db_character.sqlmodel_update(update_data)
71+
session.add(db_character)
72+
session.commit()
73+
session.refresh(db_character)
74+
return db_character
75+
76+
77+
def delete_character(*, session: Session, db_character: Character) -> None:
78+
"""Deletes a character."""
79+
# Add cascade delete for conversations/messages if needed, handled by relationship cascade
80+
session.delete(db_character)
81+
session.commit()

0 commit comments

Comments
 (0)