File size: 1,948 Bytes
070c931
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""One "All posts" notebook per workspace

Two requests opening the Notebooks page at once could each create the archive notebook. This merges the copies into
the oldest one (its posts, chats and artifacts move over) and adds a unique index so it cannot happen again.

Revision ID: 0011_one_archive_notebook
Revises: 0010_b2v_video_refs
Create Date: 2026-10-01
"""
import sqlalchemy as sa
from alembic import op

revision = "0011_one_archive_notebook"
down_revision = "0010_b2v_video_refs"
branch_labels = None
depends_on = None

INDEX = "uq_notebooks_one_archive"


def upgrade() -> None:
    conn = op.get_bind()
    rows = conn.execute(sa.text(
        "SELECT workspace_id, id FROM notebooks WHERE is_archive ORDER BY workspace_id, created_at, id"
    )).all()
    keep: dict = {}
    for ws, nb in rows:
        if ws not in keep:
            keep[ws] = nb
            continue
        ids = {"keep": keep[ws], "dup": nb}
        conn.execute(sa.text(
            "INSERT INTO notebook_documents (notebook_id, document_id) "
            "SELECT :keep, document_id FROM notebook_documents WHERE notebook_id = :dup AND document_id NOT IN "
            "(SELECT document_id FROM notebook_documents WHERE notebook_id = :keep)"
        ), ids)
        conn.execute(sa.text("UPDATE chats SET notebook_id = :keep WHERE notebook_id = :dup"), ids)
        conn.execute(sa.text("UPDATE artifacts SET notebook_id = :keep WHERE notebook_id = :dup"), ids)
        conn.execute(sa.text("DELETE FROM notebooks WHERE id = :dup"), ids)

    # 0001 builds from the live models, so a fresh database may already have the index.
    if INDEX not in {i["name"] for i in sa.inspect(conn).get_indexes("notebooks")}:
        op.create_index(INDEX, "notebooks", ["workspace_id"], unique=True,
                        postgresql_where=sa.text("is_archive"), sqlite_where=sa.text("is_archive"))


def downgrade() -> None:
    op.drop_index(INDEX, table_name="notebooks")