Phase 1: Communications Module - Complete
Backend infrastructure: - PostgreSQL models (users, channels, messages, DMs, files, artifacts) - JWT authentication with password hashing - Auth API (register, login, logout, get user) - Channels API (create, list, join, leave) - Messages API with @grimlock mention detection - AI responds automatically when @mentioned - Background task processing for AI responses Database: - SQLAlchemy ORM models - Alembic ready for migrations - PostgreSQL + Redis in docker-compose Features working: - User registration and login - Create/join public channels - Send messages in channels - @grimlock triggers AI response with channel context - Real-time ready (WebSocket next) Next: WebSocket for real-time updates, frontend interface
This commit is contained in:
188
backend/api/channels.py
Normal file
188
backend/api/channels.py
Normal file
@@ -0,0 +1,188 @@
|
||||
"""
|
||||
Channels API - Channel management and operations
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
|
||||
from core.database import get_db
|
||||
from core.models import Channel, User, ChannelType
|
||||
from api.auth import get_current_user
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# Pydantic models
|
||||
class ChannelCreate(BaseModel):
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
type: ChannelType = ChannelType.PUBLIC
|
||||
|
||||
class ChannelResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
description: Optional[str]
|
||||
type: ChannelType
|
||||
member_count: int
|
||||
created_at: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class ChannelListResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
description: Optional[str]
|
||||
type: ChannelType
|
||||
unread_count: int = 0 # TODO: Implement actual unread counting
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
@router.post("/", response_model=ChannelResponse)
|
||||
async def create_channel(
|
||||
channel_data: ChannelCreate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Create a new channel"""
|
||||
|
||||
# Check if channel name already exists
|
||||
existing = db.query(Channel).filter(Channel.name == channel_data.name).first()
|
||||
if existing:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Channel name already exists"
|
||||
)
|
||||
|
||||
# Create channel
|
||||
channel = Channel(
|
||||
name=channel_data.name,
|
||||
description=channel_data.description,
|
||||
type=channel_data.type,
|
||||
created_by=current_user.id
|
||||
)
|
||||
|
||||
db.add(channel)
|
||||
db.commit()
|
||||
db.refresh(channel)
|
||||
|
||||
# Add creator as member
|
||||
channel.members.append(current_user)
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
**channel.__dict__,
|
||||
"member_count": len(channel.members),
|
||||
"created_at": channel.created_at.isoformat()
|
||||
}
|
||||
|
||||
@router.get("/", response_model=List[ChannelListResponse])
|
||||
async def list_channels(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""List all channels user is a member of"""
|
||||
|
||||
channels = current_user.channels
|
||||
|
||||
return [
|
||||
{
|
||||
"id": c.id,
|
||||
"name": c.name,
|
||||
"description": c.description,
|
||||
"type": c.type,
|
||||
"unread_count": 0 # TODO: Implement
|
||||
}
|
||||
for c in channels
|
||||
]
|
||||
|
||||
@router.get("/{channel_id}", response_model=ChannelResponse)
|
||||
async def get_channel(
|
||||
channel_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get channel details"""
|
||||
|
||||
channel = db.query(Channel).filter(Channel.id == channel_id).first()
|
||||
if not channel:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Channel not found"
|
||||
)
|
||||
|
||||
# Check if user is member (for private channels)
|
||||
if channel.type == ChannelType.PRIVATE:
|
||||
if current_user not in channel.members:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not a member of this channel"
|
||||
)
|
||||
|
||||
return {
|
||||
**channel.__dict__,
|
||||
"member_count": len(channel.members),
|
||||
"created_at": channel.created_at.isoformat()
|
||||
}
|
||||
|
||||
@router.post("/{channel_id}/join")
|
||||
async def join_channel(
|
||||
channel_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Join a channel"""
|
||||
|
||||
channel = db.query(Channel).filter(Channel.id == channel_id).first()
|
||||
if not channel:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Channel not found"
|
||||
)
|
||||
|
||||
# Can't join private channels (must be invited)
|
||||
if channel.type == ChannelType.PRIVATE:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Cannot join private channel"
|
||||
)
|
||||
|
||||
# Check if already a member
|
||||
if current_user in channel.members:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Already a member"
|
||||
)
|
||||
|
||||
channel.members.append(current_user)
|
||||
db.commit()
|
||||
|
||||
return {"message": f"Joined channel #{channel.name}"}
|
||||
|
||||
@router.post("/{channel_id}/leave")
|
||||
async def leave_channel(
|
||||
channel_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Leave a channel"""
|
||||
|
||||
channel = db.query(Channel).filter(Channel.id == channel_id).first()
|
||||
if not channel:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Channel not found"
|
||||
)
|
||||
|
||||
if current_user not in channel.members:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Not a member of this channel"
|
||||
)
|
||||
|
||||
channel.members.remove(current_user)
|
||||
db.commit()
|
||||
|
||||
return {"message": f"Left channel #{channel.name}"}
|
||||
Reference in New Issue
Block a user