Files
arma-server-web-manager/phases/phase-01-skeleton-db.md
Khoa (Revenovich) Tran Gia e02db3ddde feat: add Java→Python migration plan with 9 self-contained phase files
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
2026-04-14 15:06:56 +07:00

8.0 KiB

Phase 1 — Project Skeleton + Database

Status: PENDING Depends on: nothing Next phase: phase-02-auth-security.md


Goal

Produce a runnable FastAPI app that starts, connects to SQLite, runs Alembic migrations, and responds to health checks. All ORM models and repositories must exist. No business logic yet.


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>application/config/ASWGConfig.java Every @Value field name and default
<JAVA_SRC>ArmaServerWebGuiApplication.java Startup hooks
<JAVA_SRC>DefaultConfigGenerator.java How default config file is written on first run
<JAVA_SRC>domain/server/mod/model/InstalledModEntity.java JPA mapping, ListOfLongsConverter for dependencies_ids
<JAVA_SRC>domain/server/mod/model/ModPresetEntity.java JPA + relations
<JAVA_SRC>domain/server/mod/model/ModSettingsEntity.java JPA mapping
<JAVA_SRC>domain/server/mission/model/MissionEntity.java JPA mapping
<JAVA_SRC>domain/server/difficulty/model/DifficultyProfileEntity.java JPA mapping
<JAVA_SRC>domain/server/cdlc/model/CdlcEntity.java JPA mapping
<JAVA_SRC>application/security/jwt/model/InvalidJwtTokenEntity.java JPA mapping
<JAVA_SRC>application/scheduling/model/JobExecutionEntity.java JPA mapping
<JAVA_SRC>application/scheduling/model/JobExecutionStatus.java Enum values
<JAVA_SRC>repository/ (all .java files) Custom query methods beyond findById/findAll
src/main/resources/application.properties server.port, multipart size limits, datasource
src/main/resources/aswg-default-config.properties All aswg.* keys and defaults
src/main/resources/db/changelog/ (all files) Full DDL for all 14 migration sets

Output Files to Create

Relative to E:\TestScript\Arma_Server_Web_Manager\:

pyproject.toml
alembic.ini
alembic/env.py
alembic/versions/001_init.py
alembic/versions/002_installed_mod_last_workshop_update.py
alembic/versions/003_mission.py
alembic/versions/004_mod_settings.py
alembic/versions/005_invalid_jwt_token.py
alembic/versions/006_aswg_user.py
alembic/versions/007_overwrite_startup_params_authority.py
alembic/versions/008_cdlc.py
alembic/versions/009_aswg_user_last_login_column.py
alembic/versions/010_job_last_execution_time.py
alembic/versions/011_add_authorities.py
alembic/versions/012_last_mod_update_attempt.py
alembic/versions/013_job_execution_history.py
alembic/versions/014_mod_dependencies.py
config/settings.py
config/aswg_default_config.properties   (copy content from Java resources file)
src/__init__.py
src/main.py
src/dependencies.py
src/repository/__init__.py
src/repository/base.py
src/repository/models.py
src/repository/installed_mod_repo.py
src/repository/mod_preset_repo.py
src/repository/mod_settings_repo.py
src/repository/mission_repo.py
src/repository/difficulty_profile_repo.py
src/repository/cdlc_repo.py
src/repository/user_repo.py
src/repository/authority_repo.py
src/repository/invalid_jwt_token_repo.py
src/repository/job_execution_repo.py

Implementation Notes

config/settings.py

Use pydantic-settings BaseSettings. The properties file uses Java .properties format (key=value), so implement a custom customise_sources that reads via jproperties.Properties.

Map every key in aswg-default-config.properties as a field with alias="aswg.the-key-name". Key fields:

server_directory_path: str = Field(default="arma-server", alias="aswg.server-directory-path")
security_enabled: bool = Field(default=True, alias="aswg.security.enabled")
jwt_expiration_time: str = Field(default="PT2H", alias="aswg.security.jwt.expiration-time")
default_user_username: str = Field(default="user", alias="aswg.default-user.username")
default_user_password: str = Field(default="changeme", alias="aswg.default-user.password")
steamcmd_path: str = Field(default="", alias="aswg.steamcmd.path")

src/repository/base.py

from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase

DATABASE_URL = "sqlite+aiosqlite:///./data/aswg.db"
engine = create_async_engine(DATABASE_URL, echo=False)
SessionLocal = async_sessionmaker(engine, expire_on_commit=False)

class Base(DeclarativeBase):
    pass

src/repository/models.py — Key mapping notes

  • InstalledModEntity.dependencies_ids: Java stores as comma-separated long string. Create a SQLAlchemy TypeDecorator that serializes list[int]"1,2,3".
  • @GeneratedValue(IDENTITY)Integer, primary_key=True, autoincrement=True
  • LocalDateTime / OffsetDateTimeDateTime(timezone=True)
  • @ManyToMany user↔authority → explicit association table aswg_user_authority
  • @Enumerated(STRING) → store as String, convert in application layer

Alembic migrations

Replace H2-specific syntax:

  • IDENTITY / BIGINT GENERATED BY DEFAULT AS IDENTITYINTEGER PRIMARY KEY AUTOINCREMENT
  • TIMESTAMPDATETIME
  • No sequences needed

Use alembic/env.py async pattern:

import asyncio
from alembic import context
from src.repository.base import engine, Base

def run_migrations_online():
    async def _run():
        async with engine.begin() as conn:
            await conn.run_sync(context.run_migrations)
    asyncio.run(_run())

src/main.py — minimal skeleton

from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from alembic.config import Config
from alembic import command

@asynccontextmanager
async def lifespan(app: FastAPI):
    Config("alembic.ini")
    command.upgrade(Config("alembic.ini"), "head")
    # Phase 2 will add: create default user
    # Phase 6 will add: start scheduler
    yield
    # Phase 6 will add: stop scheduler

app = FastAPI(lifespan=lifespan)

@app.get("/api/v1/actuator/health")
async def health():
    return {"status": "UP"}

@app.get("/api/v1/actuator/info")
async def info():
    return {"application": {"name": "ASWG"}}

# Mount Angular SPA — must be last
app.mount("/", StaticFiles(directory="static", html=True), name="static")

src/dependencies.py

from typing import Annotated
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
from src.repository.base import SessionLocal

async def get_db():
    async with SessionLocal() as session:
        yield session

DbSession = Annotated[AsyncSession, Depends(get_db)]

Repository pattern

Plain async classes receiving AsyncSession. Port every Spring Data method name:

  • findAll()async def find_all()
  • findById(id)async def find_by_id(id)
  • save(entity)async def save(entity) (uses session.merge())
  • deleteById(id)async def delete_by_id(id)
  • Custom @Query methods → SQLAlchemy select() with .where()

Completion Checklist

  • pyproject.toml with all dependencies from PLAN.md
  • config/settings.py covers every aswg.* property with correct defaults
  • src/repository/base.py creates async SQLite engine
  • src/repository/models.py has all 12 ORM entity classes
  • All 14 Alembic migrations run cleanly (alembic upgrade head)
  • All 10 repository classes exist with full method set
  • uvicorn src.main:app --port 8085 starts without errors
  • GET /api/v1/actuator/health{"status":"UP"}
  • GET /api/v1/actuator/info → returns JSON

Contract for Phase 2

Phase 2 will import:

  • from config.settings import settings
  • from src.dependencies import DbSession
  • from src.repository.base import Base, engine, SessionLocal
  • from src.repository.models import AswgUserEntity, AuthorityEntity, InvalidJwtTokenEntity
  • from src.repository.user_repo import UserRepository
  • from src.repository.authority_repo import AuthorityRepository
  • from src.repository.invalid_jwt_token_repo import InvalidJwtTokenRepository
  • from src.main import app