File size: 3,384 Bytes
6ca3b92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
from __future__ import annotations

from sqlalchemy import delete, select, update
from sqlalchemy.orm import Session

from app.models.chat_session import ChatSession
from app.models.document import Document
from app.models.flashcard import FlashcardSet
from app.models.generation import Generation
from app.models.generation_cache import GenerationCache
from app.models.learning_state import GeneratedResource
from app.models.previous_paper import PreviousPaper
from app.models.quiz import Quiz
from app.models.study_profile import StudyProfile
from app.models.syllabus_item import SyllabusItem
from app.models.video_render_job import VideoRenderJob
from app.services.file_storage import delete_upload_file


def delete_document_and_derivatives(db: Session, document: Document) -> None:
    """Delete one document and all account data derived directly from it.

    Callers must perform ownership checks before invoking this service. Database
    changes are committed together, then the physical upload is removed. A
    rendered video is allowed to outlive its source, but its source reference is
    cleared so no stale document link remains.
    """

    document_id = document.id
    user_id = document.user_id
    file_path = document.file_path

    db.execute(delete(Generation).where(Generation.document_id == document_id))
    db.execute(delete(Quiz).where(Quiz.document_id == document_id))
    db.execute(
        delete(FlashcardSet).where(FlashcardSet.document_id == document_id),
    )
    db.execute(
        delete(SyllabusItem)
        .where(SyllabusItem.user_id == user_id)
        .where(SyllabusItem.source_id == document_id),
    )
    db.execute(
        delete(GeneratedResource)
        .where(GeneratedResource.user_id == user_id)
        .where(GeneratedResource.source_id == document_id),
    )

    # Delete through the ORM so related questions/messages follow their
    # delete-orphan cascades even when a local SQLite test DB does not enforce
    # database-level cascades in exactly the same way as Postgres.
    previous_papers = db.scalars(
        select(PreviousPaper)
        .where(PreviousPaper.user_id == user_id)
        .where(PreviousPaper.file_path == file_path),
    ).all()
    for paper in previous_papers:
        db.delete(paper)

    linked_sessions = db.scalars(
        select(ChatSession)
        .where(ChatSession.user_id == user_id)
        .where(ChatSession.source_id == document_id),
    ).all()
    for session in linked_sessions:
        db.delete(session)

    # Cache keys are hashed, so metadata is the portable source of truth across
    # SQLite and Postgres. Remove any legacy row whose unhashed key still
    # contains the document id as well.
    for cache_row in db.scalars(select(GenerationCache)).all():
        metadata = cache_row.metadata_json or {}
        if (
            metadata.get("document_id") == document_id
            or document_id in cache_row.cache_key
        ):
            db.delete(cache_row)

    db.execute(
        update(VideoRenderJob)
        .where(VideoRenderJob.source_document_id == document_id)
        .values(source_document_id=None),
    )
    db.execute(
        update(StudyProfile)
        .where(StudyProfile.uploaded_document_id == document_id)
        .values(uploaded_document_id=None),
    )

    db.delete(document)
    db.commit()
    delete_upload_file(file_path)