feat(P2.2): add default mgr/dev role seeds with preset permissions for milestone/task/propose actions

This commit is contained in:
zhi
2026-03-17 19:02:44 +00:00
parent 0c75045f6f
commit 8e38d4cf4d

View File

@@ -151,61 +151,93 @@ def init_default_permissions(db: Session) -> list[Permission]:
return db.query(Permission).all()
def init_admin_role(db: Session, admin_user: models.User) -> None:
"""Create admin role with all permissions and guest role with minimal permissions."""
# Check if admin role already exists
admin_role = db.query(Role).filter(Role.name == "admin").first()
if not admin_role:
admin_role = Role(
name="admin",
description="Administrator - full access to all features",
is_global=True
)
db.add(admin_role)
db.commit()
db.refresh(admin_role)
logger.info("Created admin role (id=%d)", admin_role.id)
# ---------------------------------------------------------------------------
# Default role → permission mapping
# ---------------------------------------------------------------------------
# Check if guest role already exists
guest_role = db.query(Role).filter(Role.name == "guest").first()
if not guest_role:
guest_role = Role(
name="guest",
description="Guest - read-only access",
is_global=True
)
db.add(guest_role)
db.commit()
db.refresh(guest_role)
logger.info("Created guest role (id=%d)", guest_role.id)
# mgr: project management + all milestone/task/propose actions
_MGR_PERMISSIONS = {
"project.read", "project.write", "project.manage_members",
"task.create", "task.read", "task.write", "task.delete",
"milestone.create", "milestone.read", "milestone.write", "milestone.delete",
"milestone.freeze", "milestone.start", "milestone.close",
"task.close", "task.reopen_closed", "task.reopen_completed",
"propose.accept", "propose.reject", "propose.reopen",
"monitor.read",
}
# Get all permissions
# dev: day-to-day development work — no freeze/start/close milestone, no accept/reject propose
_DEV_PERMISSIONS = {
"project.read",
"task.create", "task.read", "task.write",
"milestone.read",
"task.close", "task.reopen_closed", "task.reopen_completed",
"monitor.read",
}
# Role definitions: (name, description, permission_set)
_DEFAULT_ROLES = [
("admin", "Administrator - full access to all features", None), # None ⇒ all perms
("mgr", "Manager - project & milestone management", _MGR_PERMISSIONS),
("dev", "Developer - task execution & daily work", _DEV_PERMISSIONS),
("guest", "Guest - read-only access", None), # special: *.read only
]
def _ensure_role(db: Session, name: str, description: str, is_global: bool = True) -> Role:
"""Get or create a role by name."""
role = db.query(Role).filter(Role.name == name).first()
if not role:
role = Role(name=name, description=description, is_global=is_global)
db.add(role)
db.commit()
db.refresh(role)
logger.info("Created role '%s' (id=%d)", name, role.id)
return role
def _sync_role_permissions(db: Session, role: Role, target_perm_names: set[str] | None) -> None:
"""Ensure *role* has exactly the permissions in *target_perm_names*.
* ``None`` means **all** permissions (admin).
* The special sentinel ``"__read_only__"`` is handled by the caller passing
just the ``*.read`` names.
Only adds missing permissions; never removes manually-granted ones (additive).
"""
all_perms = db.query(Permission).all()
perm_by_name = {p.name: p for p in all_perms}
# Assign all permissions to admin role
existing_admin_perm_ids = {rp.permission_id for rp in admin_role.permissions}
for perm in all_perms:
if perm.id not in existing_admin_perm_ids:
rp = RolePermission(role_id=admin_role.id, permission_id=perm.id)
db.add(rp)
if target_perm_names is None:
wanted_ids = {p.id for p in all_perms}
else:
wanted_ids = {perm_by_name[n].id for n in target_perm_names if n in perm_by_name}
if all_perms:
existing_ids = {rp.permission_id for rp in role.permissions}
added = 0
for pid in wanted_ids - existing_ids:
db.add(RolePermission(role_id=role.id, permission_id=pid))
added += 1
if added:
db.commit()
logger.info("Assigned %d permissions to admin role", len(all_perms))
logger.info("Assigned %d new permissions to role '%s'", added, role.name)
# Assign only read permissions to guest role
read_perms = db.query(Permission).filter(Permission.name.like("%.read")).all()
existing_guest_perm_ids = {rp.permission_id for rp in guest_role.permissions}
for perm in read_perms:
if perm.id not in existing_guest_perm_ids:
rp = RolePermission(role_id=guest_role.id, permission_id=perm.id)
db.add(rp)
if read_perms:
db.commit()
logger.info("Assigned %d read permissions to guest role", len(read_perms))
def init_admin_role(db: Session, admin_user: models.User) -> None:
"""Create default roles (admin / mgr / dev / guest) with preset permissions."""
logger.info("Admin and guest roles setup complete")
all_perms = db.query(Permission).all()
read_perm_names = {p.name for p in all_perms if p.name.endswith(".read")}
for name, description, perm_set in _DEFAULT_ROLES:
role = _ensure_role(db, name, description)
if name == "guest":
_sync_role_permissions(db, role, read_perm_names)
else:
_sync_role_permissions(db, role, perm_set)
logger.info("Default roles setup complete (admin, mgr, dev, guest)")
def run_init(db: Session) -> None: