mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 12:19:06 +00:00
Permanent room urls
This commit is contained in:
@@ -5,26 +5,22 @@ Revises: b9348748bbbc
|
||||
Create Date: 2024-07-31 16:41:29.415218
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '1340c04426b8'
|
||||
down_revision: Union[str, None] = 'b9348748bbbc'
|
||||
revision: str = "1340c04426b8"
|
||||
down_revision: Union[str, None] = "b9348748bbbc"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
op.add_column("transcript", sa.Column("meeting_id", sa.String(), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
op.drop_column("transcript", "meeting_id")
|
||||
|
||||
@@ -12,6 +12,7 @@ from reflector.logger import logger
|
||||
from reflector.metrics import metrics_init
|
||||
from reflector.settings import settings
|
||||
from reflector.views.meetings import router as meetings_router
|
||||
from reflector.views.rooms import router as rooms_router
|
||||
from reflector.views.rtc_offer import router as rtc_offer_router
|
||||
from reflector.views.transcripts import router as transcripts_router
|
||||
from reflector.views.transcripts_audio import router as transcripts_audio_router
|
||||
@@ -70,6 +71,7 @@ metrics_init(app, instrumentator)
|
||||
# register views
|
||||
app.include_router(rtc_offer_router)
|
||||
app.include_router(meetings_router, prefix="/v1")
|
||||
app.include_router(rooms_router, prefix="/v1")
|
||||
app.include_router(transcripts_router, prefix="/v1")
|
||||
app.include_router(transcripts_audio_router, prefix="/v1")
|
||||
app.include_router(transcripts_participants_router, prefix="/v1")
|
||||
|
||||
@@ -8,6 +8,7 @@ metadata = sqlalchemy.MetaData()
|
||||
|
||||
# import models
|
||||
import reflector.db.meetings # noqa
|
||||
import reflector.db.rooms # noqa
|
||||
import reflector.db.transcripts # noqa
|
||||
|
||||
engine = sqlalchemy.create_engine(
|
||||
|
||||
@@ -16,6 +16,7 @@ meetings = sqlalchemy.Table(
|
||||
sqlalchemy.Column("start_date", sqlalchemy.DateTime),
|
||||
sqlalchemy.Column("end_date", sqlalchemy.DateTime),
|
||||
sqlalchemy.Column("user_id", sqlalchemy.String),
|
||||
sqlalchemy.Column("room_id", sqlalchemy.String),
|
||||
)
|
||||
|
||||
|
||||
@@ -28,10 +29,11 @@ class Meeting(BaseModel):
|
||||
start_date: datetime
|
||||
end_date: datetime
|
||||
user_id: str
|
||||
room_id: str | None = None
|
||||
|
||||
|
||||
class MeetingController:
|
||||
async def add(
|
||||
async def create(
|
||||
self,
|
||||
id: str,
|
||||
room_name: str,
|
||||
@@ -41,9 +43,10 @@ class MeetingController:
|
||||
start_date: datetime,
|
||||
end_date: datetime,
|
||||
user_id: str,
|
||||
room_id: str = None,
|
||||
):
|
||||
"""
|
||||
Add a new meeting
|
||||
Create a new meeting
|
||||
"""
|
||||
meeting = Meeting(
|
||||
id=id,
|
||||
@@ -54,6 +57,7 @@ class MeetingController:
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
user_id=user_id,
|
||||
room_id=room_id,
|
||||
)
|
||||
query = meetings.insert().values(**meeting.model_dump())
|
||||
await database.execute(query)
|
||||
@@ -73,6 +77,20 @@ class MeetingController:
|
||||
|
||||
return Meeting(**result)
|
||||
|
||||
async def get_latest(self, room_id: str) -> Meeting:
|
||||
"""
|
||||
Get latest meeting for a room.
|
||||
"""
|
||||
start_date = getattr(meetings.c, "start_date").desc()
|
||||
query = (
|
||||
meetings.select().where(meetings.c.room_id == room_id).order_by(start_date)
|
||||
)
|
||||
result = await database.fetch_one(query)
|
||||
if not result:
|
||||
return None
|
||||
|
||||
return Meeting(**result)
|
||||
|
||||
async def get_by_id_for_http(self, meeting_id: str, user_id: str | None) -> Meeting:
|
||||
"""
|
||||
Get a meeting by ID for HTTP request.
|
||||
|
||||
139
server/reflector/db/rooms.py
Normal file
139
server/reflector/db/rooms.py
Normal file
@@ -0,0 +1,139 @@
|
||||
from datetime import datetime
|
||||
|
||||
import sqlalchemy
|
||||
from fastapi import HTTPException
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from reflector.db import database, metadata
|
||||
from reflector.db.transcripts import generate_uuid4
|
||||
from sqlalchemy.sql import false
|
||||
|
||||
rooms = sqlalchemy.Table(
|
||||
"room",
|
||||
metadata,
|
||||
sqlalchemy.Column("id", sqlalchemy.String, primary_key=True),
|
||||
sqlalchemy.Column("name", sqlalchemy.String, nullable=False),
|
||||
sqlalchemy.Column("user_id", sqlalchemy.String, nullable=False),
|
||||
sqlalchemy.Column("created_at", sqlalchemy.DateTime, nullable=False),
|
||||
sqlalchemy.Column(
|
||||
"zulip_auto_post", sqlalchemy.Boolean, nullable=False, server_default=false()
|
||||
),
|
||||
sqlalchemy.Column("zulip_stream", sqlalchemy.String),
|
||||
sqlalchemy.Column("zulip_topic", sqlalchemy.String),
|
||||
)
|
||||
|
||||
|
||||
class Room(BaseModel):
|
||||
id: str = Field(default_factory=generate_uuid4)
|
||||
name: str
|
||||
user_id: str
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
zulip_auto_post: bool = False
|
||||
zulip_stream: str = ""
|
||||
zulip_topic: str = ""
|
||||
|
||||
|
||||
class RoomController:
|
||||
async def get_all(
|
||||
self,
|
||||
user_id: str | None = None,
|
||||
order_by: str | None = None,
|
||||
return_query: bool = False,
|
||||
) -> list[Room]:
|
||||
"""
|
||||
Get all rooms
|
||||
|
||||
If `user_id` is specified, only return rooms that belong to the user.
|
||||
Otherwise, return all rooms.
|
||||
|
||||
Parameters:
|
||||
- `order_by`: field to order by, e.g. "-created_at"
|
||||
"""
|
||||
query = rooms.select()
|
||||
if user_id is not None:
|
||||
query = query.where(rooms.c.user_id == user_id)
|
||||
|
||||
if order_by is not None:
|
||||
field = getattr(rooms.c, order_by[1:])
|
||||
if order_by.startswith("-"):
|
||||
field = field.desc()
|
||||
query = query.order_by(field)
|
||||
|
||||
if return_query:
|
||||
return query
|
||||
|
||||
results = await database.fetch_all(query)
|
||||
return results
|
||||
|
||||
async def add(
|
||||
self,
|
||||
name: str,
|
||||
user_id: str,
|
||||
):
|
||||
"""
|
||||
Add a new room
|
||||
"""
|
||||
room = Room(
|
||||
name=name,
|
||||
user_id=user_id,
|
||||
)
|
||||
query = rooms.insert().values(**room.model_dump())
|
||||
await database.execute(query)
|
||||
return room
|
||||
|
||||
async def get_by_id(self, room_id: str, **kwargs) -> Room | None:
|
||||
"""
|
||||
Get a room by id
|
||||
"""
|
||||
query = rooms.select().where(rooms.c.id == room_id)
|
||||
if "user_id" in kwargs:
|
||||
query = query.where(rooms.c.user_id == kwargs["user_id"])
|
||||
result = await database.fetch_one(query)
|
||||
if not result:
|
||||
return None
|
||||
return Room(**result)
|
||||
|
||||
async def get_by_name(self, room_name: str, **kwargs) -> Room | None:
|
||||
"""
|
||||
Get a room by name
|
||||
"""
|
||||
query = rooms.select().where(rooms.c.name == room_name)
|
||||
if "user_id" in kwargs:
|
||||
query = query.where(rooms.c.user_id == kwargs["user_id"])
|
||||
result = await database.fetch_one(query)
|
||||
if not result:
|
||||
return None
|
||||
return Room(**result)
|
||||
|
||||
async def get_by_id_for_http(self, meeting_id: str, user_id: str | None) -> Room:
|
||||
"""
|
||||
Get a room by ID for HTTP request.
|
||||
|
||||
If not found, it will raise a 404 error.
|
||||
"""
|
||||
query = rooms.select().where(rooms.c.id == meeting_id)
|
||||
result = await database.fetch_one(query)
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="Room not found")
|
||||
|
||||
room = Room(**result)
|
||||
|
||||
return room
|
||||
|
||||
async def remove_by_id(
|
||||
self,
|
||||
room_id: str,
|
||||
user_id: str | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
Remove a room by id
|
||||
"""
|
||||
room = await self.get_by_id(room_id, user_id=user_id)
|
||||
if not room:
|
||||
return
|
||||
if user_id is not None and room.user_id != user_id:
|
||||
return
|
||||
query = rooms.delete().where(rooms.c.id == room_id)
|
||||
await database.execute(query)
|
||||
|
||||
|
||||
rooms_controller = RoomController()
|
||||
@@ -1,10 +1,11 @@
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Annotated, Optional
|
||||
|
||||
import reflector.auth as auth
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel
|
||||
from reflector.db.meetings import meetings_controller
|
||||
from reflector.whereby import create_meeting
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -26,3 +27,30 @@ async def meeting_get(
|
||||
):
|
||||
user_id = user["sub"] if user else None
|
||||
return await meetings_controller.get_by_id_for_http(meeting_id, user_id=user_id)
|
||||
|
||||
|
||||
@router.post("/meetings/", response_model=GetMeeting)
|
||||
async def meeting_create(
|
||||
room_id: str,
|
||||
user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)],
|
||||
):
|
||||
user_id = user["sub"] if user else None
|
||||
meeting = await meetings_controller.get_latest(room_id)
|
||||
if meeting is None:
|
||||
start_date = datetime.now(timezone.utc)
|
||||
end_date = start_date + timedelta(minutes=1)
|
||||
meeting = await create_meeting("", start_date=start_date, end_date=end_date)
|
||||
|
||||
meeting = await meetings_controller.add(
|
||||
id=meeting["meetingId"],
|
||||
room_name=meeting["roomName"],
|
||||
room_url=meeting["roomUrl"],
|
||||
host_room_url=meeting["hostRoomUrl"],
|
||||
viewer_room_url=meeting["viewerRoomUrl"],
|
||||
start_date=datetime.fromisoformat(meeting["startDate"]),
|
||||
end_date=datetime.fromisoformat(meeting["endDate"]),
|
||||
user_id=user_id,
|
||||
room_id=room_id,
|
||||
)
|
||||
|
||||
return await meetings_controller.get_by_id_for_http(meeting.id, user_id=user_id)
|
||||
|
||||
107
server/reflector/views/rooms.py
Normal file
107
server/reflector/views/rooms.py
Normal file
@@ -0,0 +1,107 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from http.client import HTTPException
|
||||
from typing import Annotated, Optional
|
||||
|
||||
import reflector.auth as auth
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi_pagination import Page
|
||||
from fastapi_pagination.ext.databases import paginate
|
||||
from pydantic import BaseModel, Field
|
||||
from reflector.db import database
|
||||
from reflector.db.meetings import meetings_controller
|
||||
from reflector.db.rooms import rooms_controller
|
||||
from reflector.settings import settings
|
||||
from reflector.views.meetings import GetMeeting
|
||||
from reflector.whereby import create_meeting
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class Room(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
user_id: str
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class CreateRoom(BaseModel):
|
||||
name: str
|
||||
|
||||
|
||||
class DeletionStatus(BaseModel):
|
||||
status: str
|
||||
|
||||
|
||||
@router.get("/rooms", response_model=Page[Room])
|
||||
async def rooms_list(
|
||||
user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)],
|
||||
) -> list[Room]:
|
||||
user_id = user["sub"] if user else None
|
||||
|
||||
if not user and not settings.PUBLIC_MODE:
|
||||
raise HTTPException(status_code=401, detail="Not authenticated")
|
||||
|
||||
user_id = user["sub"] if user else None
|
||||
return await paginate(
|
||||
database,
|
||||
await rooms_controller.get_all(
|
||||
user_id=user_id, order_by="-created_at", return_query=True
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/rooms", response_model=Room)
|
||||
async def rooms_create(
|
||||
room: CreateRoom,
|
||||
user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)],
|
||||
):
|
||||
user_id = user["sub"] if user else None
|
||||
|
||||
return await rooms_controller.add(
|
||||
name=room.name,
|
||||
user_id=user_id,
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/rooms/{room_id}", response_model=DeletionStatus)
|
||||
async def rooms_delete(
|
||||
room_id: str,
|
||||
user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)],
|
||||
):
|
||||
user_id = user["sub"] if user else None
|
||||
room = await rooms_controller.get_by_id(room_id, user_id=user_id)
|
||||
if not room:
|
||||
raise HTTPException(status_code=404, detail="Room not found")
|
||||
await rooms_controller.remove_by_id(room.id, user_id=user_id)
|
||||
return DeletionStatus(status="ok")
|
||||
|
||||
|
||||
@router.post("/rooms/{room_name}/meeting", response_model=GetMeeting)
|
||||
async def rooms_create_meeting(
|
||||
room_name: str,
|
||||
user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)],
|
||||
):
|
||||
user_id = user["sub"] if user else None
|
||||
room = await rooms_controller.get_by_name(room_name)
|
||||
if not room:
|
||||
raise HTTPException(status_code=404, detail="Room not found")
|
||||
|
||||
meeting = await meetings_controller.get_latest(room_id=room.id)
|
||||
if meeting is None:
|
||||
start_date = datetime.now(timezone.utc)
|
||||
end_date = start_date + timedelta(minutes=1)
|
||||
meeting = await create_meeting("", start_date=start_date, end_date=end_date)
|
||||
|
||||
meeting = await meetings_controller.create(
|
||||
id=meeting["meetingId"],
|
||||
room_name=meeting["roomName"],
|
||||
room_url=meeting["roomUrl"],
|
||||
host_room_url=meeting["hostRoomUrl"],
|
||||
viewer_room_url=meeting["viewerRoomUrl"],
|
||||
start_date=datetime.fromisoformat(meeting["startDate"]),
|
||||
end_date=datetime.fromisoformat(meeting["endDate"]),
|
||||
user_id=user_id,
|
||||
room_id=room.id,
|
||||
)
|
||||
|
||||
return meeting
|
||||
@@ -121,7 +121,7 @@ async def transcripts_create_meeting(
|
||||
end_date = start_date + timedelta(minutes=1)
|
||||
meeting = await create_meeting("", start_date=start_date, end_date=end_date)
|
||||
|
||||
meeting = await meetings_controller.add(
|
||||
meeting = await meetings_controller.create(
|
||||
id=meeting["meetingId"],
|
||||
room_name=meeting["roomName"],
|
||||
room_url=meeting["roomUrl"],
|
||||
@@ -133,7 +133,7 @@ async def transcripts_create_meeting(
|
||||
)
|
||||
|
||||
return await transcripts_controller.add(
|
||||
info.name,
|
||||
"",
|
||||
source_language=info.source_language,
|
||||
target_language=info.target_language,
|
||||
user_id=user_id,
|
||||
|
||||
@@ -65,6 +65,15 @@ async def process_recording(bucket_name: str, object_key: str):
|
||||
room_name = f"/{object_key[:36]}"
|
||||
meeting = await meetings_controller.get_by_room_name(room_name)
|
||||
transcript = await transcripts_controller.get_by_meeting_id(meeting.id)
|
||||
if transcript is None:
|
||||
transcript = await transcripts_controller.add(
|
||||
"",
|
||||
source_language="en",
|
||||
target_language="en",
|
||||
user_id=meeting.user_id,
|
||||
meeting_id=meeting.id,
|
||||
share_mode="public",
|
||||
)
|
||||
|
||||
_, extension = os.path.splitext(object_key)
|
||||
upload_filename = transcript.data_path / f"upload{extension}"
|
||||
|
||||
Reference in New Issue
Block a user