Repository: aipotheosis-labs/aci
File: backend/aci/server/routes/webhooks.py (Python)

Code before:
        "created a default personal org for new user",
        extra={
            "user_id": user.user_id,
            "org_id": org.org_id,
        },
    )

    auth.update_org_metadata(org_id=org.org_id, metadata={"personal": True})
    logger.info(
        "updated org metadata (personal=True) for default personal org",
        extra={
            "user_id": user.user_id,
            "org_id": org.org_id,
        },
    )

    auth.add_user_to_org(user_id=user.user_id, org_id=org.org_id, role=OrganizationRole.OWNER)
    logger.info(
        "added new user to default personal org",
        extra={
            "user_id": user.user_id,
            "org_id": org.org_id,
        },
    )

    project = crud.projects.create_project(db_session, UUID(org.org_id), "Default Project")

    # Create a default Agent for the project
    agent = crud.projects.create_agent(
        db_session,
        project.id,
        name="Default Agent",
        description="Default Agent",
        allowed_apps=[],
        custom_instructions={},
    )
    db_session.commit()

    logger.info(
        "created default project and agent for new user",
        extra={
            "user_id": user.user_id,
            "org_id": org.org_id,
            "project_id": project.id,
            "agent_id": agent.id,
        },
    )


def _generate_secure_random_alphanumeric_string(length: int = 6) -> str:
    charset = string.ascii_letters + string.digits

Review comment (security):
`create_project` expects the `org_id` to be an `UUID`, but the `org.org_id` from PropelAuth is `str`. This has been working fine because SQLAlchemy's `PGUUID` automatically performs the type coercion from the `str` type. Using `UUID(org.org_id)` here helps us find the problem earlier if the `org.org_id` one day becomes an invalid UUID.

We should also consider making the org_id field in our Project table to be just a string.

More details:
SQLAlchemy's PGUUID Type Coercion: Your Project model in aci/common/db/sql_models.py defines the org_id field like this:
```python
class Project(Base):
    # ...
    org_id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False)
    # ...
```
The key here is PGUUID(as_uuid=True). This SQLAlchemy type is designed to work with UUIDs in PostgreSQL and Python. When you create a Project instance like so:
```python
project = Project(
    org_id=org.org_id,  # org.org_id is a string here
    name=name,
    visibility_access=visibility_access,
)
```
SQLAlchemy's PGUUID type handler performs type coercion. It attempts to convert the provided string value (org.org_id) into a Python uuid.UUID object.
If the string is a valid UUID format (e.g., "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"), the conversion succeeds silently, and a uuid.UUID object is stored in the project.org_id attribute. This is what's happening in your case.
If the string were not a valid UUID format, SQLAlchemy would raise an error (typically a ValueError) during this conversion attempt.

Code after:
        "created a default personal org for new user",
        extra={
            "user_id": user.user_id,
            "org_id": org.org_id,
        },
    )

    auth.update_org_metadata(org_id=org.org_id, metadata={"personal": True})
    logger.info(
        "updated org metadata (personal=True) for default personal org",
        extra={
            "user_id": user.user_id,
            "org_id": org.org_id,
        },
    )

    auth.add_user_to_org(user_id=user.user_id, org_id=org.org_id, role=OrganizationRole.OWNER)
    logger.info(
        "added new user to default personal org",
        extra={
            "user_id": user.user_id,
            "org_id": org.org_id,
        },
    )

    org_id_uuid = _convert_org_id_to_uuid(org.org_id)
    project = crud.projects.create_project(db_session, org_id_uuid, "Default Project")

    # Create a default Agent for the project
    agent = crud.projects.create_agent(
        db_session,
        project.id,
        name="Default Agent",
        description="Default Agent",
        allowed_apps=[],
        custom_instructions={},
    )
    db_session.commit()

    logger.info(
        "created default project and agent for new user",
        extra={
            "user_id": user.user_id,
            "org_id": org.org_id,
            "project_id": project.id,
            "agent_id": agent.id,
        },
    )


def _generate_secure_random_alphanumeric_string(length: int = 6) -> str: