Skip to content

Add DPC community API integration for cross-server faction sharing#1959

Open
Copilot wants to merge 27 commits into
mainfrom
copilot/add-dpc-community-api-integration
Open

Add DPC community API integration for cross-server faction sharing#1959
Copilot wants to merge 27 commits into
mainfrom
copilot/add-dpc-community-api-integration

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 27, 2026

Integrates Medieval Factions with the DPC community API (POST /api/v1/factions) to allow opt-in sharing of faction data to the DPC website. Strictly opt-in, disabled by default.

Config (config.yml)

  • New dpc-api section: enabled, url, key, server-id, login-reminder, share-server-ip, server-address, discord-link, sync-interval-minutes — all safe defaults
  • server-id is a dedicated config option for unique server identification in the DPC registry
  • server-address allows explicit server address (e.g. play.myserver.com:25565) for proxied setups; auto-detects from server binding when blank
  • key includes inline comment directing operators to https://dansplugins.com to register and generate an API key
  • sync-interval-minutes controls how often faction data is synced (default: 10 minutes); also used as initial delay; minimum 1 enforced via coerceAtLeast(1)
  • url is the full endpoint URL (default: https://dansplugins.com/api/v1/factions); supports port numbers and is used directly without path appending

Commands (MfFactionDpcCommand)

All behind mf.dpc permission (declared in plugin.yml with default: op), wrappers around config writes:

Command Effect
/mf dpc optin|optout Toggle dpc-api.enabled
/mf dpc reminder on|off Toggle dpc-api.login-reminder
/mf dpc shareip on|off Toggle dpc-api.share-server-ip
/mf dpc discord <link>|clear Set/clear dpc-api.discord-link — link validated at command time

The /mf dpc command is discoverable via /mf help.

HTTP Sync (MfDpcApiService)

  • Uses java.net.http.HttpClient.sendAsync — no blocking the server thread
  • HttpClient is an injectable constructor parameter (default: standard client) for testability
  • Scheduled via runTaskTimerAsynchronously with configurable interval from dpc-api.sync-interval-minutes (default: 10 min)
  • dpc-api.url validated on each sync: must be non-blank, parseable, absolute, and use http/https; clear warnings logged on failure
  • X-API-Key header on all requests; serverIp/discordLink conditionally included
  • serverIp resolved once per sync cycle from dpc-api.server-address config when set, otherwise builds host:port from server binding; omitted when empty; empty-IP warning logged once per plugin enable
  • serverId sourced from dpc-api.server-id (required; sync skips with warning if blank)
  • Failed requests logged, never crash the plugin
  • Payload serialization: DpcFactionPayload data class with Gson
  • Payload field truncation: All string fields truncated to schema limits (name: 64, serverId: 64, description: 512, serverIp: 253, discordLink: 512)
  • memberCount: Non-negative integer from faction member list size
  • discordLink validation: Only included if the configured value starts with https://discord.gg/ or https://discord.com/; invalid values omitted with a once-per-cycle warning. Also validated at command time — invalid links are rejected with a user-facing error.

Operator Login Reminder

  • Fires on PlayerJoinEvent when isOp && !enabled && loginReminder
  • Directs operators to https://dansplugins.com to obtain an API key, and to set dpc-api.key and dpc-api.server-id before opting in
  • Mentions optin, shareip, discord, and reminder off commands

API Key Acquisition

Operators visit https://dansplugins.com, create an account or sign in, then generate a key from their account page. Documented in the config comments, CONFIG.md, and the login reminder message.

bStats

Four SimplePie charts: dpc_api_opt_in, dpc_api_login_reminder, dpc_api_share_server_ip, dpc_api_discord_link_set

Docs & i18n

  • CONFIG.md and COMMANDS.md updated
  • Language strings added to all 5 locale files (en_US, en_GB, de_DE, fr_FR, pt_BR)
  • Fixed pre-existing locale bug: CannotInteractBlockInWilderness and CmdFactionAdmin were concatenated on one line in all 5 locale files
  • Help text in non-English locales corrected to describe config/sharing management
  • docs/dpc-api-schema.asn1: Formal ASN.1 schema documenting the DPC API payload: memberCount INTEGER (0..100000), description OPTIONAL, serverIp/discordLink OPTIONAL
  • .github/copilot-instructions.md: Stack, build, DPC API conventions, Pact contract test documentation, and OMCSI integration testing roadmap

Build & CI

  • build.gradle: Added mavenLocal() as the first repository so locally-built Ponder artifacts resolve when repo.dansplugins.com is unavailable
  • build.yml: Added "Build Ponder dependency" step and docker-build job
  • release.yml: Same Ponder build step added before shadowJar
  • Dockerfile: Now self-contained — builds Ponder from source, then builds the plugin (shadowJar) inside the container; installs both JDK 17 (Gradle toolchain) and JDK 21 (MC 1.21 server runtime)

Tests

  • MfFactionDpcCommandTest — 14 cases: permission denial, all subcommands, edge cases, invalid discord link rejected at command time, discord.com link accepted
  • MfDpcApiServiceTest — 9 cases using mock HttpClient: correct fields for normal factions, serverIp omitted when disabled, discordLink omitted when blank or invalid, memberCount non-negative, field truncation, sync skipped when serverId blank, non-default port in serverIp, server-address override
  • MfDpcApiPactConsumerTest (Pact 4.6.7) — 2 contract interactions against the live POST /api/v1/factions wire format: required fields only, and all optional fields included. Uses DpcFactionPayload + Gson directly (same serialization as production). Generates pacts/medieval-factions-dpc-api.json (committed so the provider can verify without re-running the consumer test).

Review fixes (this iteration)

Fix: discord link not validated at command time

/mf dpc discord <link> previously accepted any string and saved it to config. During the sync, an invalid link would be silently omitted with a log-only warning — the operator had no immediate feedback. Fixed: handleDiscord now validates the format before saving; invalid links are rejected with a red in-game message (CommandFactionDpcDiscordInvalidLink). New locale string added to all 5 locales.

Fix: ASN.1 schema inaccuracies

  • memberCount was INTEGER (0..MAX) (ASN.1 unbounded) — corrected to INTEGER (0..100000) to match the API's @Max(100000) constraint
  • description was required in the schema but the DPC API allows null (FactionRequest has no @NotNull) — marked OPTIONAL

Docs: COMMANDS.md discord usage

Changed discord [link] to discord <link> (angle = required; square = optional). Added note that the link must start with https://discord.gg/ or https://discord.com/.

Integration testing roadmap

Added OMCSI-based integration testing steps to .github/copilot-instructions.md, mirroring the Dans-Plugin-Manager pattern. Steps: build MF JAR → spin up OMCSI server → start local DPC API (TestContainers or WireMock) → configure opt-in → assert POST received with correct payload shape.

Pact contract tests

Added MfDpcApiPactConsumerTest as the consumer side of a Pact contract. Committed pacts/medieval-factions-dpc-api.json captures the expected HTTP interactions. The provider verification test lives in Dans-Plugins/dansplugins-dot-com#110.


Contract reference

See docs/dpc-api-schema.asn1 for the formal wire format. Server-side enforcement is in Dans-Plugins/dansplugins-dot-comdpc-api/src/main/java/com/dansplugins/api/dto/FactionRequest.java (Bean Validation).

Copilot AI and others added 2 commits March 27, 2026 05:54
- Add dpc-api config entries with safe defaults (enabled: false, login-reminder: true, etc.)
- Add MfDpcApiService for async HTTP faction data sync via java.net.http.HttpClient
- Add MfFactionDpcCommand with subcommands: optin, optout, reminder, shareip, discord
- Register dpc subcommand in MfFactionCommand with aliases and tab completion
- Add operator login reminder to PlayerJoinListener (shown when !enabled && loginReminder)
- Add periodic async sync task (every 10 minutes) in MedievalFactions.kt
- Add 4 bStats pie charts: dpc_api_opt_in, dpc_api_login_reminder, dpc_api_share_server_ip, dpc_api_discord_link_set
- Add language strings to all 5 lang files (en_US, en_GB, de_DE, fr_FR, pt_BR)
- Add DPC command documentation to COMMANDS.md
- Add DPC config documentation to CONFIG.md
- Add unit tests for MfFactionDpcCommand

Agent-Logs-Url: https://github.com/Dans-Plugins/Medieval-Factions/sessions/e497d56e-78a1-43e7-a56e-4c7764cf07f6

Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
…t, decouple DPC reminder

Agent-Logs-Url: https://github.com/Dans-Plugins/Medieval-Factions/sessions/e497d56e-78a1-43e7-a56e-4c7764cf07f6

Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
Copilot AI changed the title [WIP] Add DPC community API integration for faction sharing Add DPC community API integration for cross-server faction sharing Mar 27, 2026
Copilot AI requested a review from dmccoystephenson March 27, 2026 05:57
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in integration that periodically syncs faction data to the DPC community API and exposes server-admin controls via a new /mf dpc subcommand, with associated config/docs/i18n and metrics.

Changes:

  • Introduces MfDpcApiService and schedules periodic async sync of faction data to POST /api/v1/factions.
  • Adds /mf dpc ... command handlers (opt-in/out + toggles) and an operator login reminder when not opted in.
  • Extends config, docs, bStats charts, and locale strings to support the new integration; adds command unit tests.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/main/kotlin/com/dansplugins/factionsystem/dpc/MfDpcApiService.kt New service that builds JSON payloads and POSTs them asynchronously to the DPC API.
src/main/kotlin/com/dansplugins/factionsystem/MedievalFactions.kt Registers new bStats charts and schedules periodic DPC sync task.
src/main/kotlin/com/dansplugins/factionsystem/command/faction/dpc/MfFactionDpcCommand.kt New /mf dpc subcommand implementation with config-backed toggles.
src/main/kotlin/com/dansplugins/factionsystem/command/faction/MfFactionCommand.kt Routes the new dpc subcommand via aliases and tab completion.
src/main/kotlin/com/dansplugins/factionsystem/listener/PlayerJoinListener.kt Adds op-only opt-in reminder message on join (when configured).
src/main/resources/config.yml Adds dpc-api configuration section with defaults.
CONFIG.md Documents the new dpc-api.* configuration options.
COMMANDS.md Documents the new /faction dpc / /mf dpc commands and permission.
src/main/resources/lang/lang_en_US.properties Adds DPC command/reminder strings.
src/main/resources/lang/lang_en_GB.properties Adds DPC command/reminder strings.
src/main/resources/lang/lang_de_DE.properties Adds DPC command/reminder strings.
src/main/resources/lang/lang_fr_FR.properties Adds DPC command/reminder strings.
src/main/resources/lang/lang_pt_BR.properties Adds DPC command/reminder strings.
src/test/kotlin/com/dansplugins/factionsystem/command/faction/dpc/MfFactionDpcCommandTest.kt Adds unit tests covering the new command behavior.

Comment on lines +36 to +42
val serverName = plugin.server.name

val jsonArray = JsonArray()
for (faction in factions) {
val obj = JsonObject()
obj.addProperty("name", faction.name)
obj.addProperty("serverId", serverName)
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

serverId is currently derived from plugin.server.name, which in Bukkit/Paper is the server implementation name (e.g., "CraftBukkit"/"Paper") rather than a unique identifier for this specific server. That will cause different servers to collide on the same serverId value in the DPC API payload. Use a stable per-server identifier instead (e.g., plugin.server.serverName if available, or a dedicated config value such as dpc-api.server-id).

Suggested change
val serverName = plugin.server.name
val jsonArray = JsonArray()
for (faction in factions) {
val obj = JsonObject()
obj.addProperty("name", faction.name)
obj.addProperty("serverId", serverName)
val serverIdConfig = plugin.config.getString("dpc-api.server-id")
val serverId = serverIdConfig?.takeIf { it.isNotBlank() } ?: plugin.server.serverName
if (serverId.isBlank()) {
plugin.logger.warning("DPC server ID is not configured and server name is blank. Skipping faction sync.")
return
}
val jsonArray = JsonArray()
for (faction in factions) {
val obj = JsonObject()
obj.addProperty("name", faction.name)
obj.addProperty("serverId", serverId)

Copilot uses AI. Check for mistakes.
@dmccoystephenson
Copy link
Copy Markdown
Member

@copilot address comments and also how do operators get an API key are we gonna have account creation in this PR?

…ition

- Replace plugin.server.name with dpc-api.server-id config for unique server identification
- Add API key instructions to login reminder, CONFIG.md, and config.yml comments
- Update all 5 lang files with setup instructions (visit dansplugins.com for key)

Agent-Logs-Url: https://github.com/Dans-Plugins/Medieval-Factions/sessions/cf701e83-94fd-4778-87bd-fc73ae05dc90

Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 28, 2026

@copilot address comments and also how do operators get an API key are we gonna have account creation in this PR?

Addressed in 4890171:

  1. Reviewer comment fixedserverId now comes from a dedicated dpc-api.server-id config option instead of plugin.server.name (which returns "CraftBukkit"/"Paper"). The sync will log a warning and skip if server-id is blank.

  2. API key acquisition — Account creation is on the DPC website side (dansplugins.com), not in this PR. The plugin now documents the workflow in three places:

    • config.yml comment: # API key from https://dansplugins.com — register or sign in, then generate a key from your account page
    • CONFIG.md: full description pointing operators to dansplugins.com
    • Login reminder message: tells operators to "Get an API key at https://dansplugins.com, set dpc-api.key and dpc-api.server-id in config.yml, then run /mf dpc optin"

@dmccoystephenson
Copy link
Copy Markdown
Member

@copilot align the solution with the latest changes in Dans-Plugins/dansplugins-dot-com#110

New /mf dpc subcommands: register, login, profile, generatekey, deletekey.
MfDpcApiService gains account API methods (register, login, profile, API
key create/delete) with JWT token storage per player.
Updated all 5 locale files, COMMANDS.md, CONFIG.md, and tests (24 cases).

Agent-Logs-Url: https://github.com/Dans-Plugins/Medieval-Factions/sessions/6b5b26ea-fd68-45ee-bfec-29f54a488143

Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 28, 2026

@copilot align the solution with the latest changes in Dans-Plugins/dansplugins-dot-com#110

Implemented in e56dbcd. The plugin now aligns with the DPC API from dansplugins-dot-com PR #110:

New /mf dpc subcommands — players can register, login, view profile, generate API keys, and delete API keys directly from in-game without needing a browser:

  • /mf dpc register <username> <password>POST /api/v1/accounts/register
  • /mf dpc login <username> <password>POST /api/v1/accounts/login
  • /mf dpc profileGET /api/v1/accounts/me
  • /mf dpc generatekey <serverName>POST /api/v1/accounts/me/api-keys
  • /mf dpc deletekey <keyId>DELETE /api/v1/accounts/me/api-keys/{keyId}

MfDpcApiService stores JWT tokens per player UUID (ConcurrentHashMap), auto-clears on 401. All HTTP calls are async via HttpClient.sendAsync. Login reminder updated to mention in-game registration as primary flow.

Tests expanded to 24 cases covering all new subcommands. All 5 locale files, COMMANDS.md, and CONFIG.md updated.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • hub.spigotmc.org
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)
  • jitpack.io
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)
  • repo.dansplugins.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)
  • repo.extendedclip.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)
  • repo.mikeprimm.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)
  • repo.rpkit.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title Add DPC community API integration for cross-server faction sharing Add DPC community API integration with in-game account management Mar 28, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 6 comments.

Comment on lines +258 to +260
if (shareServerIp) {
obj.addProperty("serverIp", plugin.server.ip.ifEmpty { plugin.server.port.toString() })
}
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When Server#getIp is empty, the code sends only the port as serverIp (e.g., 25565), which is not a valid address and is very likely not what the API expects. Build a proper host:port (and/or introduce a dedicated dpc-api.server-address config) instead of falling back to port.toString().

Copilot uses AI. Check for mistakes.
Comment on lines +123 to +124
val username = args[1]
val password = args[2]
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepting passwords as command arguments is risky because commands are commonly logged by servers/proxies and may be visible to staff tooling. Prefer a non-logging input flow (e.g., conversation-style prompt collected from chat with cancellation/timeout, or directing users to register/login via the website), and avoid storing/echoing raw credentials anywhere.

Copilot uses AI. Check for mistakes.
Comment on lines +101 to +105
} else if (response.statusCode() == 401) {
onFailure("InvalidCredentials")
} else {
onFailure("HTTP ${response.statusCode()}")
}
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The service returns internal/error-code strings like InvalidCredentials, NotLoggedIn, SessionExpired, and HTTP 500 which end up shown to players (via {0} placeholders). Map these to localized, player-friendly messages (or structured error types) and consider including a sanitized server-provided error message (e.g., JSON message) for non-2xx responses to make failures actionable.

Copilot uses AI. Check for mistakes.
GateCreateAreaRestrictedBlock=O port�o que voc� est� tentando criar cont�m um bloco restrito.
CannotBreakBlockInWilderness=Voc� n�o pode quebrar blocos na natureza.
CannotPlaceBlockInWilderness=Voc� n�o pode colocar blocos na natureza.
CannotInteractBlockInWilderness=Voc� n�o pode interagir com blocos na natureza.CmdFactionAdmin=admin
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line appears to accidentally concatenate two properties (CannotInteractBlockInWilderness and CmdFactionAdmin), which would prevent CmdFactionAdmin from being parsed as its own key and would corrupt the wilderness message. Split it into two separate lines so both keys are correctly defined.

Suggested change
CannotInteractBlockInWilderness=Voc� n�o pode interagir com blocos na natureza.CmdFactionAdmin=admin
CannotInteractBlockInWilderness=Voc� n�o pode interagir com blocos na natureza.
CmdFactionAdmin=admin

Copilot uses AI. Check for mistakes.
GateCreateAreaRestrictedBlock=Das Tor, das Sie zu erstellen versuchen, enth�lt einen eingeschr�nkten Block.
CannotBreakBlockInWilderness=Du kannst keine Bl�cke in der Wildnis brechen.
CannotPlaceBlockInWilderness=Du kannst keine Bl�cke in der Wildnis platzieren.
CannotInteractBlockInWilderness=Du kannst nicht mit Bl�cken in der Wildnis interagieren.CmdFactionAdmin=admin
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as the pt_BR locale: two properties are concatenated on one line, which breaks parsing for CmdFactionAdmin and pollutes the wilderness message. Split into two separate property lines.

Suggested change
CannotInteractBlockInWilderness=Du kannst nicht mit Bl�cken in der Wildnis interagieren.CmdFactionAdmin=admin
CannotInteractBlockInWilderness=Du kannst nicht mit Bl�cken in der Wildnis interagieren.
CmdFactionAdmin=admin

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +21
class MfDpcApiService(private val plugin: MedievalFactions) {

private val httpClient: HttpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build()
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are thorough tests for MfFactionDpcCommand, but the new MfDpcApiService behavior is untested (token storage/clearing on 401, request/response parsing, and sync payload composition/headers). Consider adding tests by injecting an HTTP abstraction (or a configurable HttpClient) so responses can be simulated deterministically without actual network calls.

Suggested change
class MfDpcApiService(private val plugin: MedievalFactions) {
private val httpClient: HttpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build()
class MfDpcApiService(
private val plugin: MedievalFactions,
private val httpClient: HttpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build()
) {

Copilot uses AI. Check for mistakes.
@dmccoystephenson
Copy link
Copy Markdown
Member

@copilot address comments

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 24 changed files in this pull request and generated 2 comments.

Comment thread docs/dpc-api-schema.asn1
name UTF8String (SIZE(1..64)),
serverId UTF8String (SIZE(1..64)),
memberCount INTEGER (0..MAX),
description UTF8String (SIZE(0..512)) OPTIONAL,
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The schema marks description as OPTIONAL, but the implementation/tests always include description in the JSON payload (and DpcFactionPayload.description is non-nullable). Please align the schema with the actual payload contract (either make description required in the schema, or update the payload to omit it when empty/null).

Suggested change
description UTF8String (SIZE(0..512)) OPTIONAL,
description UTF8String (SIZE(0..512)),

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +86
val serverIp: String? = if (shareServerIp) {
val serverAddress = plugin.config.getString("dpc-api.server-address")?.takeIf { it.isNotBlank() }
if (serverAddress != null) {
truncate(serverAddress, MAX_SERVER_IP)
} else {
val ip = plugin.server.ip
val port = plugin.server.port
if (ip.isNotEmpty()) {
val addr = if (port != 25565) "$ip:$port" else ip
truncate(addr, MAX_SERVER_IP)
} else {
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

serverIp formatting/selection logic (explicit dpc-api.server-address vs auto-detected ip and conditional :port when port != 25565) isn't covered by tests. Adding test cases for (1) non-default port includes ":port" and (2) configured server-address overrides server binding would help prevent regressions.

Copilot uses AI. Check for mistakes.
@dmccoystephenson
Copy link
Copy Markdown
Member

@copilot address comments

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 4, 2026

@copilot address comments

Addressed both reviewer comments in cf079db:

  1. Schema description alignment — Removed OPTIONAL from description in docs/dpc-api-schema.asn1 since DpcFactionPayload.description is non-nullable and always included in the payload.
  2. serverIp test coverage — Added 2 test cases: (a) non-default port includes :port in serverIp, (b) configured server-address overrides server binding.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 24 changed files in this pull request and generated 3 comments.

Comment on lines +106 to +108
val body = gson.toJson(payloads)
val url = apiUrl.trimEnd('/') + "/api/v1/factions"

Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dpc-api.url is treated as a base URL, but the code unconditionally appends /api/v1/factions. If an operator follows documentation/examples that include the endpoint path (or any base path), the request URL becomes duplicated (e.g. .../api/v1/factions/api/v1/factions). Consider either (1) documenting/enforcing that dpc-api.url must be the base host only, with validation that rejects/strips /api/v1/factions, or (2) allowing dpc-api.url to be the full endpoint and removing the hard-coded append (or appending only when missing).

Copilot uses AI. Check for mistakes.
Comment on lines +83 to +88
if (ip.isNotEmpty()) {
val addr = if (port != 25565) "$ip:$port" else ip
truncate(addr, MAX_SERVER_IP)
} else {
plugin.logger.warning("Server IP is empty and dpc-api.server-address is not configured. Omitting serverIp from sync payload.")
null
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When share-server-ip is enabled and dpc-api.server-address is blank, many Bukkit servers report server.ip as an empty string (bind-all). This will log a warning every sync cycle and always omit serverIp, even though the operator likely just needs to set dpc-api.server-address. Consider reducing log spam (e.g., log once per plugin run / per enable) and/or making the warning explicitly instruct to set dpc-api.server-address for typical setups where the bound IP is empty.

Copilot uses AI. Check for mistakes.
Comment on lines +272 to +274
override fun onNext(item: ByteBuffer) {
body.append(StandardCharsets.UTF_8.decode(item))
}
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

captureRequestBody() decodes each emitted ByteBuffer chunk independently via UTF_8.decode(item). If the publisher splits a multi-byte UTF-8 sequence across buffers, this can corrupt the decoded string and make tests flaky for non-ASCII payloads. Consider collecting the bytes (or using a CharsetDecoder that preserves partial sequences across chunks) and decoding once at the end.

Copilot uses AI. Check for mistakes.
@dmccoystephenson
Copy link
Copy Markdown
Member

@copilot You are a coding agent tasked with resolving all open comments and review threads
on this pull request. Work through every comment systematically and apply the
appropriate code changes.


Instructions

1. Discover all comments

  • Read every review thread, inline comment, and general PR comment
  • Include comments from: Copilot, human reviewers, and any other bots
  • Do not skip comments marked as suggestions — apply them if they make sense

2. For each comment, do the following:

  • Understand what is being requested or flagged
  • Locate the relevant file(s) and line(s)
  • Apply the fix directly in code
  • If a Copilot suggestion diff is provided, apply it (or an equivalent fix)
  • If the comment is ambiguous, use your best judgment and reply in the PR thread or review comment explaining what you did; only add a code comment when explicitly requested or when future maintainers would benefit from it
  • After addressing a comment, reply to the thread summarising what you did and mark it as resolved if your environment supports it

3. Types of issues to handle:

  • Code quality / style issues (naming, formatting, readability)
  • Logic bugs or edge cases flagged by reviewers
  • Security concerns (e.g. input validation, secrets, injection risks)
  • Performance suggestions
  • Missing or incomplete error handling
  • Test coverage gaps
  • Documentation / comment improvements
  • Dependency or import issues

4. Do NOT:

  • Dismiss or ignore any comment without acting on it — note that "acting" can also mean replying to the thread with a rationale (e.g., won't fix, already addressed elsewhere) and leaving the code unchanged when appropriate
  • Leave TODO comments as a substitute for fixing the issue
  • Break existing tests while addressing comments
  • Change functionality beyond the scope of what the comment asks for

After making changes

  • Ensure all existing tests still pass
  • Add or update tests if a comment explicitly asks for them, or if a bug fix warrants it
  • Double-check that every open thread has been addressed, replied to, and resolved

Context

  • This PR may contain Copilot-generated review comments — treat these with the same priority as human comments
  • If two comments conflict with each other, prefer the human reviewer's intent and document the conflict and resolution in the PR conversation or commit message, not in a code comment
  • Commit changes in logical groups if there are many unrelated fixes

…teBuffer decoding

Agent-Logs-Url: https://github.com/Dans-Plugins/Medieval-Factions/sessions/c04624da-5bf9-4802-b321-1fd8e297e5df

Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 12, 2026

@copilot You are a coding agent tasked with resolving all open comments and review threads
on this pull request. Work through every comment systematically and apply the
appropriate code changes....

Addressed all 3 open review comments in 665b3be:

  1. URL duplication fixdpc-api.url is now the full endpoint URL (default: https://dansplugins.com/api/v1/factions). Removed the hard-coded /api/v1/factions append that could cause URL duplication if operators included the path.
  2. Empty server IP log spam — Added emptyServerIpWarningLogged flag to log the warning only once per plugin enable instead of every sync cycle. Warning now explicitly instructs operators to set dpc-api.server-address.
  3. ByteBuffer decodingcaptureRequestBody() now collects raw bytes from all chunks then decodes once at the end using String(combined, UTF_8) to correctly handle multi-byte UTF-8 sequences split across buffers.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • hub.spigotmc.org
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)
  • jitpack.io
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)
  • repo.dansplugins.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)
  • repo.extendedclip.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)
  • repo.mikeprimm.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)
  • repo.rpkit.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED --add-opens java.base/java.nio.charset=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED -XX:MaxMetaspaceSize=256m (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@dmccoystephenson
Copy link
Copy Markdown
Member

@copilot You are a coding agent tasked with auditing and aligning this repository's REST API
with the Richardson Maturity Model (RMM). The four levels are:

  • Level 0 – The Swamp of POX: A single URI, a single HTTP method (usually POST),
    all operations expressed in the request body.
  • Level 1 – Resources: Individual URIs per resource, but HTTP methods are not
    yet used semantically.
  • Level 2 – HTTP Verbs: Resources use HTTP methods correctly (GET, POST, PUT,
    PATCH, DELETE) and return semantically correct status codes.
  • Level 3 – Hypermedia Controls (HATEOAS): Responses include hypermedia links
    that guide the client to available next actions.

Work through each phase below in order. Before making any change that would alter
an existing public API contract (URI paths, request/response shapes, HTTP methods,
or status codes), follow the breaking-change protocol in Phase 0.


Phase 0 — Breaking-Change Protocol

Before touching any existing endpoint, complete the following steps:

  1. Inventory public endpoints. List every route, HTTP method, request shape,
    and response shape that is currently exposed. Record this inventory in a file
    called docs/api-breaking-changes.md.

  2. Identify breaking changes. For each change required by all subsequent phases, decide
    whether it is breaking:

    • Changing a URI path — breaking
    • Changing an HTTP method for an existing operation — breaking
    • Removing or renaming a request/response field — breaking
    • Changing a success status code (e.g., 200201) — breaking
    • Adding optional response fields or new hypermedia links — non-breaking
  3. Version the API. If any breaking changes are required:

    • Introduce a version prefix on all routes (e.g., /v1/, /v2/) if one does
      not already exist.
    • Keep the original unversioned or lower-version routes active and mark them
      deprecated in the OpenAPI spec; when the framework and API stack support it,
      also expose deprecation metadata via appropriate HTTP mechanisms such as
      Sunset (RFC 8594) and/or relevant Link relations.
    • Implement the aligned behavior under a new version prefix.
    • Document the deprecation timeline and migration path in docs/api-breaking-changes.md.
  4. Do not remove deprecated routes. Leave them in place — removal is out of
    scope for this task and must be a separate, explicitly approved change.


Phase 1 — Assess Current RMM Level

Inspect the codebase and determine the current RMM level:

  • Find all route/controller definitions and note their URI patterns.
  • Check whether operations are distinguished by URI, HTTP method, or only by a
    field in the request body.
  • Record the current level and a one-paragraph rationale in docs/api-breaking-changes.md.

Phase 2 — Advance to Level 1 (Resources)

Audit for:

  • Multiple logically distinct operations sharing a single URI.
  • Operations on sub-resources embedded in the root URI rather than addressed
    by their own path segment (e.g., /api?action=getUser instead of /api/users/{id}).

Fix by:

  • Assigning a dedicated URI to every resource and sub-resource
    (e.g., /users, /users/{id}, /orders/{id}/items).
  • Following the breaking-change protocol for any URI that changes.
  • Using noun-based, plural path segments for collection resources.

Phase 3 — Advance to Level 2 (HTTP Verbs)

Audit for:

  • GET requests that mutate state.
  • POST requests used for retrieval, update, or deletion.
  • PUT used for partial updates (should be PATCH).
  • Missing or incorrect HTTP status codes.

Fix by:

  • Mapping each operation to the correct HTTP method:
    • GET — safe, idempotent retrieval (no side effects).
    • POST — resource creation or non-idempotent actions.
    • PUT — idempotent full replacement of an existing resource.
    • PATCH — partial update of an existing resource.
    • DELETE — idempotent removal of a resource.
  • Standardizing status codes:
    • 200 OK — successful retrieval or update with response body.
    • 201 Created — successful resource creation; include a Location header
      pointing to the new resource URI.
    • 204 No Content — successful operation with no response body (e.g., DELETE).
    • 400 Bad Request — malformed, unparsable, or syntactically invalid request.
    • 404 Not Found — resource does not exist.
    • 405 Method Not Allowed — HTTP method not supported on this resource.
    • 409 Conflict — state conflict (e.g., duplicate creation).
    • 422 Unprocessable Entity — well-formed request with semantic or business-rule
      validation errors.
    • 500 Internal Server Error — unexpected server-side failure.
  • Following the breaking-change protocol for any method or status code that changes.

Phase 4 — Advance to Level 3 (HATEOAS)

Audit for:

  • Responses that return data but no links describing what the client can do next.
  • Clients that must construct URIs by hand rather than following links returned
    by the server.

Fix by:

  • Adding a _links object (HAL convention) or equivalent to every resource
    response, containing at minimum:
    • self — the canonical URI of the returned resource.
    • Links to related state transitions available from the current resource state
      (e.g., cancel, approve, next-page, previous-page).
  • Adding a _links.self to every collection response, plus next and prev
    pagination links when the collection is paginated.
  • Updating the OpenAPI specification to document the _links structure for
    every response schema.
  • Ensuring new link fields are additive and non-breaking; follow the
    breaking-change protocol only if existing response fields are removed or renamed.

Phase 5 — Update API Documentation

  • Update or create an OpenAPI (Swagger) specification reflecting the aligned API.
  • Mark any deprecated endpoints with deprecated: true in the spec and include
    a description note explaining the migration path.
  • Ensure every endpoint documents its request schema, all possible response
    status codes, and (at Level 3) the _links structure.
  • Record a summary of all changes made, the before/after RMM level, and the
    list of breaking changes with their versioning decisions in
    docs/api-breaking-changes.md.

Constraints

  • Do not remove deprecated endpoint versions — that is out of scope.
  • Do not change behavior that is not required by the target RMM level.
  • Follow the naming, package, and formatting conventions already present in
    the codebase.
  • When uncertain about a convention, find an existing endpoint that follows
    it correctly and match it exactly.
  • Apply the breaking-change protocol before every change that alters a public
    API contract.

Definition of Done

All of the following must be true before considering the task complete:

  • docs/api-breaking-changes.md exists and lists every breaking change,
    its versioning decision, and the deprecation timeline.
  • Every resource has a dedicated URI using noun-based, plural path segments
    (Level 1).
  • Every operation uses the correct HTTP method and returns a semantically
    correct status code (Level 2).
  • Every resource response includes a _links object with at least a self
    link and relevant state-transition links (Level 3).
  • All collection responses include pagination links where applicable.
  • The OpenAPI specification is updated to reflect the aligned API and marks
    deprecated endpoints.
  • No previously working endpoint has been removed — deprecated versions remain
    active.
  • The build and all existing tests pass.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 17, 2026

@copilot You are a coding agent tasked with auditing and aligning this repository's REST API
with the Richardson Maturity Model (RMM). The four levels are:

...

After auditing the codebase, the Richardson Maturity Model doesn't apply here. Medieval Factions is a Bukkit/Paper Minecraft plugin — it does not expose any REST API endpoints. The only HTTP interaction is MfDpcApiService, which is an outbound HTTP client that POSTs faction data to the external DPC API at dansplugins.com.

There are no HTTP server frameworks, no routes, no endpoint handlers, and no web server dependencies anywhere in the project. All user interaction happens through Minecraft commands and in-game events.

The RMM framework evaluates how a server designs its API endpoints for clients. Since this plugin is purely a consumer (client) of one external API, there are no endpoints to audit, version, or add HATEOAS links to. The RMM audit would be applicable to the DPC API server itself (dansplugins.com), but that's a separate project outside this repository.

@dmccoystephenson dmccoystephenson changed the base branch from develop to main April 25, 2026 21:49
@dmccoystephenson dmccoystephenson modified the milestones: 5.9.0, 5.10.0 Apr 26, 2026
… integration testing path

- MfFactionDpcCommand: reject invalid discord links at command time with user-facing error (previously accepted any string; sync would silently omit it later with only a log warning)
- Add CommandFactionDpcDiscordInvalidLink locale string to all 5 locales (en_US, en_GB, de_DE, fr_FR, pt_BR)
- MfFactionDpcCommandTest: add tests for invalid link rejection and discord.com link acceptance
- docs/dpc-api-schema.asn1: fix memberCount bound (0..MAX → 0..100000 matching API @max constraint); mark description as OPTIONAL (API accepts null)
- COMMANDS.md: change discord [link] to discord <link>, document required URL format
- copilot-instructions.md: note description is OPTIONAL in schema; add OMCSI integration testing roadmap

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@dmccoystephenson dmccoystephenson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — DPC community API integration

Reviewed documentation accuracy, code correctness, contract formalization, and integration testing paths. Changes pushed to branch.


Fix: discord link not validated at command time

/mf dpc discord <link> accepted any string and saved it to config. During the sync cycle, MfDpcApiService.syncFactions would silently omit an invalid link (log-only warning) — the operator would have no immediate feedback that their link was ignored.

Fixed: handleDiscord now checks startsWith("https://discord.gg/") or startsWith("https://discord.com/") before saving. Invalid links are rejected with a red user-facing message (CommandFactionDpcDiscordInvalidLink). New locale string added to all 5 locales.

Two new command tests cover this:

  • testOnCommand_discordInvalidLinkRejected — verifies config.set is never called on bad URL
  • testOnCommand_discordComLinkAccepted — verifies https://discord.com/invite/… form is accepted

Fix: ASN.1 schema inaccuracies

docs/dpc-api-schema.asn1:

  • memberCount was INTEGER (0..MAX) (ASN.1 unbounded). The DPC API enforces @Max(100000). Fixed to INTEGER (0..100000).
  • description was required (no OPTIONAL). The DPC API's FactionRequest allows null (no @NotNull). Fixed to OPTIONAL.

Docs: COMMANDS.md discord usage line

Changed discord [link]discord <link> (angle brackets for required args; square brackets conventionally mean optional). Added a note that the link must start with https://discord.gg/ or https://discord.com/.


Integration testing path forward

Added an OMCSI roadmap section to .github/copilot-instructions.md documenting the recommended steps for full end-to-end testing, mirroring the Dans-Plugin-Manager integration test pattern:

  1. Build MF shadow JAR
  2. Spin up an OMCSI Minecraft server
  3. Start a local DPC API instance (TestContainers or WireMock)
  4. Configure opt-in via config injection, wait one sync interval
  5. Assert the mock received a well-formed POST matching the schema

Until OMCSI tests exist, the mock-HttpClient unit tests in MfDpcApiServiceTest are the primary contract verification — keep them comprehensive for all field/omission/truncation/validation cases.


Remaining observation (no code change required)

The serverIp field in the payload is called serverIp in the Kotlin data class and in the API FactionRequest DTO — consistent. But the server-address config key is only for the "explicit override" path. If share-server-ip is true and both server.ip is empty and server-address is blank, the field is omitted with a once-per-enable warning. This behavior is correct and documented; no change needed, just noting it for visibility.

dmccoystephenson and others added 2 commits May 19, 2026 09:00
Adds MfDpcApiPactConsumerTest covering two interactions — required fields
only and all optional fields — against the POST /api/v1/factions endpoint.
Commits the generated pact file so the provider can verify without running
the consumer test first. Documents the contract test layer in copilot-instructions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously only triggered on PRs targeting main or develop.
Removing the branch filter ensures CI runs on every PR so
status checks can be required for merging.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants