mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 20:29:06 +00:00
Make sure room names are unique
This commit is contained in:
@@ -1,11 +1,9 @@
|
|||||||
from logging.config import fileConfig
|
from logging.config import fileConfig
|
||||||
from reflector.settings import settings
|
|
||||||
from reflector.db import metadata
|
|
||||||
|
|
||||||
from sqlalchemy import engine_from_config
|
|
||||||
from sqlalchemy import pool
|
|
||||||
|
|
||||||
from alembic import context
|
from alembic import context
|
||||||
|
from reflector.db import metadata
|
||||||
|
from reflector.settings import settings
|
||||||
|
from sqlalchemy import engine_from_config, pool
|
||||||
|
|
||||||
# this is the Alembic Config object, which provides
|
# this is the Alembic Config object, which provides
|
||||||
# access to the values within the .ini file in use.
|
# access to the values within the .ini file in use.
|
||||||
@@ -45,6 +43,7 @@ def run_migrations_offline() -> None:
|
|||||||
target_metadata=target_metadata,
|
target_metadata=target_metadata,
|
||||||
literal_binds=True,
|
literal_binds=True,
|
||||||
dialect_opts={"paramstyle": "named"},
|
dialect_opts={"paramstyle": "named"},
|
||||||
|
render_as_batch=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
with context.begin_transaction():
|
with context.begin_transaction():
|
||||||
@@ -67,7 +66,9 @@ def run_migrations_online() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
with connectable.connect() as connection:
|
with connectable.connect() as connection:
|
||||||
context.configure(connection=connection, target_metadata=target_metadata)
|
context.configure(
|
||||||
|
connection=connection, target_metadata=target_metadata, render_as_batch=True
|
||||||
|
)
|
||||||
|
|
||||||
with context.begin_transaction():
|
with context.begin_transaction():
|
||||||
context.run_migrations()
|
context.run_migrations()
|
||||||
|
|||||||
34
server/migrations/versions/0925da921477_unique_room_names.py
Normal file
34
server/migrations/versions/0925da921477_unique_room_names.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
"""Unique room names
|
||||||
|
|
||||||
|
Revision ID: 0925da921477
|
||||||
|
Revises: 764ce6db4388
|
||||||
|
Create Date: 2024-09-24 16:12:56.944133
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "0925da921477"
|
||||||
|
down_revision: Union[str, None] = "764ce6db4388"
|
||||||
|
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! ###
|
||||||
|
with op.batch_alter_table("room", schema=None) as batch_op:
|
||||||
|
batch_op.create_unique_constraint("uq_room_name", ["name"])
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table("room", schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint("uq_room_name", type_="unique")
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from sqlite3 import IntegrityError
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
@@ -12,7 +13,7 @@ rooms = sqlalchemy.Table(
|
|||||||
"room",
|
"room",
|
||||||
metadata,
|
metadata,
|
||||||
sqlalchemy.Column("id", sqlalchemy.String, primary_key=True),
|
sqlalchemy.Column("id", sqlalchemy.String, primary_key=True),
|
||||||
sqlalchemy.Column("name", sqlalchemy.String, nullable=False),
|
sqlalchemy.Column("name", sqlalchemy.String, nullable=False, unique=True),
|
||||||
sqlalchemy.Column("user_id", sqlalchemy.String, nullable=False),
|
sqlalchemy.Column("user_id", sqlalchemy.String, nullable=False),
|
||||||
sqlalchemy.Column("created_at", sqlalchemy.DateTime, nullable=False),
|
sqlalchemy.Column("created_at", sqlalchemy.DateTime, nullable=False),
|
||||||
sqlalchemy.Column(
|
sqlalchemy.Column(
|
||||||
@@ -113,7 +114,10 @@ class RoomController:
|
|||||||
recording_trigger=recording_trigger,
|
recording_trigger=recording_trigger,
|
||||||
)
|
)
|
||||||
query = rooms.insert().values(**room.model_dump())
|
query = rooms.insert().values(**room.model_dump())
|
||||||
|
try:
|
||||||
await database.execute(query)
|
await database.execute(query)
|
||||||
|
except IntegrityError:
|
||||||
|
raise HTTPException(status_code=400, detail="Room name is not unique")
|
||||||
return room
|
return room
|
||||||
|
|
||||||
async def update(self, room: Room, values: dict, mutate=True):
|
async def update(self, room: Room, values: dict, mutate=True):
|
||||||
@@ -121,7 +125,11 @@ class RoomController:
|
|||||||
Update a room fields with key/values in values
|
Update a room fields with key/values in values
|
||||||
"""
|
"""
|
||||||
query = rooms.update().where(rooms.c.id == room.id).values(**values)
|
query = rooms.update().where(rooms.c.id == room.id).values(**values)
|
||||||
|
try:
|
||||||
await database.execute(query)
|
await database.execute(query)
|
||||||
|
except IntegrityError:
|
||||||
|
raise HTTPException(status_code=400, detail="Room name is not unique")
|
||||||
|
|
||||||
if mutate:
|
if mutate:
|
||||||
for key, value in values.items():
|
for key, value in values.items():
|
||||||
setattr(room, key, value)
|
setattr(room, key, value)
|
||||||
|
|||||||
@@ -36,11 +36,7 @@ import { FaEllipsisVertical, FaTrash, FaPencil, FaLink } from "react-icons/fa6";
|
|||||||
import useApi from "../../lib/useApi";
|
import useApi from "../../lib/useApi";
|
||||||
import useRoomList from "./useRoomList";
|
import useRoomList from "./useRoomList";
|
||||||
import { Select, Options, OptionBase } from "chakra-react-select";
|
import { Select, Options, OptionBase } from "chakra-react-select";
|
||||||
|
import { ApiError } from "../../api";
|
||||||
interface Stream {
|
|
||||||
stream_id: number;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SelectOption extends OptionBase {
|
interface SelectOption extends OptionBase {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -81,8 +77,7 @@ export default function RoomsList() {
|
|||||||
const { loading, response, refetch } = useRoomList(page);
|
const { loading, response, refetch } = useRoomList(page);
|
||||||
const [streams, setStreams] = useState<Stream[]>([]);
|
const [streams, setStreams] = useState<Stream[]>([]);
|
||||||
const [topics, setTopics] = useState<Topic[]>([]);
|
const [topics, setTopics] = useState<Topic[]>([]);
|
||||||
|
const [nameError, setNameError] = useState("");
|
||||||
const [error, setError] = useState("");
|
|
||||||
const [linkCopied, setLinkCopied] = useState("");
|
const [linkCopied, setLinkCopied] = useState("");
|
||||||
interface Stream {
|
interface Stream {
|
||||||
stream_id: number;
|
stream_id: number;
|
||||||
@@ -151,7 +146,7 @@ export default function RoomsList() {
|
|||||||
const handleSaveRoom = async () => {
|
const handleSaveRoom = async () => {
|
||||||
try {
|
try {
|
||||||
if (RESERVED_PATHS.includes(room.name)) {
|
if (RESERVED_PATHS.includes(room.name)) {
|
||||||
setError("This room name is reserved. Please choose another name.");
|
setNameError("This room name is reserved. Please choose another name.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,12 +175,24 @@ export default function RoomsList() {
|
|||||||
setRoom(roomInitialState);
|
setRoom(roomInitialState);
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
setEditRoomId("");
|
setEditRoomId("");
|
||||||
setError("");
|
setNameError("");
|
||||||
refetch();
|
refetch();
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
onClose();
|
onClose();
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
const apiError = err as ApiError;
|
||||||
|
if (
|
||||||
|
apiError.status === 400 &&
|
||||||
|
(apiError.body as any).detail == "Room name is not unique"
|
||||||
|
) {
|
||||||
|
setNameError(
|
||||||
|
"This room name is already taken. Please choose a different name.",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setNameError("An error occurred. Please try again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditRoom = (roomId, roomData) => {
|
const handleEditRoom = (roomId, roomData) => {
|
||||||
@@ -201,6 +208,7 @@ export default function RoomsList() {
|
|||||||
});
|
});
|
||||||
setEditRoomId(roomId);
|
setEditRoomId(roomId);
|
||||||
setIsEditing(true);
|
setIsEditing(true);
|
||||||
|
setNameError("");
|
||||||
onOpen();
|
onOpen();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -222,7 +230,7 @@ export default function RoomsList() {
|
|||||||
.replace(/[^a-zA-Z0-9\s-]/g, "")
|
.replace(/[^a-zA-Z0-9\s-]/g, "")
|
||||||
.replace(/\s+/g, "-")
|
.replace(/\s+/g, "-")
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
setError("");
|
setNameError("");
|
||||||
}
|
}
|
||||||
setRoom({
|
setRoom({
|
||||||
...room,
|
...room,
|
||||||
@@ -254,7 +262,7 @@ export default function RoomsList() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
setRoom(roomInitialState);
|
setRoom(roomInitialState);
|
||||||
setError("");
|
setNameError("");
|
||||||
onOpen();
|
onOpen();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -277,7 +285,7 @@ export default function RoomsList() {
|
|||||||
<FormHelperText>
|
<FormHelperText>
|
||||||
No spaces or special characters allowed
|
No spaces or special characters allowed
|
||||||
</FormHelperText>
|
</FormHelperText>
|
||||||
{error && <Text color="red.500">{error}</Text>}
|
{nameError && <Text color="red.500">{nameError}</Text>}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl mt={4}>
|
<FormControl mt={4}>
|
||||||
|
|||||||
Reference in New Issue
Block a user