Switch all imports to relative paths
Some checks failed
Deploy Tracker / deploy (push) Failing after 3s

This commit is contained in:
markov 2026-02-26 10:46:09 +01:00
parent 659454c2e6
commit c7b073b36c
20 changed files with 89 additions and 89 deletions

View File

@ -9,9 +9,9 @@ from fastapi.responses import FileResponse
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from tracker.database import get_db from ..database import get_db
from tracker.models import Attachment, Message, Member from ..models import Attachment, Message, Member
from tracker.api.schemas import UploadOut from .schemas import UploadOut
router = APIRouter(tags=["attachments"]) router = APIRouter(tags=["attachments"])

View File

@ -10,9 +10,9 @@ from pydantic import BaseModel
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from tracker.config import settings from ..config import settings
from tracker.database import get_db from ..database import get_db
from tracker.models import Member from ..models import Member
router = APIRouter(tags=["auth"]) router = APIRouter(tags=["auth"])

View File

@ -1,8 +1,8 @@
"""ORM → Pydantic converters. Single place for all model-to-schema transformations.""" """ORM → Pydantic converters. Single place for all model-to-schema transformations."""
from tracker.models import Attachment, Member, Message, ProjectFile, Step, Task from ..models import Attachment, Member, Message, ProjectFile, Step, Task
from tracker.enums import MemberType from ..enums import MemberType
from tracker.api.schemas import ( from .schemas import (
AgentConfigOut, AgentConfigOut,
AttachmentOut, AttachmentOut,
MemberBrief, MemberBrief,

View File

@ -9,11 +9,11 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from tracker.database import get_db from ..database import get_db
from tracker.enums import AuthMethod, ListenMode, MemberRole, MemberStatus, MemberType from ..enums import AuthMethod, ListenMode, MemberRole, MemberStatus, MemberType
from tracker.models import Member, AgentConfig from ..models import Member, AgentConfig
from tracker.api.schemas import MemberOut, AgentConfigOut, OkResponse from .schemas import MemberOut, AgentConfigOut, OkResponse
from tracker.api.converters import member_out from .converters import member_out
router = APIRouter(tags=["members"]) router = APIRouter(tags=["members"])

View File

@ -10,11 +10,11 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from tracker.database import get_db from ..database import get_db
from tracker.enums import AuthorType, ChatKind from ..enums import AuthorType, ChatKind
from tracker.models import Message, Chat, Attachment from ..models import Message, Chat, Attachment
from tracker.api.schemas import MessageOut from .schemas import MessageOut
from tracker.api.converters import message_out from .converters import message_out
router = APIRouter(tags=["messages"]) 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() msg_data = message_out(msg).model_dump()
# Broadcast via WebSocket # Broadcast via WebSocket
from tracker.ws.manager import manager from ..ws.manager import manager
project_id = None project_id = None
if req.chat_id: if req.chat_id:

View File

@ -12,9 +12,9 @@ from sqlalchemy import select, or_
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from tracker.database import get_db from ..database import get_db
from tracker.models import ProjectFile, Project, Member from ..models import ProjectFile, Project, Member
from tracker.api.schemas import ProjectFileOut, MemberBrief from .schemas import ProjectFileOut, MemberBrief
router = APIRouter(tags=["project-files"]) router = APIRouter(tags=["project-files"])

View File

@ -6,10 +6,10 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from tracker.database import get_db from ..database import get_db
from tracker.enums import ChatKind, MemberRole, ProjectStatus from ..enums import ChatKind, MemberRole, ProjectStatus
from tracker.models import Project, Chat, Member, ProjectMember from ..models import Project, Chat, Member, ProjectMember
from tracker.api.schemas import ProjectOut from .schemas import ProjectOut
router = APIRouter(tags=["projects"]) router = APIRouter(tags=["projects"])

View File

@ -7,11 +7,11 @@ from pydantic import BaseModel
from sqlalchemy import select, func from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from tracker.database import get_db from ..database import get_db
from tracker.models import Step, Task from ..models import Step, Task
from tracker.api.schemas import StepOut from .schemas import StepOut
from tracker.api.schemas import StepOut from .schemas import StepOut
from tracker.api.converters import step_out from .converters import step_out
router = APIRouter(tags=["steps"]) router = APIRouter(tags=["steps"])

View File

@ -10,14 +10,14 @@ from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload, joinedload from sqlalchemy.orm import selectinload, joinedload
from tracker.database import get_db from ..database import get_db
from tracker.enums import ( from ..enums import (
AuthorType, ChatKind, TaskActionType, TaskPriority, TaskStatus, TaskType, WSEventType, AuthorType, ChatKind, TaskActionType, TaskPriority, TaskStatus, TaskType, WSEventType,
) )
from tracker.models import Task, Step, Project, Member, Message, Chat, TaskAction from ..models import Task, Step, Project, Member, Message, Chat, TaskAction
from tracker.api.auth import get_current_member from .auth import get_current_member
from tracker.api.schemas import TaskOut, MessageOut from .schemas import TaskOut, MessageOut
from tracker.api.converters import task_out, message_out, member_brief from .converters import task_out, message_out, member_brief
router = APIRouter(tags=["tasks"]) router = APIRouter(tags=["tasks"])
@ -97,7 +97,7 @@ async def _system_message(
): ):
"""Create system messages: one in project chat + one in task comments. """Create system messages: one in project chat + one in task comments.
Uses MessageOut schema for WS broadcasts.""" 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" prefix = project_slug[:2].upper() if project_slug else "XX"
key = f"{prefix}-{task.number}" key = f"{prefix}-{task.number}"
@ -262,7 +262,7 @@ async def create_task(
task_full = await _get_task(str(task.id), db) task_full = await _get_task(str(task.id), db)
# Broadcast using TaskOut schema # Broadcast using TaskOut schema
from tracker.ws.manager import manager from ..ws.manager import manager
task_schema = task_out(task_full, project.slug) task_schema = task_out(task_full, project.slug)
key = f"{project.slug[:2].upper()}-{task.number}" key = f"{project.slug[:2].upper()}-{task.number}"
await manager.broadcast_task_event(str(project.id), WSEventType.TASK_CREATED, task_schema.model_dump()) 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) await db.refresh(task)
# Broadcast simplified update # Broadcast simplified update
from tracker.ws.manager import manager from ..ws.manager import manager
task_full = await _get_task(task_id, db) task_full = await _get_task(task_id, db)
task_schema = task_out(task_full, slug) task_schema = task_out(task_full, slug)
await manager.broadcast_task_event(str(task.project_id), WSEventType.TASK_UPDATED, task_schema.model_dump()) 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.commit()
await db.delete(task) await db.delete(task)
await db.commit() await db.commit()
from tracker.ws.manager import manager from ..ws.manager import manager
await manager.broadcast_task_event(project_id, WSEventType.TASK_DELETED, { await manager.broadcast_task_event(project_id, WSEventType.TASK_DELETED, {
"id": task_id, "project_id": project_id, "id": task_id, "project_id": project_id,
}) })
@ -433,7 +433,7 @@ async def take_task(
) )
await db.commit() await db.commit()
from tracker.ws.manager import manager from ..ws.manager import manager
task_full = await _get_task(task_id, db) task_full = await _get_task(task_id, db)
task_schema = task_out(task_full, proj_slug) task_schema = task_out(task_full, proj_slug)
await manager.broadcast_task_event(str(task.project_id), WSEventType.TASK_ASSIGNED, task_schema.model_dump()) 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) db.add(comment)
await db.commit() await db.commit()
from tracker.ws.manager import manager from ..ws.manager import manager
task_full = await _get_task(task_id, db) task_full = await _get_task(task_id, db)
proj_slug = task.project.slug if task.project else "" proj_slug = task.project.slug if task.project else ""
task_schema = task_out(task_full, proj_slug) task_schema = task_out(task_full, proj_slug)
@ -507,7 +507,7 @@ async def assign_task(
) )
await db.commit() await db.commit()
from tracker.ws.manager import manager from ..ws.manager import manager
task_full = await _get_task(str(task.id), db) task_full = await _get_task(str(task.id), db)
task_schema = task_out(task_full, proj_slug) task_schema = task_out(task_full, proj_slug)
await manager.broadcast_task_event(str(task.project_id), WSEventType.TASK_ASSIGNED, task_schema.model_dump()) await manager.broadcast_task_event(str(task.project_id), WSEventType.TASK_ASSIGNED, task_schema.model_dump())

View File

@ -11,9 +11,9 @@ from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from tracker.config import settings from .config import settings
from tracker.database import engine, async_session from .database import engine, async_session
from tracker.models import Base, Member from .models import Base, Member
from sqlalchemy import select, update from sqlalchemy import select, update
@ -26,7 +26,7 @@ logger = logging.getLogger("tracker")
async def heartbeat_monitor(): async def heartbeat_monitor():
"""Monitor heartbeat timeout — set status=offline after 90 seconds.""" """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 from datetime import datetime, timezone, timedelta
while True: while True:
@ -69,7 +69,7 @@ async def lifespan(app: FastAPI):
result = await session.execute(select(Member).limit(1)) result = await session.execute(select(Member).limit(1))
if not result.scalar_one_or_none(): if not result.scalar_one_or_none():
logger.info("Empty DB detected in dev mode — running seed...") 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) await seed_dev_data(session)
logger.info("Dev seed complete.") logger.info("Dev seed complete.")
@ -120,7 +120,7 @@ async def auth_middleware(request: Request, call_next):
member = result.scalar_one_or_none() member = result.scalar_one_or_none()
if not member: if not member:
# Try JWT # Try JWT
from tracker.api.auth import decode_jwt from .api.auth import decode_jwt
try: try:
payload = decode_jwt(token) payload = decode_jwt(token)
result = await db.execute(select(Member).where(Member.id == payload["sub"])) result = await db.execute(select(Member).where(Member.id == payload["sub"]))
@ -167,8 +167,8 @@ app.add_middleware(
) )
# Routers # Routers
from tracker.api import auth, members, projects, tasks, messages, steps, attachments, project_files # noqa: E402 from .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 .ws.handler import router as ws_router # noqa: E402
app.include_router(auth.router, prefix="/api/v1") app.include_router(auth.router, prefix="/api/v1")
app.include_router(members.router, prefix="/api/v1") app.include_router(members.router, prefix="/api/v1")

View File

@ -2,7 +2,7 @@
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine 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")) engine = create_async_engine(settings.database_url, echo=(settings.env == "dev"))
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)

View File

@ -6,8 +6,8 @@ import logging
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from tracker.enums import AuthMethod, ChatKind, MemberRole, MemberStatus, MemberType, ProjectStatus from .enums import AuthMethod, ChatKind, MemberRole, MemberStatus, MemberType, ProjectStatus
from tracker.models import Base, Member, Chat, Project, ProjectMember, AgentConfig from .models import Base, Member, Chat, Project, ProjectMember, AgentConfig
logger = logging.getLogger("tracker.init_db") logger = logging.getLogger("tracker.init_db")
@ -71,7 +71,7 @@ async def seed_dev_data(session: AsyncSession):
async def reset_db(): async def reset_db():
"""Drop all tables, recreate, and seed. For dev use only.""" """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: async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all) await conn.run_sync(Base.metadata.drop_all)

View File

@ -1,11 +1,11 @@
"""Models package — import all models for metadata discovery.""" """Models package — import all models for metadata discovery."""
from tracker.models.base import Base from .base import Base
from tracker.models.member import AgentConfig, Member from .member import AgentConfig, Member
from tracker.models.project import Project, ProjectMember from .project import Project, ProjectMember
from tracker.models.task import Step, Task, TaskAction from .task import Step, Task, TaskAction
from tracker.models.chat import Attachment, Chat, Message from .chat import Attachment, Chat, Message
from tracker.models.project_file import ProjectFile from .project_file import ProjectFile
__all__ = [ __all__ = [
"Base", "Base",

View File

@ -7,12 +7,12 @@ from sqlalchemy import ForeignKey, Integer, String, Text
from sqlalchemy.dialects.postgresql import ARRAY, UUID from sqlalchemy.dialects.postgresql import ARRAY, UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from tracker.enums import ChatKind from ..enums import ChatKind
from tracker.models.base import Base from .base import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from tracker.models.member import Member from .member import Member
from tracker.models.project import Project from .project import Project
class Chat(Base): class Chat(Base):

View File

@ -7,11 +7,11 @@ from sqlalchemy import ForeignKey, String, Text
from sqlalchemy.dialects.postgresql import ARRAY, UUID from sqlalchemy.dialects.postgresql import ARRAY, UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from tracker.enums import AuthMethod, ListenMode, MemberRole, MemberStatus, MemberType from ..enums import AuthMethod, ListenMode, MemberRole, MemberStatus, MemberType
from tracker.models.base import Base from .base import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from tracker.models.project import ProjectMember from .project import ProjectMember
class Member(Base): class Member(Base):

View File

@ -7,13 +7,13 @@ from sqlalchemy import ForeignKey, Integer, String, Text, UniqueConstraint
from sqlalchemy.dialects.postgresql import ARRAY, UUID from sqlalchemy.dialects.postgresql import ARRAY, UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from tracker.enums import MemberRole, ProjectStatus from ..enums import MemberRole, ProjectStatus
from tracker.models.base import Base from .base import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from tracker.models.task import Task from .task import Task
from tracker.models.chat import Chat from .chat import Chat
from tracker.models.member import Member from .member import Member
class Project(Base): class Project(Base):

View File

@ -7,11 +7,11 @@ from sqlalchemy import ForeignKey, Integer, String, Text
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from tracker.models.base import Base from .base import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from tracker.models.project import Project from .project import Project
from tracker.models.member import Member from .member import Member
class ProjectFile(Base): class ProjectFile(Base):

View File

@ -7,12 +7,12 @@ from sqlalchemy import Boolean, ForeignKey, Integer, String, Text
from sqlalchemy.dialects.postgresql import ARRAY, UUID from sqlalchemy.dialects.postgresql import ARRAY, UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from tracker.enums import TaskPriority, TaskStatus, TaskType from ..enums import TaskPriority, TaskStatus, TaskType
from tracker.models.base import Base from .base import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from tracker.models.member import Member from .member import Member
from tracker.models.project import Project from .project import Project
class Task(Base): class Task(Base):

View File

@ -7,14 +7,14 @@ from fastapi import APIRouter, WebSocket, WebSocketDisconnect
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from tracker.database import async_session from ..database import async_session
from tracker.enums import ( from ..enums import (
AuthMethod, ChatKind, ListenMode, MemberRole, MemberStatus, MemberType, AuthMethod, ChatKind, ListenMode, MemberRole, MemberStatus, MemberType,
ProjectStatus, WSEventType, ProjectStatus, WSEventType,
) )
from tracker.models import Member, AgentConfig, Chat, Message, Project, ProjectMember from ..models import Member, AgentConfig, Chat, Message, Project, ProjectMember
from tracker.api.schemas import MessageOut, MemberBrief from ..api.schemas import MessageOut, MemberBrief
from tracker.ws.manager import ConnectedClient, manager from .manager import ConnectedClient, manager
logger = logging.getLogger("tracker.ws") logger = logging.getLogger("tracker.ws")
router = APIRouter() 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() member = result.scalar_one_or_none()
else: else:
# Try JWT decode # Try JWT decode
from tracker.api.auth import decode_jwt from ..api.auth import decode_jwt
try: try:
payload = decode_jwt(token) payload = decode_jwt(token)
member_id = payload["sub"] member_id = payload["sub"]

View File

@ -7,7 +7,7 @@ from datetime import datetime, timezone
from fastapi import WebSocket from fastapi import WebSocket
from tracker.enums import AuthorType, ListenMode, MemberType, WSEventType from ..enums import AuthorType, ListenMode, MemberType, WSEventType
logger = logging.getLogger("tracker.ws") logger = logging.getLogger("tracker.ws")