991 lines
27 KiB
Python
991 lines
27 KiB
Python
import os
|
|
import sys
|
|
|
|
from fastapi import HTTPException
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.db import Base, SessionLocal, get_engine
|
|
from app.models.rbac import AdminResource, AdminRole, AdminRoleResource, AdminUser, AdminUserRole, utcnow
|
|
from app.services.password import hash_password, validate_password
|
|
|
|
SUPER_CODE = "R_SUPER"
|
|
SUPER_NAME = "超级管理员"
|
|
|
|
PAGES = [
|
|
{
|
|
"name": "User",
|
|
"title": "用户管理",
|
|
"path": "/system/user",
|
|
"component": "/system/user",
|
|
"sort": 1,
|
|
"buttons": [
|
|
("user:add", "新增"),
|
|
("user:edit", "编辑"),
|
|
("user:reset-password", "重置密码"),
|
|
("user:disable", "停用"),
|
|
("user:cancel", "注销"),
|
|
],
|
|
},
|
|
{
|
|
"name": "Role",
|
|
"title": "角色管理",
|
|
"path": "/system/role",
|
|
"component": "/system/role",
|
|
"sort": 2,
|
|
"buttons": [
|
|
("role:add", "新增"),
|
|
("role:edit", "编辑"),
|
|
("role:disable", "停用"),
|
|
("role:grant", "授权"),
|
|
],
|
|
},
|
|
{
|
|
"name": "Menu",
|
|
"title": "菜单管理",
|
|
"path": "/system/menu",
|
|
"component": "/system/menu",
|
|
"sort": 3,
|
|
"buttons": [
|
|
("menu:add", "新增"),
|
|
("menu:edit", "编辑"),
|
|
("menu:delete", "删除"),
|
|
],
|
|
},
|
|
{
|
|
"name": "AuditLog",
|
|
"title": "操作日志",
|
|
"path": "/system/audit-log",
|
|
"component": "/system/audit-log",
|
|
"sort": 4,
|
|
"buttons": [],
|
|
},
|
|
{
|
|
"name": "LoginSecurity",
|
|
"title": "登录安全",
|
|
"path": "/system/login-security",
|
|
"component": "/system/login-security",
|
|
"sort": 5,
|
|
"buttons": [("security:edit", "编辑")],
|
|
},
|
|
{
|
|
"name": "File",
|
|
"title": "附件",
|
|
"path": "/system/file",
|
|
"component": "/system/file",
|
|
"sort": 6,
|
|
"buttons": [("file:upload", "上传"), ("file:delete", "删除")],
|
|
},
|
|
{
|
|
"name": "Site",
|
|
"title": "站点配置",
|
|
"path": "/system/site",
|
|
"component": "/system/site",
|
|
"sort": 7,
|
|
"buttons": [("site:edit", "编辑")],
|
|
},
|
|
{
|
|
"name": "SensitiveWord",
|
|
"title": "敏感词",
|
|
"path": "/system/sensitive-word",
|
|
"component": "/system/sensitive-word",
|
|
"sort": 8,
|
|
"buttons": [("word:add", "新增"), ("word:delete", "删除"), ("word:preview", "预览")],
|
|
},
|
|
]
|
|
|
|
|
|
def _find_resource(db: Session, parent_id: int | None, name: str) -> AdminResource | None:
|
|
stmt = select(AdminResource).where(AdminResource.name == name)
|
|
if parent_id is None:
|
|
stmt = stmt.where(AdminResource.parent_id.is_(None))
|
|
else:
|
|
stmt = stmt.where(AdminResource.parent_id == parent_id)
|
|
return db.scalars(stmt.order_by(AdminResource.id.asc())).first()
|
|
|
|
|
|
def _ensure_resource(
|
|
db: Session,
|
|
*,
|
|
parent_id: int | None,
|
|
resource_type: str,
|
|
name: str,
|
|
title: str,
|
|
path: str,
|
|
component: str,
|
|
icon: str,
|
|
permission: str,
|
|
sort: int,
|
|
visible: bool,
|
|
) -> AdminResource:
|
|
row = _find_resource(db, parent_id, name)
|
|
if row is None:
|
|
row = AdminResource(
|
|
parent_id=parent_id,
|
|
type=resource_type,
|
|
name=name,
|
|
title=title,
|
|
path=path,
|
|
component=component,
|
|
icon=icon,
|
|
permission=permission,
|
|
sort=sort,
|
|
visible=visible,
|
|
status="active",
|
|
)
|
|
db.add(row)
|
|
db.flush()
|
|
return row
|
|
row.parent_id = parent_id
|
|
row.type = resource_type
|
|
row.title = title
|
|
row.path = path
|
|
row.component = component
|
|
row.icon = icon
|
|
row.permission = permission
|
|
row.sort = sort
|
|
row.visible = visible
|
|
row.status = "active"
|
|
return row
|
|
|
|
|
|
HOME_MENUS = (
|
|
("HomeTemplate", "模板", "/home/template", "ri:layout-2-line", 1),
|
|
("HomeSlides", "轮播", "/home/slides", "ri:slideshow-3-line", 2),
|
|
("HomeArticles", "文章", "/home/articles", "ri:article-line", 3),
|
|
("HomeEntries", "入口与菜单", "/home/entries", "ri:apps-2-line", 4),
|
|
("HomeMarquees", "荣誉与会员", "/home/marquees", "ri:award-line", 5),
|
|
("HomeFooter", "页脚", "/home/footer", "ri:layout-bottom-line", 6),
|
|
)
|
|
|
|
ABOUT_ORG_MENUS = (
|
|
("AboutIntro", "协会简介", "/about/intro", "ri:building-line", 1),
|
|
("AboutOrg", "组织机构", "/about/org", "ri:node-tree", 2),
|
|
("AboutExperts", "专家委员会", "/about/experts", "ri:team-line", 3),
|
|
("AboutReports", "年度报告/工作计划", "/about/reports", "ri:file-list-3-line", 4),
|
|
("AboutBriefs", "工作简报", "/about/briefs", "ri:newspaper-line", 5),
|
|
)
|
|
|
|
|
|
def ensure_home_menu(db: Session) -> None:
|
|
directory = _ensure_resource(
|
|
db,
|
|
parent_id=None,
|
|
resource_type="directory",
|
|
name="Home",
|
|
title="首页配置",
|
|
path="/home",
|
|
component="",
|
|
icon="ri:home-4-line",
|
|
permission="",
|
|
sort=2,
|
|
visible=True,
|
|
)
|
|
legacy = _find_resource(db, directory.id, "HomeConfig")
|
|
if legacy is not None and _find_resource(db, directory.id, "HomeTemplate") is None:
|
|
legacy.name = "HomeTemplate"
|
|
db.flush()
|
|
pages = [
|
|
_ensure_resource(
|
|
db,
|
|
parent_id=directory.id,
|
|
resource_type="menu",
|
|
name=name,
|
|
title=title,
|
|
path=path,
|
|
component=path,
|
|
icon=icon,
|
|
permission="",
|
|
sort=sort,
|
|
visible=True,
|
|
)
|
|
for name, title, path, icon, sort in HOME_MENUS
|
|
]
|
|
button = _ensure_resource(
|
|
db,
|
|
parent_id=pages[0].id,
|
|
resource_type="button",
|
|
name="home:edit",
|
|
title="保存",
|
|
path="",
|
|
component="",
|
|
icon="",
|
|
permission="home:edit",
|
|
sort=1,
|
|
visible=False,
|
|
)
|
|
role = db.scalar(select(AdminRole).where(AdminRole.code == SUPER_CODE))
|
|
if role is None:
|
|
db.flush()
|
|
return
|
|
linked = set(
|
|
db.scalars(
|
|
select(AdminRoleResource.resource_id).where(AdminRoleResource.role_id == role.id)
|
|
).all()
|
|
)
|
|
for resource_id in (directory.id, button.id, *(page.id for page in pages)):
|
|
if resource_id not in linked:
|
|
db.add(AdminRoleResource(role_id=role.id, resource_id=resource_id))
|
|
db.flush()
|
|
|
|
|
|
def ensure_party_menu(db: Session) -> None:
|
|
party = _ensure_resource(
|
|
db,
|
|
parent_id=None,
|
|
resource_type="menu",
|
|
name="PartyBuilding",
|
|
title="行业党建",
|
|
path="/party",
|
|
component="/party",
|
|
icon="iconoven:national-emblem",
|
|
permission="",
|
|
sort=5,
|
|
visible=True,
|
|
)
|
|
button = _ensure_resource(
|
|
db,
|
|
parent_id=party.id,
|
|
resource_type="button",
|
|
name="party:edit",
|
|
title="保存",
|
|
path="",
|
|
component="",
|
|
icon="",
|
|
permission="party:edit",
|
|
sort=1,
|
|
visible=False,
|
|
)
|
|
role = db.scalar(select(AdminRole).where(AdminRole.code == SUPER_CODE))
|
|
if role is None:
|
|
db.flush()
|
|
return
|
|
linked = set(
|
|
db.scalars(
|
|
select(AdminRoleResource.resource_id).where(AdminRoleResource.role_id == role.id)
|
|
).all()
|
|
)
|
|
for resource_id in (party.id, button.id):
|
|
if resource_id not in linked:
|
|
db.add(AdminRoleResource(role_id=role.id, resource_id=resource_id))
|
|
db.flush()
|
|
|
|
|
|
def ensure_expert_menu(db: Session) -> None:
|
|
directory = _ensure_resource(
|
|
db,
|
|
parent_id=None,
|
|
resource_type="directory",
|
|
name="ExpertService",
|
|
title="专家服务",
|
|
path="/expert",
|
|
component="",
|
|
icon="ri:customer-service-2-line",
|
|
permission="",
|
|
sort=6,
|
|
visible=True,
|
|
)
|
|
guides = _ensure_resource(
|
|
db,
|
|
parent_id=directory.id,
|
|
resource_type="menu",
|
|
name="ExpertGuides",
|
|
title="估价指南",
|
|
path="/expert/guides",
|
|
component="/expert/guides",
|
|
icon="ri:book-open-line",
|
|
permission="",
|
|
sort=1,
|
|
visible=True,
|
|
)
|
|
qa = _ensure_resource(
|
|
db,
|
|
parent_id=directory.id,
|
|
resource_type="menu",
|
|
name="ExpertQa",
|
|
title="专家答疑",
|
|
path="/expert/qa",
|
|
component="/expert/qa",
|
|
icon="ri:question-answer-line",
|
|
permission="",
|
|
sort=2,
|
|
visible=True,
|
|
)
|
|
edit_button = _ensure_resource(
|
|
db,
|
|
parent_id=guides.id,
|
|
resource_type="button",
|
|
name="expert:edit",
|
|
title="保存",
|
|
path="",
|
|
component="",
|
|
icon="",
|
|
permission="expert:edit",
|
|
sort=1,
|
|
visible=False,
|
|
)
|
|
reply_button = _ensure_resource(
|
|
db,
|
|
parent_id=qa.id,
|
|
resource_type="button",
|
|
name="expert:reply",
|
|
title="回复",
|
|
path="",
|
|
component="",
|
|
icon="",
|
|
permission="expert:reply",
|
|
sort=1,
|
|
visible=False,
|
|
)
|
|
role = db.scalar(select(AdminRole).where(AdminRole.code == SUPER_CODE))
|
|
if role is None:
|
|
db.flush()
|
|
return
|
|
linked = set(
|
|
db.scalars(
|
|
select(AdminRoleResource.resource_id).where(AdminRoleResource.role_id == role.id)
|
|
).all()
|
|
)
|
|
for resource_id in (directory.id, guides.id, qa.id, edit_button.id, reply_button.id):
|
|
if resource_id not in linked:
|
|
db.add(AdminRoleResource(role_id=role.id, resource_id=resource_id))
|
|
db.flush()
|
|
|
|
|
|
def ensure_directory_menu(db: Session) -> None:
|
|
directory = _ensure_resource(
|
|
db,
|
|
parent_id=None,
|
|
resource_type="menu",
|
|
name="InstitutionDirectory",
|
|
title="机构名录",
|
|
path="/institutions",
|
|
component="/institutions",
|
|
icon="ri:building-2-line",
|
|
permission="",
|
|
sort=8,
|
|
visible=True,
|
|
)
|
|
button = _ensure_resource(
|
|
db,
|
|
parent_id=directory.id,
|
|
resource_type="button",
|
|
name="directory:edit",
|
|
title="保存",
|
|
path="",
|
|
component="",
|
|
icon="",
|
|
permission="directory:edit",
|
|
sort=1,
|
|
visible=False,
|
|
)
|
|
role = db.scalar(select(AdminRole).where(AdminRole.code == SUPER_CODE))
|
|
if role is None:
|
|
db.flush()
|
|
return
|
|
linked = set(
|
|
db.scalars(
|
|
select(AdminRoleResource.resource_id).where(AdminRoleResource.role_id == role.id)
|
|
).all()
|
|
)
|
|
for resource_id in (directory.id, button.id):
|
|
if resource_id not in linked:
|
|
db.add(AdminRoleResource(role_id=role.id, resource_id=resource_id))
|
|
db.flush()
|
|
|
|
|
|
def ensure_library_menu(db: Session) -> None:
|
|
directory = _ensure_resource(
|
|
db,
|
|
parent_id=None,
|
|
resource_type="directory",
|
|
name="Library",
|
|
title="资料库",
|
|
path="/library",
|
|
component="",
|
|
icon="ri:folder-open-line",
|
|
permission="",
|
|
sort=9,
|
|
visible=True,
|
|
)
|
|
policies = _ensure_resource(
|
|
db,
|
|
parent_id=directory.id,
|
|
resource_type="menu",
|
|
name="LibraryPolicies",
|
|
title="政策分析",
|
|
path="/library/policies",
|
|
component="/library/policies",
|
|
icon="ri:file-text-line",
|
|
permission="",
|
|
sort=1,
|
|
visible=True,
|
|
)
|
|
cases = _ensure_resource(
|
|
db,
|
|
parent_id=directory.id,
|
|
resource_type="menu",
|
|
name="LibraryCases",
|
|
title="案例库",
|
|
path="/library/cases",
|
|
component="/library/cases",
|
|
icon="ri:archive-line",
|
|
permission="",
|
|
sort=2,
|
|
visible=True,
|
|
)
|
|
policy_button = _ensure_resource(
|
|
db,
|
|
parent_id=policies.id,
|
|
resource_type="button",
|
|
name="library:edit",
|
|
title="保存",
|
|
path="",
|
|
component="",
|
|
icon="",
|
|
permission="library:edit",
|
|
sort=1,
|
|
visible=False,
|
|
)
|
|
case_button = _ensure_resource(
|
|
db,
|
|
parent_id=cases.id,
|
|
resource_type="button",
|
|
name="library:edit",
|
|
title="保存",
|
|
path="",
|
|
component="",
|
|
icon="",
|
|
permission="library:edit",
|
|
sort=1,
|
|
visible=False,
|
|
)
|
|
uploads = _ensure_resource(
|
|
db,
|
|
parent_id=directory.id,
|
|
resource_type="menu",
|
|
name="LibraryUploads",
|
|
title="上传的案例库",
|
|
path="/library/uploads",
|
|
component="/library/uploads",
|
|
icon="ri:upload-2-line",
|
|
permission="",
|
|
sort=3,
|
|
visible=True,
|
|
)
|
|
role = db.scalar(select(AdminRole).where(AdminRole.code == SUPER_CODE))
|
|
if role is None:
|
|
db.flush()
|
|
return
|
|
linked = set(
|
|
db.scalars(
|
|
select(AdminRoleResource.resource_id).where(AdminRoleResource.role_id == role.id)
|
|
).all()
|
|
)
|
|
for resource_id in (directory.id, policies.id, cases.id, uploads.id, policy_button.id, case_button.id):
|
|
if resource_id not in linked:
|
|
db.add(AdminRoleResource(role_id=role.id, resource_id=resource_id))
|
|
db.flush()
|
|
|
|
|
|
def ensure_report_menu(db: Session) -> None:
|
|
menu = _ensure_resource(
|
|
db,
|
|
parent_id=None,
|
|
resource_type="menu",
|
|
name="ReportCenter",
|
|
title="报告中心",
|
|
path="/reports",
|
|
component="/reports",
|
|
icon="ri:file-list-3-line",
|
|
permission="",
|
|
sort=10,
|
|
visible=True,
|
|
)
|
|
button = _ensure_resource(
|
|
db,
|
|
parent_id=menu.id,
|
|
resource_type="button",
|
|
name="report:edit",
|
|
title="保存",
|
|
path="",
|
|
component="",
|
|
icon="",
|
|
permission="report:edit",
|
|
sort=1,
|
|
visible=False,
|
|
)
|
|
role = db.scalar(select(AdminRole).where(AdminRole.code == SUPER_CODE))
|
|
if role is None:
|
|
db.flush()
|
|
return
|
|
linked = set(
|
|
db.scalars(select(AdminRoleResource.resource_id).where(AdminRoleResource.role_id == role.id)).all()
|
|
)
|
|
for resource_id in (menu.id, button.id):
|
|
if resource_id not in linked:
|
|
db.add(AdminRoleResource(role_id=role.id, resource_id=resource_id))
|
|
db.flush()
|
|
|
|
|
|
def ensure_discipline_menu(db: Session) -> None:
|
|
directory = _ensure_resource(
|
|
db,
|
|
parent_id=None,
|
|
resource_type="directory",
|
|
name="IndustryDiscipline",
|
|
title="行业自律",
|
|
path="/discipline",
|
|
component="",
|
|
icon="ri:shield-check-line",
|
|
permission="",
|
|
sort=11,
|
|
visible=True,
|
|
)
|
|
complaints = _ensure_resource(
|
|
db,
|
|
parent_id=directory.id,
|
|
resource_type="menu",
|
|
name="DisciplineComplaints",
|
|
title="投诉举报",
|
|
path="/discipline/complaints",
|
|
component="/discipline/complaints",
|
|
icon="ri:feedback-line",
|
|
permission="",
|
|
sort=1,
|
|
visible=True,
|
|
)
|
|
warnings = _ensure_resource(
|
|
db,
|
|
parent_id=directory.id,
|
|
resource_type="menu",
|
|
name="DisciplineWarnings",
|
|
title="警示案例",
|
|
path="/discipline/warnings",
|
|
component="/discipline/warnings",
|
|
icon="ri:alarm-warning-line",
|
|
permission="",
|
|
sort=2,
|
|
visible=True,
|
|
)
|
|
integrity = _ensure_resource(
|
|
db,
|
|
parent_id=directory.id,
|
|
resource_type="menu",
|
|
name="DisciplineIntegrity",
|
|
title="诚信档案",
|
|
path="/discipline/integrity",
|
|
component="/discipline/integrity",
|
|
icon="ri:medal-line",
|
|
permission="",
|
|
sort=3,
|
|
visible=True,
|
|
)
|
|
results = _ensure_resource(
|
|
db,
|
|
parent_id=directory.id,
|
|
resource_type="menu",
|
|
name="DisciplineResults",
|
|
title="结果公示",
|
|
path="/discipline/results",
|
|
component="/discipline/results",
|
|
icon="ri:megaphone-line",
|
|
permission="",
|
|
sort=4,
|
|
visible=True,
|
|
)
|
|
handle_button = _ensure_resource(
|
|
db,
|
|
parent_id=complaints.id,
|
|
resource_type="button",
|
|
name="discipline:handle",
|
|
title="处理",
|
|
path="",
|
|
component="",
|
|
icon="",
|
|
permission="discipline:handle",
|
|
sort=1,
|
|
visible=False,
|
|
)
|
|
edit_buttons = []
|
|
for parent in (warnings, integrity, results):
|
|
edit_buttons.append(
|
|
_ensure_resource(
|
|
db,
|
|
parent_id=parent.id,
|
|
resource_type="button",
|
|
name="discipline:edit",
|
|
title="保存",
|
|
path="",
|
|
component="",
|
|
icon="",
|
|
permission="discipline:edit",
|
|
sort=1,
|
|
visible=False,
|
|
)
|
|
)
|
|
role = db.scalar(select(AdminRole).where(AdminRole.code == SUPER_CODE))
|
|
if role is None:
|
|
db.flush()
|
|
return
|
|
linked = set(
|
|
db.scalars(
|
|
select(AdminRoleResource.resource_id).where(AdminRoleResource.role_id == role.id)
|
|
).all()
|
|
)
|
|
resource_ids = [
|
|
directory.id,
|
|
complaints.id,
|
|
warnings.id,
|
|
integrity.id,
|
|
results.id,
|
|
handle_button.id,
|
|
*(button.id for button in edit_buttons),
|
|
]
|
|
for resource_id in resource_ids:
|
|
if resource_id not in linked:
|
|
db.add(AdminRoleResource(role_id=role.id, resource_id=resource_id))
|
|
db.flush()
|
|
|
|
|
|
def ensure_edu_menu(db: Session) -> None:
|
|
menu = _ensure_resource(
|
|
db,
|
|
parent_id=None,
|
|
resource_type="menu",
|
|
name="ContinuingEducation",
|
|
title="继续教育",
|
|
path="/edu",
|
|
component="/edu",
|
|
icon="ri:graduation-cap-line",
|
|
permission="",
|
|
sort=4,
|
|
visible=True,
|
|
)
|
|
button = _ensure_resource(
|
|
db,
|
|
parent_id=menu.id,
|
|
resource_type="button",
|
|
name="edu:edit",
|
|
title="保存",
|
|
path="",
|
|
component="",
|
|
icon="",
|
|
permission="edu:edit",
|
|
sort=1,
|
|
visible=False,
|
|
)
|
|
role = db.scalar(select(AdminRole).where(AdminRole.code == SUPER_CODE))
|
|
if role is None:
|
|
db.flush()
|
|
return
|
|
linked = set(
|
|
db.scalars(
|
|
select(AdminRoleResource.resource_id).where(AdminRoleResource.role_id == role.id)
|
|
).all()
|
|
)
|
|
for resource_id in (menu.id, button.id):
|
|
if resource_id not in linked:
|
|
db.add(AdminRoleResource(role_id=role.id, resource_id=resource_id))
|
|
db.flush()
|
|
|
|
|
|
def ensure_tech_menu(db: Session) -> None:
|
|
tech = _ensure_resource(
|
|
db,
|
|
parent_id=None,
|
|
resource_type="menu",
|
|
name="TechExchange",
|
|
title="技术交流",
|
|
path="/tech",
|
|
component="/tech",
|
|
icon="ri:discuss-line",
|
|
permission="",
|
|
sort=7,
|
|
visible=True,
|
|
)
|
|
button = _ensure_resource(
|
|
db,
|
|
parent_id=tech.id,
|
|
resource_type="button",
|
|
name="tech:edit",
|
|
title="保存",
|
|
path="",
|
|
component="",
|
|
icon="",
|
|
permission="tech:edit",
|
|
sort=1,
|
|
visible=False,
|
|
)
|
|
role = db.scalar(select(AdminRole).where(AdminRole.code == SUPER_CODE))
|
|
if role is None:
|
|
db.flush()
|
|
return
|
|
linked = set(
|
|
db.scalars(
|
|
select(AdminRoleResource.resource_id).where(AdminRoleResource.role_id == role.id)
|
|
).all()
|
|
)
|
|
for resource_id in (tech.id, button.id):
|
|
if resource_id not in linked:
|
|
db.add(AdminRoleResource(role_id=role.id, resource_id=resource_id))
|
|
db.flush()
|
|
|
|
|
|
def ensure_about_menu(db: Session) -> None:
|
|
org_dir = _ensure_resource(
|
|
db,
|
|
parent_id=None,
|
|
resource_type="directory",
|
|
name="AboutOrgDir",
|
|
title="组织概况",
|
|
path="/about",
|
|
component="",
|
|
icon="ri:building-4-line",
|
|
permission="",
|
|
sort=3,
|
|
visible=True,
|
|
)
|
|
org_pages = [
|
|
_ensure_resource(
|
|
db,
|
|
parent_id=org_dir.id,
|
|
resource_type="menu",
|
|
name=name,
|
|
title=title,
|
|
path=path,
|
|
component=path,
|
|
icon=icon,
|
|
permission="",
|
|
sort=sort,
|
|
visible=True,
|
|
)
|
|
for name, title, path, icon, sort in ABOUT_ORG_MENUS
|
|
]
|
|
qualifications = _ensure_resource(
|
|
db,
|
|
parent_id=None,
|
|
resource_type="menu",
|
|
name="AboutQualifications",
|
|
title="资质荣誉",
|
|
path="/about/qualifications",
|
|
component="/about/qualifications",
|
|
icon="ri:medal-line",
|
|
permission="",
|
|
sort=4,
|
|
visible=True,
|
|
)
|
|
contact = _ensure_resource(
|
|
db,
|
|
parent_id=None,
|
|
resource_type="menu",
|
|
name="AboutContact",
|
|
title="联系合作",
|
|
path="/about/contact",
|
|
component="/about/contact",
|
|
icon="ri:contacts-line",
|
|
permission="",
|
|
sort=6,
|
|
visible=True,
|
|
)
|
|
button = _ensure_resource(
|
|
db,
|
|
parent_id=org_pages[0].id,
|
|
resource_type="button",
|
|
name="about:edit",
|
|
title="保存",
|
|
path="",
|
|
component="",
|
|
icon="",
|
|
permission="about:edit",
|
|
sort=1,
|
|
visible=False,
|
|
)
|
|
role = db.scalar(select(AdminRole).where(AdminRole.code == SUPER_CODE))
|
|
if role is None:
|
|
db.flush()
|
|
return
|
|
linked = set(
|
|
db.scalars(
|
|
select(AdminRoleResource.resource_id).where(AdminRoleResource.role_id == role.id)
|
|
).all()
|
|
)
|
|
resource_ids = (
|
|
org_dir.id,
|
|
button.id,
|
|
qualifications.id,
|
|
contact.id,
|
|
*(page.id for page in org_pages),
|
|
)
|
|
for resource_id in resource_ids:
|
|
if resource_id not in linked:
|
|
db.add(AdminRoleResource(role_id=role.id, resource_id=resource_id))
|
|
db.flush()
|
|
|
|
|
|
def seed_superuser(db: Session, username: str, password: str) -> None:
|
|
username = username.strip()
|
|
if not username:
|
|
raise HTTPException(status_code=400, detail="请填写用户名")
|
|
if len(username) > 64:
|
|
raise HTTPException(status_code=400, detail="用户名过长")
|
|
validate_password(password)
|
|
|
|
now = utcnow()
|
|
role = db.scalar(select(AdminRole).where(AdminRole.code == SUPER_CODE))
|
|
if role is None:
|
|
role = AdminRole(
|
|
code=SUPER_CODE,
|
|
name=SUPER_NAME,
|
|
description="系统内置角色",
|
|
status="active",
|
|
created_at=now,
|
|
updated_at=now,
|
|
)
|
|
db.add(role)
|
|
db.flush()
|
|
else:
|
|
role.name = SUPER_NAME
|
|
role.status = "active"
|
|
if not role.description:
|
|
role.description = "系统内置角色"
|
|
role.updated_at = now
|
|
|
|
directory = _ensure_resource(
|
|
db,
|
|
parent_id=None,
|
|
resource_type="directory",
|
|
name="System",
|
|
title="系统管理",
|
|
path="/system",
|
|
component="",
|
|
icon="ri:settings-3-line",
|
|
permission="",
|
|
sort=1,
|
|
visible=True,
|
|
)
|
|
resource_ids = [directory.id]
|
|
for page in PAGES:
|
|
menu = _ensure_resource(
|
|
db,
|
|
parent_id=directory.id,
|
|
resource_type="menu",
|
|
name=page["name"],
|
|
title=page["title"],
|
|
path=page["path"],
|
|
component=page["component"],
|
|
icon="",
|
|
permission="",
|
|
sort=page["sort"],
|
|
visible=True,
|
|
)
|
|
resource_ids.append(menu.id)
|
|
for index, (code, title) in enumerate(page["buttons"], start=1):
|
|
button = _ensure_resource(
|
|
db,
|
|
parent_id=menu.id,
|
|
resource_type="button",
|
|
name=code,
|
|
title=title,
|
|
path="",
|
|
component="",
|
|
icon="",
|
|
permission=code,
|
|
sort=index,
|
|
visible=False,
|
|
)
|
|
resource_ids.append(button.id)
|
|
|
|
linked = set(
|
|
db.scalars(select(AdminRoleResource.resource_id).where(AdminRoleResource.role_id == role.id)).all()
|
|
)
|
|
for resource_id in resource_ids:
|
|
if resource_id not in linked:
|
|
db.add(AdminRoleResource(role_id=role.id, resource_id=resource_id))
|
|
|
|
user = db.scalar(select(AdminUser).where(AdminUser.username == username))
|
|
password_hash = hash_password(password)
|
|
if user is None:
|
|
user = AdminUser(
|
|
username=username,
|
|
password_hash=password_hash,
|
|
display_name=username,
|
|
mobile_mask="",
|
|
status="active",
|
|
failed_login_count=0,
|
|
locked_until=None,
|
|
created_at=now,
|
|
updated_at=now,
|
|
)
|
|
db.add(user)
|
|
db.flush()
|
|
else:
|
|
user.password_hash = password_hash
|
|
user.status = "active"
|
|
user.failed_login_count = 0
|
|
user.locked_until = None
|
|
if not user.display_name:
|
|
user.display_name = username
|
|
user.updated_at = now
|
|
if db.get(AdminUserRole, (user.id, role.id)) is None:
|
|
db.add(AdminUserRole(user_id=user.id, role_id=role.id))
|
|
db.flush()
|
|
ensure_home_menu(db)
|
|
|
|
|
|
def main() -> int:
|
|
try:
|
|
from dotenv import load_dotenv
|
|
|
|
load_dotenv()
|
|
except ImportError:
|
|
pass
|
|
username = os.environ.get("BOOTSTRAP_ADMIN_USERNAME", "").strip()
|
|
password = os.environ.get("BOOTSTRAP_ADMIN_PASSWORD", "")
|
|
if username == "" or password == "":
|
|
print("缺少环境变量 BOOTSTRAP_ADMIN_USERNAME 或 BOOTSTRAP_ADMIN_PASSWORD", file=sys.stderr)
|
|
return 1
|
|
try:
|
|
validate_password(password)
|
|
except HTTPException as exc:
|
|
print(exc.detail, file=sys.stderr)
|
|
return 1
|
|
|
|
db: Session | None = None
|
|
try:
|
|
import app.models.content # noqa: F401
|
|
import app.models.security # noqa: F401
|
|
from app.models.content import ensure_site_defaults
|
|
from app.services.ip_guard import seed_ip_blacklist
|
|
|
|
engine = get_engine()
|
|
Base.metadata.create_all(bind=engine)
|
|
SessionLocal.configure(bind=engine)
|
|
db = SessionLocal()
|
|
ensure_site_defaults(db)
|
|
seed_ip_blacklist(db)
|
|
seed_superuser(db, username, password)
|
|
db.commit()
|
|
except RuntimeError as exc:
|
|
print(str(exc), file=sys.stderr)
|
|
return 1
|
|
except HTTPException as exc:
|
|
if db is not None:
|
|
db.rollback()
|
|
print(exc.detail, file=sys.stderr)
|
|
return 1
|
|
except Exception:
|
|
if db is not None:
|
|
db.rollback()
|
|
print("创建超级管理员失败", file=sys.stderr)
|
|
return 1
|
|
finally:
|
|
if db is not None:
|
|
db.close()
|
|
print(f"超级管理员已就绪:{username}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|