feat(P2.2): add default mgr/dev role seeds with preset permissions for milestone/task/propose actions
This commit is contained in:
@@ -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)
|
||||
# ---------------------------------------------------------------------------
|
||||
# Default role → permission mapping
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# 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",
|
||||
}
|
||||
|
||||
# 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(admin_role)
|
||||
logger.info("Created admin role (id=%d)", admin_role.id)
|
||||
|
||||
# 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)
|
||||
|
||||
# Get all permissions
|
||||
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()
|
||||
|
||||
# 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 all_perms:
|
||||
perm_by_name = {p.name: p for p in all_perms}
|
||||
|
||||
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}
|
||||
|
||||
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))
|
||||
|
||||
# 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))
|
||||
|
||||
logger.info("Admin and guest roles setup complete")
|
||||
logger.info("Assigned %d new permissions to role '%s'", added, role.name)
|
||||
|
||||
|
||||
def init_admin_role(db: Session, admin_user: models.User) -> None:
|
||||
"""Create default roles (admin / mgr / dev / guest) with preset permissions."""
|
||||
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user