9.8 KiB
Design Document: lorep Report Plugin
Overview
Плагин lorep — система репортов для Paper 1.21, написанная на Java 21. Использует Gradle для сборки, SQLite для хранения данных и интегрируется с Discord через webhooks.
Технологический стек:
- Java 21
- Paper API 1.21
- Gradle (Kotlin DSL)
- SQLite (встроенная БД) или PostgreSQL (внешняя БД)
- HikariCP (connection pool для PostgreSQL)
- OkHttp (HTTP клиент для webhooks)
Git репозиторий: https://git.lokili.xyz/loki/lorep.git
Architecture
graph TB
subgraph Plugin
Main[LorepPlugin]
CMD[CommandManager]
GUI[GuiManager]
DB[DatabaseManager]
WH[WebhookService]
CFG[ConfigManager]
end
subgraph External
Discord[Discord Webhook]
SQLite[(SQLite DB)]
end
Main --> CMD
Main --> GUI
Main --> DB
Main --> WH
Main --> CFG
CMD --> DB
CMD --> WH
GUI --> DB
WH --> Discord
DB --> SQLite
CFG --> Main
Структура проекта
lorep/
├── .gitignore
├── build.gradle.kts
├── settings.gradle.kts
├── src/
│ └── main/
│ ├── java/
│ │ └── dev/
│ │ └── loki/
│ │ └── lorep/
│ │ ├── LorepPlugin.java
│ │ ├── command/
│ │ │ ├── ReportCommand.java
│ │ │ ├── ReportGuiCommand.java
│ │ │ └── ReportStatsCommand.java
│ │ ├── database/
│ │ │ ├── DatabaseManager.java
│ │ │ ├── SQLiteDatabaseManager.java
│ │ │ ├── PostgreSQLDatabaseManager.java
│ │ │ └── Report.java
│ │ ├── gui/
│ │ │ ├── ReportGui.java
│ │ │ └── GuiClickListener.java
│ │ ├── webhook/
│ │ │ └── DiscordWebhook.java
│ │ ├── config/
│ │ │ └── ConfigManager.java
│ │ └── util/
│ │ ├── TimeUtil.java
│ │ └── MessageUtil.java
│ └── resources/
│ ├── plugin.yml
│ └── config.yml
└── src/
└── test/
└── java/
└── dev/
└── loki/
└── lorep/
├── database/
│ └── DatabaseManagerTest.java
└── util/
└── TimeUtilTest.java
Components and Interfaces
1. LorepPlugin (Main Class)
public final class LorepPlugin extends JavaPlugin {
private DatabaseManager databaseManager;
private ConfigManager configManager;
private DiscordWebhook discordWebhook;
@Override
public void onEnable();
@Override
public void onDisable();
// Getters for managers
}
2. Report (Data Model)
public record Report(
int id,
UUID reporterUuid,
String reporterName,
UUID targetUuid,
String targetName,
String reason,
Instant createdAt
) {}
3. DatabaseManager
public interface DatabaseManager {
void initialize();
void close();
void saveReport(Report report);
List<Report> getReportsForTarget(UUID targetUuid);
List<Report> getAllReports();
int getReportCount(UUID targetUuid);
boolean hasReported(UUID reporterUuid, UUID targetUuid);
List<Report> getReportsPaginated(int page, int pageSize);
}
// Реализации:
public class SQLiteDatabaseManager implements DatabaseManager { }
public class PostgreSQLDatabaseManager implements DatabaseManager { }
4. DiscordWebhook
public class DiscordWebhook {
public DiscordWebhook(String webhookUrl);
public void sendReport(Report report);
}
5. ConfigManager
public class ConfigManager {
public String getWebhookUrl();
public String getDatabasePath();
public String getMessage(String key);
}
6. ReportGui
public class ReportGui {
public void open(Player player, int page);
public ItemStack createReportItem(Report report, int totalReports);
}
Data Models
Report Entity
| Field | Type | Description |
|---|---|---|
| id | INTEGER | Primary key, auto-increment |
| reporter_uuid | TEXT | UUID репортера |
| reporter_name | TEXT | Имя репортера |
| target_uuid | TEXT | UUID цели |
| target_name | TEXT | Имя цели |
| reason | TEXT | Причина репорта |
| created_at | INTEGER | Unix timestamp создания |
SQL Schema
CREATE TABLE IF NOT EXISTS reports (
id INTEGER PRIMARY KEY AUTOINCREMENT,
reporter_uuid TEXT NOT NULL,
reporter_name TEXT NOT NULL,
target_uuid TEXT NOT NULL,
target_name TEXT NOT NULL,
reason TEXT NOT NULL,
created_at INTEGER NOT NULL
);
CREATE INDEX idx_target_uuid ON reports(target_uuid);
CREATE INDEX idx_reporter_target ON reports(reporter_uuid, target_uuid);
Configuration (config.yml)
# Discord Webhook URL
webhook-url: ""
# Database settings
database:
# Type: sqlite or postgresql
type: "sqlite"
# SQLite settings
sqlite:
file: "reports.db"
# PostgreSQL settings
postgresql:
host: "localhost"
port: 5432
database: "lorep"
username: "lorep"
password: "password"
pool-size: 10
# Messages
messages:
report-sent: "&aРепорт успешно отправлен!"
already-reported: "&cВы уже отправляли репорт на этого игрока!"
self-report: "&cВы не можете отправить репорт на себя!"
player-not-found: "&cИгрок не найден!"
no-permission: "&cУ вас нет прав на эту команду!"
usage: "&eИспользование: /report <ник> <причина>"
Correctness Properties
A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.
Property 1: Report Creation Integrity
For any valid reporter UUID, target UUID, and reason string, creating a report and then querying reports for that target SHALL return a list containing the created report with matching fields. Validates: Requirements 1.1, 2.1
Property 2: Duplicate Report Prevention
For any reporter-target pair, if a report already exists from that reporter to that target, attempting to create another report SHALL be rejected. Validates: Requirements 1.2
Property 3: Report Persistence Round-Trip
For any set of reports saved to the database, reloading the database SHALL return the same reports with identical data. Validates: Requirements 2.2
Property 4: Report Ordering
For any collection of reports with different timestamps, querying all reports SHALL return them sorted by creation timestamp in descending order (newest first). Validates: Requirements 2.3
Property 5: Webhook Payload Completeness
For any report sent to the webhook, the payload SHALL contain reporter name, target name, reason, and formatted timestamp. Validates: Requirements 3.1
Property 6: Pagination Correctness
For any list of N reports and page size P, requesting page K SHALL return at most P reports starting from index K*P, and the total page count SHALL equal ceil(N/P). Validates: Requirements 4.4
Property 7: Report Count Accuracy
For any target UUID, the report count returned by getReportCount SHALL equal the actual number of reports in the database for that target. Validates: Requirements 5.1
Property 8: Permission Enforcement
For any command execution, if the player lacks the required permission, the command SHALL be denied and return false. Validates: Requirements 6.1, 6.2, 6.3
Property 9: Configuration Parsing
For any valid config.yml file, parsing SHALL correctly extract webhook URL and database path values. Validates: Requirements 7.1, 7.3, 7.4
Error Handling
| Scenario | Handling |
|---|---|
| Database connection failure | Log error, disable plugin gracefully |
| Webhook request failure | Log warning, continue operation |
| Invalid player name | Return error message to player |
| Missing config values | Use default values |
| SQL exceptions | Log error, return empty result |
Testing Strategy
Property-Based Testing Library
- JQwik — библиотека для property-based тестирования в Java
- Минимум 100 итераций на каждый property test
Unit Tests
- DatabaseManager: CRUD операции, pagination
- TimeUtil: форматирование времени
- ConfigManager: парсинг конфигурации
Property-Based Tests
Каждый property test должен быть аннотирован:
// **Feature: lorep-report-plugin, Property 1: Report Creation Integrity**
@Property(tries = 100)
void reportCreationIntegrity(@ForAll UUID reporter, @ForAll UUID target, @ForAll String reason) {
// test implementation
}
Integration Tests
- Полный цикл создания репорта
- GUI открытие и навигация
- Webhook отправка (с mock сервером)