0.9.9
parent
4a22d48b19
commit
a1a7580d63
|
|
@ -11,7 +11,7 @@ from pydantic import BaseModel
|
|||
|
||||
from app.database import get_db
|
||||
from app.models.db import User, Role, Menu
|
||||
from app.services.auth import verify_password, create_access_token
|
||||
from app.services.auth import verify_password, create_access_token, hash_password
|
||||
from app.services.auth_deps import get_current_user
|
||||
from app.services.token_service import token_service
|
||||
from app.config import settings
|
||||
|
|
@ -29,6 +29,13 @@ class LoginRequest(BaseModel):
|
|||
password: str
|
||||
|
||||
|
||||
class RegisterRequest(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
email: str | None = None
|
||||
full_name: str | None = None
|
||||
|
||||
|
||||
class LoginResponse(BaseModel):
|
||||
access_token: str
|
||||
token_type: str = "bearer"
|
||||
|
|
@ -53,6 +60,91 @@ class MenuNode(BaseModel):
|
|||
children: list['MenuNode'] | None = None
|
||||
|
||||
|
||||
@router.post("/register", response_model=LoginResponse)
|
||||
async def register(
|
||||
register_data: RegisterRequest,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Register a new user
|
||||
"""
|
||||
# Check if username already exists
|
||||
result = await db.execute(
|
||||
select(User).where(User.username == register_data.username)
|
||||
)
|
||||
if result.scalar_one_or_none():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Username already registered"
|
||||
)
|
||||
|
||||
# Check if email already exists (if provided)
|
||||
if register_data.email:
|
||||
result = await db.execute(
|
||||
select(User).where(User.email == register_data.email)
|
||||
)
|
||||
if result.scalar_one_or_none():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Email already registered"
|
||||
)
|
||||
|
||||
# Get 'user' role
|
||||
result = await db.execute(
|
||||
select(Role).where(Role.name == "user")
|
||||
)
|
||||
user_role = result.scalar_one_or_none()
|
||||
if not user_role:
|
||||
# Should not happen if seeded correctly, but fallback handling
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Default role 'user' not found"
|
||||
)
|
||||
|
||||
# Create new user
|
||||
new_user = User(
|
||||
username=register_data.username,
|
||||
password_hash=hash_password(register_data.password),
|
||||
email=register_data.email,
|
||||
full_name=register_data.full_name,
|
||||
is_active=True
|
||||
)
|
||||
db.add(new_user)
|
||||
await db.flush() # Flush to get ID
|
||||
|
||||
# Assign role
|
||||
from app.models.db.user import user_roles
|
||||
await db.execute(
|
||||
user_roles.insert().values(
|
||||
user_id=new_user.id,
|
||||
role_id=user_role.id
|
||||
)
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(new_user)
|
||||
|
||||
# Create access token
|
||||
access_token = create_access_token(
|
||||
data={"sub": str(new_user.id), "username": new_user.username}
|
||||
)
|
||||
|
||||
# Save token to Redis
|
||||
await token_service.save_token(access_token, new_user.id, new_user.username)
|
||||
|
||||
# Return token and user info (simulate fetch with roles loaded)
|
||||
return LoginResponse(
|
||||
access_token=access_token,
|
||||
user={
|
||||
"id": new_user.id,
|
||||
"username": new_user.username,
|
||||
"email": new_user.email,
|
||||
"full_name": new_user.full_name,
|
||||
"roles": [user_role.name]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.post("/login", response_model=LoginResponse)
|
||||
async def login(
|
||||
login_data: LoginRequest,
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
API routes for celestial data
|
||||
"""
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, HTTPException, Query, Depends, UploadFile, File
|
||||
from fastapi import APIRouter, HTTPException, Query, Depends, UploadFile, File, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from typing import Optional
|
||||
from typing import Optional, Dict, Any
|
||||
import logging
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.models.celestial import (
|
||||
CelestialDataResponse,
|
||||
|
|
@ -31,6 +32,77 @@ logger = logging.getLogger(__name__)
|
|||
router = APIRouter(prefix="/celestial", tags=["celestial"])
|
||||
|
||||
|
||||
# Pydantic models for CRUD
|
||||
class CelestialBodyCreate(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
name_zh: Optional[str] = None
|
||||
type: str
|
||||
description: Optional[str] = None
|
||||
is_active: bool = True
|
||||
extra_data: Optional[Dict[str, Any]] = None
|
||||
|
||||
class CelestialBodyUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
name_zh: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
is_active: Optional[bool] = None
|
||||
extra_data: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
@router.post("/", status_code=status.HTTP_201_CREATED)
|
||||
async def create_celestial_body(
|
||||
body_data: CelestialBodyCreate,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Create a new celestial body"""
|
||||
# Check if exists
|
||||
existing = await celestial_body_service.get_body_by_id(body_data.id, db)
|
||||
if existing:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Body with ID {body_data.id} already exists"
|
||||
)
|
||||
|
||||
new_body = await celestial_body_service.create_body(body_data.dict(), db)
|
||||
return new_body
|
||||
|
||||
|
||||
@router.put("/{body_id}")
|
||||
async def update_celestial_body(
|
||||
body_id: str,
|
||||
body_data: CelestialBodyUpdate,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Update a celestial body"""
|
||||
# Filter out None values
|
||||
update_data = {k: v for k, v in body_data.dict().items() if v is not None}
|
||||
|
||||
updated = await celestial_body_service.update_body(body_id, update_data, db)
|
||||
if not updated:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Body {body_id} not found"
|
||||
)
|
||||
return updated
|
||||
|
||||
|
||||
@router.delete("/{body_id}")
|
||||
async def delete_celestial_body(
|
||||
body_id: str,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Delete a celestial body"""
|
||||
deleted = await celestial_body_service.delete_body(body_id, db)
|
||||
if not deleted:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Body {body_id} not found"
|
||||
)
|
||||
return {"message": "Body deleted successfully"}
|
||||
|
||||
|
||||
@router.get("/positions", response_model=CelestialDataResponse)
|
||||
async def get_celestial_positions(
|
||||
start_time: Optional[str] = Query(
|
||||
|
|
@ -476,6 +548,7 @@ async def list_bodies(
|
|||
"name_zh": body.name_zh,
|
||||
"type": body.type,
|
||||
"description": body.description,
|
||||
"is_active": body.is_active,
|
||||
}
|
||||
)
|
||||
return {"bodies": bodies_list}
|
||||
|
|
@ -519,8 +592,79 @@ async def preheat_cache(
|
|||
raise HTTPException(status_code=500, detail=f"Preheat failed: {str(e)}")
|
||||
|
||||
|
||||
# Static Data CRUD Models
|
||||
class StaticDataCreate(BaseModel):
|
||||
category: str
|
||||
name: str
|
||||
name_zh: Optional[str] = None
|
||||
data: Dict[str, Any]
|
||||
|
||||
class StaticDataUpdate(BaseModel):
|
||||
category: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
name_zh: Optional[str] = None
|
||||
data: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
# === Static Data Endpoints ===
|
||||
|
||||
@router.get("/static/list")
|
||||
async def list_static_data(db: AsyncSession = Depends(get_db)):
|
||||
"""Get all static data items"""
|
||||
items = await static_data_service.get_all_items(db)
|
||||
result = []
|
||||
for item in items:
|
||||
result.append({
|
||||
"id": item.id,
|
||||
"category": item.category,
|
||||
"name": item.name,
|
||||
"name_zh": item.name_zh,
|
||||
"data": item.data
|
||||
})
|
||||
return {"items": result}
|
||||
|
||||
|
||||
@router.post("/static", status_code=status.HTTP_201_CREATED)
|
||||
async def create_static_data(
|
||||
item_data: StaticDataCreate,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Create new static data"""
|
||||
new_item = await static_data_service.create_static(item_data.dict(), db)
|
||||
return new_item
|
||||
|
||||
|
||||
@router.put("/static/{item_id}")
|
||||
async def update_static_data(
|
||||
item_id: int,
|
||||
item_data: StaticDataUpdate,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Update static data"""
|
||||
update_data = {k: v for k, v in item_data.dict().items() if v is not None}
|
||||
updated = await static_data_service.update_static(item_id, update_data, db)
|
||||
if not updated:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Static data {item_id} not found"
|
||||
)
|
||||
return updated
|
||||
|
||||
|
||||
@router.delete("/static/{item_id}")
|
||||
async def delete_static_data(
|
||||
item_id: int,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Delete static data"""
|
||||
deleted = await static_data_service.delete_static(item_id, db)
|
||||
if not deleted:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Static data {item_id} not found"
|
||||
)
|
||||
return {"message": "Deleted successfully"}
|
||||
|
||||
|
||||
@router.get("/static/categories")
|
||||
async def get_static_categories(db: AsyncSession = Depends(get_db)):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
from sqlalchemy import select
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.database import get_db
|
||||
from app.models.db import User
|
||||
from app.services.auth import hash_password
|
||||
from app.services.auth_deps import get_current_user # To protect endpoints
|
||||
|
||||
router = APIRouter(prefix="/users", tags=["users"])
|
||||
|
||||
# Pydantic models
|
||||
class UserListItem(BaseModel):
|
||||
id: int
|
||||
username: str
|
||||
email: str | None
|
||||
full_name: str | None
|
||||
is_active: bool
|
||||
roles: list[str]
|
||||
last_login_at: str | None
|
||||
created_at: str
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class UserStatusUpdate(BaseModel):
|
||||
is_active: bool
|
||||
|
||||
@router.get("/list")
|
||||
async def get_user_list(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user) # Protect this route
|
||||
):
|
||||
"""Get a list of all users"""
|
||||
# Ensure only admins can see all users
|
||||
if "admin" not in [role.name for role in current_user.roles]:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized")
|
||||
|
||||
result = await db.execute(
|
||||
select(User).options(selectinload(User.roles)).order_by(User.id)
|
||||
)
|
||||
users = result.scalars().all()
|
||||
|
||||
users_list = []
|
||||
for user in users:
|
||||
users_list.append({
|
||||
"id": user.id,
|
||||
"username": user.username,
|
||||
"email": user.email,
|
||||
"full_name": user.full_name,
|
||||
"is_active": user.is_active,
|
||||
"roles": [role.name for role in user.roles],
|
||||
"last_login_at": user.last_login_at.isoformat() if user.last_login_at else None,
|
||||
"created_at": user.created_at.isoformat()
|
||||
})
|
||||
|
||||
return {"users": users_list}
|
||||
|
||||
|
||||
@router.put("/{user_id}/status")
|
||||
async def update_user_status(
|
||||
user_id: int,
|
||||
status_update: UserStatusUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Update a user's active status"""
|
||||
if "admin" not in [role.name for role in current_user.roles]:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized")
|
||||
|
||||
result = await db.execute(select(User).where(User.id == user_id))
|
||||
user = result.scalar_one_or_none()
|
||||
if not user:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
|
||||
|
||||
user.is_active = status_update.is_active
|
||||
await db.commit()
|
||||
|
||||
return {"message": "User status updated successfully"}
|
||||
|
||||
|
||||
@router.post("/{user_id}/reset-password")
|
||||
async def reset_user_password(
|
||||
user_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Reset a user's password to the default"""
|
||||
if "admin" not in [role.name for role in current_user.roles]:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized")
|
||||
|
||||
result = await db.execute(select(User).where(User.id == user_id))
|
||||
user = result.scalar_one_or_none()
|
||||
if not user:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
|
||||
|
||||
# Hardcoded default password for now.
|
||||
# TODO: Move to a configurable system parameter.
|
||||
default_password = "password123"
|
||||
user.password_hash = hash_password(default_password)
|
||||
|
||||
await db.commit()
|
||||
|
||||
return {"message": f"Password for user {user.username} has been reset."}
|
||||
|
|
@ -19,6 +19,7 @@ from fastapi.staticfiles import StaticFiles
|
|||
from app.config import settings
|
||||
from app.api.routes import router as celestial_router
|
||||
from app.api.auth import router as auth_router
|
||||
from app.api.user import router as user_router
|
||||
from app.services.redis_cache import redis_cache
|
||||
from app.services.cache_preheat import preheat_all_caches
|
||||
from app.database import close_db
|
||||
|
|
@ -85,6 +86,7 @@ app.add_middleware(
|
|||
# Include routers
|
||||
app.include_router(celestial_router, prefix=settings.api_prefix)
|
||||
app.include_router(auth_router, prefix=settings.api_prefix)
|
||||
app.include_router(user_router, prefix=settings.api_prefix)
|
||||
|
||||
# Mount static files for uploaded resources
|
||||
upload_dir = Path(__file__).parent.parent / "upload"
|
||||
|
|
@ -92,6 +94,12 @@ upload_dir.mkdir(exist_ok=True)
|
|||
app.mount("/upload", StaticFiles(directory=str(upload_dir)), name="upload")
|
||||
logger.info(f"Static files mounted at /upload -> {upload_dir}")
|
||||
|
||||
# Mount public assets directory
|
||||
public_assets_dir = Path(__file__).parent.parent / "public" / "assets"
|
||||
public_assets_dir.mkdir(parents=True, exist_ok=True)
|
||||
app.mount("/public/assets", StaticFiles(directory=str(public_assets_dir)), name="public_assets")
|
||||
logger.info(f"Public assets mounted at /public/assets -> {public_assets_dir}")
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
|
|
|
|||
|
|
@ -72,6 +72,63 @@ class CelestialBodyService:
|
|||
async with AsyncSessionLocal() as s:
|
||||
return await _create(s)
|
||||
|
||||
@staticmethod
|
||||
async def update_body(
|
||||
body_id: str,
|
||||
update_data: Dict[str, Any],
|
||||
session: Optional[AsyncSession] = None
|
||||
) -> Optional[CelestialBody]:
|
||||
"""Update a celestial body"""
|
||||
async def _update(s: AsyncSession):
|
||||
# Query the body
|
||||
result = await s.execute(
|
||||
select(CelestialBody).where(CelestialBody.id == body_id)
|
||||
)
|
||||
body = result.scalar_one_or_none()
|
||||
|
||||
if not body:
|
||||
return None
|
||||
|
||||
# Update fields
|
||||
for key, value in update_data.items():
|
||||
if hasattr(body, key):
|
||||
setattr(body, key, value)
|
||||
|
||||
await s.commit()
|
||||
await s.refresh(body)
|
||||
return body
|
||||
|
||||
if session:
|
||||
return await _update(session)
|
||||
else:
|
||||
async with AsyncSessionLocal() as s:
|
||||
return await _update(s)
|
||||
|
||||
@staticmethod
|
||||
async def delete_body(
|
||||
body_id: str,
|
||||
session: Optional[AsyncSession] = None
|
||||
) -> bool:
|
||||
"""Delete a celestial body"""
|
||||
async def _delete(s: AsyncSession):
|
||||
result = await s.execute(
|
||||
select(CelestialBody).where(CelestialBody.id == body_id)
|
||||
)
|
||||
body = result.scalar_one_or_none()
|
||||
|
||||
if not body:
|
||||
return False
|
||||
|
||||
await s.delete(body)
|
||||
await s.commit()
|
||||
return True
|
||||
|
||||
if session:
|
||||
return await _delete(session)
|
||||
else:
|
||||
async with AsyncSessionLocal() as s:
|
||||
return await _delete(s)
|
||||
|
||||
|
||||
class PositionService:
|
||||
"""Service for position data operations"""
|
||||
|
|
@ -354,6 +411,95 @@ class NasaCacheService:
|
|||
class StaticDataService:
|
||||
"""Service for static data operations"""
|
||||
|
||||
@staticmethod
|
||||
async def get_all_items(
|
||||
session: Optional[AsyncSession] = None
|
||||
) -> List[StaticData]:
|
||||
"""Get all static data items"""
|
||||
async def _query(s: AsyncSession):
|
||||
result = await s.execute(
|
||||
select(StaticData).order_by(StaticData.category, StaticData.name)
|
||||
)
|
||||
return result.scalars().all()
|
||||
|
||||
if session:
|
||||
return await _query(session)
|
||||
else:
|
||||
async with AsyncSessionLocal() as s:
|
||||
return await _query(s)
|
||||
|
||||
@staticmethod
|
||||
async def create_static(
|
||||
data: Dict[str, Any],
|
||||
session: Optional[AsyncSession] = None
|
||||
) -> StaticData:
|
||||
"""Create new static data"""
|
||||
async def _create(s: AsyncSession):
|
||||
item = StaticData(**data)
|
||||
s.add(item)
|
||||
await s.commit()
|
||||
await s.refresh(item)
|
||||
return item
|
||||
|
||||
if session:
|
||||
return await _create(session)
|
||||
else:
|
||||
async with AsyncSessionLocal() as s:
|
||||
return await _create(s)
|
||||
|
||||
@staticmethod
|
||||
async def update_static(
|
||||
item_id: int,
|
||||
update_data: Dict[str, Any],
|
||||
session: Optional[AsyncSession] = None
|
||||
) -> Optional[StaticData]:
|
||||
"""Update static data"""
|
||||
async def _update(s: AsyncSession):
|
||||
result = await s.execute(
|
||||
select(StaticData).where(StaticData.id == item_id)
|
||||
)
|
||||
item = result.scalar_one_or_none()
|
||||
if not item:
|
||||
return None
|
||||
|
||||
for key, value in update_data.items():
|
||||
if hasattr(item, key):
|
||||
setattr(item, key, value)
|
||||
|
||||
await s.commit()
|
||||
await s.refresh(item)
|
||||
return item
|
||||
|
||||
if session:
|
||||
return await _update(session)
|
||||
else:
|
||||
async with AsyncSessionLocal() as s:
|
||||
return await _update(s)
|
||||
|
||||
@staticmethod
|
||||
async def delete_static(
|
||||
item_id: int,
|
||||
session: Optional[AsyncSession] = None
|
||||
) -> bool:
|
||||
"""Delete static data"""
|
||||
async def _delete(s: AsyncSession):
|
||||
result = await s.execute(
|
||||
select(StaticData).where(StaticData.id == item_id)
|
||||
)
|
||||
item = result.scalar_one_or_none()
|
||||
if not item:
|
||||
return False
|
||||
|
||||
await s.delete(item)
|
||||
await s.commit()
|
||||
return True
|
||||
|
||||
if session:
|
||||
return await _delete(session)
|
||||
else:
|
||||
async with AsyncSessionLocal() as s:
|
||||
return await _delete(s)
|
||||
|
||||
@staticmethod
|
||||
async def get_by_category(
|
||||
category: str,
|
||||
|
|
|
|||
|
|
@ -36,15 +36,15 @@ class TokenService:
|
|||
await redis_cache.set(
|
||||
f"{self.prefix}{token}",
|
||||
json.dumps(token_data),
|
||||
expire=ttl_seconds
|
||||
ttl_seconds=ttl_seconds
|
||||
)
|
||||
|
||||
# Track user's active tokens (for multi-device support)
|
||||
user_tokens_key = f"{self.user_tokens_prefix}{user_id}"
|
||||
# Add token to user's token set
|
||||
if redis_cache.redis:
|
||||
await redis_cache.redis.sadd(user_tokens_key, token)
|
||||
await redis_cache.redis.expire(user_tokens_key, ttl_seconds)
|
||||
if redis_cache.client:
|
||||
await redis_cache.client.sadd(user_tokens_key, token)
|
||||
await redis_cache.client.expire(user_tokens_key, ttl_seconds)
|
||||
|
||||
async def get_token_data(self, token: str) -> Optional[dict]:
|
||||
"""
|
||||
|
|
@ -89,10 +89,10 @@ class TokenService:
|
|||
await redis_cache.delete(f"{self.prefix}{token}")
|
||||
|
||||
# Remove from user's token set
|
||||
if token_data and redis_cache.redis:
|
||||
if token_data and redis_cache.client:
|
||||
user_id = token_data.get("user_id")
|
||||
if user_id:
|
||||
await redis_cache.redis.srem(
|
||||
await redis_cache.client.srem(
|
||||
f"{self.user_tokens_prefix}{user_id}",
|
||||
token
|
||||
)
|
||||
|
|
@ -104,12 +104,12 @@ class TokenService:
|
|||
Args:
|
||||
user_id: User ID
|
||||
"""
|
||||
if not redis_cache.redis:
|
||||
if not redis_cache.client:
|
||||
return
|
||||
|
||||
# Get all user's tokens
|
||||
user_tokens_key = f"{self.user_tokens_prefix}{user_id}"
|
||||
tokens = await redis_cache.redis.smembers(user_tokens_key)
|
||||
tokens = await redis_cache.client.smembers(user_tokens_key)
|
||||
|
||||
# Revoke each token
|
||||
for token in tokens:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
-- This script adds a new top-level menu "Platform Management"
|
||||
-- with two sub-menus "User Management" and "Platform Parameters Management".
|
||||
-- These menus will be assigned to the 'admin' role.
|
||||
|
||||
-- Start Transaction for atomicity
|
||||
BEGIN;
|
||||
|
||||
-- 1. Find the ID of the 'admin' role
|
||||
-- Assuming 'admin' role name exists and is unique.
|
||||
DO $$
|
||||
DECLARE
|
||||
admin_role_id INTEGER;
|
||||
platform_management_menu_id INTEGER;
|
||||
user_management_menu_id INTEGER;
|
||||
platform_parameters_menu_id INTEGER;
|
||||
BEGIN
|
||||
SELECT id INTO admin_role_id FROM roles WHERE name = 'admin';
|
||||
|
||||
IF admin_role_id IS NULL THEN
|
||||
RAISE EXCEPTION 'Admin role not found. Please ensure the admin role exists.';
|
||||
END IF;
|
||||
|
||||
-- 2. Insert the top-level menu: "Platform Management"
|
||||
-- Check if it already exists to prevent duplicates on re-run
|
||||
SELECT id INTO platform_management_menu_id FROM menus WHERE name = 'platform_management' AND parent_id IS NULL;
|
||||
|
||||
IF platform_management_menu_id IS NULL THEN
|
||||
INSERT INTO menus (name, title, icon, path, component, sort_order, is_active, description, created_at, updated_at)
|
||||
VALUES (
|
||||
'platform_management',
|
||||
'平台管理',
|
||||
'settings', -- Using a generic settings icon for platform management
|
||||
NULL, -- It's a parent menu, no direct path
|
||||
NULL,
|
||||
3, -- Assuming sort_order 1 & 2 are for Dashboard & Data Management
|
||||
TRUE,
|
||||
'管理用户和系统参数',
|
||||
NOW(),
|
||||
NOW()
|
||||
) RETURNING id INTO platform_management_menu_id;
|
||||
RAISE NOTICE 'Inserted Platform Management menu with ID: %', platform_management_menu_id;
|
||||
|
||||
-- Assign to admin role
|
||||
INSERT INTO role_menus (role_id, menu_id, created_at)
|
||||
VALUES (admin_role_id, platform_management_menu_id, NOW());
|
||||
RAISE NOTICE 'Assigned Platform Management to admin role.';
|
||||
ELSE
|
||||
RAISE NOTICE 'Platform Management menu already exists with ID: %', platform_management_menu_id;
|
||||
END IF;
|
||||
|
||||
|
||||
-- 3. Insert sub-menu: "User Management"
|
||||
-- Check if it already exists
|
||||
SELECT id INTO user_management_menu_id FROM menus WHERE name = 'user_management' AND parent_id = platform_management_menu_id;
|
||||
|
||||
IF user_management_menu_id IS NULL THEN
|
||||
INSERT INTO menus (parent_id, name, title, icon, path, component, sort_order, is_active, description, created_at, updated_at)
|
||||
VALUES (
|
||||
platform_management_menu_id,
|
||||
'user_management',
|
||||
'用户管理',
|
||||
'users', -- Icon for user management
|
||||
'/admin/users', -- Admin users page path
|
||||
'admin/Users', -- React component path
|
||||
1,
|
||||
TRUE,
|
||||
'管理系统用户账号',
|
||||
NOW(),
|
||||
NOW()
|
||||
) RETURNING id INTO user_management_menu_id;
|
||||
RAISE NOTICE 'Inserted User Management menu with ID: %', user_management_menu_id;
|
||||
|
||||
-- Assign to admin role
|
||||
INSERT INTO role_menus (role_id, menu_id, created_at)
|
||||
VALUES (admin_role_id, user_management_menu_id, NOW());
|
||||
RAISE NOTICE 'Assigned User Management to admin role.';
|
||||
ELSE
|
||||
RAISE NOTICE 'User Management menu already exists with ID: %', user_management_menu_id;
|
||||
END IF;
|
||||
|
||||
|
||||
-- 4. Insert sub-menu: "Platform Parameters Management"
|
||||
-- Check if it already exists
|
||||
SELECT id INTO platform_parameters_menu_id FROM menus WHERE name = 'platform_parameters_management' AND parent_id = platform_management_menu_id;
|
||||
|
||||
IF platform_parameters_menu_id IS NULL THEN
|
||||
INSERT INTO menus (parent_id, name, title, icon, path, component, sort_order, is_active, description, created_at, updated_at)
|
||||
VALUES (
|
||||
platform_management_menu_id,
|
||||
'platform_parameters_management',
|
||||
'平台参数管理',
|
||||
'sliders', -- Icon for parameters/settings
|
||||
'/admin/settings', -- Admin settings page path
|
||||
'admin/Settings', -- React component path
|
||||
2,
|
||||
TRUE,
|
||||
'管理系统通用配置参数',
|
||||
NOW(),
|
||||
NOW()
|
||||
) RETURNING id INTO platform_parameters_menu_id;
|
||||
RAISE NOTICE 'Inserted Platform Parameters Management menu with ID: %', platform_parameters_menu_id;
|
||||
|
||||
-- Assign to admin role
|
||||
INSERT INTO role_menus (role_id, menu_id, created_at)
|
||||
VALUES (admin_role_id, platform_parameters_menu_id, NOW());
|
||||
RAISE NOTICE 'Assigned Platform Parameters Management to admin role.';
|
||||
ELSE
|
||||
RAISE NOTICE 'Platform Parameters Management menu already exists with ID: %', platform_parameters_menu_id;
|
||||
END IF;
|
||||
|
||||
END $$;
|
||||
|
||||
-- Commit the transaction
|
||||
COMMIT;
|
||||
Binary file not shown.
Loading…
Reference in New Issue