Hamdy005 commited on
Commit
bb87189
·
1 Parent(s): 02eef30

feat: disable renaming for topics and transition to per-user title uniqueness constraints

Browse files
Files changed (2) hide show
  1. materials/routes.py +23 -8
  2. store.py +5 -2
materials/routes.py CHANGED
@@ -64,7 +64,10 @@ async def _process_pdf_background(material_id: str, file_content: bytes):
64
  loop = asyncio.get_event_loop()
65
  # Skip processing if this user already has a material with this title
66
  mat = await loop.run_in_executor(None, get_material, material_id)
67
- if mat and await loop.run_in_executor(None, is_title_taken, mat.get("title", ""), material_id, mat.get("user_id")):
 
 
 
68
  logger.info(f"Skipping processing for {material_id}: duplicate title")
69
  await loop.run_in_executor(None, update_material_status, material_id, "failed", "Duplicate title. Please rename to retry.")
70
  return
@@ -88,7 +91,10 @@ async def _process_url_background(material_id: str, url: str):
88
  loop = asyncio.get_event_loop()
89
  # Skip if this user already has a material with this title
90
  mat = await loop.run_in_executor(None, get_material, material_id)
91
- if mat and await loop.run_in_executor(None, is_title_taken, mat.get("title", ""), material_id, mat.get("user_id")):
 
 
 
92
  logger.info(f"Skipping URL processing for {material_id}: duplicate title, waiting for rename")
93
  return
94
 
@@ -155,8 +161,11 @@ async def scrape_url(
155
  ))
156
  material_id = material["id"]
157
 
158
- # Skip processing if title conflicts — user must rename first
159
- is_taken = await loop.run_in_executor(None, is_title_taken, input.url, material_id, user_id)
 
 
 
160
  if not is_taken:
161
  background_tasks.add_task(_process_url_background, material_id, input.url)
162
 
@@ -208,12 +217,18 @@ async def rename_material_endpoint(
208
  raise HTTPException(404, "Material not found")
209
  if mat.get("user_id") != user_id:
210
  raise HTTPException(403, "Not authorized to rename this material")
 
 
 
211
  new_title = body.title.strip()
212
  if not new_title:
213
  raise HTTPException(400, "Title cannot be empty")
214
- existing = await loop.run_in_executor(None, lambda: list_materials(user_id))
215
- if any(m.get("id") != material_id and m.get("title", "").strip().lower() == new_title.lower() for m in existing):
216
- raise HTTPException(409, "A material with this title already exists")
 
 
 
217
  await loop.run_in_executor(None, lambda: rename_material(material_id, new_title))
218
 
219
  # If the material was pending due to title conflict, try processing now
@@ -233,7 +248,7 @@ async def create_topic(
233
  user_id: str = Depends(get_current_user_id)
234
  ):
235
  if is_title_taken(body.topic, user_id=user_id):
236
- raise HTTPException(409, "A material with this title already exists")
237
  mat = create_material(
238
  user_id=user_id,
239
  title=body.topic.strip(),
 
64
  loop = asyncio.get_event_loop()
65
  # Skip processing if this user already has a material with this title
66
  mat = await loop.run_in_executor(None, get_material, material_id)
67
+ if mat and await loop.run_in_executor(
68
+ None,
69
+ lambda: is_title_taken(mat.get("title", ""), exclude_id=material_id, user_id=mat.get("user_id"))
70
+ ):
71
  logger.info(f"Skipping processing for {material_id}: duplicate title")
72
  await loop.run_in_executor(None, update_material_status, material_id, "failed", "Duplicate title. Please rename to retry.")
73
  return
 
91
  loop = asyncio.get_event_loop()
92
  # Skip if this user already has a material with this title
93
  mat = await loop.run_in_executor(None, get_material, material_id)
94
+ if mat and await loop.run_in_executor(
95
+ None,
96
+ lambda: is_title_taken(mat.get("title", ""), exclude_id=material_id, user_id=mat.get("user_id"))
97
+ ):
98
  logger.info(f"Skipping URL processing for {material_id}: duplicate title, waiting for rename")
99
  return
100
 
 
161
  ))
162
  material_id = material["id"]
163
 
164
+ # Skip processing if title conflicts within user scope — user must rename first
165
+ is_taken = await loop.run_in_executor(
166
+ None,
167
+ lambda: is_title_taken(input.url, exclude_id=material_id, user_id=user_id)
168
+ )
169
  if not is_taken:
170
  background_tasks.add_task(_process_url_background, material_id, input.url)
171
 
 
217
  raise HTTPException(404, "Material not found")
218
  if mat.get("user_id") != user_id:
219
  raise HTTPException(403, "Not authorized to rename this material")
220
+ # Topics have no URL — renaming is disabled for them
221
+ if mat.get("source_type") == "url" and not mat.get("url"):
222
+ raise HTTPException(403, "Custom topic names cannot be changed")
223
  new_title = body.title.strip()
224
  if not new_title:
225
  raise HTTPException(400, "Title cannot be empty")
226
+ is_taken = await loop.run_in_executor(
227
+ None,
228
+ lambda: is_title_taken(new_title, exclude_id=material_id, user_id=user_id)
229
+ )
230
+ if is_taken:
231
+ raise HTTPException(409, "You already have a material with this title")
232
  await loop.run_in_executor(None, lambda: rename_material(material_id, new_title))
233
 
234
  # If the material was pending due to title conflict, try processing now
 
248
  user_id: str = Depends(get_current_user_id)
249
  ):
250
  if is_title_taken(body.topic, user_id=user_id):
251
+ raise HTTPException(409, "You already have a material with this title")
252
  mat = create_material(
253
  user_id=user_id,
254
  title=body.topic.strip(),
store.py CHANGED
@@ -159,6 +159,7 @@ def list_materials(user_id: str) -> list[dict]:
159
 
160
 
161
  def is_title_taken(title: str, exclude_id: Optional[str] = None, user_id: Optional[str] = None) -> bool:
 
162
  normalized = title.strip().lower()
163
  if not normalized:
164
  return False
@@ -211,7 +212,7 @@ def create_material(user_id: str, source_type: str, title: str,
211
  if source_type == "topic":
212
  actual_source_type = "url"
213
 
214
- # Auto-resolve duplicate titles to prevent DB unique constraint violation
215
  original_title = title
216
  counter = 1
217
  while is_title_taken(title, user_id=user_id):
@@ -224,8 +225,10 @@ def create_material(user_id: str, source_type: str, title: str,
224
  data["file_path"] = file_path
225
  if url:
226
  data["url"] = url
 
 
227
  result = _robust_execute(_table_supabase("materials").insert(data))
228
-
229
  ret_data = result.data[0]
230
  if ret_data.get("source_type") == "url" and not ret_data.get("url"):
231
  ret_data["source_type"] = "topic"
 
159
 
160
 
161
  def is_title_taken(title: str, exclude_id: Optional[str] = None, user_id: Optional[str] = None) -> bool:
162
+ """Check whether *title* is already used by *user_id* (or globally when user_id is None)."""
163
  normalized = title.strip().lower()
164
  if not normalized:
165
  return False
 
212
  if source_type == "topic":
213
  actual_source_type = "url"
214
 
215
+ # Auto-resolve duplicate titles PER USER so each user's material list
216
  original_title = title
217
  counter = 1
218
  while is_title_taken(title, user_id=user_id):
 
225
  data["file_path"] = file_path
226
  if url:
227
  data["url"] = url
228
+
229
+ # Insert assuming the global constraint on `title` has been replaced with a per-user one
230
  result = _robust_execute(_table_supabase("materials").insert(data))
231
+
232
  ret_data = result.data[0]
233
  if ret_data.get("source_type") == "url" and not ret_data.get("url"):
234
  ret_data["source_type"] = "topic"