Permanent room urls

This commit is contained in:
2024-08-16 22:26:00 +02:00
parent ad84e4626c
commit 55697e670d
20 changed files with 1001 additions and 31 deletions

View File

@@ -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")

View File

@@ -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")

View File

@@ -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(

View File

@@ -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.

View 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()

View File

@@ -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)

View 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

View File

@@ -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,

View File

@@ -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}"

View File

@@ -76,7 +76,7 @@ type LayoutProps = {
export default async function RootLayout({ children, params }: LayoutProps) {
const config = await getConfig(params.domain);
const { requireLogin, privacy, browse } = config.features;
const { requireLogin, privacy, browse, rooms } = config.features;
const hasAuthCookie = !!cookies().get(SESSION_COOKIE_NAME);
return (
@@ -154,6 +154,21 @@ export default async function RootLayout({ children, params }: LayoutProps) {
) : (
<></>
)}
{rooms ? (
<>
&nbsp;·&nbsp;
<Link
href="/rooms"
as={NextLink}
className="hover:underline focus-within:underline underline-offset-2 decoration-[.5px] font-light px-2"
prefetch={false}
>
Rooms
</Link>
</>
) : (
<></>
)}
&nbsp;·&nbsp;
<About buttonText="About" />
{privacy ? (

View File

@@ -0,0 +1,33 @@
"use client";
import "@whereby.com/browser-sdk/embed";
import { useCallback, useEffect, useRef } from "react";
import useRoomMeeting from "../../rooms/useRoomMeeting";
export type RoomDetails = {
params: {
roomName: string;
};
};
export default function Room(details: RoomDetails) {
const wherebyRef = useRef<HTMLElement>(null);
const roomName = details.params.roomName;
const meeting = useRoomMeeting(roomName);
const roomUrl = meeting?.response?.host_room_url
? meeting?.response?.host_room_url
: meeting?.response?.room_url;
return (
<>
{roomUrl && (
<whereby-embed
ref={wherebyRef}
room={roomUrl}
style={{ width: "100%", height: "98%" }}
/>
)}
</>
);
}

View File

@@ -0,0 +1,171 @@
"use client";
import {
Box,
Button,
Card,
CardBody,
Flex,
FormControl,
FormHelperText,
FormLabel,
Grid,
Heading,
Input,
Link,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Spacer,
Spinner,
useDisclosure,
VStack,
Text,
Menu,
MenuButton,
MenuList,
MenuItem,
AlertDialog,
IconButton,
} from "@chakra-ui/react";
import NextLink from "next";
import React, { ReactNode, useState } from "react";
import { Container } from "@chakra-ui/react";
import { PlusSquareIcon } from "@chakra-ui/icons";
import useApi from "../../lib/useApi";
import useRoomList from "./useRoomList";
import { FaEllipsisVertical, FaTrash } from "react-icons/fa6";
import next from "next";
export default function RoomsList() {
const { isOpen, onOpen, onClose } = useDisclosure();
const [roomName, setRoomName] = useState("");
const api = useApi();
const [page, setPage] = useState<number>(1);
const { loading, response, refetch } = useRoomList(page);
const handleAddRoom = async () => {
try {
const response = await api?.v1RoomsCreate({
requestBody: { name: roomName },
});
setRoomName("");
refetch();
} catch (err) {}
onClose();
};
const handleDeleteRoom = async (roomId: string) => {
try {
const response = await api?.v1RoomsDelete({
roomId,
});
refetch();
} catch (err) {}
};
const handleRoomNameChange = (e) => {
setRoomName(e.target.value);
};
if (loading && !response)
return (
<Flex flexDir="column" align="center" justify="center" h="100%">
<Spinner size="xl" />
</Flex>
);
return (
<>
<Container maxW={"container.lg"}>
<Flex
flexDir="row"
justify="flex-end"
align="center"
flexWrap={"wrap-reverse"}
mb={2}
>
<Heading>Rooms</Heading>
<Spacer />
<Button colorScheme="blue" onClick={onOpen}>
Add Room
</Button>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Add Room</ModalHeader>
<ModalCloseButton />
<ModalBody>
<FormControl>
<FormLabel>Room name</FormLabel>
<Input
placeholder="room-name"
value={roomName}
onChange={handleRoomNameChange}
/>
<FormHelperText>Please enter room name</FormHelperText>
</FormControl>
</ModalBody>
<ModalFooter>
<Button variant="ghost" mr={3} onClick={onClose}>
Cancel
</Button>
<Button colorScheme="blue" onClick={handleAddRoom}>
Add
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</Flex>
<VStack>
{response?.items && response.items.length > 0 ? (
response.items.map((room) => (
<Card w={"full"}>
<CardBody>
<Flex align={"center"}>
<Heading size="md">
<Link
// as={NextLink}
href={`/rooms/${room.name}`}
noOfLines={2}
>
{room.name}
</Link>
</Heading>
<Spacer />
<Menu closeOnSelect={true}>
<MenuButton
as={IconButton}
icon={<FaEllipsisVertical />}
aria-label="actions"
/>
<MenuList>
<MenuItem
onClick={() => handleDeleteRoom(room.id)}
icon={<FaTrash color={"red.500"} />}
>
Delete
</MenuItem>
</MenuList>
</Menu>
</Flex>
</CardBody>
</Card>
))
) : (
<Flex flexDir="column" align="center" justify="center" h="100%">
<Text>No rooms found</Text>
</Flex>
)}
</VStack>
</Container>
</>
);
}

View File

@@ -0,0 +1,47 @@
import { useEffect, useState } from "react";
import { useError } from "../../(errors)/errorContext";
import useApi from "../../lib/useApi";
import { Page_Room_ } from "../../api";
type RoomList = {
response: Page_Room_ | null;
loading: boolean;
error: Error | null;
refetch: () => void;
};
//always protected
const useRoomList = (page: number): RoomList => {
const [response, setResponse] = useState<Page_Room_ | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setErrorState] = useState<Error | null>(null);
const { setError } = useError();
const api = useApi();
const [refetchCount, setRefetchCount] = useState(0);
const refetch = () => {
setLoading(true);
setRefetchCount(refetchCount + 1);
};
useEffect(() => {
if (!api) return;
setLoading(true);
api
.v1RoomsList({ page })
.then((response) => {
setResponse(response);
setLoading(false);
})
.catch((err) => {
setResponse(null);
setLoading(false);
setError(err);
setErrorState(err);
});
}, [!api, page, refetchCount]);
return { response, loading, error, refetch };
};
export default useRoomList;

View File

@@ -0,0 +1,70 @@
import { useEffect, useState } from "react";
import { useError } from "../../(errors)/errorContext";
import { GetMeeting } from "../../api";
import { shouldShowError } from "../../lib/errorUtils";
import useApi from "../../lib/useApi";
type ErrorMeeting = {
error: Error;
loading: false;
response: null;
reload: () => void;
};
type LoadingMeeting = {
response: null;
loading: true;
error: false;
reload: () => void;
};
type SuccessMeeting = {
response: GetMeeting;
loading: false;
error: null;
reload: () => void;
};
const useRoomMeeting = (
roomName: string | null | undefined,
): ErrorMeeting | LoadingMeeting | SuccessMeeting => {
const [response, setResponse] = useState<GetMeeting | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setErrorState] = useState<Error | null>(null);
const [reload, setReload] = useState(0);
const { setError } = useError();
const api = useApi();
const reloadHandler = () => setReload((prev) => prev + 1);
useEffect(() => {
if (!roomName || !api) return;
if (!response) {
setLoading(true);
}
api
.v1RoomsCreateMeeting({ roomName })
.then((result) => {
setResponse(result);
setLoading(false);
console.debug("Meeting Loaded:", result);
})
.catch((error) => {
const shouldShowHuman = shouldShowError(error);
if (shouldShowHuman) {
setError(error, "There was an error loading the meeting");
} else {
setError(error);
}
setErrorState(error);
});
}, [roomName, !api, reload]);
return { response, loading, error, reload: reloadHandler } as
| ErrorMeeting
| LoadingMeeting
| SuccessMeeting;
};
export default useRoomMeeting;

View File

@@ -20,18 +20,6 @@ export default function TranscriptMeeting(details: TranscriptDetails) {
? meeting?.response?.host_room_url
: meeting?.response?.room_url;
const handleLeave = useCallback((event) => {
console.log("LEFT", event);
}, []);
useEffect(() => {
wherebyRef.current?.addEventListener("leave", handleLeave);
return () => {
wherebyRef.current?.removeEventListener("leave", handleLeave);
};
}, [handleLeave]);
return (
<>
{roomUrl && (

View File

@@ -53,6 +53,18 @@ export const $CreateParticipant = {
title: "CreateParticipant",
} as const;
export const $CreateRoom = {
properties: {
name: {
type: "string",
title: "Name",
},
},
type: "object",
required: ["name"],
title: "CreateRoom",
} as const;
export const $CreateTranscript = {
properties: {
name: {
@@ -529,6 +541,62 @@ export const $Page_GetTranscript_ = {
title: "Page[GetTranscript]",
} as const;
export const $Page_Room_ = {
properties: {
items: {
items: {
$ref: "#/components/schemas/Room",
},
type: "array",
title: "Items",
},
total: {
type: "integer",
minimum: 0,
title: "Total",
},
page: {
anyOf: [
{
type: "integer",
minimum: 1,
},
{
type: "null",
},
],
title: "Page",
},
size: {
anyOf: [
{
type: "integer",
minimum: 1,
},
{
type: "null",
},
],
title: "Size",
},
pages: {
anyOf: [
{
type: "integer",
minimum: 0,
},
{
type: "null",
},
],
title: "Pages",
},
},
type: "object",
required: ["items", "total", "page", "size"],
title: "Page[Room]",
} as const;
export const $Participant = {
properties: {
id: {
@@ -556,6 +624,31 @@ export const $Participant = {
title: "Participant",
} as const;
export const $Room = {
properties: {
id: {
type: "string",
title: "Id",
},
name: {
type: "string",
title: "Name",
},
user_id: {
type: "string",
title: "User Id",
},
created_at: {
type: "string",
format: "date-time",
title: "Created At",
},
},
type: "object",
required: ["id", "name", "user_id", "created_at"],
title: "Room",
} as const;
export const $RtcOffer = {
properties: {
sdp: {

View File

@@ -6,6 +6,16 @@ import type {
MetricsResponse,
V1MeetingGetData,
V1MeetingGetResponse,
V1MeetingCreateData,
V1MeetingCreateResponse,
V1RoomsListData,
V1RoomsListResponse,
V1RoomsCreateData,
V1RoomsCreateResponse,
V1RoomsDeleteData,
V1RoomsDeleteResponse,
V1RoomsCreateMeetingData,
V1RoomsCreateMeetingResponse,
V1TranscriptsListData,
V1TranscriptsListResponse,
V1TranscriptsCreateData,
@@ -93,6 +103,117 @@ export class DefaultService {
});
}
/**
* Meeting Create
* @param data The data for the request.
* @param data.roomId
* @returns GetMeeting Successful Response
* @throws ApiError
*/
public v1MeetingCreate(
data: V1MeetingCreateData,
): CancelablePromise<V1MeetingCreateResponse> {
return this.httpRequest.request({
method: "POST",
url: "/v1/meetings/",
query: {
room_id: data.roomId,
},
errors: {
422: "Validation Error",
},
});
}
/**
* Rooms List
* @param data The data for the request.
* @param data.page Page number
* @param data.size Page size
* @returns Page_Room_ Successful Response
* @throws ApiError
*/
public v1RoomsList(
data: V1RoomsListData = {},
): CancelablePromise<V1RoomsListResponse> {
return this.httpRequest.request({
method: "GET",
url: "/v1/rooms",
query: {
page: data.page,
size: data.size,
},
errors: {
422: "Validation Error",
},
});
}
/**
* Rooms Create
* @param data The data for the request.
* @param data.requestBody
* @returns Room Successful Response
* @throws ApiError
*/
public v1RoomsCreate(
data: V1RoomsCreateData,
): CancelablePromise<V1RoomsCreateResponse> {
return this.httpRequest.request({
method: "POST",
url: "/v1/rooms",
body: data.requestBody,
mediaType: "application/json",
errors: {
422: "Validation Error",
},
});
}
/**
* Rooms Delete
* @param data The data for the request.
* @param data.roomId
* @returns DeletionStatus Successful Response
* @throws ApiError
*/
public v1RoomsDelete(
data: V1RoomsDeleteData,
): CancelablePromise<V1RoomsDeleteResponse> {
return this.httpRequest.request({
method: "DELETE",
url: "/v1/rooms/{room_id}",
path: {
room_id: data.roomId,
},
errors: {
422: "Validation Error",
},
});
}
/**
* Rooms Create Meeting
* @param data The data for the request.
* @param data.roomName
* @returns GetMeeting Successful Response
* @throws ApiError
*/
public v1RoomsCreateMeeting(
data: V1RoomsCreateMeetingData,
): CancelablePromise<V1RoomsCreateMeetingResponse> {
return this.httpRequest.request({
method: "POST",
url: "/v1/rooms/{room_name}/meeting",
path: {
room_name: data.roomName,
},
errors: {
422: "Validation Error",
},
});
}
/**
* Transcripts List
* @param data The data for the request.

View File

@@ -14,6 +14,10 @@ export type CreateParticipant = {
name: string;
};
export type CreateRoom = {
name: string;
};
export type CreateTranscript = {
name: string;
source_language?: string;
@@ -103,12 +107,27 @@ export type Page_GetTranscript_ = {
pages?: number | null;
};
export type Page_Room_ = {
items: Array<Room>;
total: number;
page: number | null;
size: number | null;
pages?: number | null;
};
export type Participant = {
id: string;
speaker: number | null;
name: string;
};
export type Room = {
id: string;
name: string;
user_id: string;
created_at: string;
};
export type RtcOffer = {
sdp: string;
type: string;
@@ -184,6 +203,43 @@ export type V1MeetingGetData = {
export type V1MeetingGetResponse = GetMeeting;
export type V1MeetingCreateData = {
roomId: string;
};
export type V1MeetingCreateResponse = GetMeeting;
export type V1RoomsListData = {
/**
* Page number
*/
page?: number;
/**
* Page size
*/
size?: number;
};
export type V1RoomsListResponse = Page_Room_;
export type V1RoomsCreateData = {
requestBody: CreateRoom;
};
export type V1RoomsCreateResponse = Room;
export type V1RoomsDeleteData = {
roomId: string;
};
export type V1RoomsDeleteResponse = DeletionStatus;
export type V1RoomsCreateMeetingData = {
roomName: string;
};
export type V1RoomsCreateMeetingResponse = GetMeeting;
export type V1TranscriptsListData = {
/**
* Page number
@@ -374,6 +430,79 @@ export type $OpenApiTs = {
};
};
};
"/v1/meetings/": {
post: {
req: V1MeetingCreateData;
res: {
/**
* Successful Response
*/
200: GetMeeting;
/**
* Validation Error
*/
422: HTTPValidationError;
};
};
};
"/v1/rooms": {
get: {
req: V1RoomsListData;
res: {
/**
* Successful Response
*/
200: Page_Room_;
/**
* Validation Error
*/
422: HTTPValidationError;
};
};
post: {
req: V1RoomsCreateData;
res: {
/**
* Successful Response
*/
200: Room;
/**
* Validation Error
*/
422: HTTPValidationError;
};
};
};
"/v1/rooms/{room_id}": {
delete: {
req: V1RoomsDeleteData;
res: {
/**
* Successful Response
*/
200: DeletionStatus;
/**
* Validation Error
*/
422: HTTPValidationError;
};
};
};
"/v1/rooms/{room_name}/meeting": {
post: {
req: V1RoomsCreateMeetingData;
res: {
/**
* Successful Response
*/
200: GetMeeting;
/**
* Validation Error
*/
422: HTTPValidationError;
};
};
};
"/v1/transcripts": {
get: {
req: V1TranscriptsListData;

View File

@@ -4,6 +4,7 @@ export const localConfig = {
privacy: true,
browse: true,
sendToZulip: true,
rooms: true,
},
api_url: "http://127.0.0.1:1250",
websocket_url: "ws://127.0.0.1:1250",

View File

@@ -14,8 +14,9 @@ export async function middleware(request: NextRequest) {
) {
// Feature-flag protedted paths
if (
!config.features.browse &&
request.nextUrl.pathname.startsWith("/browse")
(!config.features.browse &&
request.nextUrl.pathname.startsWith("/browse")) ||
(!config.features.rooms && request.nextUrl.pathname.startsWith("/rooms"))
) {
return NextResponse.redirect(request.nextUrl.origin);
}
@@ -27,7 +28,8 @@ export async function middleware(request: NextRequest) {
if (
request.nextUrl.pathname == "/" ||
request.nextUrl.pathname.startsWith("/transcripts") ||
request.nextUrl.pathname.startsWith("/browse")
request.nextUrl.pathname.startsWith("/browse") ||
request.nextUrl.pathname.startsWith("/rooms")
) {
if (!fiefResponse.headers.get("x-middleware-rewrite")) {
fiefResponse.headers.set(