refac: async db
This commit is contained in:
@@ -4,8 +4,9 @@ import time
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import Base, JSONField, get_db, get_db_context
|
||||
from sqlalchemy import select, delete, update, func, case, or_, and_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from open_webui.internal.db import Base, JSONField, get_async_db_context
|
||||
from open_webui.models.groups import Groups
|
||||
from open_webui.models.access_grants import (
|
||||
AccessGrantModel,
|
||||
@@ -25,11 +26,7 @@ from sqlalchemy import (
|
||||
Text,
|
||||
JSON,
|
||||
UniqueConstraint,
|
||||
case,
|
||||
cast,
|
||||
)
|
||||
from sqlalchemy import or_, func, select, and_, text
|
||||
from sqlalchemy.sql import exists
|
||||
|
||||
####################
|
||||
# Channel DB Schema
|
||||
@@ -249,22 +246,22 @@ class ChannelWebhookForm(BaseModel):
|
||||
|
||||
|
||||
class ChannelTable:
|
||||
def _get_access_grants(self, channel_id: str, db: Optional[Session] = None) -> list[AccessGrantModel]:
|
||||
return AccessGrants.get_grants_by_resource('channel', channel_id, db=db)
|
||||
async def _get_access_grants(self, channel_id: str, db: Optional[AsyncSession] = None) -> list[AccessGrantModel]:
|
||||
return await AccessGrants.get_grants_by_resource('channel', channel_id, db=db)
|
||||
|
||||
def _to_channel_model(
|
||||
async def _to_channel_model(
|
||||
self,
|
||||
channel: Channel,
|
||||
access_grants: Optional[list[AccessGrantModel]] = None,
|
||||
db: Optional[Session] = None,
|
||||
db: Optional[AsyncSession] = None,
|
||||
) -> ChannelModel:
|
||||
channel_data = ChannelModel.model_validate(channel).model_dump(exclude={'access_grants'})
|
||||
channel_data['access_grants'] = (
|
||||
access_grants if access_grants is not None else self._get_access_grants(channel_data['id'], db=db)
|
||||
access_grants if access_grants is not None else await self._get_access_grants(channel_data['id'], db=db)
|
||||
)
|
||||
return ChannelModel.model_validate(channel_data)
|
||||
|
||||
def _collect_unique_user_ids(
|
||||
async def _collect_unique_user_ids(
|
||||
self,
|
||||
invited_by: str,
|
||||
user_ids: Optional[list[str]] = None,
|
||||
@@ -281,7 +278,8 @@ class ChannelTable:
|
||||
users.add(invited_by)
|
||||
|
||||
for group_id in group_ids or []:
|
||||
users.update(Groups.get_group_user_ids_by_id(group_id))
|
||||
group_user_ids = await Groups.get_group_user_ids_by_id(group_id)
|
||||
users.update(group_user_ids)
|
||||
|
||||
return users
|
||||
|
||||
@@ -321,10 +319,20 @@ class ChannelTable:
|
||||
|
||||
return memberships
|
||||
|
||||
def insert_new_channel(
|
||||
self, form_data: CreateChannelForm, user_id: str, db: Optional[Session] = None
|
||||
def _has_permission(self, db, query, filter: dict, permission: str = 'read'):
|
||||
return AccessGrants.has_permission_filter(
|
||||
db=db,
|
||||
query=query,
|
||||
DocumentModel=Channel,
|
||||
filter=filter,
|
||||
resource_type='channel',
|
||||
permission=permission,
|
||||
)
|
||||
|
||||
async def insert_new_channel(
|
||||
self, form_data: CreateChannelForm, user_id: str, db: Optional[AsyncSession] = None
|
||||
) -> Optional[ChannelModel]:
|
||||
with get_db_context(db) as db:
|
||||
async with get_async_db_context(db) as db:
|
||||
channel = ChannelModel(
|
||||
**{
|
||||
**form_data.model_dump(exclude={'access_grants'}),
|
||||
@@ -340,7 +348,7 @@ class ChannelTable:
|
||||
new_channel = Channel(**channel.model_dump(exclude={'access_grants'}))
|
||||
|
||||
if form_data.type in ['group', 'dm']:
|
||||
users = self._collect_unique_user_ids(
|
||||
users = await self._collect_unique_user_ids(
|
||||
invited_by=user_id,
|
||||
user_ids=form_data.user_ids,
|
||||
group_ids=form_data.group_ids,
|
||||
@@ -353,17 +361,18 @@ class ChannelTable:
|
||||
|
||||
db.add_all(memberships)
|
||||
db.add(new_channel)
|
||||
db.commit()
|
||||
AccessGrants.set_access_grants('channel', new_channel.id, form_data.access_grants, db=db)
|
||||
return self._to_channel_model(new_channel, db=db)
|
||||
await db.commit()
|
||||
await AccessGrants.set_access_grants('channel', new_channel.id, form_data.access_grants, db=db)
|
||||
return await self._to_channel_model(new_channel, db=db)
|
||||
|
||||
def get_channels(self, db: Optional[Session] = None) -> list[ChannelModel]:
|
||||
with get_db_context(db) as db:
|
||||
channels = db.query(Channel).all()
|
||||
async def get_channels(self, db: Optional[AsyncSession] = None) -> list[ChannelModel]:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(select(Channel))
|
||||
channels = result.scalars().all()
|
||||
channel_ids = [channel.id for channel in channels]
|
||||
grants_map = AccessGrants.get_grants_by_resources('channel', channel_ids, db=db)
|
||||
grants_map = await AccessGrants.get_grants_by_resources('channel', channel_ids, db=db)
|
||||
return [
|
||||
self._to_channel_model(
|
||||
await self._to_channel_model(
|
||||
channel,
|
||||
access_grants=grants_map.get(channel.id, []),
|
||||
db=db,
|
||||
@@ -371,22 +380,12 @@ class ChannelTable:
|
||||
for channel in channels
|
||||
]
|
||||
|
||||
def _has_permission(self, db, query, filter: dict, permission: str = 'read'):
|
||||
return AccessGrants.has_permission_filter(
|
||||
db=db,
|
||||
query=query,
|
||||
DocumentModel=Channel,
|
||||
filter=filter,
|
||||
resource_type='channel',
|
||||
permission=permission,
|
||||
)
|
||||
async def get_channels_by_user_id(self, user_id: str, db: Optional[AsyncSession] = None) -> list[ChannelModel]:
|
||||
async with get_async_db_context(db) as db:
|
||||
user_group_ids = [group.id for group in await Groups.get_groups_by_member_id(user_id, db=db)]
|
||||
|
||||
def get_channels_by_user_id(self, user_id: str, db: Optional[Session] = None) -> list[ChannelModel]:
|
||||
with get_db_context(db) as db:
|
||||
user_group_ids = [group.id for group in Groups.get_groups_by_member_id(user_id, db=db)]
|
||||
|
||||
membership_channels = (
|
||||
db.query(Channel)
|
||||
result = await db.execute(
|
||||
select(Channel)
|
||||
.join(ChannelMember, Channel.id == ChannelMember.channel_id)
|
||||
.filter(
|
||||
Channel.deleted_at.is_(None),
|
||||
@@ -395,10 +394,10 @@ class ChannelTable:
|
||||
ChannelMember.user_id == user_id,
|
||||
ChannelMember.is_active.is_(True),
|
||||
)
|
||||
.all()
|
||||
)
|
||||
membership_channels = result.scalars().all()
|
||||
|
||||
query = db.query(Channel).filter(
|
||||
stmt = select(Channel).filter(
|
||||
Channel.deleted_at.is_(None),
|
||||
Channel.archived_at.is_(None),
|
||||
or_(
|
||||
@@ -407,17 +406,18 @@ class ChannelTable:
|
||||
and_(Channel.type != 'group', Channel.type != 'dm'),
|
||||
),
|
||||
)
|
||||
query = self._has_permission(db, query, {'user_id': user_id, 'group_ids': user_group_ids})
|
||||
stmt = self._has_permission(db, stmt, {'user_id': user_id, 'group_ids': user_group_ids})
|
||||
|
||||
standard_channels = query.all()
|
||||
result = await db.execute(stmt)
|
||||
standard_channels = result.scalars().all()
|
||||
|
||||
all_channels = membership_channels + standard_channels
|
||||
all_channels = list(membership_channels) + list(standard_channels)
|
||||
channel_ids = [c.id for c in all_channels]
|
||||
grants_map = AccessGrants.get_grants_by_resources('channel', channel_ids, db=db)
|
||||
return [self._to_channel_model(c, access_grants=grants_map.get(c.id, []), db=db) for c in all_channels]
|
||||
grants_map = await AccessGrants.get_grants_by_resources('channel', channel_ids, db=db)
|
||||
return [await self._to_channel_model(c, access_grants=grants_map.get(c.id, []), db=db) for c in all_channels]
|
||||
|
||||
def get_dm_channel_by_user_ids(self, user_ids: list[str], db: Optional[Session] = None) -> Optional[ChannelModel]:
|
||||
with get_db_context(db) as db:
|
||||
async def get_dm_channel_by_user_ids(self, user_ids: list[str], db: Optional[AsyncSession] = None) -> Optional[ChannelModel]:
|
||||
async with get_async_db_context(db) as db:
|
||||
# Ensure uniqueness in case a list with duplicates is passed
|
||||
unique_user_ids = list(set(user_ids))
|
||||
|
||||
@@ -429,7 +429,7 @@ class ChannelTable:
|
||||
)
|
||||
|
||||
subquery = (
|
||||
db.query(ChannelMember.channel_id)
|
||||
select(ChannelMember.channel_id)
|
||||
.group_by(ChannelMember.channel_id)
|
||||
# 1. Channel must have exactly len(user_ids) members
|
||||
.having(func.count(ChannelMember.user_id) == len(unique_user_ids))
|
||||
@@ -438,33 +438,34 @@ class ChannelTable:
|
||||
.subquery()
|
||||
)
|
||||
|
||||
channel = (
|
||||
db.query(Channel)
|
||||
result = await db.execute(
|
||||
select(Channel)
|
||||
.filter(
|
||||
Channel.id.in_(subquery),
|
||||
Channel.id.in_(select(subquery.c.channel_id)),
|
||||
Channel.type == 'dm',
|
||||
)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
channel = result.scalars().first()
|
||||
|
||||
return self._to_channel_model(channel, db=db) if channel else None
|
||||
return await self._to_channel_model(channel, db=db) if channel else None
|
||||
|
||||
def add_members_to_channel(
|
||||
async def add_members_to_channel(
|
||||
self,
|
||||
channel_id: str,
|
||||
invited_by: str,
|
||||
user_ids: Optional[list[str]] = None,
|
||||
group_ids: Optional[list[str]] = None,
|
||||
db: Optional[Session] = None,
|
||||
db: Optional[AsyncSession] = None,
|
||||
) -> list[ChannelMemberModel]:
|
||||
with get_db_context(db) as db:
|
||||
async with get_async_db_context(db) as db:
|
||||
# 1. Collect all user_ids including groups + inviter
|
||||
requested_users = self._collect_unique_user_ids(invited_by, user_ids, group_ids)
|
||||
requested_users = await self._collect_unique_user_ids(invited_by, user_ids, group_ids)
|
||||
|
||||
existing_users = {
|
||||
row.user_id
|
||||
for row in db.query(ChannelMember.user_id).filter(ChannelMember.channel_id == channel_id).all()
|
||||
}
|
||||
result = await db.execute(
|
||||
select(ChannelMember.user_id).filter(ChannelMember.channel_id == channel_id)
|
||||
)
|
||||
existing_users = {row[0] for row in result.all()}
|
||||
|
||||
new_user_ids = requested_users - existing_users
|
||||
if not new_user_ids:
|
||||
@@ -473,59 +474,54 @@ class ChannelTable:
|
||||
new_memberships = self._create_membership_models(channel_id, invited_by, new_user_ids)
|
||||
|
||||
db.add_all(new_memberships)
|
||||
db.commit()
|
||||
await db.commit()
|
||||
|
||||
return [ChannelMemberModel.model_validate(membership) for membership in new_memberships]
|
||||
|
||||
def remove_members_from_channel(
|
||||
async def remove_members_from_channel(
|
||||
self,
|
||||
channel_id: str,
|
||||
user_ids: list[str],
|
||||
db: Optional[Session] = None,
|
||||
db: Optional[AsyncSession] = None,
|
||||
) -> int:
|
||||
with get_db_context(db) as db:
|
||||
result = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
delete(ChannelMember).filter(
|
||||
ChannelMember.channel_id == channel_id,
|
||||
ChannelMember.user_id.in_(user_ids),
|
||||
)
|
||||
.delete(synchronize_session=False)
|
||||
)
|
||||
db.commit()
|
||||
return result # number of rows deleted
|
||||
await db.commit()
|
||||
return result.rowcount # number of rows deleted
|
||||
|
||||
def is_user_channel_manager(self, channel_id: str, user_id: str, db: Optional[Session] = None) -> bool:
|
||||
with get_db_context(db) as db:
|
||||
# Check if the user is the creator of the channel
|
||||
# or has a 'manager' role in ChannelMember
|
||||
channel = db.query(Channel).filter(Channel.id == channel_id).first()
|
||||
async def is_user_channel_manager(self, channel_id: str, user_id: str, db: Optional[AsyncSession] = None) -> bool:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(select(Channel).filter(Channel.id == channel_id))
|
||||
channel = result.scalars().first()
|
||||
if channel and channel.user_id == user_id:
|
||||
return True
|
||||
|
||||
membership = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
result = await db.execute(
|
||||
select(ChannelMember).filter(
|
||||
ChannelMember.channel_id == channel_id,
|
||||
ChannelMember.user_id == user_id,
|
||||
ChannelMember.is_active.is_(True),
|
||||
ChannelMember.role == 'manager',
|
||||
)
|
||||
.first()
|
||||
)
|
||||
membership = result.scalars().first()
|
||||
return membership is not None
|
||||
|
||||
def join_channel(self, channel_id: str, user_id: str, db: Optional[Session] = None) -> Optional[ChannelMemberModel]:
|
||||
with get_db_context(db) as db:
|
||||
async def join_channel(self, channel_id: str, user_id: str, db: Optional[AsyncSession] = None) -> Optional[ChannelMemberModel]:
|
||||
async with get_async_db_context(db) as db:
|
||||
# Check if the membership already exists
|
||||
existing_membership = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
result = await db.execute(
|
||||
select(ChannelMember).filter(
|
||||
ChannelMember.channel_id == channel_id,
|
||||
ChannelMember.user_id == user_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
existing_membership = result.scalars().first()
|
||||
if existing_membership:
|
||||
return ChannelMemberModel.model_validate(existing_membership)
|
||||
|
||||
@@ -549,19 +545,18 @@ class ChannelTable:
|
||||
new_membership = ChannelMember(**channel_member.model_dump())
|
||||
|
||||
db.add(new_membership)
|
||||
db.commit()
|
||||
await db.commit()
|
||||
return channel_member
|
||||
|
||||
def leave_channel(self, channel_id: str, user_id: str, db: Optional[Session] = None) -> bool:
|
||||
with get_db_context(db) as db:
|
||||
membership = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
async def leave_channel(self, channel_id: str, user_id: str, db: Optional[AsyncSession] = None) -> bool:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(ChannelMember).filter(
|
||||
ChannelMember.channel_id == channel_id,
|
||||
ChannelMember.user_id == user_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
membership = result.scalars().first()
|
||||
if not membership:
|
||||
return False
|
||||
|
||||
@@ -570,126 +565,127 @@ class ChannelTable:
|
||||
membership.left_at = int(time.time_ns())
|
||||
membership.updated_at = int(time.time_ns())
|
||||
|
||||
db.commit()
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
def get_member_by_channel_and_user_id(
|
||||
self, channel_id: str, user_id: str, db: Optional[Session] = None
|
||||
async def get_member_by_channel_and_user_id(
|
||||
self, channel_id: str, user_id: str, db: Optional[AsyncSession] = None
|
||||
) -> Optional[ChannelMemberModel]:
|
||||
with get_db_context(db) as db:
|
||||
membership = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(ChannelMember).filter(
|
||||
ChannelMember.channel_id == channel_id,
|
||||
ChannelMember.user_id == user_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
membership = result.scalars().first()
|
||||
return ChannelMemberModel.model_validate(membership) if membership else None
|
||||
|
||||
def get_members_by_channel_id(self, channel_id: str, db: Optional[Session] = None) -> list[ChannelMemberModel]:
|
||||
with get_db_context(db) as db:
|
||||
memberships = db.query(ChannelMember).filter(ChannelMember.channel_id == channel_id).all()
|
||||
async def get_members_by_channel_id(self, channel_id: str, db: Optional[AsyncSession] = None) -> list[ChannelMemberModel]:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(ChannelMember).filter(ChannelMember.channel_id == channel_id)
|
||||
)
|
||||
memberships = result.scalars().all()
|
||||
return [ChannelMemberModel.model_validate(membership) for membership in memberships]
|
||||
|
||||
def pin_channel(
|
||||
async def pin_channel(
|
||||
self,
|
||||
channel_id: str,
|
||||
user_id: str,
|
||||
is_pinned: bool,
|
||||
db: Optional[Session] = None,
|
||||
db: Optional[AsyncSession] = None,
|
||||
) -> bool:
|
||||
with get_db_context(db) as db:
|
||||
membership = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(ChannelMember).filter(
|
||||
ChannelMember.channel_id == channel_id,
|
||||
ChannelMember.user_id == user_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
membership = result.scalars().first()
|
||||
if not membership:
|
||||
return False
|
||||
|
||||
membership.is_channel_pinned = is_pinned
|
||||
membership.updated_at = int(time.time_ns())
|
||||
|
||||
db.commit()
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
def update_member_last_read_at(self, channel_id: str, user_id: str, db: Optional[Session] = None) -> bool:
|
||||
with get_db_context(db) as db:
|
||||
membership = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
async def update_member_last_read_at(self, channel_id: str, user_id: str, db: Optional[AsyncSession] = None) -> bool:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(ChannelMember).filter(
|
||||
ChannelMember.channel_id == channel_id,
|
||||
ChannelMember.user_id == user_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
membership = result.scalars().first()
|
||||
if not membership:
|
||||
return False
|
||||
|
||||
membership.last_read_at = int(time.time_ns())
|
||||
membership.updated_at = int(time.time_ns())
|
||||
|
||||
db.commit()
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
def update_member_active_status(
|
||||
async def update_member_active_status(
|
||||
self,
|
||||
channel_id: str,
|
||||
user_id: str,
|
||||
is_active: bool,
|
||||
db: Optional[Session] = None,
|
||||
db: Optional[AsyncSession] = None,
|
||||
) -> bool:
|
||||
with get_db_context(db) as db:
|
||||
membership = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(ChannelMember).filter(
|
||||
ChannelMember.channel_id == channel_id,
|
||||
ChannelMember.user_id == user_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
membership = result.scalars().first()
|
||||
if not membership:
|
||||
return False
|
||||
|
||||
membership.is_active = is_active
|
||||
membership.updated_at = int(time.time_ns())
|
||||
|
||||
db.commit()
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
def is_user_channel_member(self, channel_id: str, user_id: str, db: Optional[Session] = None) -> bool:
|
||||
with get_db_context(db) as db:
|
||||
membership = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
async def is_user_channel_member(self, channel_id: str, user_id: str, db: Optional[AsyncSession] = None) -> bool:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(ChannelMember).filter(
|
||||
ChannelMember.channel_id == channel_id,
|
||||
ChannelMember.user_id == user_id,
|
||||
ChannelMember.is_active.is_(True),
|
||||
)
|
||||
.first()
|
||||
).limit(1)
|
||||
)
|
||||
membership = result.scalars().first()
|
||||
return membership is not None
|
||||
|
||||
def get_channel_by_id(self, id: str, db: Optional[Session] = None) -> Optional[ChannelModel]:
|
||||
async def get_channel_by_id(self, id: str, db: Optional[AsyncSession] = None) -> Optional[ChannelModel]:
|
||||
try:
|
||||
with get_db_context(db) as db:
|
||||
channel = db.query(Channel).filter(Channel.id == id).first()
|
||||
return self._to_channel_model(channel, db=db) if channel else None
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(select(Channel).filter(Channel.id == id))
|
||||
channel = result.scalars().first()
|
||||
return await self._to_channel_model(channel, db=db) if channel else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def get_channels_by_file_id(self, file_id: str, db: Optional[Session] = None) -> list[ChannelModel]:
|
||||
with get_db_context(db) as db:
|
||||
channel_files = db.query(ChannelFile).filter(ChannelFile.file_id == file_id).all()
|
||||
async def get_channels_by_file_id(self, file_id: str, db: Optional[AsyncSession] = None) -> list[ChannelModel]:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(select(ChannelFile).filter(ChannelFile.file_id == file_id))
|
||||
channel_files = result.scalars().all()
|
||||
channel_ids = [cf.channel_id for cf in channel_files]
|
||||
channels = db.query(Channel).filter(Channel.id.in_(channel_ids)).all()
|
||||
grants_map = AccessGrants.get_grants_by_resources('channel', channel_ids, db=db)
|
||||
result = await db.execute(select(Channel).filter(Channel.id.in_(channel_ids)))
|
||||
channels = result.scalars().all()
|
||||
grants_map = await AccessGrants.get_grants_by_resources('channel', channel_ids, db=db)
|
||||
return [
|
||||
self._to_channel_model(
|
||||
await self._to_channel_model(
|
||||
channel,
|
||||
access_grants=grants_map.get(channel.id, []),
|
||||
db=db,
|
||||
@@ -697,123 +693,123 @@ class ChannelTable:
|
||||
for channel in channels
|
||||
]
|
||||
|
||||
def get_channels_by_file_id_and_user_id(
|
||||
self, file_id: str, user_id: str, db: Optional[Session] = None
|
||||
async def get_channels_by_file_id_and_user_id(
|
||||
self, file_id: str, user_id: str, db: Optional[AsyncSession] = None
|
||||
) -> list[ChannelModel]:
|
||||
with get_db_context(db) as db:
|
||||
async with get_async_db_context(db) as db:
|
||||
# 1. Determine which channels have this file
|
||||
channel_file_rows = db.query(ChannelFile).filter(ChannelFile.file_id == file_id).all()
|
||||
result = await db.execute(select(ChannelFile).filter(ChannelFile.file_id == file_id))
|
||||
channel_file_rows = result.scalars().all()
|
||||
channel_ids = [row.channel_id for row in channel_file_rows]
|
||||
|
||||
if not channel_ids:
|
||||
return []
|
||||
|
||||
# 2. Load all channel rows that still exist
|
||||
channels = (
|
||||
db.query(Channel)
|
||||
.filter(
|
||||
result = await db.execute(
|
||||
select(Channel).filter(
|
||||
Channel.id.in_(channel_ids),
|
||||
Channel.deleted_at.is_(None),
|
||||
Channel.archived_at.is_(None),
|
||||
)
|
||||
.all()
|
||||
)
|
||||
channels = result.scalars().all()
|
||||
if not channels:
|
||||
return []
|
||||
|
||||
# Preload user's group membership
|
||||
user_group_ids = [g.id for g in Groups.get_groups_by_member_id(user_id, db=db)]
|
||||
user_group_ids = [g.id for g in await Groups.get_groups_by_member_id(user_id, db=db)]
|
||||
|
||||
allowed_channels = []
|
||||
|
||||
for channel in channels:
|
||||
# --- Case A: group or dm => user must be an active member ---
|
||||
if channel.type in ['group', 'dm']:
|
||||
membership = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
result = await db.execute(
|
||||
select(ChannelMember).filter(
|
||||
ChannelMember.channel_id == channel.id,
|
||||
ChannelMember.user_id == user_id,
|
||||
ChannelMember.is_active.is_(True),
|
||||
)
|
||||
.first()
|
||||
).limit(1)
|
||||
)
|
||||
membership = result.scalars().first()
|
||||
if membership:
|
||||
allowed_channels.append(self._to_channel_model(channel, db=db))
|
||||
allowed_channels.append(await self._to_channel_model(channel, db=db))
|
||||
continue
|
||||
|
||||
# --- Case B: standard channel => rely on ACL permissions ---
|
||||
query = db.query(Channel).filter(Channel.id == channel.id)
|
||||
stmt = select(Channel).filter(Channel.id == channel.id)
|
||||
|
||||
query = self._has_permission(
|
||||
stmt = self._has_permission(
|
||||
db,
|
||||
query,
|
||||
stmt,
|
||||
{'user_id': user_id, 'group_ids': user_group_ids},
|
||||
permission='read',
|
||||
)
|
||||
|
||||
allowed = query.first()
|
||||
result = await db.execute(stmt)
|
||||
allowed = result.scalars().first()
|
||||
if allowed:
|
||||
allowed_channels.append(self._to_channel_model(allowed, db=db))
|
||||
allowed_channels.append(await self._to_channel_model(allowed, db=db))
|
||||
|
||||
return allowed_channels
|
||||
|
||||
def get_channel_by_id_and_user_id(
|
||||
self, id: str, user_id: str, db: Optional[Session] = None
|
||||
async def get_channel_by_id_and_user_id(
|
||||
self, id: str, user_id: str, db: Optional[AsyncSession] = None
|
||||
) -> Optional[ChannelModel]:
|
||||
with get_db_context(db) as db:
|
||||
async with get_async_db_context(db) as db:
|
||||
# Fetch the channel
|
||||
channel: Channel = (
|
||||
db.query(Channel)
|
||||
.filter(
|
||||
result = await db.execute(
|
||||
select(Channel).filter(
|
||||
Channel.id == id,
|
||||
Channel.deleted_at.is_(None),
|
||||
Channel.archived_at.is_(None),
|
||||
)
|
||||
.first()
|
||||
)
|
||||
channel = result.scalars().first()
|
||||
|
||||
if not channel:
|
||||
return None
|
||||
|
||||
# If the channel is a group or dm, read access requires membership (active)
|
||||
if channel.type in ['group', 'dm']:
|
||||
membership = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
result = await db.execute(
|
||||
select(ChannelMember).filter(
|
||||
ChannelMember.channel_id == id,
|
||||
ChannelMember.user_id == user_id,
|
||||
ChannelMember.is_active.is_(True),
|
||||
)
|
||||
.first()
|
||||
).limit(1)
|
||||
)
|
||||
membership = result.scalars().first()
|
||||
if membership:
|
||||
return self._to_channel_model(channel, db=db)
|
||||
return await self._to_channel_model(channel, db=db)
|
||||
else:
|
||||
return None
|
||||
|
||||
# For channels that are NOT group/dm, fall back to ACL-based read access
|
||||
query = db.query(Channel).filter(Channel.id == id)
|
||||
stmt = select(Channel).filter(Channel.id == id)
|
||||
|
||||
# Determine user groups
|
||||
user_group_ids = [group.id for group in Groups.get_groups_by_member_id(user_id, db=db)]
|
||||
user_group_ids = [group.id for group in await Groups.get_groups_by_member_id(user_id, db=db)]
|
||||
|
||||
# Apply ACL rules
|
||||
query = self._has_permission(
|
||||
stmt = self._has_permission(
|
||||
db,
|
||||
query,
|
||||
stmt,
|
||||
{'user_id': user_id, 'group_ids': user_group_ids},
|
||||
permission='read',
|
||||
)
|
||||
|
||||
channel_allowed = query.first()
|
||||
return self._to_channel_model(channel_allowed, db=db) if channel_allowed else None
|
||||
result = await db.execute(stmt)
|
||||
channel_allowed = result.scalars().first()
|
||||
return await self._to_channel_model(channel_allowed, db=db) if channel_allowed else None
|
||||
|
||||
def update_channel_by_id(
|
||||
self, id: str, form_data: ChannelForm, db: Optional[Session] = None
|
||||
async def update_channel_by_id(
|
||||
self, id: str, form_data: ChannelForm, db: Optional[AsyncSession] = None
|
||||
) -> Optional[ChannelModel]:
|
||||
with get_db_context(db) as db:
|
||||
channel = db.query(Channel).filter(Channel.id == id).first()
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(select(Channel).filter(Channel.id == id))
|
||||
channel = result.scalars().first()
|
||||
if not channel:
|
||||
return None
|
||||
|
||||
@@ -825,16 +821,16 @@ class ChannelTable:
|
||||
channel.meta = form_data.meta
|
||||
|
||||
if form_data.access_grants is not None:
|
||||
AccessGrants.set_access_grants('channel', id, form_data.access_grants, db=db)
|
||||
await AccessGrants.set_access_grants('channel', id, form_data.access_grants, db=db)
|
||||
channel.updated_at = int(time.time_ns())
|
||||
|
||||
db.commit()
|
||||
return self._to_channel_model(channel, db=db) if channel else None
|
||||
await db.commit()
|
||||
return await self._to_channel_model(channel, db=db) if channel else None
|
||||
|
||||
def add_file_to_channel_by_id(
|
||||
self, channel_id: str, file_id: str, user_id: str, db: Optional[Session] = None
|
||||
async def add_file_to_channel_by_id(
|
||||
self, channel_id: str, file_id: str, user_id: str, db: Optional[AsyncSession] = None
|
||||
) -> Optional[ChannelFileModel]:
|
||||
with get_db_context(db) as db:
|
||||
async with get_async_db_context(db) as db:
|
||||
channel_file = ChannelFileModel(
|
||||
**{
|
||||
'id': str(uuid.uuid4()),
|
||||
@@ -849,8 +845,8 @@ class ChannelTable:
|
||||
try:
|
||||
result = ChannelFile(**channel_file.model_dump())
|
||||
db.add(result)
|
||||
db.commit()
|
||||
db.refresh(result)
|
||||
await db.commit()
|
||||
await db.refresh(result)
|
||||
if result:
|
||||
return ChannelFileModel.model_validate(result)
|
||||
else:
|
||||
@@ -858,55 +854,58 @@ class ChannelTable:
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def set_file_message_id_in_channel_by_id(
|
||||
async def set_file_message_id_in_channel_by_id(
|
||||
self,
|
||||
channel_id: str,
|
||||
file_id: str,
|
||||
message_id: str,
|
||||
db: Optional[Session] = None,
|
||||
db: Optional[AsyncSession] = None,
|
||||
) -> bool:
|
||||
try:
|
||||
with get_db_context(db) as db:
|
||||
channel_file = db.query(ChannelFile).filter_by(channel_id=channel_id, file_id=file_id).first()
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(ChannelFile).filter_by(channel_id=channel_id, file_id=file_id)
|
||||
)
|
||||
channel_file = result.scalars().first()
|
||||
if not channel_file:
|
||||
return False
|
||||
|
||||
channel_file.message_id = message_id
|
||||
channel_file.updated_at = int(time.time())
|
||||
|
||||
db.commit()
|
||||
await db.commit()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def remove_file_from_channel_by_id(self, channel_id: str, file_id: str, db: Optional[Session] = None) -> bool:
|
||||
async def remove_file_from_channel_by_id(self, channel_id: str, file_id: str, db: Optional[AsyncSession] = None) -> bool:
|
||||
try:
|
||||
with get_db_context(db) as db:
|
||||
db.query(ChannelFile).filter_by(channel_id=channel_id, file_id=file_id).delete()
|
||||
db.commit()
|
||||
async with get_async_db_context(db) as db:
|
||||
await db.execute(delete(ChannelFile).filter_by(channel_id=channel_id, file_id=file_id))
|
||||
await db.commit()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def delete_channel_by_id(self, id: str, db: Optional[Session] = None) -> bool:
|
||||
with get_db_context(db) as db:
|
||||
AccessGrants.revoke_all_access('channel', id, db=db)
|
||||
db.query(Channel).filter(Channel.id == id).delete()
|
||||
db.commit()
|
||||
async def delete_channel_by_id(self, id: str, db: Optional[AsyncSession] = None) -> bool:
|
||||
async with get_async_db_context(db) as db:
|
||||
await AccessGrants.revoke_all_access('channel', id, db=db)
|
||||
await db.execute(delete(Channel).filter(Channel.id == id))
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
####################
|
||||
# Webhook Methods
|
||||
####################
|
||||
|
||||
def insert_webhook(
|
||||
async def insert_webhook(
|
||||
self,
|
||||
channel_id: str,
|
||||
user_id: str,
|
||||
form_data: ChannelWebhookForm,
|
||||
db: Optional[Session] = None,
|
||||
db: Optional[AsyncSession] = None,
|
||||
) -> Optional[ChannelWebhookModel]:
|
||||
with get_db_context(db) as db:
|
||||
async with get_async_db_context(db) as db:
|
||||
webhook = ChannelWebhookModel(
|
||||
id=str(uuid.uuid4()),
|
||||
channel_id=channel_id,
|
||||
@@ -919,63 +918,66 @@ class ChannelTable:
|
||||
updated_at=int(time.time_ns()),
|
||||
)
|
||||
db.add(ChannelWebhook(**webhook.model_dump()))
|
||||
db.commit()
|
||||
await db.commit()
|
||||
return webhook
|
||||
|
||||
def get_webhooks_by_channel_id(self, channel_id: str, db: Optional[Session] = None) -> list[ChannelWebhookModel]:
|
||||
with get_db_context(db) as db:
|
||||
webhooks = db.query(ChannelWebhook).filter(ChannelWebhook.channel_id == channel_id).all()
|
||||
async def get_webhooks_by_channel_id(self, channel_id: str, db: Optional[AsyncSession] = None) -> list[ChannelWebhookModel]:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(select(ChannelWebhook).filter(ChannelWebhook.channel_id == channel_id))
|
||||
webhooks = result.scalars().all()
|
||||
return [ChannelWebhookModel.model_validate(w) for w in webhooks]
|
||||
|
||||
def get_webhook_by_id(self, webhook_id: str, db: Optional[Session] = None) -> Optional[ChannelWebhookModel]:
|
||||
with get_db_context(db) as db:
|
||||
webhook = db.query(ChannelWebhook).filter(ChannelWebhook.id == webhook_id).first()
|
||||
async def get_webhook_by_id(self, webhook_id: str, db: Optional[AsyncSession] = None) -> Optional[ChannelWebhookModel]:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(select(ChannelWebhook).filter(ChannelWebhook.id == webhook_id))
|
||||
webhook = result.scalars().first()
|
||||
return ChannelWebhookModel.model_validate(webhook) if webhook else None
|
||||
|
||||
def get_webhook_by_id_and_token(
|
||||
self, webhook_id: str, token: str, db: Optional[Session] = None
|
||||
async def get_webhook_by_id_and_token(
|
||||
self, webhook_id: str, token: str, db: Optional[AsyncSession] = None
|
||||
) -> Optional[ChannelWebhookModel]:
|
||||
with get_db_context(db) as db:
|
||||
webhook = (
|
||||
db.query(ChannelWebhook)
|
||||
.filter(
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(ChannelWebhook).filter(
|
||||
ChannelWebhook.id == webhook_id,
|
||||
ChannelWebhook.token == token,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
webhook = result.scalars().first()
|
||||
return ChannelWebhookModel.model_validate(webhook) if webhook else None
|
||||
|
||||
def update_webhook_by_id(
|
||||
async def update_webhook_by_id(
|
||||
self,
|
||||
webhook_id: str,
|
||||
form_data: ChannelWebhookForm,
|
||||
db: Optional[Session] = None,
|
||||
db: Optional[AsyncSession] = None,
|
||||
) -> Optional[ChannelWebhookModel]:
|
||||
with get_db_context(db) as db:
|
||||
webhook = db.query(ChannelWebhook).filter(ChannelWebhook.id == webhook_id).first()
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(select(ChannelWebhook).filter(ChannelWebhook.id == webhook_id))
|
||||
webhook = result.scalars().first()
|
||||
if not webhook:
|
||||
return None
|
||||
webhook.name = form_data.name
|
||||
webhook.profile_image_url = form_data.profile_image_url
|
||||
webhook.updated_at = int(time.time_ns())
|
||||
db.commit()
|
||||
await db.commit()
|
||||
return ChannelWebhookModel.model_validate(webhook)
|
||||
|
||||
def update_webhook_last_used_at(self, webhook_id: str, db: Optional[Session] = None) -> bool:
|
||||
with get_db_context(db) as db:
|
||||
webhook = db.query(ChannelWebhook).filter(ChannelWebhook.id == webhook_id).first()
|
||||
async def update_webhook_last_used_at(self, webhook_id: str, db: Optional[AsyncSession] = None) -> bool:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(select(ChannelWebhook).filter(ChannelWebhook.id == webhook_id))
|
||||
webhook = result.scalars().first()
|
||||
if not webhook:
|
||||
return False
|
||||
webhook.last_used_at = int(time.time_ns())
|
||||
db.commit()
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
def delete_webhook_by_id(self, webhook_id: str, db: Optional[Session] = None) -> bool:
|
||||
with get_db_context(db) as db:
|
||||
result = db.query(ChannelWebhook).filter(ChannelWebhook.id == webhook_id).delete()
|
||||
db.commit()
|
||||
return result > 0
|
||||
async def delete_webhook_by_id(self, webhook_id: str, db: Optional[AsyncSession] = None) -> bool:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(delete(ChannelWebhook).filter(ChannelWebhook.id == webhook_id))
|
||||
await db.commit()
|
||||
return result.rowcount > 0
|
||||
|
||||
|
||||
Channels = ChannelTable()
|
||||
|
||||
Reference in New Issue
Block a user