diff --git a/src/tracker/api/attachments.py b/src/tracker/api/attachments.py index 19da55a..daec357 100644 --- a/src/tracker/api/attachments.py +++ b/src/tracker/api/attachments.py @@ -9,9 +9,9 @@ from fastapi.responses import FileResponse from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -from tracker.database import get_db -from tracker.models import Attachment, Message, Member -from tracker.api.schemas import UploadOut +from ..database import get_db +from ..models import Attachment, Message, Member +from .schemas import UploadOut router = APIRouter(tags=["attachments"]) diff --git a/src/tracker/api/auth.py b/src/tracker/api/auth.py index affc17e..036fa60 100644 --- a/src/tracker/api/auth.py +++ b/src/tracker/api/auth.py @@ -10,9 +10,9 @@ from pydantic import BaseModel from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -from tracker.config import settings -from tracker.database import get_db -from tracker.models import Member +from ..config import settings +from ..database import get_db +from ..models import Member router = APIRouter(tags=["auth"]) diff --git a/src/tracker/api/converters.py b/src/tracker/api/converters.py index a422ae3..96cece3 100644 --- a/src/tracker/api/converters.py +++ b/src/tracker/api/converters.py @@ -1,8 +1,8 @@ """ORM → Pydantic converters. Single place for all model-to-schema transformations.""" -from tracker.models import Attachment, Member, Message, ProjectFile, Step, Task -from tracker.enums import MemberType -from tracker.api.schemas import ( +from ..models import Attachment, Member, Message, ProjectFile, Step, Task +from ..enums import MemberType +from .schemas import ( AgentConfigOut, AttachmentOut, MemberBrief, diff --git a/src/tracker/api/members.py b/src/tracker/api/members.py index 4062469..2b2f93d 100644 --- a/src/tracker/api/members.py +++ b/src/tracker/api/members.py @@ -9,11 +9,11 @@ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload -from tracker.database import get_db -from tracker.enums import AuthMethod, ListenMode, MemberRole, MemberStatus, MemberType -from tracker.models import Member, AgentConfig -from tracker.api.schemas import MemberOut, AgentConfigOut, OkResponse -from tracker.api.converters import member_out +from ..database import get_db +from ..enums import AuthMethod, ListenMode, MemberRole, MemberStatus, MemberType +from ..models import Member, AgentConfig +from .schemas import MemberOut, AgentConfigOut, OkResponse +from .converters import member_out router = APIRouter(tags=["members"]) diff --git a/src/tracker/api/messages.py b/src/tracker/api/messages.py index 46c1eb6..5ff10de 100644 --- a/src/tracker/api/messages.py +++ b/src/tracker/api/messages.py @@ -10,11 +10,11 @@ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload -from tracker.database import get_db -from tracker.enums import AuthorType, ChatKind -from tracker.models import Message, Chat, Attachment -from tracker.api.schemas import MessageOut -from tracker.api.converters import message_out +from ..database import get_db +from ..enums import AuthorType, ChatKind +from ..models import Message, Chat, Attachment +from .schemas import MessageOut +from .converters import message_out router = APIRouter(tags=["messages"]) @@ -127,7 +127,7 @@ async def create_message(req: MessageCreate, request: Request, db: AsyncSession msg_data = message_out(msg).model_dump() # Broadcast via WebSocket - from tracker.ws.manager import manager + from ..ws.manager import manager project_id = None if req.chat_id: diff --git a/src/tracker/api/project_files.py b/src/tracker/api/project_files.py index 1ece69a..c036079 100644 --- a/src/tracker/api/project_files.py +++ b/src/tracker/api/project_files.py @@ -12,9 +12,9 @@ from sqlalchemy import select, or_ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload -from tracker.database import get_db -from tracker.models import ProjectFile, Project, Member -from tracker.api.schemas import ProjectFileOut, MemberBrief +from ..database import get_db +from ..models import ProjectFile, Project, Member +from .schemas import ProjectFileOut, MemberBrief router = APIRouter(tags=["project-files"]) diff --git a/src/tracker/api/projects.py b/src/tracker/api/projects.py index 54f1c4e..5e35b1b 100644 --- a/src/tracker/api/projects.py +++ b/src/tracker/api/projects.py @@ -6,10 +6,10 @@ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload -from tracker.database import get_db -from tracker.enums import ChatKind, MemberRole, ProjectStatus -from tracker.models import Project, Chat, Member, ProjectMember -from tracker.api.schemas import ProjectOut +from ..database import get_db +from ..enums import ChatKind, MemberRole, ProjectStatus +from ..models import Project, Chat, Member, ProjectMember +from .schemas import ProjectOut router = APIRouter(tags=["projects"]) diff --git a/src/tracker/api/steps.py b/src/tracker/api/steps.py index c1db9c9..3a1ab14 100644 --- a/src/tracker/api/steps.py +++ b/src/tracker/api/steps.py @@ -7,11 +7,11 @@ from pydantic import BaseModel from sqlalchemy import select, func from sqlalchemy.ext.asyncio import AsyncSession -from tracker.database import get_db -from tracker.models import Step, Task -from tracker.api.schemas import StepOut -from tracker.api.schemas import StepOut -from tracker.api.converters import step_out +from ..database import get_db +from ..models import Step, Task +from .schemas import StepOut +from .schemas import StepOut +from .converters import step_out router = APIRouter(tags=["steps"]) diff --git a/src/tracker/api/tasks.py b/src/tracker/api/tasks.py index 53e76bb..9d5ea8b 100644 --- a/src/tracker/api/tasks.py +++ b/src/tracker/api/tasks.py @@ -10,14 +10,14 @@ from sqlalchemy import select, update from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload, joinedload -from tracker.database import get_db -from tracker.enums import ( +from ..database import get_db +from ..enums import ( AuthorType, ChatKind, TaskActionType, TaskPriority, TaskStatus, TaskType, WSEventType, ) -from tracker.models import Task, Step, Project, Member, Message, Chat, TaskAction -from tracker.api.auth import get_current_member -from tracker.api.schemas import TaskOut, MessageOut -from tracker.api.converters import task_out, message_out, member_brief +from ..models import Task, Step, Project, Member, Message, Chat, TaskAction +from .auth import get_current_member +from .schemas import TaskOut, MessageOut +from .converters import task_out, message_out, member_brief router = APIRouter(tags=["tasks"]) @@ -97,7 +97,7 @@ async def _system_message( ): """Create system messages: one in project chat + one in task comments. Uses MessageOut schema for WS broadcasts.""" - from tracker.ws.manager import manager + from ..ws.manager import manager prefix = project_slug[:2].upper() if project_slug else "XX" key = f"{prefix}-{task.number}" @@ -262,7 +262,7 @@ async def create_task( task_full = await _get_task(str(task.id), db) # Broadcast using TaskOut schema - from tracker.ws.manager import manager + from ..ws.manager import manager task_schema = task_out(task_full, project.slug) key = f"{project.slug[:2].upper()}-{task.number}" await manager.broadcast_task_event(str(project.id), WSEventType.TASK_CREATED, task_schema.model_dump()) @@ -372,7 +372,7 @@ async def update_task( await db.refresh(task) # Broadcast simplified update - from tracker.ws.manager import manager + from ..ws.manager import manager task_full = await _get_task(task_id, db) task_schema = task_out(task_full, slug) await manager.broadcast_task_event(str(task.project_id), WSEventType.TASK_UPDATED, task_schema.model_dump()) @@ -392,7 +392,7 @@ async def delete_task( await db.commit() await db.delete(task) await db.commit() - from tracker.ws.manager import manager + from ..ws.manager import manager await manager.broadcast_task_event(project_id, WSEventType.TASK_DELETED, { "id": task_id, "project_id": project_id, }) @@ -433,7 +433,7 @@ async def take_task( ) await db.commit() - from tracker.ws.manager import manager + from ..ws.manager import manager task_full = await _get_task(task_id, db) task_schema = task_out(task_full, proj_slug) await manager.broadcast_task_event(str(task.project_id), WSEventType.TASK_ASSIGNED, task_schema.model_dump()) @@ -469,7 +469,7 @@ async def reject_task( db.add(comment) await db.commit() - from tracker.ws.manager import manager + from ..ws.manager import manager task_full = await _get_task(task_id, db) proj_slug = task.project.slug if task.project else "" task_schema = task_out(task_full, proj_slug) @@ -507,7 +507,7 @@ async def assign_task( ) await db.commit() - from tracker.ws.manager import manager + from ..ws.manager import manager task_full = await _get_task(str(task.id), db) task_schema = task_out(task_full, proj_slug) await manager.broadcast_task_event(str(task.project_id), WSEventType.TASK_ASSIGNED, task_schema.model_dump()) diff --git a/src/tracker/app.py b/src/tracker/app.py index ef001a0..9a93e44 100644 --- a/src/tracker/app.py +++ b/src/tracker/app.py @@ -11,9 +11,9 @@ from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse -from tracker.config import settings -from tracker.database import engine, async_session -from tracker.models import Base, Member +from .config import settings +from .database import engine, async_session +from .models import Base, Member from sqlalchemy import select, update @@ -26,7 +26,7 @@ logger = logging.getLogger("tracker") async def heartbeat_monitor(): """Monitor heartbeat timeout — set status=offline after 90 seconds.""" - from tracker.ws.manager import manager + from .ws.manager import manager from datetime import datetime, timezone, timedelta while True: @@ -69,7 +69,7 @@ async def lifespan(app: FastAPI): result = await session.execute(select(Member).limit(1)) if not result.scalar_one_or_none(): logger.info("Empty DB detected in dev mode — running seed...") - from tracker.init_db import seed_dev_data + from .init_db import seed_dev_data await seed_dev_data(session) logger.info("Dev seed complete.") @@ -120,7 +120,7 @@ async def auth_middleware(request: Request, call_next): member = result.scalar_one_or_none() if not member: # Try JWT - from tracker.api.auth import decode_jwt + from .api.auth import decode_jwt try: payload = decode_jwt(token) result = await db.execute(select(Member).where(Member.id == payload["sub"])) @@ -167,8 +167,8 @@ app.add_middleware( ) # Routers -from tracker.api import auth, members, projects, tasks, messages, steps, attachments, project_files # noqa: E402 -from tracker.ws.handler import router as ws_router # noqa: E402 +from .api import auth, members, projects, tasks, messages, steps, attachments, project_files # noqa: E402 +from .ws.handler import router as ws_router # noqa: E402 app.include_router(auth.router, prefix="/api/v1") app.include_router(members.router, prefix="/api/v1") diff --git a/src/tracker/database.py b/src/tracker/database.py index 68f2c39..a7217e8 100644 --- a/src/tracker/database.py +++ b/src/tracker/database.py @@ -2,7 +2,7 @@ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine -from tracker.config import settings +from .config import settings engine = create_async_engine(settings.database_url, echo=(settings.env == "dev")) async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) diff --git a/src/tracker/init_db.py b/src/tracker/init_db.py index ceea3db..7bfc966 100644 --- a/src/tracker/init_db.py +++ b/src/tracker/init_db.py @@ -6,8 +6,8 @@ import logging from sqlalchemy.ext.asyncio import AsyncSession -from tracker.enums import AuthMethod, ChatKind, MemberRole, MemberStatus, MemberType, ProjectStatus -from tracker.models import Base, Member, Chat, Project, ProjectMember, AgentConfig +from .enums import AuthMethod, ChatKind, MemberRole, MemberStatus, MemberType, ProjectStatus +from .models import Base, Member, Chat, Project, ProjectMember, AgentConfig logger = logging.getLogger("tracker.init_db") @@ -71,7 +71,7 @@ async def seed_dev_data(session: AsyncSession): async def reset_db(): """Drop all tables, recreate, and seed. For dev use only.""" - from tracker.database import engine, async_session + from .database import engine, async_session async with engine.begin() as conn: await conn.run_sync(Base.metadata.drop_all) diff --git a/src/tracker/models/__init__.py b/src/tracker/models/__init__.py index 5e4ea39..9c53287 100644 --- a/src/tracker/models/__init__.py +++ b/src/tracker/models/__init__.py @@ -1,11 +1,11 @@ """Models package — import all models for metadata discovery.""" -from tracker.models.base import Base -from tracker.models.member import AgentConfig, Member -from tracker.models.project import Project, ProjectMember -from tracker.models.task import Step, Task, TaskAction -from tracker.models.chat import Attachment, Chat, Message -from tracker.models.project_file import ProjectFile +from .base import Base +from .member import AgentConfig, Member +from .project import Project, ProjectMember +from .task import Step, Task, TaskAction +from .chat import Attachment, Chat, Message +from .project_file import ProjectFile __all__ = [ "Base", diff --git a/src/tracker/models/chat.py b/src/tracker/models/chat.py index df9c693..d29efbf 100644 --- a/src/tracker/models/chat.py +++ b/src/tracker/models/chat.py @@ -7,12 +7,12 @@ from sqlalchemy import ForeignKey, Integer, String, Text from sqlalchemy.dialects.postgresql import ARRAY, UUID from sqlalchemy.orm import Mapped, mapped_column, relationship -from tracker.enums import ChatKind -from tracker.models.base import Base +from ..enums import ChatKind +from .base import Base if TYPE_CHECKING: - from tracker.models.member import Member - from tracker.models.project import Project + from .member import Member + from .project import Project class Chat(Base): diff --git a/src/tracker/models/member.py b/src/tracker/models/member.py index c1e2a67..73cde10 100644 --- a/src/tracker/models/member.py +++ b/src/tracker/models/member.py @@ -7,11 +7,11 @@ from sqlalchemy import ForeignKey, String, Text from sqlalchemy.dialects.postgresql import ARRAY, UUID from sqlalchemy.orm import Mapped, mapped_column, relationship -from tracker.enums import AuthMethod, ListenMode, MemberRole, MemberStatus, MemberType -from tracker.models.base import Base +from ..enums import AuthMethod, ListenMode, MemberRole, MemberStatus, MemberType +from .base import Base if TYPE_CHECKING: - from tracker.models.project import ProjectMember + from .project import ProjectMember class Member(Base): diff --git a/src/tracker/models/project.py b/src/tracker/models/project.py index bb42f37..6893e7f 100644 --- a/src/tracker/models/project.py +++ b/src/tracker/models/project.py @@ -7,13 +7,13 @@ from sqlalchemy import ForeignKey, Integer, String, Text, UniqueConstraint from sqlalchemy.dialects.postgresql import ARRAY, UUID from sqlalchemy.orm import Mapped, mapped_column, relationship -from tracker.enums import MemberRole, ProjectStatus -from tracker.models.base import Base +from ..enums import MemberRole, ProjectStatus +from .base import Base if TYPE_CHECKING: - from tracker.models.task import Task - from tracker.models.chat import Chat - from tracker.models.member import Member + from .task import Task + from .chat import Chat + from .member import Member class Project(Base): diff --git a/src/tracker/models/project_file.py b/src/tracker/models/project_file.py index 8f8fa3e..34ea50a 100644 --- a/src/tracker/models/project_file.py +++ b/src/tracker/models/project_file.py @@ -7,11 +7,11 @@ from sqlalchemy import ForeignKey, Integer, String, Text from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column, relationship -from tracker.models.base import Base +from .base import Base if TYPE_CHECKING: - from tracker.models.project import Project - from tracker.models.member import Member + from .project import Project + from .member import Member class ProjectFile(Base): diff --git a/src/tracker/models/task.py b/src/tracker/models/task.py index dab1573..ae29253 100644 --- a/src/tracker/models/task.py +++ b/src/tracker/models/task.py @@ -7,12 +7,12 @@ from sqlalchemy import Boolean, ForeignKey, Integer, String, Text from sqlalchemy.dialects.postgresql import ARRAY, UUID from sqlalchemy.orm import Mapped, mapped_column, relationship -from tracker.enums import TaskPriority, TaskStatus, TaskType -from tracker.models.base import Base +from ..enums import TaskPriority, TaskStatus, TaskType +from .base import Base if TYPE_CHECKING: - from tracker.models.member import Member - from tracker.models.project import Project + from .member import Member + from .project import Project class Task(Base): diff --git a/src/tracker/ws/handler.py b/src/tracker/ws/handler.py index 234cc0b..b035aa3 100644 --- a/src/tracker/ws/handler.py +++ b/src/tracker/ws/handler.py @@ -7,14 +7,14 @@ from fastapi import APIRouter, WebSocket, WebSocketDisconnect from sqlalchemy import select from sqlalchemy.orm import selectinload -from tracker.database import async_session -from tracker.enums import ( +from ..database import async_session +from ..enums import ( AuthMethod, ChatKind, ListenMode, MemberRole, MemberStatus, MemberType, ProjectStatus, WSEventType, ) -from tracker.models import Member, AgentConfig, Chat, Message, Project, ProjectMember -from tracker.api.schemas import MessageOut, MemberBrief -from tracker.ws.manager import ConnectedClient, manager +from ..models import Member, AgentConfig, Chat, Message, Project, ProjectMember +from ..api.schemas import MessageOut, MemberBrief +from .manager import ConnectedClient, manager logger = logging.getLogger("tracker.ws") router = APIRouter() @@ -134,7 +134,7 @@ async def _authenticate(ws: WebSocket, token: str, on_behalf_of: str | None = No member = result.scalar_one_or_none() else: # Try JWT decode - from tracker.api.auth import decode_jwt + from ..api.auth import decode_jwt try: payload = decode_jwt(token) member_id = payload["sub"] diff --git a/src/tracker/ws/manager.py b/src/tracker/ws/manager.py index 504fda7..902a70b 100644 --- a/src/tracker/ws/manager.py +++ b/src/tracker/ws/manager.py @@ -7,7 +7,7 @@ from datetime import datetime, timezone from fastapi import WebSocket -from tracker.enums import AuthorType, ListenMode, MemberType, WSEventType +from ..enums import AuthorType, ListenMode, MemberType, WSEventType logger = logging.getLogger("tracker.ws")