Spaces:
Running on Zero
Running on Zero
Commit Β·
b5b19ed
1
Parent(s): 8bc7c80
delcourse
Browse files- app/bot/handlers/admin.py +32 -1
- app/db/repos/library.py +17 -0
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 <natural|social|all> <year 0-5> <code> <name...>\n"
|
| 44 |
-
" e.g. <code>/addcourse natural 1
|
| 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 <natural|social|all> <year 0-5> <code> <name...>\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 <id> [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 <id> [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} <other_course_id></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)
|