refactor: rename package fabledassistant -> scribe (code-only)
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Successful in 54s
CI & Build / Build & push image (push) Successful in 1m14s

Renames src/fabledassistant -> src/scribe and all imports, plus the
default DB name and DB user/password (fabled -> scribe) in config +
compose. 952 refs / 154 files. Reverses the old 'internal name stays
fabledassistant' convention.

Code-only: live databases are still physically named 'fabledassistant'.
Deployed environments must set POSTGRES_DB / POSTGRES_USER (or rename the
DB) since the defaults now resolve to 'scribe'. Repo (FabledScribe), git
host (fabledsword), MCP (fabled-git) and the image name (fabledscribe)
are intentionally unchanged.

ruff check src/ clean locally; CI (typecheck + pytest) is the gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 15:48:35 -04:00
parent 1d4c206563
commit b255a0f90e
167 changed files with 1183 additions and 2368 deletions
-42
View File
@@ -1,42 +0,0 @@
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase
from fabledassistant.config import Config
engine = create_async_engine(
Config.DATABASE_URL,
echo=False,
pool_pre_ping=True,
pool_recycle=1800,
)
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
class Base(DeclarativeBase):
pass
from fabledassistant.models.base import CreatedAtMixin, TimestampMixin # noqa: E402, F401
from fabledassistant.models.note import Note, TaskPriority, TaskStatus # noqa: E402, F401
from fabledassistant.models.setting import Setting # noqa: E402, F401
from fabledassistant.models.user import User # noqa: E402, F401
from fabledassistant.models.app_log import AppLog # noqa: E402, F401
from fabledassistant.models.password_reset import PasswordResetToken # noqa: E402, F401
from fabledassistant.models.invitation import InvitationToken # noqa: E402, F401
from fabledassistant.models.embedding import NoteEmbedding # noqa: E402, F401
from fabledassistant.models.project import Project # noqa: E402, F401
from fabledassistant.models.event import Event # noqa: E402, F401
from fabledassistant.models.milestone import Milestone # noqa: E402, F401
from fabledassistant.models.task_log import TaskLog # noqa: E402, F401
from fabledassistant.models.note_draft import NoteDraft # noqa: E402, F401
from fabledassistant.models.note_version import NoteVersion # noqa: E402, F401
from fabledassistant.models.group import Group, GroupMembership # noqa: E402, F401
from fabledassistant.models.share import NoteShare, ProjectShare # noqa: E402, F401
from fabledassistant.models.notification import Notification # noqa: E402, F401
from fabledassistant.models.api_key import ApiKey # noqa: E402, F401
from fabledassistant.models.user_profile import UserProfile # noqa: E402, F401
from fabledassistant.models.rulebook import ( # noqa: E402, F401
Rulebook, RulebookTopic, Rule, project_rulebook_subscriptions,
)
@@ -5,30 +5,30 @@ from pathlib import Path
from quart import Quart, g, jsonify, make_response, request, send_from_directory
from fabledassistant.config import Config
from fabledassistant.routes.admin import admin_bp
from fabledassistant.routes.api import api
from fabledassistant.routes.auth import auth_bp
from fabledassistant.routes.export import export_bp
from fabledassistant.routes.notes import notes_bp
from fabledassistant.routes.milestones import milestones_bp
from fabledassistant.routes.task_logs import task_logs_bp
from fabledassistant.routes.projects import projects_bp
from fabledassistant.routes.settings import settings_bp
from fabledassistant.routes.tasks import tasks_bp
from fabledassistant.routes.groups import groups_bp
from fabledassistant.routes.shares import shares_bp
from fabledassistant.routes.in_app_notifications import notifications_bp
from fabledassistant.routes.users import users_bp
from fabledassistant.routes.api_keys import api_keys_bp
from fabledassistant.routes.events import events_bp
from fabledassistant.routes.search import search_bp
from fabledassistant.routes.profile import profile_bp
from fabledassistant.routes.knowledge import knowledge_bp
from fabledassistant.routes.rulebooks import rulebooks_bp
from fabledassistant.routes.trash import trash_bp
from fabledassistant.routes.dashboard import dashboard_bp
from fabledassistant.mcp import mount_mcp
from scribe.config import Config
from scribe.routes.admin import admin_bp
from scribe.routes.api import api
from scribe.routes.auth import auth_bp
from scribe.routes.export import export_bp
from scribe.routes.notes import notes_bp
from scribe.routes.milestones import milestones_bp
from scribe.routes.task_logs import task_logs_bp
from scribe.routes.projects import projects_bp
from scribe.routes.settings import settings_bp
from scribe.routes.tasks import tasks_bp
from scribe.routes.groups import groups_bp
from scribe.routes.shares import shares_bp
from scribe.routes.in_app_notifications import notifications_bp
from scribe.routes.users import users_bp
from scribe.routes.api_keys import api_keys_bp
from scribe.routes.events import events_bp
from scribe.routes.search import search_bp
from scribe.routes.profile import profile_bp
from scribe.routes.knowledge import knowledge_bp
from scribe.routes.rulebooks import rulebooks_bp
from scribe.routes.trash import trash_bp
from scribe.routes.dashboard import dashboard_bp
from scribe.mcp import mount_mcp
STATIC_DIR = Path(__file__).parent / "static"
logger = logging.getLogger(__name__)
@@ -115,7 +115,7 @@ def create_app() -> Quart:
# Log usage for API requests (skip logs endpoint to avoid recursion)
if request.path.startswith("/api/") and not request.path.startswith("/api/admin/logs"):
try:
from fabledassistant.services.logging import log_usage
from scribe.services.logging import log_usage
user = getattr(g, "user", None)
await log_usage(
@@ -150,10 +150,10 @@ def create_app() -> Quart:
async def startup():
import asyncio
from fabledassistant.services.auth import start_auth_token_retention_loop
from fabledassistant.services.embeddings import backfill_note_embeddings
from fabledassistant.services.logging import start_log_retention_loop
from fabledassistant.services.notifications import start_notification_loop
from scribe.services.auth import start_auth_token_retention_loop
from scribe.services.embeddings import backfill_note_embeddings
from scribe.services.logging import start_log_retention_loop
from scribe.services.notifications import start_notification_loop
start_log_retention_loop()
start_notification_loop()
@@ -170,36 +170,36 @@ def create_app() -> Quart:
asyncio.create_task(_delayed_backfill())
# Event scheduler (reminders + CalDAV pull sync)
from fabledassistant.services.event_scheduler import start_event_scheduler
from scribe.services.event_scheduler import start_event_scheduler
start_event_scheduler(asyncio.get_running_loop())
# Version-pinning scheduler (daily auto-pin scan at 03:00 UTC)
from fabledassistant.services.version_pinning_scheduler import (
from scribe.services.version_pinning_scheduler import (
start_version_pinning_scheduler,
)
start_version_pinning_scheduler(asyncio.get_running_loop())
# Trash retention scheduler (daily expired-trash purge at 03:30 UTC)
from fabledassistant.services.trash_scheduler import start_trash_scheduler
from scribe.services.trash_scheduler import start_trash_scheduler
start_trash_scheduler(asyncio.get_running_loop())
# Diagnostic instrumentation — heartbeat, signal handlers, asyncio
# exception hook. Cheap (~1 log line/min), high diagnostic value when
# the app crashes mysteriously. See services/diagnostics.py.
from fabledassistant.services.diagnostics import start_diagnostics
from scribe.services.diagnostics import start_diagnostics
start_diagnostics(asyncio.get_running_loop())
@app.after_serving
async def shutdown():
from fabledassistant.services.event_scheduler import stop_event_scheduler
from scribe.services.event_scheduler import stop_event_scheduler
stop_event_scheduler()
from fabledassistant.services.version_pinning_scheduler import (
from scribe.services.version_pinning_scheduler import (
stop_version_pinning_scheduler,
)
stop_version_pinning_scheduler()
from fabledassistant.services.trash_scheduler import stop_trash_scheduler
from scribe.services.trash_scheduler import stop_trash_scheduler
stop_trash_scheduler()
from fabledassistant.services.diagnostics import stop_diagnostics
from scribe.services.diagnostics import stop_diagnostics
stop_diagnostics()
@app.route("/")
@@ -249,7 +249,7 @@ def create_app() -> Quart:
logger.exception("Internal server error on %s %s", request.method, request.path)
try:
from fabledassistant.services.logging import log_error
from scribe.services.logging import log_error
user = getattr(g, "user", None)
await log_error(
@@ -2,8 +2,8 @@ import functools
from quart import g, jsonify, request, session
from fabledassistant.services.auth import get_user_by_id
from fabledassistant.services.api_keys import lookup_key
from scribe.services.auth import get_user_by_id
from scribe.services.api_keys import lookup_key
def _check_auth(f, required_role: str | None = None):
@@ -20,7 +20,7 @@ class Config:
DATABASE_URL: str = _read_secret(
"DATABASE_URL",
"DATABASE_URL_FILE",
"postgresql+asyncpg://fabled:fabled@localhost:5432/fabledassistant",
"postgresql+asyncpg://scribe:scribe@localhost:5432/scribe",
)
SECRET_KEY: str = _read_secret("SECRET_KEY", "SECRET_KEY_FILE", "dev-secret-change-me")
SECURE_COOKIES: bool = os.environ.get("SECURE_COOKIES", "").lower() in ("1", "true", "yes")
@@ -3,6 +3,6 @@
Auth uses the existing `api_keys` table: a Bearer token in the Authorization
header resolves to a user, and every tool call acts on that user's data only.
"""
from fabledassistant.mcp.server import build_mcp_server, mount_mcp
from scribe.mcp.server import build_mcp_server, mount_mcp
__all__ = ["build_mcp_server", "mount_mcp"]
@@ -1,7 +1,7 @@
"""MCP-side Bearer token resolution. Reuses the existing api_keys infrastructure."""
from __future__ import annotations
from fabledassistant.services.api_keys import lookup_key
from scribe.services.api_keys import lookup_key
async def resolve_bearer_to_user_id(auth_header: str | None) -> int | None:
@@ -220,7 +220,7 @@ def build_mcp_server() -> FastMCP:
enable_dns_rebinding_protection=False,
),
)
from fabledassistant.mcp.tools import register_all
from scribe.mcp.tools import register_all
register_all(mcp)
return mcp
@@ -239,7 +239,7 @@ def mount_mcp(app: Quart) -> None:
inside Quart, we hook the session manager's `run()` async context manager
into Quart's serving lifecycle (before_serving / after_serving).
"""
from fabledassistant.mcp.auth import resolve_bearer
from scribe.mcp.auth import resolve_bearer
mcp = build_mcp_server()
mcp_asgi = mcp.streamable_http_app()
@@ -299,7 +299,7 @@ def mount_mcp(app: Quart) -> None:
return
scope["scribe_user_id"] = user_id
from fabledassistant.mcp._context import _user_id_ctx
from scribe.mcp._context import _user_id_ctx
token = _user_id_ctx.set(user_id)
try:
await mcp_asgi(scope, receive, send)
@@ -4,7 +4,7 @@ Each tool module exposes a `register(mcp)` function that attaches its tools
to a FastMCP instance. `register_all(mcp)` is the single entry point called
from `mcp.server.build_mcp_server`.
"""
from fabledassistant.mcp.tools import (
from scribe.mcp.tools import (
entities, events, milestones, notes, processes, projects, recent, rulebooks, search, tags, tasks, trash,
)
@@ -14,9 +14,9 @@ of plain strings for ergonomics; checked-state is reset to False on each call.
"""
from __future__ import annotations
from fabledassistant.mcp._context import current_user_id
from fabledassistant.services import knowledge as knowledge_svc
from fabledassistant.services import notes as notes_svc
from scribe.mcp._context import current_user_id
from scribe.services import knowledge as knowledge_svc
from scribe.services import notes as notes_svc
async def _list_by_type(note_type: str, q: str, tag: str, limit: int) -> dict:
@@ -19,9 +19,9 @@ from __future__ import annotations
from datetime import datetime, timedelta, timezone
from fabledassistant.mcp._context import current_user_id
from fabledassistant.services import events as events_svc
from fabledassistant.services import trash as trash_svc
from scribe.mcp._context import current_user_id
from scribe.services import events as events_svc
from scribe.services import trash as trash_svc
def _combine(start_date: str, start_time: str) -> datetime:
@@ -11,9 +11,9 @@ Sentinels:
"""
from __future__ import annotations
from fabledassistant.mcp._context import current_user_id
from fabledassistant.services import milestones as milestones_svc
from fabledassistant.services import trash as trash_svc
from scribe.mcp._context import current_user_id
from scribe.services import milestones as milestones_svc
from scribe.services import trash as trash_svc
async def list_milestones(project_id: int) -> dict:
@@ -13,9 +13,9 @@ Sentinel conventions (inherited from existing fable-mcp tools):
"""
from __future__ import annotations
from fabledassistant.mcp._context import current_user_id
from fabledassistant.services import notes as notes_svc
from fabledassistant.services import trash as trash_svc
from scribe.mcp._context import current_user_id
from scribe.services import notes as notes_svc
from scribe.services import trash as trash_svc
async def list_notes(
@@ -6,9 +6,9 @@ get_process is the fire mechanism: it returns the full prompt for Claude to run.
"""
from __future__ import annotations
from fabledassistant.mcp._context import current_user_id
from fabledassistant.services import knowledge as knowledge_svc
from fabledassistant.services import notes as notes_svc
from scribe.mcp._context import current_user_id
from scribe.services import knowledge as knowledge_svc
from scribe.services import notes as notes_svc
async def list_processes(q: str = "", tag: str = "", limit: int = 50) -> dict:
@@ -16,12 +16,12 @@ keeps working.
"""
from __future__ import annotations
from fabledassistant.mcp._context import current_user_id
from fabledassistant.services import milestones as milestones_svc
from fabledassistant.services import notes as notes_svc
from fabledassistant.services import projects as projects_svc
from fabledassistant.services import rulebooks as rulebooks_svc
from fabledassistant.services import trash as trash_svc
from scribe.mcp._context import current_user_id
from scribe.services import milestones as milestones_svc
from scribe.services import notes as notes_svc
from scribe.services import projects as projects_svc
from scribe.services import rulebooks as rulebooks_svc
from scribe.services import trash as trash_svc
async def list_projects() -> dict:
@@ -13,11 +13,11 @@ from datetime import datetime, timedelta, timezone
from sqlalchemy import select
from fabledassistant.mcp._context import current_user_id
from fabledassistant.models import async_session
from fabledassistant.models.event import Event
from fabledassistant.models.note import Note
from fabledassistant.models.project import Project
from scribe.mcp._context import current_user_id
from scribe.models import async_session
from scribe.models.event import Event
from scribe.models.note import Note
from scribe.models.project import Project
async def get_recent(days: int = 7, limit: int = 25) -> dict:
@@ -9,9 +9,9 @@ spec.
"""
from __future__ import annotations
from fabledassistant.mcp._context import current_user_id
from fabledassistant.services import rulebooks as rulebooks_svc
from fabledassistant.services import trash as trash_svc
from scribe.mcp._context import current_user_id
from scribe.services import rulebooks as rulebooks_svc
from scribe.services import trash as trash_svc
# ── Rulebook CRUD ───────────────────────────────────────────────────────
@@ -7,8 +7,8 @@ working. Differences from fable-mcp:
"""
from __future__ import annotations
from fabledassistant.mcp._context import current_user_id
from fabledassistant.services.embeddings import semantic_search_notes
from scribe.mcp._context import current_user_id
from scribe.services.embeddings import semantic_search_notes
async def search(q: str, content_type: str = "all", limit: int = 10) -> dict:
@@ -8,9 +8,9 @@ from __future__ import annotations
from sqlalchemy import select
from fabledassistant.mcp._context import current_user_id
from fabledassistant.models import async_session
from fabledassistant.models.note import Note
from scribe.mcp._context import current_user_id
from scribe.models import async_session
from scribe.models.note import Note
def _aggregate_tag_counts(tag_lists) -> dict[str, int]:
@@ -18,12 +18,12 @@ Sentinels (preserved from existing fable-mcp):
"""
from __future__ import annotations
from fabledassistant.mcp._context import current_user_id
from fabledassistant.services import notes as notes_svc
from fabledassistant.services import planning as planning_svc
from fabledassistant.services import rulebooks as rulebooks_svc
from fabledassistant.services import task_logs as task_logs_svc
from fabledassistant.services import trash as trash_svc
from scribe.mcp._context import current_user_id
from scribe.services import notes as notes_svc
from scribe.services import planning as planning_svc
from scribe.services import rulebooks as rulebooks_svc
from scribe.services import task_logs as task_logs_svc
from scribe.services import trash as trash_svc
async def list_tasks(
@@ -6,8 +6,8 @@ the deleted_batch_id that a delete operation returns.
"""
from __future__ import annotations
from fabledassistant.mcp._context import current_user_id
from fabledassistant.services import trash as trash_svc
from scribe.mcp._context import current_user_id
from scribe.services import trash as trash_svc
async def list_trash() -> dict:
+42
View File
@@ -0,0 +1,42 @@
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase
from scribe.config import Config
engine = create_async_engine(
Config.DATABASE_URL,
echo=False,
pool_pre_ping=True,
pool_recycle=1800,
)
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
class Base(DeclarativeBase):
pass
from scribe.models.base import CreatedAtMixin, TimestampMixin # noqa: E402, F401
from scribe.models.note import Note, TaskPriority, TaskStatus # noqa: E402, F401
from scribe.models.setting import Setting # noqa: E402, F401
from scribe.models.user import User # noqa: E402, F401
from scribe.models.app_log import AppLog # noqa: E402, F401
from scribe.models.password_reset import PasswordResetToken # noqa: E402, F401
from scribe.models.invitation import InvitationToken # noqa: E402, F401
from scribe.models.embedding import NoteEmbedding # noqa: E402, F401
from scribe.models.project import Project # noqa: E402, F401
from scribe.models.event import Event # noqa: E402, F401
from scribe.models.milestone import Milestone # noqa: E402, F401
from scribe.models.task_log import TaskLog # noqa: E402, F401
from scribe.models.note_draft import NoteDraft # noqa: E402, F401
from scribe.models.note_version import NoteVersion # noqa: E402, F401
from scribe.models.group import Group, GroupMembership # noqa: E402, F401
from scribe.models.share import NoteShare, ProjectShare # noqa: E402, F401
from scribe.models.notification import Notification # noqa: E402, F401
from scribe.models.api_key import ApiKey # noqa: E402, F401
from scribe.models.user_profile import UserProfile # noqa: E402, F401
from scribe.models.rulebook import ( # noqa: E402, F401
Rulebook, RulebookTopic, Rule, project_rulebook_subscriptions,
)
@@ -3,8 +3,8 @@ from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, Index, Integer, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import CreatedAtMixin
from scribe.models import Base
from scribe.models.base import CreatedAtMixin
class ApiKey(Base, CreatedAtMixin):
@@ -3,7 +3,7 @@ from datetime import datetime, timezone
from sqlalchemy import DateTime, Float, Index, Integer, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from scribe.models import Base
class AppLog(Base):
@@ -4,7 +4,7 @@ from sqlalchemy import DateTime, ForeignKey, Integer
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from scribe.models import Base
class NoteEmbedding(Base):
@@ -3,8 +3,8 @@ from datetime import datetime, timedelta, timezone
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import SoftDeleteMixin
from scribe.models import Base
from scribe.models.base import SoftDeleteMixin
class Event(Base, SoftDeleteMixin):
@@ -3,8 +3,8 @@ from datetime import datetime, timezone
from sqlalchemy import ForeignKey, Integer, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from fabledassistant.models import Base
from fabledassistant.models.base import CreatedAtMixin, TimestampMixin
from scribe.models import Base
from scribe.models.base import CreatedAtMixin, TimestampMixin
class Group(Base, TimestampMixin):
@@ -3,7 +3,7 @@ from datetime import datetime, timezone
from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from scribe.models import Base
class InvitationToken(Base):
@@ -1,8 +1,8 @@
from sqlalchemy import ForeignKey, Integer, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import TimestampMixin, SoftDeleteMixin
from scribe.models import Base
from scribe.models.base import TimestampMixin, SoftDeleteMixin
class Milestone(Base, TimestampMixin, SoftDeleteMixin):
@@ -5,8 +5,8 @@ from sqlalchemy import Date, DateTime, ForeignKey, Index, Integer, Text
from sqlalchemy.dialects.postgresql import ARRAY, JSONB
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import TimestampMixin, SoftDeleteMixin
from scribe.models import Base
from scribe.models.base import TimestampMixin, SoftDeleteMixin
class TaskStatus(str, enum.Enum):
@@ -1,8 +1,8 @@
from sqlalchemy import ForeignKey, Integer, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import TimestampMixin
from scribe.models import Base
from scribe.models.base import TimestampMixin
class NoteDraft(Base, TimestampMixin):
@@ -1,8 +1,8 @@
from sqlalchemy import ARRAY, ForeignKey, Integer, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import CreatedAtMixin
from scribe.models import Base
from scribe.models.base import CreatedAtMixin
class NoteVersion(Base, CreatedAtMixin):
@@ -4,8 +4,8 @@ from sqlalchemy import DateTime, ForeignKey, Integer, Text
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import CreatedAtMixin
from scribe.models import Base
from scribe.models.base import CreatedAtMixin
class Notification(Base, CreatedAtMixin):
@@ -3,7 +3,7 @@ from datetime import datetime, timezone
from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from scribe.models import Base
class PasswordResetToken(Base):
@@ -2,8 +2,8 @@ import enum
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, Integer, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import TimestampMixin, SoftDeleteMixin
from scribe.models import Base
from scribe.models.base import TimestampMixin, SoftDeleteMixin
class ProjectStatus(str, enum.Enum):
@@ -3,8 +3,8 @@ from datetime import datetime, timezone
from sqlalchemy import BigInteger, Boolean, Column, DateTime, ForeignKey, Index, Integer, Table, Text, text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import SoftDeleteMixin
from scribe.models import Base
from scribe.models.base import SoftDeleteMixin
class Rulebook(Base, SoftDeleteMixin):
@@ -1,7 +1,7 @@
from sqlalchemy import ForeignKey, Integer, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from scribe.models import Base
class Setting(Base):
@@ -1,8 +1,8 @@
from sqlalchemy import CheckConstraint, ForeignKey, Integer, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import TimestampMixin
from scribe.models import Base
from scribe.models.base import TimestampMixin
class ProjectShare(Base, TimestampMixin):
@@ -1,8 +1,8 @@
from sqlalchemy import ForeignKey, Integer, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import TimestampMixin
from scribe.models import Base
from scribe.models.base import TimestampMixin
class TaskLog(Base, TimestampMixin):
@@ -1,8 +1,8 @@
from sqlalchemy import Index, Integer, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import CreatedAtMixin
from scribe.models import Base
from scribe.models.base import CreatedAtMixin
class User(Base, CreatedAtMixin):
@@ -2,8 +2,8 @@ from sqlalchemy import ForeignKey, Integer, Text
from sqlalchemy.dialects.postgresql import ARRAY, JSONB
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import TimestampMixin
from scribe.models import Base
from scribe.models.base import TimestampMixin
class UserProfile(Base, TimestampMixin):
@@ -3,8 +3,8 @@ import json
from quart import Blueprint, Response, g, jsonify, request
from fabledassistant.auth import admin_required, login_required, get_current_user_id
from fabledassistant.services.auth import (
from scribe.auth import admin_required, login_required, get_current_user_id
from scribe.services.auth import (
create_invitation,
delete_user,
is_registration_open,
@@ -13,15 +13,15 @@ from fabledassistant.services.auth import (
revoke_invitation,
set_registration_open,
)
from fabledassistant.services.backup import (
from scribe.services.backup import (
export_full_backup,
export_user_backup,
restore_full_backup,
)
from fabledassistant.services.email import SMTP_SETTING_KEYS, get_base_url, get_smtp_config, is_smtp_configured, send_test_email
from fabledassistant.services.logging import get_logs, get_log_stats, log_audit
from fabledassistant.services.notifications import send_invitation_email
from fabledassistant.services.settings import set_setting, set_settings_batch
from scribe.services.email import SMTP_SETTING_KEYS, get_base_url, get_smtp_config, is_smtp_configured, send_test_email
from scribe.services.logging import get_logs, get_log_stats, log_audit
from scribe.services.notifications import send_invitation_email
from scribe.services.settings import set_setting, set_settings_batch
admin_bp = Blueprint("admin", __name__, url_prefix="/api/admin")
@@ -1,7 +1,7 @@
from quart import Blueprint, jsonify, request
from fabledassistant.auth import login_required, get_current_user_id
from fabledassistant.services.api_keys import create_api_key, list_api_keys, revoke_api_key
from scribe.auth import login_required, get_current_user_id
from scribe.services.api_keys import create_api_key, list_api_keys, revoke_api_key
api_keys_bp = Blueprint("api_keys", __name__, url_prefix="/api/api-keys")
@@ -6,10 +6,10 @@ import secrets
from quart import Blueprint, g, jsonify, redirect, request, session
from fabledassistant.auth import login_required, get_current_user_id
from fabledassistant.config import Config
from fabledassistant.rate_limit import is_rate_limited
from fabledassistant.services.auth import (
from scribe.auth import login_required, get_current_user_id
from scribe.config import Config
from scribe.rate_limit import is_rate_limited
from scribe.services.auth import (
authenticate,
change_password,
create_password_reset_token,
@@ -26,14 +26,14 @@ from fabledassistant.services.auth import (
validate_invitation_token,
verify_password,
)
from fabledassistant.services.logging import log_audit
from fabledassistant.services.notifications import (
from scribe.services.logging import log_audit
from scribe.services.notifications import (
notify_security_event,
send_password_reset_email,
send_password_reset_success_email,
)
from fabledassistant.services.email import get_base_url, is_smtp_configured
from fabledassistant.services.oauth import (
from scribe.services.email import get_base_url, is_smtp_configured
from scribe.services.oauth import (
build_auth_url,
exchange_code,
get_userinfo,
@@ -1,8 +1,8 @@
"""Dashboard REST endpoint — the aggregated landing payload."""
from quart import Blueprint, g, jsonify
from fabledassistant.auth import login_required
from fabledassistant.services.dashboard import build_dashboard
from scribe.auth import login_required
from scribe.services.dashboard import build_dashboard
dashboard_bp = Blueprint("dashboard", __name__, url_prefix="/api/dashboard")
@@ -5,8 +5,8 @@ from datetime import datetime, timezone
from quart import Blueprint, g, jsonify, request
from fabledassistant.auth import login_required
import fabledassistant.services.events as events_svc
from scribe.auth import login_required
import scribe.services.events as events_svc
events_bp = Blueprint("events", __name__, url_prefix="/api/events")
@@ -126,7 +126,7 @@ async def update_event(event_id: int):
@events_bp.delete("/<int:event_id>")
@login_required
async def delete_event(event_id: int):
from fabledassistant.services.trash import delete as trash_delete
from scribe.services.trash import delete as trash_delete
batch = await trash_delete(_get_current_user_id(), "event", event_id)
if batch is None:
return jsonify({"error": "Event not found"}), 404
@@ -137,6 +137,6 @@ async def delete_event(event_id: int):
@login_required
async def sync_caldav():
"""Trigger a CalDAV pull sync for the current user."""
from fabledassistant.services.caldav_sync import sync_user_events
from scribe.services.caldav_sync import sync_user_events
result = await sync_user_events(user_id=_get_current_user_id())
return jsonify(result)
@@ -6,9 +6,9 @@ from datetime import datetime, timezone
from quart import Blueprint, Response, request
from fabledassistant.auth import login_required, get_current_user_id
from fabledassistant.models import async_session
from fabledassistant.models.note import Note
from scribe.auth import login_required, get_current_user_id
from scribe.models import async_session
from scribe.models.note import Note
from sqlalchemy import select
export_bp = Blueprint("export", __name__)
@@ -83,7 +83,7 @@ async def export_data():
status=200,
headers={
"Content-Type": "application/json; charset=utf-8",
"Content-Disposition": f'attachment; filename="fabledassistant-{stamp}.json"',
"Content-Disposition": f'attachment; filename="scribe-{stamp}.json"',
},
)
@@ -112,6 +112,6 @@ async def export_data():
status=200,
headers={
"Content-Type": "application/zip",
"Content-Disposition": f'attachment; filename="fabledassistant-{stamp}.zip"',
"Content-Disposition": f'attachment; filename="scribe-{stamp}.zip"',
},
)
@@ -2,9 +2,9 @@ import asyncio
from quart import Blueprint, g, jsonify, request
from fabledassistant.auth import get_current_user_id, login_required
from fabledassistant.services import groups as group_svc
from fabledassistant.services.notifications import notify_group_added
from scribe.auth import get_current_user_id, login_required
from scribe.services import groups as group_svc
from scribe.services.notifications import notify_group_added
groups_bp = Blueprint("groups", __name__, url_prefix="/api/groups")
@@ -1,7 +1,7 @@
from quart import Blueprint, jsonify, request
from fabledassistant.auth import get_current_user_id, login_required
from fabledassistant.services.notifications import (
from scribe.auth import get_current_user_id, login_required
from scribe.services.notifications import (
list_in_app_notifications,
mark_all_notifications_read,
mark_notification_read,
@@ -3,8 +3,8 @@ import logging
from quart import Blueprint, jsonify, request
from fabledassistant.auth import get_current_user_id, login_required
from fabledassistant.routes.utils import parse_pagination
from scribe.auth import get_current_user_id, login_required
from scribe.routes.utils import parse_pagination
logger = logging.getLogger(__name__)
@@ -42,7 +42,7 @@ async def list_knowledge():
limit, offset = parse_pagination(default_limit=24, max_limit=100)
page = max(1, int(request.args.get("page", 1)))
from fabledassistant.services.knowledge import query_knowledge
from scribe.services.knowledge import query_knowledge
items, total = await query_knowledge(
user_id=uid,
note_type=note_type,
@@ -88,7 +88,7 @@ async def list_knowledge_ids():
if note_type and note_type not in _VALID_TYPES:
return jsonify({"error": "Invalid type"}), 400
from fabledassistant.services.knowledge import query_knowledge_ids
from scribe.services.knowledge import query_knowledge_ids
ids, total = await query_knowledge_ids(
user_id=uid, note_type=note_type, tags=tags,
sort=sort, q=q, limit=limit, offset=offset,
@@ -114,7 +114,7 @@ async def get_knowledge_batch():
if len(ids) > 100:
return jsonify({"error": "Too many IDs (max 100)"}), 400
from fabledassistant.services.knowledge import get_knowledge_by_ids
from scribe.services.knowledge import get_knowledge_by_ids
items = await get_knowledge_by_ids(uid, ids)
return jsonify({"items": items})
@@ -126,7 +126,7 @@ async def list_knowledge_tags():
uid = get_current_user_id()
note_type = request.args.get("type", "").strip().lower() or None
from fabledassistant.services.knowledge import get_knowledge_tags
from scribe.services.knowledge import get_knowledge_tags
tags = await get_knowledge_tags(uid, note_type=note_type)
return jsonify({"tags": tags})
@@ -139,6 +139,6 @@ async def get_knowledge_counts():
tags_raw = request.args.get("tags", "").strip()
tags = [t.strip() for t in tags_raw.split(",") if t.strip()] if tags_raw else None
from fabledassistant.services.knowledge import get_knowledge_counts as _counts
from scribe.services.knowledge import get_knowledge_counts as _counts
counts = await _counts(uid, tags=tags)
return jsonify(counts)
@@ -3,10 +3,10 @@ import logging
from quart import Blueprint, jsonify, request
from fabledassistant.auth import login_required, get_current_user_id
from fabledassistant.routes.utils import not_found, parse_pagination
from fabledassistant.services.access import can_write_project
from fabledassistant.services.milestones import (
from scribe.auth import login_required, get_current_user_id
from scribe.routes.utils import not_found, parse_pagination
from scribe.services.access import can_write_project
from scribe.services.milestones import (
create_milestone,
delete_milestone,
get_milestone_in_project,
@@ -14,8 +14,8 @@ from fabledassistant.services.milestones import (
list_milestones,
update_milestone,
)
from fabledassistant.services.notes import list_notes
from fabledassistant.services.projects import get_project_for_user
from scribe.services.notes import list_notes
from scribe.services.projects import get_project_for_user
logger = logging.getLogger(__name__)
@@ -113,7 +113,7 @@ async def delete_milestone_route(project_id: int, milestone_id: int):
milestone = await get_milestone_in_project(project_id, milestone_id)
if milestone is None:
return not_found("Milestone")
from fabledassistant.services.trash import delete as trash_delete
from scribe.services.trash import delete as trash_delete
batch = await trash_delete(milestone.user_id, "milestone", milestone_id)
if batch is None:
return not_found("Milestone")
@@ -2,14 +2,14 @@ import asyncio
import logging
import re
from fabledassistant.services.embeddings import upsert_note_embedding
from scribe.services.embeddings import upsert_note_embedding
from quart import Blueprint, jsonify, request
from fabledassistant.auth import login_required, get_current_user_id
from fabledassistant.routes.utils import not_found, parse_iso_date, parse_pagination
from fabledassistant.services.access import can_write_note
from fabledassistant.services.notes import (
from scribe.auth import login_required, get_current_user_id
from scribe.routes.utils import not_found, parse_iso_date, parse_pagination
from scribe.services.access import can_write_note
from scribe.services.notes import (
build_note_graph,
convert_note_to_task,
convert_task_to_note,
@@ -23,8 +23,8 @@ from fabledassistant.services.notes import (
list_notes,
update_note,
)
from fabledassistant.services.note_drafts import upsert_draft, get_draft, delete_draft
from fabledassistant.services.note_versions import list_versions, get_version
from scribe.services.note_drafts import upsert_draft, get_draft, delete_draft
from scribe.services.note_versions import list_versions, get_version
logger = logging.getLogger(__name__)
@@ -89,7 +89,7 @@ async def create_note_route():
project_id = data.get("project_id")
if project_id is None and data.get("project"):
from fabledassistant.services.projects import get_project_by_title as _gpbt
from scribe.services.projects import get_project_by_title as _gpbt
proj = await _gpbt(uid, data["project"])
if proj:
project_id = proj.id
@@ -282,7 +282,7 @@ async def delete_note_route(note_id: int):
note_obj, _ = result
if not await can_write_note(uid, note_id):
return jsonify({"error": "Permission denied"}), 403
from fabledassistant.services.trash import delete as trash_delete
from scribe.services.trash import delete as trash_delete
batch = await trash_delete(note_obj.user_id, "note", note_id)
if batch is None:
return not_found("Note")
@@ -445,7 +445,7 @@ async def pin_version_route(note_id: int, version_id: int):
label = data.get("label")
if label is not None and not isinstance(label, str):
return jsonify({"error": "label must be a string or null"}), 400
from fabledassistant.services.version_pinning import pin_version
from scribe.services.version_pinning import pin_version
try:
version = await pin_version(uid, note_id, version_id, label=label)
except ValueError as e:
@@ -462,7 +462,7 @@ async def pin_version_route(note_id: int, version_id: int):
async def unpin_version_route(note_id: int, version_id: int):
"""Downgrade a manually-pinned version back to rolling."""
uid = get_current_user_id()
from fabledassistant.services.version_pinning import unpin_version
from scribe.services.version_pinning import unpin_version
version = await unpin_version(uid, note_id, version_id)
if version is None:
return not_found("Version")
@@ -1,7 +1,7 @@
from quart import Blueprint, jsonify, request
from fabledassistant.auth import get_current_user_id, login_required
from fabledassistant.services.user_profile import (
from scribe.auth import get_current_user_id, login_required
from scribe.services.user_profile import (
VALID_EXPERTISE,
VALID_STYLES,
VALID_TONES,
@@ -4,11 +4,11 @@ import logging
from quart import Blueprint, jsonify, request
from fabledassistant.auth import login_required, get_current_user_id
from fabledassistant.routes.utils import not_found, parse_pagination
from fabledassistant.services.milestones import list_milestones
from fabledassistant.services.notes import list_notes
from fabledassistant.services.projects import (
from scribe.auth import login_required, get_current_user_id
from scribe.routes.utils import not_found, parse_pagination
from scribe.services.milestones import list_milestones
from scribe.services.notes import list_notes
from scribe.services.projects import (
create_project,
delete_project,
get_project,
@@ -101,7 +101,7 @@ async def update_project_route(project_id: int):
@login_required
async def delete_project_route(project_id: int):
uid = get_current_user_id()
from fabledassistant.services.trash import delete as trash_delete
from scribe.services.trash import delete as trash_delete
batch = await trash_delete(uid, "project", project_id)
if batch is None:
return not_found("Project")
@@ -7,9 +7,9 @@ from __future__ import annotations
from quart import Blueprint, g, jsonify, request
from fabledassistant.auth import login_required
import fabledassistant.services.rulebooks as rulebooks_svc
from fabledassistant.services.trash import delete as trash_delete
from scribe.auth import login_required
import scribe.services.rulebooks as rulebooks_svc
from scribe.services.trash import delete as trash_delete
rulebooks_bp = Blueprint("rulebooks", __name__, url_prefix="/api")
@@ -1,7 +1,7 @@
from quart import Blueprint, jsonify, request
from fabledassistant.auth import login_required, get_current_user_id
from fabledassistant.services.embeddings import semantic_search_notes
from scribe.auth import login_required, get_current_user_id
from scribe.services.embeddings import semantic_search_notes
search_bp = Blueprint("search", __name__, url_prefix="/api/search")
@@ -10,10 +10,10 @@ from urllib.parse import urlparse
from quart import Blueprint, jsonify, request
from fabledassistant.auth import login_required, get_current_user_id
from fabledassistant.config import Config
from fabledassistant.services.caldav import CALDAV_SETTING_KEYS, get_caldav_config, test_connection
from fabledassistant.services.settings import delete_setting, get_all_settings, get_setting, set_settings_batch
from scribe.auth import login_required, get_current_user_id
from scribe.config import Config
from scribe.services.caldav import CALDAV_SETTING_KEYS, get_caldav_config, test_connection
from scribe.services.settings import delete_setting, get_all_settings, get_setting, set_settings_batch
logger = logging.getLogger(__name__)
@@ -2,11 +2,11 @@ import asyncio
from quart import Blueprint, jsonify, request
from fabledassistant.auth import get_current_user_id, login_required
from fabledassistant.services import sharing as share_svc
from fabledassistant.services.access import can_admin_project, can_write_note
from fabledassistant.services.notifications import notify_note_shared, notify_project_shared
from fabledassistant.services.sharing import list_shared_with_me
from scribe.auth import get_current_user_id, login_required
from scribe.services import sharing as share_svc
from scribe.services.access import can_admin_project, can_write_note
from scribe.services.notifications import notify_note_shared, notify_project_shared
from scribe.services.sharing import list_shared_with_me
shares_bp = Blueprint("shares", __name__)
@@ -3,8 +3,8 @@ import logging
from quart import Blueprint, jsonify, request
from fabledassistant.auth import get_current_user_id, login_required
from fabledassistant.services.task_logs import create_log, delete_log, list_logs, update_log
from scribe.auth import get_current_user_id, login_required
from scribe.services.task_logs import create_log, delete_log, list_logs, update_log
logger = logging.getLogger(__name__)
@@ -49,7 +49,7 @@ async def update_log_route(task_id: int, log_id: int):
if content is not None:
content = content.strip() or None
from fabledassistant.services.task_logs import _UNSET
from scribe.services.task_logs import _UNSET
duration_minutes = data.get("duration_minutes", _UNSET)
log = await update_log(uid, log_id, content=content, duration_minutes=duration_minutes)
@@ -3,20 +3,20 @@ from datetime import date
from quart import Blueprint, jsonify, request
from fabledassistant.auth import login_required, get_current_user_id
from fabledassistant.models.note import TaskPriority, TaskStatus
from fabledassistant.routes.utils import not_found, parse_iso_date, parse_pagination
from fabledassistant.services.access import can_write_note
from fabledassistant.services.embeddings import upsert_note_embedding
from fabledassistant.services.notes import (
from scribe.auth import login_required, get_current_user_id
from scribe.models.note import TaskPriority, TaskStatus
from scribe.routes.utils import not_found, parse_iso_date, parse_pagination
from scribe.services.access import can_write_note
from scribe.services.embeddings import upsert_note_embedding
from scribe.services.notes import (
create_note,
get_note,
get_note_for_user,
list_notes,
update_note,
)
from fabledassistant.services.planning import start_planning as svc_start_planning
from fabledassistant.services.recurrence import calculate_next_due, validate_recurrence_rule
from scribe.services.planning import start_planning as svc_start_planning
from scribe.services.recurrence import calculate_next_due, validate_recurrence_rule
tasks_bp = Blueprint("tasks", __name__, url_prefix="/api/tasks")
@@ -117,7 +117,7 @@ async def create_task_route():
project_id = data.get("project_id")
if project_id is None and data.get("project"):
from fabledassistant.services.projects import get_project_by_title as _gpbt
from scribe.services.projects import get_project_by_title as _gpbt
proj = await _gpbt(uid, data["project"])
if proj:
project_id = proj.id
@@ -299,7 +299,7 @@ async def delete_task_route(task_id: int):
task_note, _ = result
if not await can_write_note(uid, task_id):
return jsonify({"error": "Permission denied"}), 403
from fabledassistant.services.trash import delete as trash_delete
from scribe.services.trash import delete as trash_delete
batch = await trash_delete(task_note.user_id, "task", task_id)
if batch is None:
return not_found("Task")
@@ -3,8 +3,8 @@ from __future__ import annotations
from quart import Blueprint, g, jsonify
from fabledassistant.auth import login_required
import fabledassistant.services.trash as trash_svc
from scribe.auth import login_required
import scribe.services.trash as trash_svc
trash_bp = Blueprint("trash", __name__, url_prefix="/api/trash")
@@ -1,9 +1,9 @@
from quart import Blueprint, jsonify, request
from sqlalchemy import or_, select
from fabledassistant.auth import get_current_user_id, login_required
from fabledassistant.models import async_session
from fabledassistant.models.user import User
from scribe.auth import get_current_user_id, login_required
from scribe.models import async_session
from scribe.models.user import User
users_bp = Blueprint("users", __name__, url_prefix="/api/users")
@@ -11,11 +11,11 @@ import logging
from sqlalchemy import select
from fabledassistant.models import async_session
from fabledassistant.models.group import GroupMembership
from fabledassistant.models.note import Note
from fabledassistant.models.project import Project
from fabledassistant.models.share import NoteShare, ProjectShare
from scribe.models import async_session
from scribe.models.group import GroupMembership
from scribe.models.note import Note
from scribe.models.project import Project
from scribe.models.share import NoteShare, ProjectShare
logger = logging.getLogger(__name__)
@@ -4,8 +4,8 @@ from datetime import datetime, timezone
from sqlalchemy import select
from fabledassistant.models import async_session
from fabledassistant.models.api_key import ApiKey
from scribe.models import async_session
from scribe.models.api_key import ApiKey
def generate_key() -> str:
@@ -6,12 +6,12 @@ from datetime import datetime, timedelta, timezone
import bcrypt
from sqlalchemy import func, select, update
from fabledassistant.models import async_session
from fabledassistant.models.note import Note
from fabledassistant.models.invitation import InvitationToken
from fabledassistant.models.password_reset import PasswordResetToken
from fabledassistant.models.setting import Setting
from fabledassistant.models.user import User
from scribe.models import async_session
from scribe.models.note import Note
from scribe.models.invitation import InvitationToken
from scribe.models.password_reset import PasswordResetToken
from scribe.models.setting import Setting
from scribe.models.user import User
logger = logging.getLogger(__name__)
@@ -172,7 +172,7 @@ async def delete_user(user_id: int) -> bool:
async def set_registration_open(admin_user_id: int, open: bool) -> None:
from fabledassistant.services.settings import set_setting
from scribe.services.settings import set_setting
await set_setting(admin_user_id, "registration_open", "true" if open else "false")
@@ -3,15 +3,15 @@ from datetime import date, datetime, timezone
from sqlalchemy import select
from fabledassistant.models import async_session
from fabledassistant.models.milestone import Milestone
from fabledassistant.models.note import Note
from fabledassistant.models.note_draft import NoteDraft
from fabledassistant.models.note_version import NoteVersion
from fabledassistant.models.project import Project
from fabledassistant.models.setting import Setting
from fabledassistant.models.task_log import TaskLog
from fabledassistant.models.user import User
from scribe.models import async_session
from scribe.models.milestone import Milestone
from scribe.models.note import Note
from scribe.models.note_draft import NoteDraft
from scribe.models.note_version import NoteVersion
from scribe.models.project import Project
from scribe.models.setting import Setting
from scribe.models.task_log import TaskLog
from scribe.models.user import User
logger = logging.getLogger(__name__)
@@ -8,7 +8,7 @@ from zoneinfo import ZoneInfo
import caldav
import icalendar
from fabledassistant.services.settings import get_all_settings
from scribe.services.settings import get_all_settings
logger = logging.getLogger(__name__)
@@ -194,7 +194,7 @@ async def create_event(
tz = timezone or config.get("caldav_timezone") or None
cal = icalendar.Calendar()
cal.add("prodid", "-//FabledAssistant//EN")
cal.add("prodid", "-//Scribe//EN")
cal.add("version", "2.0")
event = icalendar.Event()
@@ -415,7 +415,7 @@ async def update_event(
# Rebuild ical data and save
cal_data = icalendar.Calendar()
cal_data.add("prodid", "-//FabledAssistant//EN")
cal_data.add("prodid", "-//Scribe//EN")
cal_data.add("version", "2.0")
cal_data.add_component(component)
event_obj.data = cal_data.to_ical().decode("utf-8")
@@ -690,7 +690,7 @@ async def update_todo(
# Rebuild ical data and save
cal_data = icalendar.Calendar()
cal_data.add("prodid", "-//FabledAssistant//EN")
cal_data.add("prodid", "-//Scribe//EN")
cal_data.add("version", "2.0")
cal_data.add_component(component)
todo_obj.data = cal_data.to_ical().decode("utf-8")
@@ -13,8 +13,8 @@ from typing import Any
from sqlalchemy import select, update
from fabledassistant.models import async_session
from fabledassistant.models.event import Event
from scribe.models import async_session
from scribe.models.event import Event
logger = logging.getLogger(__name__)
@@ -112,7 +112,7 @@ async def sync_user_events(user_id: int) -> dict:
Returns a summary dict: {created, updated, unchanged}.
"""
from fabledassistant.services.caldav import get_caldav_config, is_caldav_configured # noqa: PLC0415
from scribe.services.caldav import get_caldav_config, is_caldav_configured # noqa: PLC0415
if not await is_caldav_configured(user_id):
return {"skipped": True, "reason": "CalDAV not configured"}
@@ -234,7 +234,7 @@ async def sync_all_users() -> None:
"""Pull CalDAV events for all users with CalDAV configured."""
from sqlalchemy import select as sa_select # noqa: PLC0415
from fabledassistant.models.user import User # noqa: PLC0415
from scribe.models.user import User # noqa: PLC0415
async with async_session() as session:
result = await session.execute(sa_select(User.id))
@@ -11,11 +11,11 @@ from datetime import datetime, timedelta, timezone
from sqlalchemy import case, func, select
from fabledassistant.models import async_session
from fabledassistant.models.note import Note
from fabledassistant.models.project import Project
from fabledassistant.models.milestone import Milestone
from fabledassistant.services import milestones as milestones_svc
from scribe.models import async_session
from scribe.models.note import Note
from scribe.models.project import Project
from scribe.models.milestone import Milestone
from scribe.services import milestones as milestones_svc
logger = logging.getLogger(__name__)
@@ -145,7 +145,7 @@ async def _recently_completed(user_id: int) -> list[dict]:
async def _upcoming_events(user_id: int) -> list[dict]:
from fabledassistant.services import events as events_svc
from scribe.services import events as events_svc
now = datetime.now(timezone.utc)
rows = await events_svc.list_events(user_id, now, now + timedelta(days=WINDOW_DAYS))
out = []
@@ -86,7 +86,7 @@ def _process_rss_mb() -> float | None:
def _db_pool_stats() -> dict[str, Any]:
"""Pool checked-in / checked-out / overflow. Direct from SQLAlchemy."""
try:
from fabledassistant.models import engine
from scribe.models import engine
pool = engine.pool
# Async engines wrap a sync pool; .checkedin() / .checkedout() exist
# on the underlying QueuePool. Attribute access is documented but
@@ -172,7 +172,7 @@ def _setup_file_logger() -> logging.Logger:
return _file_logger
try:
_DIAG_DIR.mkdir(parents=True, exist_ok=True)
flog = logging.getLogger("fabledassistant.diagnostics.persistent")
flog = logging.getLogger("scribe.diagnostics.persistent")
flog.setLevel(logging.INFO)
flog.propagate = False # don't double-log into the root stdout stream
# Don't re-add handlers across module reloads / re-runs.
@@ -6,10 +6,10 @@ from email.message import EmailMessage
import aiosmtplib
from sqlalchemy import select
from fabledassistant.config import Config
from fabledassistant.models import async_session
from fabledassistant.models.setting import Setting
from fabledassistant.models.user import User
from scribe.config import Config
from scribe.models import async_session
from scribe.models.setting import Setting
from scribe.models.user import User
logger = logging.getLogger(__name__)
@@ -16,9 +16,9 @@ import os
from sqlalchemy import delete, select
from fabledassistant.models import async_session
from fabledassistant.models.embedding import NoteEmbedding
from fabledassistant.models.note import Note
from scribe.models import async_session
from scribe.models.embedding import NoteEmbedding
from scribe.models.note import Note
logger = logging.getLogger(__name__)
@@ -19,8 +19,8 @@ from apscheduler.triggers.interval import IntervalTrigger
from dateutil.rrule import rrulestr
from sqlalchemy import and_, or_, select
from fabledassistant.models import async_session
from fabledassistant.models.event import Event
from scribe.models import async_session
from scribe.models.event import Event
logger = logging.getLogger(__name__)
@@ -84,7 +84,7 @@ async def _fire_reminders() -> None:
return
# Deliver via the in-app notification feed (push was removed in Phase 8).
from fabledassistant.services.notifications import create_in_app_notification
from scribe.services.notifications import create_in_app_notification
async with async_session() as session:
for event_id, occurrence_start in to_notify:
@@ -116,7 +116,7 @@ def _run_reminders(loop: asyncio.AbstractEventLoop) -> None:
# ---------------------------------------------------------------------------
async def _run_caldav_sync() -> None:
from fabledassistant.services.caldav_sync import sync_all_users # noqa: PLC0415
from scribe.services.caldav_sync import sync_all_users # noqa: PLC0415
try:
await sync_all_users()
except Exception:
@@ -132,7 +132,7 @@ def _run_caldav_sync_threadsafe(loop: asyncio.AbstractEventLoop) -> None:
# ---------------------------------------------------------------------------
async def _run_recurrence_spawn() -> None:
from fabledassistant.services.recurrence import spawn_recurring_tasks # noqa: PLC0415
from scribe.services.recurrence import spawn_recurring_tasks # noqa: PLC0415
try:
await spawn_recurring_tasks()
except Exception:
@@ -18,8 +18,8 @@ from datetime import datetime, timedelta, timezone
from dateutil.rrule import rrulestr
from sqlalchemy import or_, select
from fabledassistant.models import async_session
from fabledassistant.models.event import Event
from scribe.models import async_session
from scribe.models.event import Event
logger = logging.getLogger(__name__)
@@ -76,7 +76,7 @@ async def _localize_naive(user_id: int, dt: datetime | None) -> datetime | None:
asyncpg store the correct UTC instant, matching the REST/UI path.
"""
if dt is not None and dt.tzinfo is None:
from fabledassistant.services.tz import get_user_tz # noqa: PLC0415
from scribe.services.tz import get_user_tz # noqa: PLC0415
return dt.replace(tzinfo=await get_user_tz(user_id))
return dt
@@ -398,7 +398,7 @@ async def find_events_by_query(user_id: int, query: str) -> list[Event]:
async def _push_create(event: Event, user_id: int, extra: dict) -> None:
try:
from fabledassistant.services.caldav import (
from scribe.services.caldav import (
create_event as caldav_create,
is_caldav_configured,
)
@@ -438,7 +438,7 @@ async def _push_update(event: Event, user_id: int, old_title: str = "") -> None:
if not event.caldav_uid:
return
try:
from fabledassistant.services.caldav import (
from scribe.services.caldav import (
update_event as caldav_update,
is_caldav_configured,
)
@@ -466,7 +466,7 @@ async def _push_update(event: Event, user_id: int, old_title: str = "") -> None:
async def _push_delete(caldav_uid: str, event_title: str, user_id: int) -> None:
"""Push a delete to CalDAV. Uses event_title to locate the event by SUMMARY."""
try:
from fabledassistant.services.caldav import (
from scribe.services.caldav import (
delete_event as caldav_delete,
is_caldav_configured,
)
@@ -5,9 +5,9 @@ import logging
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
from fabledassistant.models import async_session
from fabledassistant.models.group import Group, GroupMembership
from fabledassistant.models.user import User
from scribe.models import async_session
from scribe.models.group import Group, GroupMembership
from scribe.models.user import User
logger = logging.getLogger(__name__)
@@ -3,8 +3,8 @@ import logging
from sqlalchemy import func, select
from fabledassistant.models import async_session
from fabledassistant.models.note import Note
from scribe.models import async_session
from scribe.models.note import Note
logger = logging.getLogger(__name__)
@@ -174,7 +174,7 @@ async def _semantic_knowledge_search(
# 2. Semantic search — conceptual similarity
semantic_notes: list[Note] = []
try:
from fabledassistant.services.embeddings import semantic_search_notes
from scribe.services.embeddings import semantic_search_notes
is_task_filter = True if note_type in ("task", "plan") else (False if note_type else None)
candidates = await semantic_search_notes(
user_id=user_id,
@@ -8,9 +8,9 @@ from datetime import datetime, timedelta, timezone
from sqlalchemy import delete, func, select, text
from fabledassistant.config import Config
from fabledassistant.models import async_session
from fabledassistant.models.app_log import AppLog
from scribe.config import Config
from scribe.models import async_session
from scribe.models.app_log import AppLog
logger = logging.getLogger(__name__)
@@ -4,9 +4,9 @@ from datetime import datetime, timezone
from sqlalchemy import func, select
from fabledassistant.models import async_session
from fabledassistant.models.milestone import Milestone
from fabledassistant.models.note import Note
from scribe.models import async_session
from scribe.models.milestone import Milestone
from scribe.models.note import Note
logger = logging.getLogger(__name__)
@@ -1,7 +1,7 @@
from sqlalchemy import select, text
from fabledassistant.models import async_session
from fabledassistant.models.note_draft import NoteDraft
from scribe.models import async_session
from scribe.models.note_draft import NoteDraft
async def upsert_draft(
@@ -2,8 +2,8 @@ from datetime import datetime, timezone
from sqlalchemy import select, text
from fabledassistant.models import async_session
from fabledassistant.models.note_version import NoteVersion
from scribe.models import async_session
from scribe.models.note_version import NoteVersion
# Maximum snapshots retained per note.
MAX_VERSIONS = 50
@@ -4,8 +4,8 @@ from datetime import date, datetime, timezone
from sqlalchemy import func, or_, select, text
from fabledassistant.models import async_session
from fabledassistant.models.note import Note, TaskPriority, TaskStatus
from scribe.models import async_session
from scribe.models.note import Note, TaskPriority, TaskStatus
logger = logging.getLogger(__name__)
@@ -35,7 +35,7 @@ def _strip_type_nouns(q: str) -> list[str]:
async def _maybe_reactivate_project(project_id: int) -> None:
"""If a project is paused, reactivate it — activity indicates resumed work."""
from fabledassistant.models.project import Project
from scribe.models.project import Project
try:
async with async_session() as session:
project = (await session.execute(
@@ -82,7 +82,7 @@ async def create_note(
# Auto-populate project_id from milestone when not explicitly provided
if milestone_id is not None and project_id is None:
from fabledassistant.models.milestone import Milestone
from scribe.models.milestone import Milestone
async with async_session() as lookup:
result = await lookup.execute(
select(Milestone).where(Milestone.id == milestone_id, Milestone.user_id == user_id)
@@ -227,7 +227,7 @@ async def list_notes(
count_query = count_query.where(Note.project_id.is_(None))
if exclude_paused_projects:
from fabledassistant.models.project import Project
from scribe.models.project import Project
paused_ids = (
select(Project.id)
.where(Project.user_id == user_id, Project.status == "paused")
@@ -308,7 +308,7 @@ async def update_note(user_id: int, note_id: int, **fields: object) -> Note | No
elif note.status in (TaskStatus.done.value, TaskStatus.cancelled.value):
note.completed_at = _now
if note.recurrence_rule:
from fabledassistant.services.recurrence import calculate_next_due
from scribe.services.recurrence import calculate_next_due
base = note.due_date or _now.date()
next_due = calculate_next_due(note.recurrence_rule, base)
note.recurrence_next_spawn_at = datetime(
@@ -324,7 +324,7 @@ async def update_note(user_id: int, note_id: int, **fields: object) -> Note | No
# Create a version snapshot when body actually changes
if "body" in fields and fields["body"] != old_body:
from fabledassistant.services.note_versions import create_version
from scribe.services.note_versions import create_version
await create_version(user_id, note_id, old_body, old_title, old_tags)
if note.project_id is not None:
@@ -501,7 +501,7 @@ async def build_note_graph(
project_ids = {n.project_id for n in notes if n.project_id is not None}
project_colors: dict[int, str] = {}
if project_ids:
from fabledassistant.models.project import Project
from scribe.models.project import Project
async with async_session() as session:
result = await session.execute(
select(Project.id, Project.color).where(Project.id.in_(project_ids))
@@ -572,7 +572,7 @@ async def get_note_for_user(
accessing_user_id: int, note_id: int
) -> tuple["Note", str] | None:
"""Returns (note, permission) if user has any access, else None."""
from fabledassistant.services.access import get_note_permission
from scribe.services.access import get_note_permission
perm = await get_note_permission(accessing_user_id, note_id)
if perm is None:
return None
@@ -7,13 +7,13 @@ from datetime import date, datetime, timezone
from sqlalchemy import func, select, text
from fabledassistant.models import async_session
from fabledassistant.models.app_log import AppLog
from fabledassistant.models.note import Note
from fabledassistant.models.setting import Setting
from fabledassistant.models.user import User
from fabledassistant.services.email import _email_html, is_smtp_configured, send_email
from fabledassistant.services.logging import log_audit
from scribe.models import async_session
from scribe.models.app_log import AppLog
from scribe.models.note import Note
from scribe.models.setting import Setting
from scribe.models.user import User
from scribe.services.email import _email_html, is_smtp_configured, send_email
from scribe.services.logging import log_audit
logger = logging.getLogger(__name__)
@@ -245,7 +245,7 @@ async def purge_old_read_notifications(retention_days: int = _NOTIFICATION_RETEN
"""Delete already-read in-app notifications older than retention_days."""
from datetime import timedelta
from sqlalchemy import delete
from fabledassistant.models.notification import Notification
from scribe.models.notification import Notification
cutoff = datetime.now(timezone.utc) - timedelta(days=retention_days)
async with async_session() as session:
result = await session.execute(
@@ -285,7 +285,7 @@ def start_notification_loop() -> None:
async def create_in_app_notification(user_id: int, notif_type: str, payload: dict):
"""Create an in-app Notification record."""
from fabledassistant.models.notification import Notification
from scribe.models.notification import Notification
async with async_session() as session:
n = Notification(user_id=user_id, type=notif_type, payload=payload)
session.add(n)
@@ -308,7 +308,7 @@ async def _fire_share_email(user_id: int, subject: str, body_text: str) -> None:
async def _group_member_ids(group_id: int) -> list[int]:
from fabledassistant.models.group import GroupMembership
from scribe.models.group import GroupMembership
async with async_session() as session:
rows = (await session.execute(
select(GroupMembership.user_id).where(GroupMembership.group_id == group_id)
@@ -323,7 +323,7 @@ async def notify_project_shared(
target_user_id: int | None,
target_group_id: int | None,
) -> None:
from fabledassistant.models.project import Project
from scribe.models.project import Project
async with async_session() as session:
project = await session.get(Project, project_id)
inviter = await session.get(User, invited_by_user_id)
@@ -357,7 +357,7 @@ async def notify_note_shared(
target_user_id: int | None,
target_group_id: int | None,
) -> None:
from fabledassistant.models.note import Note
from scribe.models.note import Note
async with async_session() as session:
note = await session.get(Note, note_id)
inviter = await session.get(User, invited_by_user_id)
@@ -388,7 +388,7 @@ async def notify_note_shared(
async def notify_group_added(
group_id: int, role: str, invited_by_user_id: int, target_user_id: int
) -> None:
from fabledassistant.models.group import Group
from scribe.models.group import Group
async with async_session() as session:
group = await session.get(Group, group_id)
inviter = await session.get(User, invited_by_user_id)
@@ -409,7 +409,7 @@ async def notify_group_added(
async def list_in_app_notifications(user_id: int, unread_only: bool = True) -> list[dict]:
from fabledassistant.models.notification import Notification
from scribe.models.notification import Notification
async with async_session() as session:
q = select(Notification).where(Notification.user_id == user_id)
if unread_only:
@@ -420,7 +420,7 @@ async def list_in_app_notifications(user_id: int, unread_only: bool = True) -> l
async def unread_notification_count(user_id: int) -> int:
from fabledassistant.models.notification import Notification
from scribe.models.notification import Notification
async with async_session() as session:
result = await session.execute(
select(func.count()).where(
@@ -432,7 +432,7 @@ async def unread_notification_count(user_id: int) -> int:
async def mark_notification_read(user_id: int, notification_id: int) -> bool:
from fabledassistant.models.notification import Notification
from scribe.models.notification import Notification
from datetime import timezone as tz
async with async_session() as session:
n = (await session.execute(
@@ -450,7 +450,7 @@ async def mark_notification_read(user_id: int, notification_id: int) -> bool:
async def mark_all_notifications_read(user_id: int) -> int:
from fabledassistant.models.notification import Notification
from scribe.models.notification import Notification
from datetime import datetime, timezone as tz
from sqlalchemy import update as sa_update
async with async_session() as session:
@@ -3,9 +3,9 @@ import urllib.parse
import httpx
from fabledassistant.config import Config
from fabledassistant.models import async_session
from fabledassistant.models.user import User
from scribe.config import Config
from scribe.models import async_session
from scribe.models.user import User
from sqlalchemy import select
logger = logging.getLogger(__name__)
@@ -4,9 +4,9 @@ in Scribe and rules surface at the planning moment.
"""
from __future__ import annotations
from fabledassistant.services import notes as notes_svc
from fabledassistant.services import projects as projects_svc
from fabledassistant.services import rulebooks as rulebooks_svc
from scribe.services import notes as notes_svc
from scribe.services import projects as projects_svc
from scribe.services import rulebooks as rulebooks_svc
PLAN_TEMPLATE = """## Goal
@@ -4,9 +4,9 @@ from datetime import datetime, timezone
from sqlalchemy import func, select
from fabledassistant.models import async_session
from fabledassistant.models.note import Note
from fabledassistant.models.project import Project, ProjectStatus
from scribe.models import async_session
from scribe.models.note import Note
from scribe.models.project import Project, ProjectStatus
logger = logging.getLogger(__name__)
@@ -167,7 +167,7 @@ async def get_project_summary(user_id: int, project_id: int) -> dict:
)
)
from fabledassistant.services.milestones import get_project_milestone_summary
from scribe.services.milestones import get_project_milestone_summary
milestone_summary = await get_project_milestone_summary(user_id, project_id)
return {
@@ -186,7 +186,7 @@ async def get_project_for_user(
accessing_user_id: int, project_id: int
) -> tuple[Project, str] | None:
"""Returns (project, permission) if user has any access, else None."""
from fabledassistant.services.access import get_project_permission
from scribe.services.access import get_project_permission
perm = await get_project_permission(accessing_user_id, project_id)
if perm is None:
return None
@@ -197,9 +197,9 @@ async def get_project_for_user(
async def list_projects_for_user(user_id: int, status: str | None = None) -> list[dict]:
"""Owned projects + shared projects, each dict has 'permission' field."""
from fabledassistant.models.group import GroupMembership
from fabledassistant.models.share import ProjectShare
from fabledassistant.services.access import PERMISSION_RANK
from scribe.models.group import GroupMembership
from scribe.models.share import ProjectShare
from scribe.services.access import PERMISSION_RANK
owned = await list_projects(user_id, status)
owned_ids = {p.id for p in owned}
@@ -6,8 +6,8 @@ from datetime import date, datetime, timedelta, timezone
from sqlalchemy import and_, select
from fabledassistant.models import async_session
from fabledassistant.models.note import Note, TaskStatus
from scribe.models import async_session
from scribe.models.note import Note, TaskStatus
logger = logging.getLogger(__name__)
@@ -103,8 +103,8 @@ async def spawn_recurring_tasks() -> int:
Returns the number of tasks spawned.
"""
from fabledassistant.services.embeddings import upsert_note_embedding
from fabledassistant.services.notes import create_note
from scribe.services.embeddings import upsert_note_embedding
from scribe.services.notes import create_note
now = datetime.now(timezone.utc)
spawned = 0
@@ -12,8 +12,8 @@ from typing import Optional
from sqlalchemy import select
from fabledassistant.models import async_session
from fabledassistant.models.rulebook import Rulebook
from scribe.models import async_session
from scribe.models.rulebook import Rulebook
logger = logging.getLogger(__name__)
@@ -114,7 +114,7 @@ async def find_rulebook_by_title(
# ── Topic CRUD ──────────────────────────────────────────────────────────
from fabledassistant.models.rulebook import RulebookTopic
from scribe.models.rulebook import RulebookTopic
async def _assert_rulebook_owned(session, rulebook_id: int, user_id: int) -> None:
@@ -223,7 +223,7 @@ async def delete_topic(topic_id: int, user_id: int) -> None:
# ── Rule CRUD ──────────────────────────────────────────────────────────
from fabledassistant.models.rulebook import Rule
from scribe.models.rulebook import Rule
async def _assert_topic_owned(session, topic_id: int, user_id: int) -> None:
@@ -244,7 +244,7 @@ async def _assert_topic_owned(session, topic_id: int, user_id: int) -> None:
async def _assert_project_owned(session, project_id: int, user_id: int) -> None:
"""Raise ValueError if project doesn't exist or isn't owned by user."""
from fabledassistant.models.project import Project
from scribe.models.project import Project
result = await session.execute(
select(Project).where(
Project.id == project_id,
@@ -263,7 +263,7 @@ async def _assert_rulebook_rule_owned(session, rule_id: int, user_id: int) -> No
suppressible they belong to the project; delete them instead. This
helper deliberately excludes them.
"""
from fabledassistant.models.rulebook import Rule
from scribe.models.rulebook import Rule
result = await session.execute(
select(Rule)
.join(RulebookTopic, Rule.topic_id == RulebookTopic.id)
@@ -341,7 +341,7 @@ async def list_rules(
returned adding all of a user's project-scoped rules unprompted would
surprise existing callers.
"""
from fabledassistant.models.rulebook import project_rulebook_subscriptions
from scribe.models.rulebook import project_rulebook_subscriptions
async with async_session() as session:
stmt = (
@@ -378,7 +378,7 @@ async def list_rules(
# Project-scoped rules (topic_id IS NULL, project_id matches).
# Verifies ownership by joining Project on user_id.
from fabledassistant.models.project import Project
from scribe.models.project import Project
proj_stmt = (
select(Rule)
.join(Project, Rule.project_id == Project.id)
@@ -426,7 +426,7 @@ async def _fetch_owned_rule(session, rule_id: int, user_id: int) -> Optional[Rul
(via topic) or its project (via project_id). Honors soft-delete.
Returns None when not found or not owned.
"""
from fabledassistant.models.project import Project
from scribe.models.project import Project
# Path A — rulebook rule.
rulebook_rule = (await session.execute(
@@ -495,7 +495,7 @@ async def subscribe_project(
project_id: int, rulebook_id: int, user_id: int,
) -> None:
"""Add a subscription. Idempotent — duplicates raise; we swallow."""
from fabledassistant.models.rulebook import project_rulebook_subscriptions
from scribe.models.rulebook import project_rulebook_subscriptions
async with async_session() as session:
await _assert_project_owned(session, project_id, user_id)
@@ -515,7 +515,7 @@ async def subscribe_project(
async def unsubscribe_project(
project_id: int, rulebook_id: int, user_id: int,
) -> None:
from fabledassistant.models.rulebook import project_rulebook_subscriptions
from scribe.models.rulebook import project_rulebook_subscriptions
async with async_session() as session:
await _assert_project_owned(session, project_id, user_id)
@@ -535,7 +535,7 @@ async def suppress_rule_for_project(
project_id: int, rule_id: int, user_id: int,
) -> None:
"""Mute one rulebook rule for one project. Idempotent."""
from fabledassistant.models.rulebook import project_rule_suppressions
from scribe.models.rulebook import project_rule_suppressions
async with async_session() as session:
await _assert_project_owned(session, project_id, user_id)
@@ -555,7 +555,7 @@ async def unsuppress_rule_for_project(
project_id: int, rule_id: int, user_id: int,
) -> None:
"""Unmute one rulebook rule for one project. Idempotent."""
from fabledassistant.models.rulebook import project_rule_suppressions
from scribe.models.rulebook import project_rule_suppressions
async with async_session() as session:
await _assert_project_owned(session, project_id, user_id)
@@ -572,7 +572,7 @@ async def suppress_topic_for_project(
project_id: int, topic_id: int, user_id: int,
) -> None:
"""Mute every rule under one topic for one project. Idempotent."""
from fabledassistant.models.rulebook import project_topic_suppressions
from scribe.models.rulebook import project_topic_suppressions
async with async_session() as session:
await _assert_project_owned(session, project_id, user_id)
@@ -592,7 +592,7 @@ async def unsuppress_topic_for_project(
project_id: int, topic_id: int, user_id: int,
) -> None:
"""Unmute a topic for one project. Idempotent."""
from fabledassistant.models.rulebook import project_topic_suppressions
from scribe.models.rulebook import project_topic_suppressions
async with async_session() as session:
await _assert_project_owned(session, project_id, user_id)
@@ -633,7 +633,7 @@ async def get_applicable_rules(
titles + rulebook context callers need to display what was filtered
without round-tripping for names.
"""
from fabledassistant.models.rulebook import (
from scribe.models.rulebook import (
project_rulebook_subscriptions,
project_rule_suppressions,
project_topic_suppressions,
@@ -753,7 +753,7 @@ async def get_applicable_rules(
]
# Project-scoped rules — verifies ownership via Project.user_id.
from fabledassistant.models.project import Project
from scribe.models.project import Project
proj_rules_q = (
select(Rule.id, Rule.title, Rule.statement)
.join(Project, Rule.project_id == Project.id)
@@ -2,8 +2,8 @@ import logging
from sqlalchemy import delete as sa_delete, select
from fabledassistant.models import async_session
from fabledassistant.models.setting import Setting
from scribe.models import async_session
from scribe.models.setting import Setting
logger = logging.getLogger(__name__)
@@ -4,12 +4,12 @@ import logging
from sqlalchemy import select
from fabledassistant.models import async_session
from fabledassistant.models.group import Group, GroupMembership
from fabledassistant.models.note import Note
from fabledassistant.models.project import Project
from fabledassistant.models.share import NoteShare, ProjectShare
from fabledassistant.models.user import User
from scribe.models import async_session
from scribe.models.group import Group, GroupMembership
from scribe.models.note import Note
from scribe.models.project import Project
from scribe.models.share import NoteShare, ProjectShare
from scribe.models.user import User
logger = logging.getLogger(__name__)
@@ -33,7 +33,7 @@ async def _enrich_shares(session, shares) -> list[dict]:
def _deduplicate_by_permission(shares, id_attr: str) -> dict[int, str]:
"""Return {resource_id: best_permission} keeping the highest-ranked permission per resource."""
from fabledassistant.services.access import PERMISSION_RANK
from scribe.services.access import PERMISSION_RANK
seen: dict[int, str] = {}
for share in shares:
rid = getattr(share, id_attr)
@@ -57,7 +57,7 @@ async def share_project(
) -> ProjectShare | None:
if permission not in VALID_PERMISSIONS:
return None
from fabledassistant.services.access import can_admin_project
from scribe.services.access import can_admin_project
if not await can_admin_project(acting_user_id, project_id):
return None
async with async_session() as session:
@@ -83,7 +83,7 @@ async def update_project_share(
share = await session.get(ProjectShare, share_id)
if not share:
return None
from fabledassistant.services.access import can_admin_project
from scribe.services.access import can_admin_project
if not await can_admin_project(acting_user_id, share.project_id):
return None
share.permission = permission
@@ -97,7 +97,7 @@ async def remove_project_share(acting_user_id: int, share_id: int) -> bool:
share = await session.get(ProjectShare, share_id)
if not share:
return False
from fabledassistant.services.access import can_admin_project
from scribe.services.access import can_admin_project
if not await can_admin_project(acting_user_id, share.project_id):
return False
await session.delete(share)
@@ -127,7 +127,7 @@ async def share_note(
) -> NoteShare | None:
if permission not in VALID_PERMISSIONS:
return None
from fabledassistant.services.access import get_note_permission
from scribe.services.access import get_note_permission
perm = await get_note_permission(acting_user_id, note_id)
if perm not in ("admin", "owner"):
return None
@@ -154,7 +154,7 @@ async def update_note_share(
share = await session.get(NoteShare, share_id)
if not share:
return None
from fabledassistant.services.access import get_note_permission
from scribe.services.access import get_note_permission
perm = await get_note_permission(acting_user_id, share.note_id)
if perm not in ("admin", "owner"):
return None
@@ -169,7 +169,7 @@ async def remove_note_share(acting_user_id: int, share_id: int) -> bool:
share = await session.get(NoteShare, share_id)
if not share:
return False
from fabledassistant.services.access import get_note_permission
from scribe.services.access import get_note_permission
perm = await get_note_permission(acting_user_id, share.note_id)
if perm not in ("admin", "owner"):
return False
@@ -4,9 +4,9 @@ from datetime import datetime, timezone
from sqlalchemy import select
from fabledassistant.models import async_session
from fabledassistant.models.task_log import TaskLog
from fabledassistant.models.note import Note
from scribe.models import async_session
from scribe.models.task_log import TaskLog
from scribe.models.note import Note
logger = logging.getLogger(__name__)
@@ -12,12 +12,12 @@ from datetime import datetime, timezone
from sqlalchemy import or_, select, update
from fabledassistant.models import async_session
from fabledassistant.models.note import Note
from fabledassistant.models.event import Event
from fabledassistant.models.project import Project
from fabledassistant.models.milestone import Milestone
from fabledassistant.models.rulebook import Rulebook, RulebookTopic, Rule
from scribe.models import async_session
from scribe.models.note import Note
from scribe.models.event import Event
from scribe.models.project import Project
from scribe.models.milestone import Milestone
from scribe.models.rulebook import Rulebook, RulebookTopic, Rule
# entity_type -> Model. Used to resolve which table a trash op targets.
_MODEL_FOR = {
@@ -90,7 +90,7 @@ async def _cascade(session, user_id: int, etype: str, eid: int, batch: str, now)
# soft-delete path keeps the project row alive; this guarantees the
# rows are gone whether or not the project ever gets purged.
from sqlalchemy import delete as _sql_delete
from fabledassistant.models.rulebook import (
from scribe.models.rulebook import (
project_rule_suppressions, project_topic_suppressions,
)
await session.execute(
@@ -169,7 +169,7 @@ async def delete(user_id: int, entity_type: str, entity_id: int) -> str | None:
# copy lingers forever (and re-appears on any client syncing that server).
if caldav_event:
import asyncio
from fabledassistant.services.events import _push_delete
from scribe.services.events import _push_delete
asyncio.create_task(_push_delete(caldav_event[0], caldav_event[1], user_id))
return batch
@@ -13,8 +13,8 @@ import logging
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from fabledassistant.services import trash as trash_svc
from fabledassistant.services.settings import get_setting
from scribe.services import trash as trash_svc
from scribe.services.settings import get_setting
logger = logging.getLogger(__name__)
@@ -32,8 +32,8 @@ def _run_purge_threadsafe() -> None:
try:
from sqlalchemy import select
from fabledassistant.models import async_session
from fabledassistant.models.user import User
from scribe.models import async_session
from scribe.models.user import User
async with async_session() as session:
user_ids = (await session.execute(select(User.id))).scalars().all()
@@ -11,7 +11,7 @@ from __future__ import annotations
from datetime import date, datetime, timedelta
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
from fabledassistant.services.settings import get_setting
from scribe.services.settings import get_setting
# Day-rollover boundary for journal/day-anchored views. The "day" flips at
# this local hour (not midnight) so the 00:0004:00 local window still

Some files were not shown because too many files have changed in this diff Show More