mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
feat: Add delete_notes parameter to remove project endpoint (#391)
Signed-off-by: phernandez <paul@basicmachines.co> Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""Router for project management."""
|
||||
|
||||
import os
|
||||
from fastapi import APIRouter, HTTPException, Path, Body, BackgroundTasks, Response
|
||||
from fastapi import APIRouter, HTTPException, Path, Body, BackgroundTasks, Response, Query
|
||||
from typing import Optional
|
||||
from loguru import logger
|
||||
|
||||
@@ -247,11 +247,15 @@ async def add_project(
|
||||
async def remove_project(
|
||||
project_service: ProjectServiceDep,
|
||||
name: str = Path(..., description="Name of the project to remove"),
|
||||
delete_notes: bool = Query(
|
||||
False, description="If True, delete project directory from filesystem"
|
||||
),
|
||||
) -> ProjectStatusResponse:
|
||||
"""Remove a project from configuration and database.
|
||||
|
||||
Args:
|
||||
name: The name of the project to remove
|
||||
delete_notes: If True, delete the project directory from the filesystem
|
||||
|
||||
Returns:
|
||||
Response confirming the project was removed
|
||||
@@ -276,7 +280,7 @@ async def remove_project(
|
||||
detail += "This is the only project in your configuration."
|
||||
raise HTTPException(status_code=400, detail=detail)
|
||||
|
||||
await project_service.remove_project(name)
|
||||
await project_service.remove_project(name, delete_notes=delete_notes)
|
||||
|
||||
return ProjectStatusResponse(
|
||||
message=f"Project '{name}' removed successfully",
|
||||
|
||||
@@ -119,13 +119,18 @@ def add_project(
|
||||
@project_app.command("remove")
|
||||
def remove_project(
|
||||
name: str = typer.Argument(..., help="Name of the project to remove"),
|
||||
delete_notes: bool = typer.Option(
|
||||
False, "--delete-notes", help="Delete project files from disk"
|
||||
),
|
||||
) -> None:
|
||||
"""Remove a project."""
|
||||
|
||||
async def _remove_project():
|
||||
async with get_client() as client:
|
||||
project_permalink = generate_permalink(name)
|
||||
response = await call_delete(client, f"/projects/{project_permalink}")
|
||||
response = await call_delete(
|
||||
client, f"/projects/{project_permalink}?delete_notes={delete_notes}"
|
||||
)
|
||||
return ProjectStatusResponse.model_validate(response.json())
|
||||
|
||||
try:
|
||||
@@ -135,8 +140,9 @@ def remove_project(
|
||||
console.print(f"[red]Error removing project: {str(e)}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
# Show this message regardless of method used
|
||||
console.print("[yellow]Note: The project files have not been deleted from disk.[/yellow]")
|
||||
# Show this message only if files were not deleted
|
||||
if not delete_notes:
|
||||
console.print("[yellow]Note: The project files have not been deleted from disk.[/yellow]")
|
||||
|
||||
|
||||
@project_app.command("default")
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
"""Project management service for Basic Memory."""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional, Sequence
|
||||
@@ -219,11 +221,12 @@ class ProjectService:
|
||||
|
||||
logger.info(f"Project '{name}' added at {resolved_path}")
|
||||
|
||||
async def remove_project(self, name: str) -> None:
|
||||
async def remove_project(self, name: str, delete_notes: bool = False) -> None:
|
||||
"""Remove a project from configuration and database.
|
||||
|
||||
Args:
|
||||
name: The name of the project to remove
|
||||
delete_notes: If True, delete the project directory from filesystem
|
||||
|
||||
Raises:
|
||||
ValueError: If the project doesn't exist or is the default project
|
||||
@@ -231,16 +234,33 @@ class ProjectService:
|
||||
if not self.repository: # pragma: no cover
|
||||
raise ValueError("Repository is required for remove_project")
|
||||
|
||||
# Get project path before removing from config
|
||||
project = await self.get_project(name)
|
||||
project_path = project.path if project else None
|
||||
|
||||
# First remove from config (this will validate the project exists and is not default)
|
||||
self.config_manager.remove_project(name)
|
||||
|
||||
# Then remove from database using robust lookup
|
||||
project = await self.get_project(name)
|
||||
if project:
|
||||
await self.repository.delete(project.id)
|
||||
|
||||
logger.info(f"Project '{name}' removed from configuration and database")
|
||||
|
||||
# Optionally delete the project directory
|
||||
if delete_notes and project_path:
|
||||
try:
|
||||
path_obj = Path(project_path)
|
||||
if path_obj.exists() and path_obj.is_dir():
|
||||
await asyncio.to_thread(shutil.rmtree, project_path)
|
||||
logger.info(f"Deleted project directory: {project_path}")
|
||||
else:
|
||||
logger.warning(
|
||||
f"Project directory not found or not a directory: {project_path}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to delete project directory {project_path}: {e}")
|
||||
|
||||
async def set_default_project(self, name: str) -> None:
|
||||
"""Set the default project in configuration and database.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user