cipher-attacks commited on
Commit
b5b19ed
Β·
1 Parent(s): 8bc7c80

delcourse

Browse files
app/bot/handlers/admin.py CHANGED
@@ -41,10 +41,11 @@ router.callback_query.filter(IsAdmin())
41
  HELP = (
42
  "<b>Admin tools</b>\n"
43
  "/addcourse &lt;natural|social|all&gt; &lt;year 0-5&gt; &lt;code&gt; &lt;name...&gt;\n"
44
- " e.g. <code>/addcourse natural 1 Math1011 Calculus I</code>\n"
45
  " (use <code>-</code> as the code if a course has none)\n"
46
  "/seedcourses : add the Year-1 course list (safe to run twice)\n"
47
  "/courses : list courses with ids\n"
 
48
  "/stats : numbers\n"
49
  "/ping : measure database and Telegram speed\n"
50
  "/season : live weekly standings + fraud flags\n"
@@ -168,6 +169,36 @@ async def seed_courses(message: Message, session: AsyncSession) -> None:
168
  await message.answer(f"βœ… Added <b>{added}</b> courses, skipped {skipped} that already existed.\nSee /courses for ids.")
169
 
170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  @router.message(Command("courses"))
172
  async def list_courses(message: Message, session: AsyncSession) -> None:
173
  courses = await repo.all_courses(session)
 
41
  HELP = (
42
  "<b>Admin tools</b>\n"
43
  "/addcourse &lt;natural|social|all&gt; &lt;year 0-5&gt; &lt;code&gt; &lt;name...&gt;\n"
44
+ " e.g. <code>/addcourse natural 1 XXX0000 My New Course</code> (use <code>-</code> if no code)\n"
45
  " (use <code>-</code> as the code if a course has none)\n"
46
  "/seedcourses : add the Year-1 course list (safe to run twice)\n"
47
  "/courses : list courses with ids\n"
48
+ "/delcourse &lt;id&gt; [move_files_to_id] : delete a course (refuses if it still has files)\n"
49
  "/stats : numbers\n"
50
  "/ping : measure database and Telegram speed\n"
51
  "/season : live weekly standings + fraud flags\n"
 
169
  await message.answer(f"βœ… Added <b>{added}</b> courses, skipped {skipped} that already existed.\nSee /courses for ids.")
170
 
171
 
172
+ @router.message(Command("delcourse"))
173
+ async def del_course(message: Message, command: CommandObject, session: AsyncSession) -> None:
174
+ parts = (command.args or "").split()
175
+ if not parts or not parts[0].isdigit() or (len(parts) > 1 and not parts[1].isdigit()):
176
+ await message.answer("Usage: <code>/delcourse &lt;id&gt; [move_files_to_id]</code>")
177
+ return
178
+ cid, target = int(parts[0]), (int(parts[1]) if len(parts) > 1 else None)
179
+ course = await repo.get_course(session, cid)
180
+ if not course:
181
+ await message.answer(f"No course with id {cid}. See /courses")
182
+ return
183
+ files = await repo.count_course_files(session, cid)
184
+ if files and target is None:
185
+ await message.answer(
186
+ f"❗ Course #{cid} β€œ{course.name}” still has <b>{files}</b> file(s), so I did not delete it.\n"
187
+ f"Move them and delete in one go: <code>/delcourse {cid} &lt;other_course_id&gt;</code>"
188
+ )
189
+ return
190
+ if target is not None:
191
+ if target == cid or not await repo.get_course(session, target):
192
+ await message.answer("The target course must exist and be a different course.")
193
+ return
194
+ await repo.move_course_files(session, cid, target)
195
+ await repo.delete_course(session, cid)
196
+ session.sync_session.info["wrote"] = True
197
+ library_service.invalidate_courses()
198
+ moved = f" ({files} file(s) moved to #{target})" if files else ""
199
+ await message.answer(f"πŸ—‘ Deleted course #{cid} β€œ{course.name}”{moved}.")
200
+
201
+
202
  @router.message(Command("courses"))
203
  async def list_courses(message: Message, session: AsyncSession) -> None:
204
  courses = await repo.all_courses(session)
app/db/repos/library.py CHANGED
@@ -263,3 +263,20 @@ async def unstamped_pdfs(session: AsyncSession, limit: int, max_size: int) -> li
263
  .limit(limit)
264
  )
265
  return list(res.scalars())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  .limit(limit)
264
  )
265
  return list(res.scalars())
266
+
267
+
268
+ async def count_course_files(session: AsyncSession, course_id: int) -> int:
269
+ res = await session.execute(select(func.count()).select_from(Material).where(Material.course_id == course_id))
270
+ return int(res.scalar_one())
271
+
272
+
273
+ async def move_course_files(session: AsyncSession, from_id: int, to_id: int) -> None:
274
+ await session.execute(
275
+ update(Material).where(Material.course_id == from_id).values(course_id=to_id)
276
+ .execution_options(synchronize_session=False)
277
+ )
278
+
279
+
280
+ async def delete_course(session: AsyncSession, course_id: int) -> bool:
281
+ res = await session.execute(delete(Course).where(Course.id == course_id))
282
+ return bool(res.rowcount)