diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..1c99266b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,28 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-yaml + - id: check-added-large-files + + - repo: https://github.com/psf/black + rev: 24.10.0 + hooks: + - id: black + language_version: python3 + files: ^backend/.*\.py$ + + - repo: https://github.com/PyCQA/isort + rev: 5.13.2 + hooks: + - id: isort + files: ^backend/.*\.py$ + + - repo: https://github.com/PyCQA/flake8 + rev: 7.1.1 + hooks: + - id: flake8 + files: ^backend/.*\.py$ + args: [--config=backend/.flake8] diff --git a/backend/.flake8 b/backend/.flake8 new file mode 100644 index 00000000..e2203bb5 --- /dev/null +++ b/backend/.flake8 @@ -0,0 +1,10 @@ +[flake8] +max-line-length = 100 +extend-ignore = E203, W503, E501 +exclude = + .venv, + backend/.venv, + alembic, + backend/alembic, + **/__pycache__, + **/*.pyc diff --git a/backend/alembic.ini b/backend/alembic.ini index e8b4e3c1..f0da7e5b 100644 --- a/backend/alembic.ini +++ b/backend/alembic.ini @@ -86,7 +86,7 @@ path_separator = os # database URL. This is consumed by the user-maintained env.py script only. # other means of configuring database URLs may be customized within the env.py # file. -sqlalchemy.url = +sqlalchemy.url = [post_write_hooks] diff --git a/backend/alembic/README b/backend/alembic/README index 98e4f9c4..2500aa1b 100644 --- a/backend/alembic/README +++ b/backend/alembic/README @@ -1 +1 @@ -Generic single-database configuration. \ No newline at end of file +Generic single-database configuration. diff --git a/backend/alembic/__pycache__/env.cpython-312.pyc b/backend/alembic/__pycache__/env.cpython-312.pyc deleted file mode 100644 index ddf714ab..00000000 Binary files a/backend/alembic/__pycache__/env.cpython-312.pyc and /dev/null differ diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 3a5cd094..cdb40c21 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -2,14 +2,14 @@ from __future__ import annotations from logging.config import fileConfig -from alembic import context from sqlalchemy import engine_from_config, pool - -from app.core.config import settings from sqlmodel import SQLModel +from alembic import context + # Import models to register tables in metadata from app import models # noqa: F401 +from app.core.config import settings config = context.config diff --git a/backend/alembic/versions/3f2c1b9c8e12_add_teams_and_team_ownership.py b/backend/alembic/versions/3f2c1b9c8e12_add_teams_and_team_ownership.py index 0898fad3..cdeba32d 100644 --- a/backend/alembic/versions/3f2c1b9c8e12_add_teams_and_team_ownership.py +++ b/backend/alembic/versions/3f2c1b9c8e12_add_teams_and_team_ownership.py @@ -8,9 +8,9 @@ Create Date: 2026-02-02 from __future__ import annotations -from alembic import op import sqlalchemy as sa +from alembic import op # revision identifiers, used by Alembic. revision = "3f2c1b9c8e12" diff --git a/backend/alembic/versions/bacd5e6a253d_baseline_schema.py b/backend/alembic/versions/bacd5e6a253d_baseline_schema.py index 662689b1..e7964341 100644 --- a/backend/alembic/versions/bacd5e6a253d_baseline_schema.py +++ b/backend/alembic/versions/bacd5e6a253d_baseline_schema.py @@ -10,6 +10,7 @@ from typing import Sequence, Union import sqlalchemy as sa import sqlmodel + from alembic import op # revision identifiers, used by Alembic. diff --git a/backend/app/api/activities.py b/backend/app/api/activities.py index d5daadf0..be014180 100644 --- a/backend/app/api/activities.py +++ b/backend/app/api/activities.py @@ -13,7 +13,9 @@ router = APIRouter(prefix="/activities", tags=["activities"]) @router.get("") def list_activities(limit: int = 50, session: Session = Depends(get_session)): - items = session.exec(select(Activity).order_by(Activity.id.desc()).limit(max(1, min(limit, 200)))).all() + items = session.exec( + select(Activity).order_by(Activity.id.desc()).limit(max(1, min(limit, 200))) + ).all() out = [] for a in items: out.append( diff --git a/backend/app/api/org.py b/backend/app/api/org.py index 66b84f7f..ee7127b8 100644 --- a/backend/app/api/org.py +++ b/backend/app/api/org.py @@ -7,14 +7,14 @@ from sqlmodel import Session, select from app.api.utils import get_actor_employee_id, log_activity from app.db.session import get_session from app.integrations.openclaw import OpenClawClient -from app.models.org import Department, Team, Employee +from app.models.org import Department, Employee, Team from app.schemas.org import ( DepartmentCreate, DepartmentUpdate, - TeamCreate, - TeamUpdate, EmployeeCreate, EmployeeUpdate, + TeamCreate, + TeamUpdate, ) router = APIRouter(tags=["org"]) @@ -31,15 +31,14 @@ def _public_api_base_url() -> str: Never returns localhost/ because agents may run on another machine.""" - - import os, re, subprocess - + import os + import re + import subprocess explicit = os.environ.get("MISSION_CONTROL_BASE_URL") if explicit: return explicit.rstrip("/") - try: out = subprocess.check_output(["bash", "-lc", "hostname -I"], text=True).strip() # pick first RFC1918-ish IPv4, skip docker/loopback @@ -49,12 +48,16 @@ def _public_api_base_url() -> str: continue if ip.startswith("172.17."): continue - if ip.startswith("192.168.") or ip.startswith("10.") or ip.startswith("172.16.") or ip.startswith("172."): + if ( + ip.startswith("192.168.") + or ip.startswith("10.") + or ip.startswith("172.16.") + or ip.startswith("172.") + ): return f"http://{ip}:8000" except Exception: pass - # Fallback placeholder (should be overridden by env var) return "http://:8000" @@ -202,7 +205,11 @@ def create_team( entity_type="team", entity_id=team.id, verb="created", - payload={"name": team.name, "department_id": team.department_id, "lead_employee_id": team.lead_employee_id}, + payload={ + "name": team.name, + "department_id": team.department_id, + "lead_employee_id": team.lead_employee_id, + }, ) session.commit() except IntegrityError: @@ -231,7 +238,14 @@ def update_team( session.add(team) try: session.flush() - log_activity(session, actor_employee_id=actor_employee_id, entity_type="team", entity_id=team.id, verb="updated", payload=data) + log_activity( + session, + actor_employee_id=actor_employee_id, + entity_type="team", + entity_id=team.id, + verb="updated", + payload=data, + ) session.commit() except IntegrityError: session.rollback() @@ -241,7 +255,6 @@ def update_team( return team - @router.post("/departments", response_model=Department) def create_department( payload: DepartmentCreate, @@ -270,7 +283,9 @@ def create_department( session.commit() except IntegrityError: session.rollback() - raise HTTPException(status_code=409, detail="Department already exists or violates constraints") + raise HTTPException( + status_code=409, detail="Department already exists or violates constraints" + ) session.refresh(dept) return dept @@ -294,7 +309,14 @@ def update_department( session.add(dept) session.commit() session.refresh(dept) - log_activity(session, actor_employee_id=actor_employee_id, entity_type="department", entity_id=dept.id, verb="updated", payload=data) + log_activity( + session, + actor_employee_id=actor_employee_id, + entity_type="department", + entity_id=dept.id, + verb="updated", + payload=data, + ) session.commit() return dept @@ -354,7 +376,14 @@ def update_employee( session.add(emp) try: session.flush() - log_activity(session, actor_employee_id=actor_employee_id, entity_type="employee", entity_id=emp.id, verb="updated", payload=data) + log_activity( + session, + actor_employee_id=actor_employee_id, + entity_type="employee", + entity_id=emp.id, + verb="updated", + payload=data, + ) session.commit() except IntegrityError: session.rollback() @@ -401,7 +430,10 @@ def deprovision_employee_agent( try: client.tools_invoke( "sessions_send", - {"sessionKey": emp.openclaw_session_key, "message": "You are being deprovisioned. Stop all work and ignore future messages."}, + { + "sessionKey": emp.openclaw_session_key, + "message": "You are being deprovisioned. Stop all work and ignore future messages.", + }, timeout_s=5.0, ) except Exception: diff --git a/backend/app/api/projects.py b/backend/app/api/projects.py index 2b788eee..2f90232f 100644 --- a/backend/app/api/projects.py +++ b/backend/app/api/projects.py @@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.exc import IntegrityError from sqlmodel import Session, select -from app.api.utils import log_activity, get_actor_employee_id +from app.api.utils import get_actor_employee_id, log_activity from app.db.session import get_session from app.models.projects import Project, ProjectMember from app.schemas.projects import ProjectCreate, ProjectUpdate @@ -45,15 +45,21 @@ def create_project( session.commit() except IntegrityError: session.rollback() - raise HTTPException(status_code=409, detail="Project already exists or violates constraints") + raise HTTPException( + status_code=409, detail="Project already exists or violates constraints" + ) session.refresh(proj) return proj - @router.patch("/{project_id}", response_model=Project) -def update_project(project_id: int, payload: ProjectUpdate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): +def update_project( + project_id: int, + payload: ProjectUpdate, + session: Session = Depends(get_session), + actor_employee_id: int = Depends(get_actor_employee_id), +): proj = session.get(Project, project_id) if not proj: raise HTTPException(status_code=404, detail="Project not found") @@ -65,7 +71,14 @@ def update_project(project_id: int, payload: ProjectUpdate, session: Session = D session.add(proj) session.commit() session.refresh(proj) - log_activity(session, actor_employee_id=actor_employee_id, entity_type="project", entity_id=proj.id, verb="updated", payload=data) + log_activity( + session, + actor_employee_id=actor_employee_id, + entity_type="project", + entity_id=proj.id, + verb="updated", + payload=data, + ) session.commit() return proj @@ -73,16 +86,29 @@ def update_project(project_id: int, payload: ProjectUpdate, session: Session = D @router.get("/{project_id}/members", response_model=list[ProjectMember]) def list_project_members(project_id: int, session: Session = Depends(get_session)): return session.exec( - select(ProjectMember).where(ProjectMember.project_id == project_id).order_by(ProjectMember.id.asc()) + select(ProjectMember) + .where(ProjectMember.project_id == project_id) + .order_by(ProjectMember.id.asc()) ).all() @router.post("/{project_id}/members", response_model=ProjectMember) -def add_project_member(project_id: int, payload: ProjectMember, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): - existing = session.exec(select(ProjectMember).where(ProjectMember.project_id == project_id, ProjectMember.employee_id == payload.employee_id)).first() +def add_project_member( + project_id: int, + payload: ProjectMember, + session: Session = Depends(get_session), + actor_employee_id: int = Depends(get_actor_employee_id), +): + existing = session.exec( + select(ProjectMember).where( + ProjectMember.project_id == project_id, ProjectMember.employee_id == payload.employee_id + ) + ).first() if existing: raise HTTPException(status_code=409, detail="Member already added") - member = ProjectMember(project_id=project_id, employee_id=payload.employee_id, role=payload.role) + member = ProjectMember( + project_id=project_id, employee_id=payload.employee_id, role=payload.role + ) session.add(member) session.commit() session.refresh(member) @@ -99,7 +125,12 @@ def add_project_member(project_id: int, payload: ProjectMember, session: Session @router.delete("/{project_id}/members/{member_id}") -def remove_project_member(project_id: int, member_id: int, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): +def remove_project_member( + project_id: int, + member_id: int, + session: Session = Depends(get_session), + actor_employee_id: int = Depends(get_actor_employee_id), +): member = session.get(ProjectMember, member_id) if not member or member.project_id != project_id: raise HTTPException(status_code=404, detail="Project member not found") @@ -118,7 +149,13 @@ def remove_project_member(project_id: int, member_id: int, session: Session = De @router.patch("/{project_id}/members/{member_id}", response_model=ProjectMember) -def update_project_member(project_id: int, member_id: int, payload: ProjectMember, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): +def update_project_member( + project_id: int, + member_id: int, + payload: ProjectMember, + session: Session = Depends(get_session), + actor_employee_id: int = Depends(get_actor_employee_id), +): member = session.get(ProjectMember, member_id) if not member or member.project_id != project_id: raise HTTPException(status_code=404, detail="Project member not found") diff --git a/backend/app/api/work.py b/backend/app/api/work.py index dc584696..2a01510c 100644 --- a/backend/app/api/work.py +++ b/backend/app/api/work.py @@ -2,16 +2,16 @@ from __future__ import annotations from datetime import datetime -from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks -from sqlmodel import Session, select +from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException from sqlalchemy.exc import IntegrityError +from sqlmodel import Session, select -from app.api.utils import log_activity, get_actor_employee_id +from app.api.utils import get_actor_employee_id, log_activity from app.db.session import get_session +from app.integrations.notify import NotifyContext, notify_openclaw from app.models.org import Employee from app.models.work import Task, TaskComment from app.schemas.work import TaskCommentCreate, TaskCreate, TaskUpdate -from app.integrations.notify import NotifyContext, notify_openclaw router = APIRouter(tags=["work"]) @@ -33,7 +33,9 @@ def _validate_task_assignee(session: Session, assignee_employee_id: int) -> None if emp.status != "active": raise HTTPException(status_code=400, detail="Cannot assign task to inactive agent") if not emp.notify_enabled: - raise HTTPException(status_code=400, detail="Cannot assign task to agent with notifications disabled") + raise HTTPException( + status_code=400, detail="Cannot assign task to agent with notifications disabled" + ) if not emp.openclaw_session_key: raise HTTPException(status_code=400, detail="Cannot assign task to unprovisioned agent") @@ -47,9 +49,16 @@ def list_tasks(project_id: int | None = None, session: Session = Depends(get_ses @router.post("/tasks", response_model=Task) -def create_task(payload: TaskCreate, background: BackgroundTasks, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): +def create_task( + payload: TaskCreate, + background: BackgroundTasks, + session: Session = Depends(get_session), + actor_employee_id: int = Depends(get_actor_employee_id), +): if payload.created_by_employee_id is None: - payload = TaskCreate(**{**payload.model_dump(), "created_by_employee_id": actor_employee_id}) + payload = TaskCreate( + **{**payload.model_dump(), "created_by_employee_id": actor_employee_id} + ) if payload.assignee_employee_id is not None: _validate_task_assignee(session, payload.assignee_employee_id) @@ -58,7 +67,9 @@ def create_task(payload: TaskCreate, background: BackgroundTasks, session: Sessi if payload.reviewer_employee_id is None and payload.assignee_employee_id is not None: assignee = session.get(Employee, payload.assignee_employee_id) if assignee is not None and assignee.manager_id is not None: - payload = TaskCreate(**{**payload.model_dump(), "reviewer_employee_id": assignee.manager_id}) + payload = TaskCreate( + **{**payload.model_dump(), "reviewer_employee_id": assignee.manager_id} + ) task = Task(**payload.model_dump()) if task.status not in ALLOWED_STATUSES: @@ -82,18 +93,32 @@ def create_task(payload: TaskCreate, background: BackgroundTasks, session: Sessi raise HTTPException(status_code=409, detail="Task create violates constraints") session.refresh(task) - background.add_task(notify_openclaw, session, NotifyContext(event="task.created", actor_employee_id=actor_employee_id, task=task)) + background.add_task( + notify_openclaw, + session, + NotifyContext(event="task.created", actor_employee_id=actor_employee_id, task=task), + ) # Explicitly return a serializable payload (guards against empty {} responses) return Task.model_validate(task) @router.patch("/tasks/{task_id}", response_model=Task) -def update_task(task_id: int, payload: TaskUpdate, background: BackgroundTasks, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): +def update_task( + task_id: int, + payload: TaskUpdate, + background: BackgroundTasks, + session: Session = Depends(get_session), + actor_employee_id: int = Depends(get_actor_employee_id), +): task = session.get(Task, task_id) if not task: raise HTTPException(status_code=404, detail="Task not found") - before = {"assignee_employee_id": task.assignee_employee_id, "reviewer_employee_id": task.reviewer_employee_id, "status": task.status} + before = { + "assignee_employee_id": task.assignee_employee_id, + "reviewer_employee_id": task.reviewer_employee_id, + "status": task.status, + } data = payload.model_dump(exclude_unset=True) if "assignee_employee_id" in data and data["assignee_employee_id"] is not None: @@ -108,7 +133,14 @@ def update_task(task_id: int, payload: TaskUpdate, background: BackgroundTasks, try: session.flush() - log_activity(session, actor_employee_id=actor_employee_id, entity_type="task", entity_id=task.id, verb="updated", payload=data) + log_activity( + session, + actor_employee_id=actor_employee_id, + entity_type="task", + entity_id=task.id, + verb="updated", + payload=data, + ) session.commit() except IntegrityError: session.rollback() @@ -119,19 +151,53 @@ def update_task(task_id: int, payload: TaskUpdate, background: BackgroundTasks, # notify based on meaningful changes changed = {} if before.get("assignee_employee_id") != task.assignee_employee_id: - changed["assignee_employee_id"] = {"from": before.get("assignee_employee_id"), "to": task.assignee_employee_id} - background.add_task(notify_openclaw, session, NotifyContext(event="task.assigned", actor_employee_id=actor_employee_id, task=task, changed_fields=changed)) + changed["assignee_employee_id"] = { + "from": before.get("assignee_employee_id"), + "to": task.assignee_employee_id, + } + background.add_task( + notify_openclaw, + session, + NotifyContext( + event="task.assigned", + actor_employee_id=actor_employee_id, + task=task, + changed_fields=changed, + ), + ) if before.get("status") != task.status: changed["status"] = {"from": before.get("status"), "to": task.status} - background.add_task(notify_openclaw, session, NotifyContext(event="status.changed", actor_employee_id=actor_employee_id, task=task, changed_fields=changed)) + background.add_task( + notify_openclaw, + session, + NotifyContext( + event="status.changed", + actor_employee_id=actor_employee_id, + task=task, + changed_fields=changed, + ), + ) if not changed and data: - background.add_task(notify_openclaw, session, NotifyContext(event="task.updated", actor_employee_id=actor_employee_id, task=task, changed_fields=data)) + background.add_task( + notify_openclaw, + session, + NotifyContext( + event="task.updated", + actor_employee_id=actor_employee_id, + task=task, + changed_fields=data, + ), + ) return Task.model_validate(task) @router.delete("/tasks/{task_id}") -def delete_task(task_id: int, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): +def delete_task( + task_id: int, + session: Session = Depends(get_session), + actor_employee_id: int = Depends(get_actor_employee_id), +): task = session.get(Task, task_id) if not task: raise HTTPException(status_code=404, detail="Task not found") @@ -139,7 +205,13 @@ def delete_task(task_id: int, session: Session = Depends(get_session), actor_emp session.delete(task) try: session.flush() - log_activity(session, actor_employee_id=actor_employee_id, entity_type="task", entity_id=task_id, verb="deleted") + log_activity( + session, + actor_employee_id=actor_employee_id, + entity_type="task", + entity_id=task_id, + verb="deleted", + ) session.commit() except IntegrityError: session.rollback() @@ -150,20 +222,35 @@ def delete_task(task_id: int, session: Session = Depends(get_session), actor_emp @router.get("/task-comments", response_model=list[TaskComment]) def list_task_comments(task_id: int, session: Session = Depends(get_session)): - return session.exec(select(TaskComment).where(TaskComment.task_id == task_id).order_by(TaskComment.id.asc())).all() + return session.exec( + select(TaskComment).where(TaskComment.task_id == task_id).order_by(TaskComment.id.asc()) + ).all() @router.post("/task-comments", response_model=TaskComment) -def create_task_comment(payload: TaskCommentCreate, background: BackgroundTasks, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): +def create_task_comment( + payload: TaskCommentCreate, + background: BackgroundTasks, + session: Session = Depends(get_session), + actor_employee_id: int = Depends(get_actor_employee_id), +): if payload.author_employee_id is None: - payload = TaskCommentCreate(**{**payload.model_dump(), "author_employee_id": actor_employee_id}) + payload = TaskCommentCreate( + **{**payload.model_dump(), "author_employee_id": actor_employee_id} + ) c = TaskComment(**payload.model_dump()) session.add(c) try: session.flush() - log_activity(session, actor_employee_id=actor_employee_id, entity_type="task", entity_id=c.task_id, verb="commented") + log_activity( + session, + actor_employee_id=actor_employee_id, + entity_type="task", + entity_id=c.task_id, + verb="commented", + ) session.commit() except IntegrityError: session.rollback() @@ -172,5 +259,11 @@ def create_task_comment(payload: TaskCommentCreate, background: BackgroundTasks, session.refresh(c) task = session.get(Task, c.task_id) if task is not None: - background.add_task(notify_openclaw, session, NotifyContext(event="comment.created", actor_employee_id=actor_employee_id, task=task, comment=c)) + background.add_task( + notify_openclaw, + session, + NotifyContext( + event="comment.created", actor_employee_id=actor_employee_id, task=task, comment=c + ), + ) return TaskComment.model_validate(c) diff --git a/backend/app/core/__pycache__/config.cpython-312.pyc b/backend/app/core/__pycache__/config.cpython-312.pyc index 29ad631d..2695f548 100644 Binary files a/backend/app/core/__pycache__/config.cpython-312.pyc and b/backend/app/core/__pycache__/config.cpython-312.pyc differ diff --git a/backend/app/db/__pycache__/session.cpython-312.pyc b/backend/app/db/__pycache__/session.cpython-312.pyc index 0d1077b3..7d7d2836 100644 Binary files a/backend/app/db/__pycache__/session.cpython-312.pyc and b/backend/app/db/__pycache__/session.cpython-312.pyc differ diff --git a/backend/app/integrations/notify.py b/backend/app/integrations/notify.py index 712d1f63..5f44c32f 100644 --- a/backend/app/integrations/notify.py +++ b/backend/app/integrations/notify.py @@ -100,7 +100,9 @@ def build_message(ctx: NotifyContext) -> str: if len(snippet) > 180: snippet = snippet[:177] + "..." snippet = f"\nComment: {snippet}" - return f"New comment on {base}.{snippet}\nWork ONE task only; reply/update in Mission Control." + return ( + f"New comment on {base}.{snippet}\nWork ONE task only; reply/update in Mission Control." + ) if ctx.event == "status.changed": return f"Status changed on {base} → {t.status}.\nWork ONE task only; update Mission Control with next step." diff --git a/backend/app/integrations/openclaw.py b/backend/app/integrations/openclaw.py index 26fba7dd..b5301123 100644 --- a/backend/app/integrations/openclaw.py +++ b/backend/app/integrations/openclaw.py @@ -19,7 +19,14 @@ class OpenClawClient: return None return cls(url, token) - def tools_invoke(self, tool: str, args: dict[str, Any], *, session_key: str | None = None, timeout_s: float = 5.0) -> dict[str, Any]: + def tools_invoke( + self, + tool: str, + args: dict[str, Any], + *, + session_key: str | None = None, + timeout_s: float = 5.0, + ) -> dict[str, Any]: payload: dict[str, Any] = {"tool": tool, "args": args} if session_key is not None: payload["sessionKey"] = session_key diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 9bd9a65b..85f0fd7a 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -1,5 +1,5 @@ from app.models.activity import Activity -from app.models.org import Department, Team, Employee +from app.models.org import Department, Employee, Team from app.models.projects import Project, ProjectMember from app.models.work import Task, TaskComment diff --git a/backend/app/models/org.py b/backend/app/models/org.py index 81a07c49..826841da 100644 --- a/backend/app/models/org.py +++ b/backend/app/models/org.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Optional - from sqlmodel import Field, SQLModel diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 00000000..02f5b30a --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,9 @@ +[tool.black] +line-length = 100 +target-version = ["py312"] +extend-exclude = '(\.venv|alembic/versions)' + +[tool.isort] +profile = "black" +line_length = 100 +skip = [".venv", "alembic/versions"] diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt new file mode 100644 index 00000000..4d452174 --- /dev/null +++ b/backend/requirements-dev.txt @@ -0,0 +1,4 @@ +black==24.10.0 +isort==5.13.2 +flake8==7.1.1 +pre-commit==4.1.0 diff --git a/backend/scripts/lint.sh b/backend/scripts/lint.sh new file mode 100755 index 00000000..36a08089 --- /dev/null +++ b/backend/scripts/lint.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")/.." + +. .venv/bin/activate + +python -m black . +python -m isort . +python -m flake8 . diff --git a/frontend/public/file.svg b/frontend/public/file.svg index 004145cd..16fe3d3a 100644 --- a/frontend/public/file.svg +++ b/frontend/public/file.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/frontend/public/globe.svg b/frontend/public/globe.svg index 567f17b0..c7215fe0 100644 --- a/frontend/public/globe.svg +++ b/frontend/public/globe.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/frontend/public/next.svg b/frontend/public/next.svg index 5174b28c..5bb00d40 100644 --- a/frontend/public/next.svg +++ b/frontend/public/next.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/frontend/public/vercel.svg b/frontend/public/vercel.svg index 77053960..52151572 100644 --- a/frontend/public/vercel.svg +++ b/frontend/public/vercel.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/frontend/public/window.svg b/frontend/public/window.svg index b2b2a44f..d05e7a1b 100644 --- a/frontend/public/window.svg +++ b/frontend/public/window.svg @@ -1 +1 @@ - \ No newline at end of file +