fix: restore visibility arg on /core team create + auto-migrate leader_id
/core team create now takes [visibility] [leader] (both optional, positional in this order). Variants: - /core team create N T C → PRIVATE, leaderless - /core team create N T C PUBLIC → PUBLIC, leaderless - /core team create N T C PRIVATE Alice → PRIVATE, Alice as leader - /core team create N T C PUBLIC Alice → PUBLIC, Alice as leader Auto-migration for pre-existing SQLite databases: SqliteTeamRepository.ensureSchema() now checks pragma_table_info for the leader_id column. If it still has the NOT NULL constraint from an older schema, it recreates the table (in a transaction) with leader_id nullable and copies the rows over. Required because CREATE TABLE IF NOT EXISTS skips the alteration on existing tables, so production databases were crashing with SQLITE_CONSTRAINT_NOTNULL when an admin tried to create a leaderless team. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import fr.luc.crcore.team.Team;
|
||||
import fr.luc.crcore.team.TeamColor;
|
||||
import fr.luc.crcore.team.TeamException;
|
||||
import fr.luc.crcore.team.TeamService;
|
||||
import fr.luc.crcore.team.TeamVisibility;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Objects;
|
||||
@@ -15,21 +16,20 @@ import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* {@code /core team create <name> <tag> <color> [leader]}
|
||||
* {@code /core team create <name> <tag> <color> [visibility] [leader]}
|
||||
*
|
||||
* <p><b>Admin uniquement</b>. Crée une équipe en {@link
|
||||
* fr.luc.crcore.team.TeamVisibility#PRIVATE} par défaut.
|
||||
* <p><b>Admin uniquement</b>.
|
||||
*
|
||||
* <p>Les deux derniers arguments sont optionnels mais <b>positionnels</b> :
|
||||
* pour passer un {@code leader}, il faut aussi taper la {@code visibility}
|
||||
* d'abord (PUBLIC ou PRIVATE). Variantes valides :
|
||||
*
|
||||
* <p>Le chef est <b>optionnel</b> :
|
||||
* <ul>
|
||||
* <li>Sans argument {@code leader} → équipe leaderless. L'admin assignera
|
||||
* plus tard via {@code /core team setleader}.</li>
|
||||
* <li>Avec argument {@code leader} (nom d'un joueur connecté) → ce joueur
|
||||
* devient chef et membre de l'équipe.</li>
|
||||
* <li>{@code /core team create Wolves WOLF RED} → PRIVATE, leaderless</li>
|
||||
* <li>{@code /core team create Wolves WOLF RED PUBLIC} → PUBLIC, leaderless</li>
|
||||
* <li>{@code /core team create Wolves WOLF RED PRIVATE Alice} → Alice chef, PRIVATE</li>
|
||||
* <li>{@code /core team create Wolves WOLF RED PUBLIC Alice} → Alice chef, PUBLIC</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>La visibilité (PUBLIC/PRIVATE) se change ensuite via {@code /core team
|
||||
* visibility} (action du chef).
|
||||
*/
|
||||
public class TeamCreateSubCommand extends SubCommand {
|
||||
|
||||
@@ -43,6 +43,7 @@ public class TeamCreateSubCommand extends SubCommand {
|
||||
argument("name", ArgumentTypes.STRING);
|
||||
argument("tag", ArgumentTypes.STRING);
|
||||
argument("color", ArgumentTypes.enumOf(TeamColor.class));
|
||||
optionalArgument("visibility", ArgumentTypes.enumOf(TeamVisibility.class));
|
||||
optionalArgument("leader", ArgumentTypes.ONLINE_PLAYER);
|
||||
}
|
||||
|
||||
@@ -51,15 +52,18 @@ public class TeamCreateSubCommand extends SubCommand {
|
||||
String name = ctx.get("name");
|
||||
String tag = ctx.get("tag");
|
||||
TeamColor color = ctx.get("color");
|
||||
TeamVisibility visibility = ctx.<TeamVisibility>getOptional("visibility")
|
||||
.orElse(TeamVisibility.PRIVATE);
|
||||
Optional<Player> leaderOpt = ctx.getOptional("leader");
|
||||
UUID leaderId = leaderOpt.map(Player::getUniqueId).orElse(null);
|
||||
|
||||
try {
|
||||
Team team = service.createTeam(name, tag, color, leaderId);
|
||||
String suffix = leaderOpt.isPresent()
|
||||
? " (chef : " + leaderOpt.get().getName() + ")"
|
||||
: " (sans chef)";
|
||||
return CommandResult.success("Équipe " + team.getName() + " [#" + team.getTag() + "] créée" + suffix + ".");
|
||||
Team team = service.createTeam(name, tag, color, leaderId, visibility);
|
||||
String chefPart = leaderOpt.isPresent()
|
||||
? "chef : " + leaderOpt.get().getName()
|
||||
: "sans chef";
|
||||
return CommandResult.success("Équipe " + team.getName() + " [#" + team.getTag()
|
||||
+ "] créée (" + visibility + ", " + chefPart + ").");
|
||||
} catch (TeamException ex) {
|
||||
return CommandResult.failure(ex.getMessage());
|
||||
}
|
||||
|
||||
@@ -69,6 +69,52 @@ public class SqliteTeamRepository extends InMemoryTeamRepository {
|
||||
.column("score_name", ColumnType.TEXT).notNull()
|
||||
.column("value", ColumnType.INTEGER).notNull()
|
||||
.create();
|
||||
|
||||
// Migration : ancien schéma avait leader_id NOT NULL ; on le rend nullable
|
||||
// pour permettre des équipes leaderless. SQLite ne supporte pas ALTER COLUMN,
|
||||
// donc on recrée la table en copiant les données.
|
||||
migrateLeaderIdToNullableIfNeeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si la colonne {@code crcore_teams.leader_id} a une contrainte
|
||||
* {@code NOT NULL} (ancien schéma) et la migre vers nullable si besoin.
|
||||
* Idempotent — no-op si la colonne est déjà nullable ou si la table est
|
||||
* neuve.
|
||||
*/
|
||||
private void migrateLeaderIdToNullableIfNeeded() {
|
||||
boolean hasNotNull = db.queryOne(
|
||||
"SELECT \"notnull\" FROM pragma_table_info('" + TABLE_TEAMS + "') WHERE name='leader_id'",
|
||||
rs -> rs.getInt(1) == 1
|
||||
).orElse(false);
|
||||
if (!hasNotNull) return;
|
||||
|
||||
// Recréation via table temporaire — séquence standard SQLite pour
|
||||
// changer une contrainte de colonne.
|
||||
db.inTransaction(() -> {
|
||||
db.execute("ALTER TABLE " + TABLE_TEAMS + " RENAME TO " + TABLE_TEAMS + "_old");
|
||||
db.execute("CREATE TABLE " + TABLE_TEAMS + " ("
|
||||
+ "\"id\" TEXT PRIMARY KEY, "
|
||||
+ "\"name\" TEXT NOT NULL UNIQUE, "
|
||||
+ "\"tag\" TEXT NOT NULL UNIQUE, "
|
||||
+ "\"color\" TEXT NOT NULL, "
|
||||
+ "\"leader_id\" TEXT, "
|
||||
+ "\"visibility\" TEXT NOT NULL, "
|
||||
+ "\"spawn_world\" TEXT, "
|
||||
+ "\"spawn_x\" REAL, "
|
||||
+ "\"spawn_y\" REAL, "
|
||||
+ "\"spawn_z\" REAL, "
|
||||
+ "\"spawn_yaw\" REAL, "
|
||||
+ "\"spawn_pitch\" REAL"
|
||||
+ ")");
|
||||
db.execute("INSERT INTO " + TABLE_TEAMS + " "
|
||||
+ "(id, name, tag, color, leader_id, visibility, "
|
||||
+ " spawn_world, spawn_x, spawn_y, spawn_z, spawn_yaw, spawn_pitch) "
|
||||
+ "SELECT id, name, tag, color, leader_id, visibility, "
|
||||
+ " spawn_world, spawn_x, spawn_y, spawn_z, spawn_yaw, spawn_pitch "
|
||||
+ "FROM " + TABLE_TEAMS + "_old");
|
||||
db.execute("DROP TABLE " + TABLE_TEAMS + "_old");
|
||||
});
|
||||
}
|
||||
|
||||
/** Recharge tous les Teams depuis la DB dans le cache mémoire hérité. */
|
||||
|
||||
Reference in New Issue
Block a user