# 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 - [x] Gradle build configuration (`build.gradle`) - [x] Application configuration (`application.yml`) - [x] Main application class (`GroupIronmenApplication.java`) - [x] Package structure created ### 2. Database Layer - [x] 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 - [x] Blake2TokenHasher (`Blake2TokenHasher.java`) - 100% compatible with Rust implementation - 2-iteration Blake2b-256 hashing - Token verification logic - [x] TokenAuthenticationFilter (`TokenAuthenticationFilter.java`) - Extracts group_name from path - Validates Authorization header - Queries database for group_id - Sets Spring Security context - [x] SecurityConfig (`SecurityConfig.java`) - Public endpoints (no auth): /api/create-group, /api/ge-prices, etc. - Protected endpoints: /api/group/** - Stateless session management - [x] 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**: ```java // GroupRepository Optional findGroupIdByNameAndTokenHash(String name, String hash); Optional findByGroupName(String name); // MemberRepository List findByGroupIdAndLastUpdatedAfter(Long groupId, Instant timestamp); Optional 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` ```java POST /api/create-group GET /api/ge-prices GET /api/captcha-enabled GET /api/collection-log-info ``` - `controller/GroupController.java` ```java GET /api/group/{group_name}/get-group-data?from_time= GET /api/group/{group_name}/am-i-logged-in GET /api/group/{group_name}/am-i-in-group ``` - `controller/MemberController.java` ```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` ```java GET /api/group/{group_name}/get-skill-data?period= ``` - `controller/CollectionLogController.java` ```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` ```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= DB_PORT=3306 DB_NAME=groupironman DB_USER= DB_PASSWORD= SERVER_PORT=8080 BACKEND_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**: ```java @Type(JsonType.class) @Column(name = "skills", columnDefinition = "json") private List 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**: ```rust GroupMember { name: String, stats: Option>, // null if not updated skills: Option>, // ... } ``` **Java DTO must output**: ```json { "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 --- ## ๐Ÿ”— Related Documentation - 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