Files
leagues-tools/spring-backend/IMPLEMENTATION_STATUS.md

9.5 KiB

Group Ironmen Backend - Implementation Status

Project Overview

Goal: Migrate group-ironmen-master Rust backend to Java/Spring Boot while maintaining 100% API compatibility with RuneLite plugins.

Technology Stack:

  • Java 17
  • Spring Boot 3.2.0
  • MariaDB
  • Gradle
  • Flyway (database migrations)
  • Bouncy Castle (Blake2 hashing)

Completed Components

1. Project Setup

  • Gradle build configuration (build.gradle)
  • Application configuration (application.yml)
  • Main application class (GroupIronmenApplication.java)
  • Package structure created

2. Database Layer

  • Flyway migration script (V1__init_schema.sql)
    • MariaDB-compatible schema
    • JSON columns for arrays (instead of PostgreSQL arrays)
    • All tables: groups, members, skills_, collection_
    • Indexes and foreign keys

3. Security & Authentication

  • Blake2TokenHasher (Blake2TokenHasher.java)

    • 100% compatible with Rust implementation
    • 2-iteration Blake2b-256 hashing
    • Token verification logic
  • TokenAuthenticationFilter (TokenAuthenticationFilter.java)

    • Extracts group_name from path
    • Validates Authorization header
    • Queries database for group_id
    • Sets Spring Security context
  • SecurityConfig (SecurityConfig.java)

    • Public endpoints (no auth): /api/create-group, /api/ge-prices, etc.
    • Protected endpoints: /api/group/**
    • Stateless session management
  • CorsConfig (CorsConfig.java)

    • Configurable allowed origins
    • Supports RuneLite plugin + frontend

🚧 Next Steps (In Order of Priority)

Phase 1A: Core Data Layer (CRITICAL)

1. JPA Entities

Files to create:

  • model/Group.java
  • model/Member.java
  • model/SkillDataDay.java, SkillDataMonth.java, SkillDataYear.java
  • model/CollectionTab.java, CollectionPage.java
  • model/CollectionLog.java, CollectionLogNew.java

Key Requirements:

  • Use @Type(JsonType.class) for array fields (Hibernate JSON support)
  • Map to MariaDB schema exactly
  • Include all timestamp fields (*_last_update)
  • Lombok annotations (@Data, @Entity, @Table)

2. Spring Data Repositories

Files to create:

  • repository/GroupRepository.java
  • repository/MemberRepository.java
  • repository/SkillDataRepository.java
  • repository/CollectionLogRepository.java

Key Methods:

// GroupRepository
Optional<Long> findGroupIdByNameAndTokenHash(String name, String hash);
Optional<Group> findByGroupName(String name);

// MemberRepository
List<Member> findByGroupIdAndLastUpdatedAfter(Long groupId, Instant timestamp);
Optional<Member> findByGroupIdAndMemberName(Long groupId, String name);
int countByGroupId(Long groupId);

Phase 1B: Service Layer

3. Business Logic Services

Files to create:

  • service/GroupService.java

    • createGroup(CreateGroupRequest) → Returns token + groupId
    • getGroupData(groupId, fromTimestamp) → Delta updates
  • service/MemberService.java

    • updateMember(groupId, memberName, updateData)
    • addMember(groupId, memberName)
    • deleteMember(groupId, memberName)
    • renameMember(groupId, oldName, newName)
  • service/SkillAggregationService.java

    • aggregateSkills(period) → Scheduled task
    • applyRetentionPolicy(period, maxAge)
  • service/GrandExchangeService.java

    • fetchAndCachePrices() → HTTP call to RuneScape Wiki API
    • getCachedPrices() → Return cached prices
  • service/CollectionLogService.java

    • updateCollectionLog(memberId, collectionLogData)
    • getCollectionLog(groupId)

Phase 1C: API Controllers

4. REST Controllers

Files to create:

  • controller/PublicController.java

    POST /api/create-group
    GET /api/ge-prices
    GET /api/captcha-enabled
    GET /api/collection-log-info
    
  • controller/GroupController.java

    GET /api/group/{group_name}/get-group-data?from_time=<timestamp>
    GET /api/group/{group_name}/am-i-logged-in
    GET /api/group/{group_name}/am-i-in-group
    
  • controller/MemberController.java

    POST /api/group/{group_name}/update-group-member
    POST /api/group/{group_name}/add-group-member
    DELETE /api/group/{group_name}/delete-group-member
    PUT /api/group/{group_name}/rename-group-member
    
  • controller/SkillController.java

    GET /api/group/{group_name}/get-skill-data?period=<day|month|year>
    
  • controller/CollectionLogController.java

    GET /api/group/{group_name}/collection-log
    

5. DTOs (Data Transfer Objects)

Files to create:

  • dto/CreateGroupRequest.java
  • dto/CreateGroupResponse.java
  • dto/UpdateMemberRequest.java
  • dto/GroupMemberResponse.java
  • dto/SkillDataResponse.java
  • dto/GePricesResponse.java
  • dto/CollectionLogResponse.java

Critical: DTOs must match Rust JSON structure EXACTLY for plugin compatibility.

Phase 1D: Background Jobs

6. Scheduled Tasks

File to create:

  • service/ScheduledTasks.java
    @Scheduled(fixedRate = 14400000) // 4 hours
    public void updateGePrices()
    
    @Scheduled(fixedRate = 1800000) // 30 minutes
    public void aggregateSkills()
    

Phase 1E: Exception Handling

7. Custom Exceptions & Global Handler

Files to create:

  • exception/GroupNotFoundException.java
  • exception/MemberNotFoundException.java
  • exception/GroupFullException.java (max 5 members)
  • exception/ValidationException.java
  • exception/GlobalExceptionHandler.java (@ControllerAdvice)

Phase 1F: Utilities

8. Helper Classes

Files to create:

  • util/ValidationUtils.java

    • validateMemberName(name) → Regex: [A-Za-z 0-9-_]{1,16}
    • validateArrayLengths(member) → Ensure correct array sizes
  • util/CollectionLogLoader.java

    • Load collection_log_info.json at startup
    • Populate collection_page table

🧪 Phase 2: Testing

Unit Tests

  • Blake2TokenHasher test (verify matches Rust output)
  • Service layer tests (Mockito)
  • Repository tests (TestContainers + MariaDB)

Integration Tests

  • Full API flow tests (create group → update member → get data)
  • Authentication tests (valid/invalid tokens)
  • CORS tests

Plugin Compatibility Tests

  • Test with actual RuneLite plugin
  • Verify JSON payload structure matches
  • Test all plugin → server endpoints

📦 Phase 3: Deployment

Docker Configuration

Files to create:

  • Dockerfile (multi-stage build)
  • docker-compose.yml (backend + MariaDB)
  • .dockerignore
  • Deployment scripts for Proxmox/Debian containers

Environment Variables

Required for production:

DB_HOST=<mariadb_host>
DB_PORT=3306
DB_NAME=groupironman
DB_USER=<user>
DB_PASSWORD=<password>
SERVER_PORT=8080
BACKEND_SECRET=<generate_strong_secret>
CAPTCHA_ENABLED=false
CORS_ORIGINS=https://yourfrontend.com

📋 Implementation Checklist

Critical Path (Must Complete for MVP)

  • JPA Entities (Group, Member, SkillData, CollectionLog)
  • Repositories (GroupRepository, MemberRepository)
  • GroupService (createGroup, getGroupData)
  • MemberService (updateMember, addMember)
  • PublicController (createGroup, gePrices)
  • GroupController (getGroupData)
  • MemberController (updateMember)
  • DTOs matching Rust JSON structure
  • GrandExchangeService (fetch prices from Wiki API)
  • ScheduledTasks (GE prices updater)
  • Exception handling (@ControllerAdvice)
  • Docker build & deployment config

Nice-to-Have (Post-MVP)

  • Skill aggregation service
  • Collection log features
  • Captcha validation
  • Metrics/monitoring (Spring Actuator)
  • Comprehensive test suite

🐛 Known Challenges

1. Array Type Handling

Issue: MariaDB doesn't natively support arrays like PostgreSQL. Solution: Use JSON columns and custom Hibernate type converters.

Example:

@Type(JsonType.class)
@Column(name = "skills", columnDefinition = "json")
private List<Integer> skills; // 24 skills

2. Timestamp Precision

Issue: PostgreSQL TIMESTAMPTZ vs MariaDB TIMESTAMP Solution: Use TIMESTAMP(6) for microsecond precision, convert to/from Instant in Java.

3. Blake2 Hashing Compatibility

Critical: Hash output MUST match Rust implementation exactly. Testing: Create unit test with known token/salt/hash triplets from Rust server.

4. JSON Payload Structure

Critical: Plugin expects specific JSON keys and types. Solution: Create DTOs that serialize EXACTLY as Rust structs.

Example from Rust:

GroupMember {
    name: String,
    stats: Option<Vec<i32>>,  // null if not updated
    skills: Option<Vec<i32>>,
    // ...
}

Java DTO must output:

{
  "name": "PlayerName",
  "stats": [99, 99, 100, 301],  // or null
  "skills": [13034431, ...],    // or null
  "last_updated": "2024-01-01T12:00:00Z"
}

📞 Next Actions

  1. Immediate: Create JPA entities and repositories
  2. Then: Implement GroupService and MemberService
  3. Then: Create REST controllers with DTOs
  4. Test: Run Gradle build, start application, test endpoints with Postman
  5. Deploy: Create Docker image, test in container
  6. Plugin Test: Point RuneLite plugin to new backend, verify functionality

  • Rust Source: group-ironmen-master/server/src/
  • Database Schema: group-ironmen-master/server/src/db.rs (lines 959-1170)
  • API Endpoints: group-ironmen-master/server/src/authed.rs + unauthed.rs
  • Frontend Site: group-ironmen-master/site/src/
  • Collection Log Info: group-ironmen-master/site/public/data/collection_log_info.json

Status: Phase 1A in progress (foundation complete, data layer next) Last Updated: 2025-10-27