mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 19:23:42 +08:00
fix(workspace): make default-workspace owner bootstrap first-run-only
Replace the per-startup admin reconciliation in WorkspaceSchemaMigration.ensureDefaultWorkspaceMembership() with a one-shot bootstrap. Once the default workspace has any owner, the method returns immediately, so an operator's deliberate removal of an admin from the default workspace persists across restarts. If no owner exists yet, pick the lowest-id active admin and add them as owner; if no admin exists at all, log a warning and skip rather than failing startup. Refs https://github.com/matevip/mateclaw/issues/29
This commit is contained in:
parent
a73b640c87
commit
ae820f0f94
@ -9,11 +9,19 @@ import org.springframework.dao.DataAccessException;
|
|||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作区 Schema 迁移
|
* Workspace schema bootstrap.
|
||||||
* <p>
|
* <p>
|
||||||
* 确保默认工作区(id=1, slug='default')存在。
|
* Ensures the default workspace (id=1, slug='default') exists, and performs a
|
||||||
* 在 DatabaseBootstrapRunner (@Order(1)) 之后执行。
|
* <em>first-run-only</em> bootstrap of an admin owner. This is not a
|
||||||
|
* reconciliation loop: once the default workspace has any owner, subsequent
|
||||||
|
* startups make no membership changes — operators may legitimately remove an
|
||||||
|
* admin from the default workspace and that decision must persist across
|
||||||
|
* restarts (see issue #29).
|
||||||
|
* <p>
|
||||||
|
* Runs after {@code DatabaseBootstrapRunner} ({@code @Order(1)}).
|
||||||
*
|
*
|
||||||
* @author MateClaw Team
|
* @author MateClaw Team
|
||||||
*/
|
*/
|
||||||
@ -60,27 +68,41 @@ public class WorkspaceSchemaMigration implements ApplicationRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure all admin users have an owner-role membership in the default workspace.
|
* First-run-only bootstrap: if the default workspace has no owner yet, pick
|
||||||
* Non-admin users must be added explicitly via WorkspaceController.addMember.
|
* the lowest-id active admin and add them as owner. If an owner already
|
||||||
|
* exists (or was deliberately removed and re-installed by an operator), do
|
||||||
|
* nothing. If no admin exists at all, log a warning and skip — failing
|
||||||
|
* startup here would be worse than the recoverable "no owner" state.
|
||||||
*/
|
*/
|
||||||
private void ensureDefaultWorkspaceMembership() {
|
private void ensureDefaultWorkspaceMembership() {
|
||||||
try {
|
try {
|
||||||
int inserted = jdbcTemplate.update("""
|
Integer ownerCount = jdbcTemplate.queryForObject(
|
||||||
INSERT INTO mate_workspace_member (id, workspace_id, user_id, role, create_time, update_time, deleted)
|
"SELECT COUNT(1) FROM mate_workspace_member "
|
||||||
SELECT u.id, 1, u.id, 'owner', NOW(), NOW(), 0
|
+ "WHERE workspace_id = 1 AND role = 'owner' AND deleted = 0",
|
||||||
FROM mate_user u
|
Integer.class);
|
||||||
WHERE u.deleted = 0
|
if (ownerCount != null && ownerCount > 0) {
|
||||||
AND u.role = 'admin'
|
return;
|
||||||
AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM mate_workspace_member wm
|
|
||||||
WHERE wm.workspace_id = 1 AND wm.user_id = u.id AND wm.deleted = 0
|
|
||||||
)
|
|
||||||
""");
|
|
||||||
if (inserted > 0) {
|
|
||||||
log.info("Added {} admin user(s) as owner of default workspace", inserted);
|
|
||||||
}
|
}
|
||||||
|
List<Long> adminIds = jdbcTemplate.queryForList(
|
||||||
|
"SELECT id FROM mate_user WHERE deleted = 0 AND role = 'admin' "
|
||||||
|
+ "ORDER BY id ASC LIMIT 1",
|
||||||
|
Long.class);
|
||||||
|
if (adminIds.isEmpty()) {
|
||||||
|
log.warn("Default workspace has no owner and no admin user exists; "
|
||||||
|
+ "skipping bootstrap. Operator must assign an owner manually.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Long adminId = adminIds.get(0);
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"INSERT INTO mate_workspace_member "
|
||||||
|
+ "(id, workspace_id, user_id, role, create_time, update_time, deleted) "
|
||||||
|
+ "VALUES (?, 1, ?, 'owner', NOW(), NOW(), 0)",
|
||||||
|
adminId, adminId);
|
||||||
|
log.info("Bootstrapped admin user {} as owner of default workspace", adminId);
|
||||||
} catch (DataAccessException e) {
|
} catch (DataAccessException e) {
|
||||||
log.debug("Skipping default workspace admin membership init: {}", e.getMessage());
|
// Defensive guard: tables may not exist yet during very early startup,
|
||||||
|
// or the insert may collide with a soft-deleted row's primary key.
|
||||||
|
log.debug("Skipping default workspace owner bootstrap: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user