feat: fix mods tab, add client/server split, and scaffold server dirs

Mods tab bug fixes:
- mod_manager: fix wrong kwargs in set_enabled_mods, fix scan dir to use
  mods/ subdir instead of server root, migrate old string-list format to
  dict format on read
- service: replace dead server_mods SQL JOIN with get_enabled_mods()
  call through the mod_manager capability; pass is_server_mod to
  build_mod_args
- mods_router: accept list[EnabledModEntry] objects (name + is_server_mod)
  instead of bare strings

Client/server mod split:
- Mods now stored as list[{"name": str, "is_server_mod": bool}]; old
  string-list format auto-migrated on read
- is_server_mod=true routes to -serverMod= arg; false to -mod= arg
- ModList UI: amber Client/Server badge in selected pane; toggle button
  in split-pane selector

Directory scaffold:
- process_config: adds "mods" to dir layout; provides get_dir_readme()
  with per-directory README.txt content
- file_utils: ensure_server_dirs() gains readme_provider kwarg; writes
  README.txt idempotently if absent
- service.create_server: passes readme_provider via hasattr probe
- main.py startup: backfills all existing servers with correct subdirs
  and README files (idempotent)

Docs: API.md and FRONTEND.md updated for new mod schema and types
Test __init__.py files added for pytest discovery
This commit is contained in:
Tran G. (Revernomad) Khoa
2026-04-20 10:54:56 +07:00
parent fa95587567
commit d45345a094
12 changed files with 209 additions and 67 deletions

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
import re
from pathlib import Path
from typing import Callable
def get_server_dir(server_id: int) -> Path:
@@ -12,16 +13,27 @@ def get_server_dir(server_id: int) -> Path:
return base / str(server_id)
def ensure_server_dirs(server_id: int, layout: list[str] | None = None) -> None:
def ensure_server_dirs(
server_id: int,
layout: list[str] | None = None,
readme_provider: Callable[[str], str | None] | None = None,
) -> None:
"""
Create servers/{id}/ and any subdirectories from adapter layout.
layout example: ["server", "battleye", "mpmissions"]
If readme_provider is given, writes README.txt into each subdir (skips if file already exists).
"""
server_dir = get_server_dir(server_id)
server_dir.mkdir(parents=True, exist_ok=True)
if layout:
for subdir in layout:
(server_dir / subdir).mkdir(parents=True, exist_ok=True)
subdir_path = server_dir / subdir
subdir_path.mkdir(parents=True, exist_ok=True)
if readme_provider:
content = readme_provider(subdir)
if content:
readme_path = subdir_path / "README.txt"
if not readme_path.exists():
readme_path.write_text(content, encoding="utf-8")
def safe_delete_file(path: Path) -> bool: