database id fixes
This commit is contained in:
35
app.py
35
app.py
@@ -7,7 +7,7 @@ from textual.app import App, ComposeResult
|
||||
from textual.containers import Container, Horizontal, Vertical
|
||||
from textual.widgets import Header, Footer, Input, Button, Select, ListView, ListItem, DataTable, Label, Static
|
||||
from textual.screen import ModalScreen
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, cast
|
||||
from database import Database, Session
|
||||
from datetime import datetime
|
||||
import os
|
||||
@@ -31,7 +31,8 @@ class MoveCopyDialog(ModalScreen):
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
# Get currently selected project from tree to exclude from destination list
|
||||
current_project_id = self.app.filter_project_id if hasattr(self.app, 'filter_project_id') else None
|
||||
main_app = cast("SessionMoverApp", self.app)
|
||||
current_project_id = main_app.filter_project_id if hasattr(main_app, 'filter_project_id') else None
|
||||
|
||||
project_options = sorted(
|
||||
[
|
||||
@@ -68,7 +69,7 @@ class MoveCopyDialog(ModalScreen):
|
||||
yield Button("Confirm", variant="primary", id="confirm-btn", disabled=True)
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.query_one("#dialog").border_title = f"{'Copy' if self.is_copy else 'Move'} Sessions"
|
||||
self.query_one("#dialog", Container).border_title = f"{'Copy' if self.is_copy else 'Move'} Sessions"
|
||||
|
||||
def on_select_changed(self, event):
|
||||
if event.select.id == "project-select":
|
||||
@@ -77,12 +78,12 @@ class MoveCopyDialog(ModalScreen):
|
||||
self.target_workspace = event.value if event.value else None
|
||||
|
||||
self.update_preview()
|
||||
confirm_btn = self.query_one("#confirm-btn")
|
||||
confirm_btn = self.query_one("#confirm-btn", Button)
|
||||
confirm_btn.disabled = not (self.target_project is not None)
|
||||
|
||||
def update_preview(self):
|
||||
if not self.target_project:
|
||||
preview = self.query_one("#sql-preview")
|
||||
preview = self.query_one("#sql-preview", Static)
|
||||
preview.update("Select a project to see preview")
|
||||
return
|
||||
|
||||
@@ -111,7 +112,7 @@ class MoveCopyDialog(ModalScreen):
|
||||
if self.is_copy:
|
||||
lines.append("\nNote: Copy creates new sessions with all related data.")
|
||||
|
||||
self.query_one("#sql-preview").update("\n".join(lines))
|
||||
self.query_one("#sql-preview", Static).update("\n".join(lines))
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
if event.button.id == "cancel-btn":
|
||||
@@ -141,13 +142,11 @@ class MoveCopyDialog(ModalScreen):
|
||||
)
|
||||
|
||||
if success:
|
||||
self.app.notify(f"{'Copied' if self.is_copy else 'Moved'} {len(self.sessions)} session(s) successfully!", severity="information")
|
||||
self.app.pop_screen()
|
||||
# Get reference to main app and refresh
|
||||
app = self.app
|
||||
if hasattr(app, 'refresh_sessions'):
|
||||
app.refresh_project_list()
|
||||
app.refresh_sessions()
|
||||
main_app = cast("SessionMoverApp", self.app)
|
||||
main_app.notify(f"{'Copied' if self.is_copy else 'Moved'} {len(self.sessions)} session(s) successfully!", severity="information")
|
||||
main_app.pop_screen()
|
||||
main_app.refresh_project_list()
|
||||
main_app.refresh_sessions()
|
||||
else:
|
||||
self.app.notify(f"Operation failed: {sql}", severity="error")
|
||||
|
||||
@@ -194,7 +193,7 @@ class DeleteConfirmDialog(ModalScreen):
|
||||
yield Button("Delete", variant="error", id="confirm-btn")
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.query_one("#dialog").border_title = "Confirm Delete"
|
||||
self.query_one("#dialog", Container).border_title = "Confirm Delete"
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
if event.button.id == "cancel-btn":
|
||||
@@ -470,7 +469,7 @@ class SessionMoverApp(App):
|
||||
|
||||
def refresh_sessions(self) -> None:
|
||||
"""Update the sessions table."""
|
||||
table = self.query_one("#sessions-table")
|
||||
table = self.query_one("#sessions-table", DataTable)
|
||||
|
||||
# Save cursor position before clearing
|
||||
cursor_row = table.cursor_row
|
||||
@@ -509,7 +508,7 @@ class SessionMoverApp(App):
|
||||
|
||||
# Update selection count
|
||||
count = len(self.selected_session_ids)
|
||||
count_widget = self.query_one("#selection-count")
|
||||
count_widget = self.query_one("#selection-count", Static)
|
||||
if count > 0:
|
||||
count_widget.update(f"✓ {count} session(s) selected")
|
||||
else:
|
||||
@@ -542,7 +541,7 @@ class SessionMoverApp(App):
|
||||
self.filter_project_id = proj_id
|
||||
self.filter_workspace_id = None
|
||||
|
||||
status = self.query_one("#current-selection")
|
||||
status = self.query_one("#current-selection", Static)
|
||||
if proj:
|
||||
status.update(f"📁 {proj.worktree}")
|
||||
|
||||
@@ -555,7 +554,7 @@ class SessionMoverApp(App):
|
||||
|
||||
def action_toggle_selection(self) -> None:
|
||||
"""Toggle selection of current row."""
|
||||
table = self.query_one("#sessions-table")
|
||||
table = self.query_one("#sessions-table", DataTable)
|
||||
row_key = table.cursor_row
|
||||
if row_key is not None:
|
||||
sessions = self.db.get_sessions(
|
||||
|
||||
120
database.py
120
database.py
@@ -217,11 +217,43 @@ class Database:
|
||||
|
||||
def get_session(self, session_id: str) -> Optional[Session]:
|
||||
"""Get a single session by ID."""
|
||||
sessions = self.get_sessions(include_archived=True)
|
||||
for s in sessions:
|
||||
if s.id == session_id:
|
||||
return s
|
||||
return None
|
||||
if self.conn is None:
|
||||
raise RuntimeError("Database is not connected. Call connect() first.")
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute("SELECT * FROM session WHERE id = ?", (session_id,))
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
return None
|
||||
|
||||
sess = Session(
|
||||
id=row["id"],
|
||||
project_id=row["project_id"],
|
||||
workspace_id=row["workspace_id"],
|
||||
parent_id=row["parent_id"],
|
||||
slug=row["slug"],
|
||||
directory=row["directory"],
|
||||
title=row["title"],
|
||||
version=row["version"],
|
||||
time_created=row["time_created"],
|
||||
time_updated=row["time_updated"],
|
||||
time_archived=row["time_archived"],
|
||||
)
|
||||
|
||||
# Message count
|
||||
cursor.execute("SELECT COUNT(*) FROM message WHERE session_id = ?", (session_id,))
|
||||
sess.message_count = cursor.fetchone()[0]
|
||||
|
||||
# Project name
|
||||
if sess.project_id in self.projects:
|
||||
proj = self.projects[sess.project_id]
|
||||
sess.project_name = proj.name or proj.worktree
|
||||
|
||||
# Workspace name
|
||||
if sess.workspace_id and sess.workspace_id in self.workspaces:
|
||||
ws = self.workspaces[sess.workspace_id]
|
||||
sess.workspace_name = ws.name or ws.branch or ws.id
|
||||
|
||||
return sess
|
||||
|
||||
def move_sessions(self, session_ids: List[str], target_project_id: str,
|
||||
target_workspace_id: Optional[str] = None) -> Tuple[bool, List[str]]:
|
||||
@@ -297,9 +329,7 @@ class Database:
|
||||
continue
|
||||
|
||||
# Create new session ID
|
||||
new_id = str(uuid.uuid4()).replace("-", "")
|
||||
while new_id[:3] != "ses":
|
||||
new_id = "ses_" + new_id
|
||||
new_id = "ses_" + uuid.uuid4().hex
|
||||
|
||||
# Insert new session
|
||||
cols = [k for k in row.keys() if k != "id"]
|
||||
@@ -325,28 +355,70 @@ class Database:
|
||||
cursor.execute(sql, [new_id] + values)
|
||||
sql_statements.append(f"INSERT INTO session ... VALUES ({new_id}, ...)")
|
||||
|
||||
# Copy related records
|
||||
for table, foreign_key in [("message", "session_id"), ("part", "session_id"), ("todo", "session_id"), ("session_share", "session_id")]:
|
||||
try:
|
||||
cursor.execute(f"SELECT * FROM {table} WHERE {foreign_key} = ?", (sess_id,))
|
||||
except sqlite3.OperationalError:
|
||||
continue # Table doesn't exist in this schema version
|
||||
rows = cursor.fetchall()
|
||||
for r in rows:
|
||||
# Include foreign_key in cols so the new session_id is written
|
||||
# Copy messages first, building old_id -> new_id map so parts can be relinked
|
||||
msg_id_map: dict = {}
|
||||
try:
|
||||
cursor.execute("SELECT * FROM message WHERE session_id = ?", (sess_id,))
|
||||
except sqlite3.OperationalError:
|
||||
pass
|
||||
else:
|
||||
for r in cursor.fetchall():
|
||||
new_msg_id = uuid.uuid4().hex
|
||||
msg_id_map[r["id"]] = new_msg_id
|
||||
cols = [k for k in r.keys() if k != "id"]
|
||||
values = [new_id if k == foreign_key else r[k] for k in cols]
|
||||
values = [new_id if k == "session_id" else r[k] for k in cols]
|
||||
col_list = ", ".join(cols)
|
||||
placeholders = ", ".join(["?"] * len(cols))
|
||||
cursor.execute(
|
||||
f"INSERT INTO message (id, {col_list}) VALUES (?, {placeholders})",
|
||||
[new_msg_id] + values,
|
||||
)
|
||||
|
||||
# Generate new ID for tables with id column
|
||||
# Copy parts, relinking both session_id and message_id
|
||||
try:
|
||||
cursor.execute("SELECT * FROM part WHERE session_id = ?", (sess_id,))
|
||||
except sqlite3.OperationalError:
|
||||
pass
|
||||
else:
|
||||
for r in cursor.fetchall():
|
||||
new_part_id = uuid.uuid4().hex
|
||||
cols = [k for k in r.keys() if k != "id"]
|
||||
values = []
|
||||
for k in cols:
|
||||
if k == "session_id":
|
||||
values.append(new_id)
|
||||
elif k == "message_id":
|
||||
values.append(msg_id_map.get(r[k], r[k]))
|
||||
else:
|
||||
values.append(r[k])
|
||||
col_list = ", ".join(cols)
|
||||
placeholders = ", ".join(["?"] * len(cols))
|
||||
cursor.execute(
|
||||
f"INSERT INTO part (id, {col_list}) VALUES (?, {placeholders})",
|
||||
[new_part_id] + values,
|
||||
)
|
||||
|
||||
# Copy todo and session_share (session_id only, no secondary FKs)
|
||||
for table in ("todo", "session_share"):
|
||||
try:
|
||||
cursor.execute(f"SELECT * FROM {table} WHERE session_id = ?", (sess_id,))
|
||||
except sqlite3.OperationalError:
|
||||
continue
|
||||
for r in cursor.fetchall():
|
||||
cols = [k for k in r.keys() if k != "id"]
|
||||
values = [new_id if k == "session_id" else r[k] for k in cols]
|
||||
col_list = ", ".join(cols)
|
||||
placeholders = ", ".join(["?"] * len(cols))
|
||||
if "id" in r.keys():
|
||||
new_table_id = str(uuid.uuid4()).replace("-", "")
|
||||
sql = f"INSERT INTO {table} (id, {col_list}) VALUES (?, {placeholders})"
|
||||
cursor.execute(sql, [new_table_id] + values)
|
||||
cursor.execute(
|
||||
f"INSERT INTO {table} (id, {col_list}) VALUES (?, {placeholders})",
|
||||
[uuid.uuid4().hex] + values,
|
||||
)
|
||||
else:
|
||||
sql = f"INSERT INTO {table} ({col_list}) VALUES ({placeholders})"
|
||||
cursor.execute(sql, values)
|
||||
cursor.execute(
|
||||
f"INSERT INTO {table} ({col_list}) VALUES ({placeholders})",
|
||||
values,
|
||||
)
|
||||
|
||||
new_session_ids.append(new_id)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user