Converts Spring Boot 4.0.3 ARMA Server Web GUI to FastAPI/Python. Each phase file is fully self-contained: lists Java source files to read, output files to create, implementation patterns, REST endpoint contracts, and a completion checklist. A future agent can execute any single phase without rescanning the Java project. Phases: - 01: Foundation — SQLAlchemy models, Alembic, settings, base schemas - 02: Auth & Users — JWT middleware, RBAC, user CRUD - 03: CFG parser + server process — server.cfg round-trip, start/stop - 04: Server settings — general/network/logging/security/difficulty - 05: Mod management — mod CRUD, presets, settings, WebSocket progress - 06: Steam integration — SteamCMD queue, Workshop API, python-a2s - 07: Missions, CDLC, Discord, APScheduler jobs - 08: Middleware & polish — global exception handler, SPA redirect, structlog - 09: Testing — pytest-asyncio, respx, 80% coverage target
7.3 KiB
Phase 4 — Server Settings Subdomains
Status: PENDING
Depends on: Phase 3 complete (CFG parser + ServerConfigStorage must exist)
Next phase: phase-05-mod-management.md
Goal
Five settings services that read/write Arma .cfg files and their REST endpoints. All follow the same read→model→return / receive→model→write pattern.
After this phase: general, network, security, logging settings and difficulty profiles work end-to-end.
Java Source Files to Read
<JAVA_SRC> = E:\TestScript\ARMA-Server-Web-Gui\src\main\java\pl\bartlomiejstepien\armaserverwebgui\
| File | What to extract |
|---|---|
<JAVA_SRC>domain/server/general/GeneralServiceImpl.java |
getGeneralProperties(), saveGeneralProperties() |
<JAVA_SRC>domain/server/general/model/GeneralProperties.java |
Field names/types |
<JAVA_SRC>domain/server/network/ServerNetworkServiceImpl.java |
getNetworkProperties(), saveNetworkProperties() |
<JAVA_SRC>domain/server/network/model/NetworkProperties.java |
Fields |
<JAVA_SRC>domain/server/network/model/KickTimeoutType.java |
Enum values |
<JAVA_SRC>domain/server/logging/LoggingServiceImpl.java |
getLoggingProperties(), getLatestLogs() |
<JAVA_SRC>domain/server/logging/model/LoggingProperties.java |
Fields |
<JAVA_SRC>domain/server/difficulty/DifficultyServiceImpl.java |
Full implementation (DB + filesystem) |
<JAVA_SRC>domain/server/difficulty/model/DifficultyProfile.java |
Fields |
<JAVA_SRC>domain/server/difficulty/DifficultyScanJob.java |
Filesystem scan logic |
<JAVA_SRC>web/GeneralController.java |
Exact paths, JSON shapes |
<JAVA_SRC>web/ServerNetworkRestController.java |
Exact paths, JSON shapes |
<JAVA_SRC>web/LoggingRestController.java |
Properties endpoints (SSE already done in Phase 3) |
<JAVA_SRC>web/ServerSecurityRestController.java |
Exact paths, JSON shapes |
<JAVA_SRC>web/DifficultyRestController.java |
Exact paths, JSON shapes |
<JAVA_SRC>web/request/SaveGeneralProperties.java |
Request fields |
<JAVA_SRC>web/request/NetworkPropertiesRequest.java |
Request fields |
<JAVA_SRC>web/request/SaveServerSecurityRequest.java |
Request fields |
<JAVA_SRC>web/response/GeneralPropertiesResponse.java |
Response fields |
<JAVA_SRC>web/response/NetworkPropertiesResponse.java |
Response fields |
<JAVA_SRC>web/model/DifficultyProfileApiModel.java |
API model fields |
Output Files to Create
src/domain/server/general/__init__.py
src/domain/server/general/general_service.py
src/domain/server/general/models.py
src/domain/server/network/__init__.py
src/domain/server/network/network_service.py
src/domain/server/network/models.py
src/domain/server/logging_settings/__init__.py
src/domain/server/logging_settings/logging_service.py
src/domain/server/logging_settings/models.py
src/domain/server/security_settings/__init__.py
src/domain/server/security_settings/security_service.py
src/domain/server/security_settings/models.py
src/domain/server/difficulty/__init__.py
src/domain/server/difficulty/difficulty_service.py
src/domain/server/difficulty/jobs.py (DifficultyScanJob — registered in Phase 7)
src/domain/server/difficulty/models.py
src/web/schemas/general.py
src/web/schemas/network.py
src/web/schemas/security_settings.py
src/web/schemas/difficulty.py
src/web/general_router.py
src/web/network_router.py
src/web/logging_router.py
src/web/security_router.py
src/web/difficulty_router.py
Update src/main.py: register all 5 routers.
Implementation Notes
Common service pattern (general / network / logging / security)
class GeneralService:
def __init__(self, config_storage: ServerConfigStorage):
self.storage = config_storage
async def get_properties(self) -> GeneralProperties:
cfg = self.storage.read()
return GeneralProperties(hostname=cfg.hostname, ...)
async def save_properties(self, props: GeneralProperties) -> None:
cfg = self.storage.read()
cfg.hostname = props.hostname
# ... apply all fields
self.storage.write(cfg)
Apply the same pattern to Network, Logging, and Security services.
Difficulty service — DB + filesystem
DifficultyServiceImpl.java manages profiles in both the DB and <server_dir>/Users/*.Arma3Profile files.
class DifficultyService:
async def get_profiles(self) -> list[DifficultyProfile]:
# Read from difficulty_profile table + scan filesystem for .Arma3Profile files
async def save_profile(self, profile: DifficultyProfile) -> None:
# Write to DB + write .Arma3Profile via cfg_writer
async def delete_profile(self, id: int | None, name: str | None) -> None:
# Remove from DB + delete file
async def set_active(self, id: int) -> None:
# Mark active in DB; only one can be active at a time
DifficultyScanJob: scans <server_dir>/Users/ for new .Arma3Profile files and inserts them into the DB if not already present. Register this job with APScheduler in Phase 7 — do not register it here.
REST endpoints
General — permissions: GENERAL_SETTINGS_VIEW, GENERAL_SETTINGS_SAVE
GET /api/v1/general/properties → GeneralPropertiesResponse
POST /api/v1/general/properties body: SaveGeneralProperties → 200
Network — permissions: NETWORK_SETTINGS_VIEW, NETWORK_SETTINGS_SAVE
GET /api/v1/network/properties → NetworkPropertiesResponse
POST /api/v1/network/properties body: NetworkPropertiesRequest → 200
Logging — permission: LOGS_VIEW
GET /api/v1/logging/properties → LoggingPropertiesResponse
POST /api/v1/logging/properties body: LoggingPropertiesRequest → 200
GET /api/v1/logging/latest-logs → list[str]
Note: GET /api/v1/logging/logs-sse was registered in Phase 3.
Security settings — permissions: SECURITY_SETTINGS_VIEW, SECURITY_SETTINGS_SAVE
GET /api/v1/security → ServerSecurityResponse
POST /api/v1/security body: SaveServerSecurityRequest → 200
Difficulty — permissions: DIFFICULTY_VIEW, DIFFICULTY_ADD, DIFFICULTY_UPDATE, DIFFICULTY_DELETE
GET /api/v1/difficulties → list[DifficultyProfileApiModel]
POST /api/v1/difficulties body: DifficultyProfileApiModel → 201
PUT /api/v1/difficulties/{id} body: DifficultyProfileApiModel → 200
DELETE /api/v1/difficulties/{id} → 200
DELETE /api/v1/difficulties?name={n} → 200
Completion Checklist
GET /api/v1/general/propertiesreturns correct camelCase JSONPOST /api/v1/general/propertiespersists to .cfg fileGET /api/v1/network/propertiesreturns correct JSONPOST /api/v1/network/propertiespersists to .cfg fileGET /api/v1/logging/propertiesreturns correct JSONGET /api/v1/logging/latest-logsreturns list of log line stringsGET /api/v1/securityreturns correct JSONGET /api/v1/difficultiesreturns profiles listPOST /api/v1/difficultiescreates profile in DB and .Arma3Profile fileDELETE /api/v1/difficulties/{id}removes profile from DB and filesystem- All endpoints return 403 without required permission
Contract for Phase 5
Phase 5 has no direct dependency on Phase 4. It imports only from Phases 1–3.