Spaces:
Sleeping
Sleeping
Download app/dda/rbac/seed.py from coderuday21/satdetect-dev: direct link, hf CLI and curl.
- Browser
- Download file 8.66 kB
-
https://huggingface.co/spaces/coderuday21/satdetect-dev/resolve/main/app/dda/rbac/seed.py
- Command line
-
hf download hf://spaces/coderuday21/satdetect-dev/app/dda/rbac/seed.py
-
curl -L -o seed.py https://huggingface.co/spaces/coderuday21/satdetect-dev/resolve/main/app/dda/rbac/seed.py
8.66 kB
| """Seed the default roles, modules, menu items, and role permissions, and | |
| backfill role_id on any user still only carrying the legacy role string. | |
| Idempotent — safe to call on every startup (mirrors app/dda/seed.py).""" | |
| from __future__ import annotations | |
| import logging | |
| from sqlalchemy.orm import Session | |
| from ...models import User | |
| from .models import MenuItem, Module, Role, RolePermission | |
| logger = logging.getLogger(__name__) | |
| # (name, description, rank) — rank preserves the ordinal check dda_auth.py | |
| # already relied on (ROLE_RANK) before this table existed. | |
| DEFAULT_ROLES = [ | |
| ("viewer", "Read-only access", 0), | |
| ("uploader", "Can view and upload images", 1), | |
| ("analyst", "Can upload, compare, and run detections", 2), | |
| ("admin", "Full system access", 3), | |
| ] | |
| # (key, name, description) | |
| DEFAULT_MODULES = [ | |
| ("home", "Dashboard", "System dashboard — detection overview, regions, reports"), | |
| ("library", "Master Library", "Browse and upload satellite / drone imagery"), | |
| ("masters", "Masters", "Zone / district and village / location master data"), | |
| ("detect", "Change Detection", "Compare imagery and run detections"), | |
| ("reports", "Reports", "Detection history, exports, and PDF reports"), | |
| ("logs", "Logs", "Application log viewer"), | |
| ("admin", "Administration", "Users, roles, modules, and menu management"), | |
| ("audit_log", "Audit Log", "User activity audit trail"), | |
| ("exception_log", "Exception Log", "System error log"), | |
| ] | |
| # (label, url, module_key, sort_order, parent_label) | |
| # Items with a parent_label render as a collapsible group in the sidebar | |
| # (see templates/partials/navbar_dda.html) — group headers themselves have no | |
| # module (url "#…" never navigated to directly) so they stay structurally | |
| # present; the navbar only shows the group once it has a visible child. | |
| DEFAULT_MENU_ITEMS = [ | |
| ("Dashboard", "/", "home", 0, None), | |
| ("Masters", "#masters", None, 1, None), | |
| ("Master Library", "/library", "library", 0, "Masters"), | |
| ("Zone / District", "/masters/zones", "masters", 1, "Masters"), | |
| ("Village / Location", "/masters/villages", "masters", 2, "Masters"), | |
| ("Change Detection", "/detect", "detect", 2, None), | |
| ("Reports", "/reports", "reports", 3, None), | |
| # "Logs" (top-level) was retired below — Application Logs is now a tab on | |
| # the Exception Log page instead of its own sidebar entry. | |
| ("Configuration", "#", None, 5, None), | |
| ("Roles & Users", "/admin/roles-users", "admin", 0, "Configuration"), | |
| ("App Modules", "/admin/modules", "admin", 1, "Configuration"), | |
| ("Menu Management", "/admin/menu", "admin", 2, "Configuration"), | |
| ("Audit Log", "/audit-log", "audit_log", 3, "Configuration"), | |
| ("Exception Log", "/exception-log", "exception_log", 4, "Configuration"), | |
| ] | |
| # module_key -> {role_name: (can_view, can_create, can_edit, can_delete)} | |
| # Approximates current viewer < uploader < analyst < admin behavior. | |
| DEFAULT_PERMISSIONS = { | |
| "home": { | |
| "viewer": (1, 0, 0, 0), "uploader": (1, 0, 0, 0), | |
| "analyst": (1, 0, 0, 0), "admin": (1, 1, 1, 1), | |
| }, | |
| "library": { | |
| "viewer": (1, 0, 0, 0), "uploader": (1, 1, 0, 0), | |
| "analyst": (1, 1, 1, 0), "admin": (1, 1, 1, 1), | |
| }, | |
| "masters": { | |
| "viewer": (1, 0, 0, 0), "uploader": (1, 0, 0, 0), | |
| "analyst": (1, 1, 1, 0), "admin": (1, 1, 1, 1), | |
| }, | |
| "detect": { | |
| "viewer": (1, 0, 0, 0), "uploader": (1, 1, 0, 0), | |
| "analyst": (1, 1, 1, 0), "admin": (1, 1, 1, 1), | |
| }, | |
| "reports": { | |
| "viewer": (1, 0, 0, 0), "uploader": (1, 0, 0, 0), | |
| "analyst": (1, 1, 1, 0), "admin": (1, 1, 1, 1), | |
| }, | |
| "logs": { | |
| "viewer": (0, 0, 0, 0), "uploader": (0, 0, 0, 0), | |
| "analyst": (0, 0, 0, 0), "admin": (1, 1, 1, 1), | |
| }, | |
| "admin": { | |
| "viewer": (0, 0, 0, 0), "uploader": (0, 0, 0, 0), | |
| "analyst": (0, 0, 0, 0), "admin": (1, 1, 1, 1), | |
| }, | |
| "audit_log": { | |
| "viewer": (0, 0, 0, 0), "uploader": (0, 0, 0, 0), | |
| "analyst": (0, 0, 0, 0), "admin": (1, 1, 1, 1), | |
| }, | |
| "exception_log": { | |
| "viewer": (0, 0, 0, 0), "uploader": (0, 0, 0, 0), | |
| "analyst": (0, 0, 0, 0), "admin": (1, 1, 1, 1), | |
| }, | |
| } | |
| def seed_rbac(db: Session) -> None: | |
| roles_by_name = {} | |
| for name, description, rank in DEFAULT_ROLES: | |
| role = db.query(Role).filter(Role.name == name).first() | |
| if not role: | |
| role = Role(name=name, description=description, rank=rank, is_system=True) | |
| db.add(role) | |
| db.flush() | |
| roles_by_name[name] = role | |
| modules_by_key = {} | |
| for key, name, description in DEFAULT_MODULES: | |
| module = db.query(Module).filter(Module.key == key).first() | |
| if not module: | |
| module = Module(key=key, name=name, description=description, status="in_use") | |
| db.add(module) | |
| db.flush() | |
| else: | |
| if module.name != name: | |
| module.name = name | |
| if description and module.description != description: | |
| module.description = description | |
| modules_by_key[key] = module | |
| # Two passes: top-level items (incl. "Configuration") first, so their ids | |
| # exist when the second pass wires up children via parent_label. | |
| items_by_label = {} | |
| for label, url, module_key, sort_order, parent_label in DEFAULT_MENU_ITEMS: | |
| if parent_label is not None: | |
| continue | |
| module_id = modules_by_key[module_key].id if module_key else None | |
| existing_item = db.query(MenuItem).filter(MenuItem.url == url).first() | |
| if not existing_item: | |
| existing_item = MenuItem( | |
| label=label, url=url, module_id=module_id, | |
| sort_order=sort_order, is_active=True, | |
| ) | |
| db.add(existing_item) | |
| db.flush() | |
| else: | |
| existing_item.label = label | |
| existing_item.module_id = module_id | |
| existing_item.sort_order = sort_order | |
| existing_item.parent_id = None | |
| items_by_label[label] = existing_item | |
| for label, url, module_key, sort_order, parent_label in DEFAULT_MENU_ITEMS: | |
| if parent_label is None: | |
| continue | |
| parent = items_by_label[parent_label] | |
| module_id = modules_by_key[module_key].id if module_key else None | |
| existing_item = db.query(MenuItem).filter(MenuItem.url == url).first() | |
| if not existing_item: | |
| db.add(MenuItem( | |
| label=label, url=url, module_id=module_id, parent_id=parent.id, | |
| sort_order=sort_order, is_active=True, | |
| )) | |
| else: | |
| # Fixup for rows seeded before this item had a parent group (e.g. | |
| # Image Library was top-level; admin pages were under a flat | |
| # "Admin" label) — re-parent + re-label known system nav rows. | |
| if existing_item.label in ("Admin", "Image Library") or existing_item.label != label: | |
| existing_item.label = label | |
| existing_item.module_id = module_id | |
| existing_item.parent_id = parent.id | |
| existing_item.sort_order = sort_order | |
| for module_key, role_perms in DEFAULT_PERMISSIONS.items(): | |
| module = modules_by_key[module_key] | |
| for role_name, (view, create, edit, delete) in role_perms.items(): | |
| role = roles_by_name[role_name] | |
| existing = ( | |
| db.query(RolePermission) | |
| .filter(RolePermission.role_id == role.id, RolePermission.module_id == module.id) | |
| .first() | |
| ) | |
| if not existing: | |
| db.add(RolePermission( | |
| role_id=role.id, module_id=module.id, | |
| can_view=bool(view), can_create=bool(create), | |
| can_edit=bool(edit), can_delete=bool(delete), | |
| )) | |
| # Retire the old top-level "Logs" sidebar entry — its content (Application | |
| # Logs) is now a tab on the Exception Log page (see exception_log_dda.html). | |
| retired_logs_item = ( | |
| db.query(MenuItem) | |
| .filter(MenuItem.url == "/logs", MenuItem.parent_id.is_(None), MenuItem.is_active.is_(True)) | |
| .first() | |
| ) | |
| if retired_logs_item: | |
| retired_logs_item.is_active = False | |
| db.commit() | |
| unmigrated = db.query(User).filter(User.role_id.is_(None)).all() | |
| if unmigrated: | |
| for u in unmigrated: | |
| legacy = (u.role or "analyst").strip().lower() | |
| role = roles_by_name.get(legacy, roles_by_name["analyst"]) | |
| u.role_id = role.id | |
| db.commit() | |
| logger.info("RBAC: backfilled role_id for %d user(s)", len(unmigrated)) | |