feat: 사용자별 페이지 접근 권한 시스템 구현
- 기존 4단계 권한을 admin/user 2단계로 단순화 - 페이지별 세부 접근 권한 관리 시스템 추가 - 부적합 조회 시 일반 사용자는 본인 등록 건만 조회 가능하도록 제한 - 관리자 전용 전체 부적합 조회 API 추가 (/api/issues/admin/all) Backend Changes: - models.py: UserPagePermission 모델 추가, UserRole 단순화 - page_permissions.py: 페이지 권한 관리 API 라우터 추가 - auth.py: 사용자 목록 조회 및 비밀번호 초기화 API 추가 - issues.py: 권한별 부적합 조회 제한 로직 구현 - 마이그레이션: 010~012 권한 시스템 관련 DB 스키마 변경
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
from sqlalchemy import Column, Integer, BigInteger, String, DateTime, Float, Boolean, Text, ForeignKey, Enum
|
from sqlalchemy import Column, Integer, BigInteger, String, DateTime, Float, Boolean, Text, ForeignKey, Enum, Index
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from datetime import datetime, timezone, timedelta
|
from datetime import datetime, timezone, timedelta
|
||||||
@@ -14,8 +14,8 @@ def get_kst_now():
|
|||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
class UserRole(str, enum.Enum):
|
class UserRole(str, enum.Enum):
|
||||||
admin = "admin"
|
admin = "admin" # 관리자
|
||||||
user = "user"
|
user = "user" # 일반 사용자
|
||||||
|
|
||||||
class IssueStatus(str, enum.Enum):
|
class IssueStatus(str, enum.Enum):
|
||||||
new = "new"
|
new = "new"
|
||||||
@@ -44,6 +44,28 @@ class User(Base):
|
|||||||
issues = relationship("Issue", back_populates="reporter")
|
issues = relationship("Issue", back_populates="reporter")
|
||||||
daily_works = relationship("DailyWork", back_populates="created_by")
|
daily_works = relationship("DailyWork", back_populates="created_by")
|
||||||
projects = relationship("Project", back_populates="created_by")
|
projects = relationship("Project", back_populates="created_by")
|
||||||
|
page_permissions = relationship("UserPagePermission", back_populates="user", foreign_keys="UserPagePermission.user_id")
|
||||||
|
|
||||||
|
class UserPagePermission(Base):
|
||||||
|
__tablename__ = "user_page_permissions"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
|
||||||
|
page_name = Column(String(50), nullable=False)
|
||||||
|
can_access = Column(Boolean, default=False)
|
||||||
|
granted_by_id = Column(Integer, ForeignKey("users.id"))
|
||||||
|
granted_at = Column(DateTime, default=get_kst_now)
|
||||||
|
notes = Column(Text)
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
user = relationship("User", back_populates="page_permissions", foreign_keys=[user_id])
|
||||||
|
granted_by = relationship("User", foreign_keys=[granted_by_id], post_update=True)
|
||||||
|
|
||||||
|
# Unique constraint
|
||||||
|
__table_args__ = (
|
||||||
|
Index('idx_user_page_permissions_user_id', 'user_id'),
|
||||||
|
Index('idx_user_page_permissions_page_name', 'page_name'),
|
||||||
|
)
|
||||||
|
|
||||||
class Issue(Base):
|
class Issue(Base):
|
||||||
__tablename__ = "issues"
|
__tablename__ = "issues"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import uvicorn
|
|||||||
|
|
||||||
from database.database import engine, get_db
|
from database.database import engine, get_db
|
||||||
from database.models import Base
|
from database.models import Base
|
||||||
from routers import auth, issues, daily_work, reports, projects
|
from routers import auth, issues, daily_work, reports, projects, page_permissions
|
||||||
from services.auth_service import create_admin_user
|
from services.auth_service import create_admin_user
|
||||||
|
|
||||||
# 데이터베이스 테이블 생성
|
# 데이터베이스 테이블 생성
|
||||||
@@ -36,6 +36,7 @@ app.include_router(issues.router)
|
|||||||
app.include_router(daily_work.router)
|
app.include_router(daily_work.router)
|
||||||
app.include_router(reports.router)
|
app.include_router(reports.router)
|
||||||
app.include_router(projects.router)
|
app.include_router(projects.router)
|
||||||
|
app.include_router(page_permissions.router)
|
||||||
|
|
||||||
# 시작 시 관리자 계정 생성
|
# 시작 시 관리자 계정 생성
|
||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
|
|||||||
15
backend/migrations/010_add_etc_category.sql
Normal file
15
backend/migrations/010_add_etc_category.sql
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
-- 부적합 카테고리에 'etc' (기타) 값 추가
|
||||||
|
-- 백엔드 코드와 데이터베이스 enum 타입 불일치 해결
|
||||||
|
|
||||||
|
-- issuecategory enum 타입에 'etc' 값 추가
|
||||||
|
ALTER TYPE issuecategory ADD VALUE 'etc';
|
||||||
|
|
||||||
|
-- 확인 쿼리 (주석)
|
||||||
|
-- SELECT enumlabel FROM pg_enum WHERE enumtypid = (SELECT oid FROM pg_type WHERE typname = 'issuecategory') ORDER BY enumsortorder;
|
||||||
|
|
||||||
|
-- 이제 사용 가능한 카테고리:
|
||||||
|
-- 1. material_missing (자재누락)
|
||||||
|
-- 2. design_error (설계미스)
|
||||||
|
-- 3. incoming_defect (입고자재 불량)
|
||||||
|
-- 4. inspection_miss (검사미스)
|
||||||
|
-- 5. etc (기타) ✅ 새로 추가됨
|
||||||
137
backend/migrations/011_add_permission_system.sql
Normal file
137
backend/migrations/011_add_permission_system.sql
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
-- 권한 시스템 개선 마이그레이션
|
||||||
|
-- 새로운 사용자 역할 추가 및 개별 권한 테이블 생성
|
||||||
|
|
||||||
|
-- 1. 새로운 사용자 역할 추가
|
||||||
|
ALTER TYPE userrole ADD VALUE 'super_admin';
|
||||||
|
ALTER TYPE userrole ADD VALUE 'manager';
|
||||||
|
|
||||||
|
-- 2. 사용자별 개별 권한 테이블 생성
|
||||||
|
CREATE TABLE IF NOT EXISTS user_permissions (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
permission VARCHAR(50) NOT NULL,
|
||||||
|
granted BOOLEAN DEFAULT TRUE,
|
||||||
|
granted_by_id INTEGER REFERENCES users(id),
|
||||||
|
granted_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
revoked_at TIMESTAMP WITH TIME ZONE,
|
||||||
|
notes TEXT,
|
||||||
|
UNIQUE(user_id, permission)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 3. 인덱스 생성
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_user_permissions_user_id ON user_permissions(user_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_user_permissions_permission ON user_permissions(permission);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_user_permissions_granted ON user_permissions(granted);
|
||||||
|
|
||||||
|
-- 4. 기본 권한 설정 (기존 관리자에게 super_admin 권한 부여)
|
||||||
|
UPDATE users SET role = 'super_admin' WHERE username = 'hyungi';
|
||||||
|
|
||||||
|
-- 5. 권한 확인 함수 생성
|
||||||
|
CREATE OR REPLACE FUNCTION check_user_permission(p_user_id INTEGER, p_permission VARCHAR)
|
||||||
|
RETURNS BOOLEAN AS $$
|
||||||
|
DECLARE
|
||||||
|
user_role userrole;
|
||||||
|
has_permission BOOLEAN := FALSE;
|
||||||
|
BEGIN
|
||||||
|
-- 사용자 역할 가져오기
|
||||||
|
SELECT role INTO user_role FROM users WHERE id = p_user_id AND is_active = TRUE;
|
||||||
|
|
||||||
|
IF user_role IS NULL THEN
|
||||||
|
RETURN FALSE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- super_admin은 모든 권한 보유
|
||||||
|
IF user_role = 'super_admin' THEN
|
||||||
|
RETURN TRUE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- 개별 권한 확인
|
||||||
|
SELECT granted INTO has_permission
|
||||||
|
FROM user_permissions
|
||||||
|
WHERE user_id = p_user_id
|
||||||
|
AND permission = p_permission
|
||||||
|
AND granted = TRUE
|
||||||
|
AND revoked_at IS NULL;
|
||||||
|
|
||||||
|
-- 개별 권한이 없으면 역할 기반 기본 권한 확인
|
||||||
|
IF has_permission IS NULL THEN
|
||||||
|
-- 기본 권한 매트릭스
|
||||||
|
CASE
|
||||||
|
WHEN p_permission IN ('issues.create', 'issues.view') THEN
|
||||||
|
has_permission := TRUE; -- 모든 사용자
|
||||||
|
WHEN p_permission IN ('issues.edit', 'issues.review', 'daily_work.create', 'daily_work.view', 'daily_work.edit') THEN
|
||||||
|
has_permission := user_role IN ('admin', 'manager'); -- 관리자, 매니저
|
||||||
|
WHEN p_permission IN ('projects.create', 'projects.edit', 'issues.delete', 'daily_work.delete') THEN
|
||||||
|
has_permission := user_role = 'admin'; -- 관리자만
|
||||||
|
WHEN p_permission IN ('projects.delete', 'users.create', 'users.edit', 'users.delete', 'users.change_role') THEN
|
||||||
|
has_permission := user_role = 'super_admin'; -- 최고 관리자만
|
||||||
|
ELSE
|
||||||
|
has_permission := FALSE;
|
||||||
|
END CASE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN COALESCE(has_permission, FALSE);
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- 6. 권한 부여 함수 생성
|
||||||
|
CREATE OR REPLACE FUNCTION grant_user_permission(
|
||||||
|
p_user_id INTEGER,
|
||||||
|
p_permission VARCHAR,
|
||||||
|
p_granted_by_id INTEGER,
|
||||||
|
p_notes TEXT DEFAULT NULL
|
||||||
|
)
|
||||||
|
RETURNS BOOLEAN AS $$
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO user_permissions (user_id, permission, granted, granted_by_id, notes)
|
||||||
|
VALUES (p_user_id, p_permission, TRUE, p_granted_by_id, p_notes)
|
||||||
|
ON CONFLICT (user_id, permission)
|
||||||
|
DO UPDATE SET
|
||||||
|
granted = TRUE,
|
||||||
|
granted_by_id = p_granted_by_id,
|
||||||
|
granted_at = NOW(),
|
||||||
|
revoked_at = NULL,
|
||||||
|
notes = p_notes;
|
||||||
|
|
||||||
|
RETURN TRUE;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- 7. 권한 취소 함수 생성
|
||||||
|
CREATE OR REPLACE FUNCTION revoke_user_permission(
|
||||||
|
p_user_id INTEGER,
|
||||||
|
p_permission VARCHAR,
|
||||||
|
p_revoked_by_id INTEGER,
|
||||||
|
p_notes TEXT DEFAULT NULL
|
||||||
|
)
|
||||||
|
RETURNS BOOLEAN AS $$
|
||||||
|
BEGIN
|
||||||
|
UPDATE user_permissions
|
||||||
|
SET granted = FALSE,
|
||||||
|
revoked_at = NOW(),
|
||||||
|
notes = p_notes
|
||||||
|
WHERE user_id = p_user_id
|
||||||
|
AND permission = p_permission;
|
||||||
|
|
||||||
|
RETURN TRUE;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- 8. 사용자 권한 목록 조회 뷰 생성
|
||||||
|
CREATE OR REPLACE VIEW user_permissions_view AS
|
||||||
|
SELECT
|
||||||
|
u.id as user_id,
|
||||||
|
u.username,
|
||||||
|
u.full_name,
|
||||||
|
u.role,
|
||||||
|
up.permission,
|
||||||
|
up.granted,
|
||||||
|
up.granted_at,
|
||||||
|
up.revoked_at,
|
||||||
|
granted_by.username as granted_by_username,
|
||||||
|
up.notes
|
||||||
|
FROM users u
|
||||||
|
LEFT JOIN user_permissions up ON u.id = up.user_id
|
||||||
|
LEFT JOIN users granted_by ON up.granted_by_id = granted_by.id
|
||||||
|
WHERE u.is_active = TRUE
|
||||||
|
ORDER BY u.username, up.permission;
|
||||||
111
backend/migrations/012_simplify_permissions.sql
Normal file
111
backend/migrations/012_simplify_permissions.sql
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
-- 권한 시스템 단순화
|
||||||
|
-- admin/user 구조로 변경하고 페이지별 접근 권한으로 변경
|
||||||
|
|
||||||
|
-- 1. 기존 복잡한 권한 테이블 삭제하고 단순한 페이지 권한 테이블로 변경
|
||||||
|
DROP TABLE IF EXISTS user_permissions CASCADE;
|
||||||
|
|
||||||
|
-- 2. 페이지별 접근 권한 테이블 생성
|
||||||
|
CREATE TABLE user_page_permissions (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
page_name VARCHAR(50) NOT NULL,
|
||||||
|
can_access BOOLEAN DEFAULT FALSE,
|
||||||
|
granted_by_id INTEGER REFERENCES users(id),
|
||||||
|
granted_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
notes TEXT,
|
||||||
|
UNIQUE(user_id, page_name)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 3. 인덱스 생성
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_user_page_permissions_user_id ON user_page_permissions(user_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_user_page_permissions_page_name ON user_page_permissions(page_name);
|
||||||
|
|
||||||
|
-- 4. 기존 복잡한 함수들 삭제
|
||||||
|
DROP FUNCTION IF EXISTS check_user_permission(INTEGER, VARCHAR);
|
||||||
|
DROP FUNCTION IF EXISTS grant_user_permission(INTEGER, VARCHAR, INTEGER, TEXT);
|
||||||
|
DROP FUNCTION IF EXISTS revoke_user_permission(INTEGER, VARCHAR, INTEGER, TEXT);
|
||||||
|
|
||||||
|
-- 5. 단순한 페이지 접근 권한 체크 함수
|
||||||
|
CREATE OR REPLACE FUNCTION check_page_access(p_user_id INTEGER, p_page_name VARCHAR)
|
||||||
|
RETURNS BOOLEAN AS $$
|
||||||
|
DECLARE
|
||||||
|
user_role userrole;
|
||||||
|
has_access BOOLEAN := FALSE;
|
||||||
|
BEGIN
|
||||||
|
-- 사용자 역할 가져오기
|
||||||
|
SELECT role INTO user_role FROM users WHERE id = p_user_id AND is_active = TRUE;
|
||||||
|
|
||||||
|
IF user_role IS NULL THEN
|
||||||
|
RETURN FALSE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- admin은 모든 페이지 접근 가능
|
||||||
|
IF user_role = 'admin' THEN
|
||||||
|
RETURN TRUE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- 일반 사용자는 개별 페이지 권한 확인
|
||||||
|
SELECT can_access INTO has_access
|
||||||
|
FROM user_page_permissions
|
||||||
|
WHERE user_id = p_user_id
|
||||||
|
AND page_name = p_page_name;
|
||||||
|
|
||||||
|
-- 권한이 설정되지 않은 경우 기본값 (부적합 등록/조회만 허용)
|
||||||
|
IF has_access IS NULL THEN
|
||||||
|
CASE p_page_name
|
||||||
|
WHEN 'issues_create' THEN has_access := TRUE;
|
||||||
|
WHEN 'issues_view' THEN has_access := TRUE;
|
||||||
|
ELSE has_access := FALSE;
|
||||||
|
END CASE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN COALESCE(has_access, FALSE);
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- 6. 페이지 권한 부여 함수
|
||||||
|
CREATE OR REPLACE FUNCTION grant_page_access(
|
||||||
|
p_user_id INTEGER,
|
||||||
|
p_page_name VARCHAR,
|
||||||
|
p_can_access BOOLEAN,
|
||||||
|
p_granted_by_id INTEGER,
|
||||||
|
p_notes TEXT DEFAULT NULL
|
||||||
|
)
|
||||||
|
RETURNS BOOLEAN AS $$
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO user_page_permissions (user_id, page_name, can_access, granted_by_id, notes)
|
||||||
|
VALUES (p_user_id, p_page_name, p_can_access, p_granted_by_id, p_notes)
|
||||||
|
ON CONFLICT (user_id, page_name)
|
||||||
|
DO UPDATE SET
|
||||||
|
can_access = p_can_access,
|
||||||
|
granted_by_id = p_granted_by_id,
|
||||||
|
granted_at = NOW(),
|
||||||
|
notes = p_notes;
|
||||||
|
|
||||||
|
RETURN TRUE;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- 7. 사용자 페이지 권한 조회 뷰
|
||||||
|
CREATE OR REPLACE VIEW user_page_access_view AS
|
||||||
|
SELECT
|
||||||
|
u.id as user_id,
|
||||||
|
u.username,
|
||||||
|
u.full_name,
|
||||||
|
u.role,
|
||||||
|
upp.page_name,
|
||||||
|
upp.can_access,
|
||||||
|
upp.granted_at,
|
||||||
|
granted_by.username as granted_by_username,
|
||||||
|
upp.notes
|
||||||
|
FROM users u
|
||||||
|
LEFT JOIN user_page_permissions upp ON u.id = upp.user_id
|
||||||
|
LEFT JOIN users granted_by ON upp.granted_by_id = granted_by.id
|
||||||
|
WHERE u.is_active = TRUE
|
||||||
|
ORDER BY u.username, upp.page_name;
|
||||||
|
|
||||||
|
-- 8. 기존 super_admin, manager 역할을 admin으로 변경
|
||||||
|
UPDATE users SET role = 'admin' WHERE role IN ('super_admin', 'manager');
|
||||||
|
|
||||||
|
-- 9. 기존 뷰 삭제
|
||||||
|
DROP VIEW IF EXISTS user_permissions_view;
|
||||||
@@ -2,6 +2,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
|||||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from database.database import get_db
|
from database.database import get_db
|
||||||
from database.models import User, UserRole
|
from database.models import User, UserRole
|
||||||
@@ -61,6 +62,15 @@ async def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session =
|
|||||||
async def read_users_me(current_user: User = Depends(get_current_user)):
|
async def read_users_me(current_user: User = Depends(get_current_user)):
|
||||||
return current_user
|
return current_user
|
||||||
|
|
||||||
|
@router.get("/users", response_model=List[schemas.User])
|
||||||
|
async def get_all_users(
|
||||||
|
current_admin: User = Depends(get_current_admin),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""모든 사용자 목록 조회 (관리자 전용)"""
|
||||||
|
users = db.query(User).filter(User.is_active == True).all()
|
||||||
|
return users
|
||||||
|
|
||||||
@router.post("/users", response_model=schemas.User)
|
@router.post("/users", response_model=schemas.User)
|
||||||
async def create_user(
|
async def create_user(
|
||||||
user: schemas.UserCreate,
|
user: schemas.UserCreate,
|
||||||
@@ -153,3 +163,24 @@ async def change_password(
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
return {"detail": "Password changed successfully"}
|
return {"detail": "Password changed successfully"}
|
||||||
|
|
||||||
|
class PasswordReset(BaseModel):
|
||||||
|
new_password: str
|
||||||
|
|
||||||
|
@router.post("/users/{user_id}/reset-password")
|
||||||
|
async def reset_user_password(
|
||||||
|
user_id: int,
|
||||||
|
password_reset: PasswordReset,
|
||||||
|
current_admin: User = Depends(get_current_admin),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""사용자 비밀번호 초기화 (관리자 전용)"""
|
||||||
|
db_user = db.query(User).filter(User.id == user_id).first()
|
||||||
|
if not db_user:
|
||||||
|
raise HTTPException(status_code=404, detail="User not found")
|
||||||
|
|
||||||
|
# 새 비밀번호로 업데이트
|
||||||
|
db_user.hashed_password = get_password_hash(password_reset.new_password)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return {"detail": f"Password reset successfully for user {db_user.username}"}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from datetime import datetime
|
|||||||
from database.database import get_db
|
from database.database import get_db
|
||||||
from database.models import Issue, IssueStatus, User, UserRole
|
from database.models import Issue, IssueStatus, User, UserRole
|
||||||
from database import schemas
|
from database import schemas
|
||||||
from routers.auth import get_current_user
|
from routers.auth import get_current_user, get_current_admin
|
||||||
from services.file_service import save_base64_image, delete_file
|
from services.file_service import save_base64_image, delete_file
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/issues", tags=["issues"])
|
router = APIRouter(prefix="/api/issues", tags=["issues"])
|
||||||
@@ -54,8 +54,30 @@ async def read_issues(
|
|||||||
):
|
):
|
||||||
query = db.query(Issue)
|
query = db.query(Issue)
|
||||||
|
|
||||||
# 모든 사용자가 모든 이슈를 조회 가능
|
# 권한별 조회 제한
|
||||||
# (필터링 제거 - 협업을 위해 모두가 볼 수 있어야 함)
|
if current_user.role == UserRole.admin:
|
||||||
|
# 관리자는 모든 이슈 조회 가능
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# 일반 사용자는 본인이 등록한 이슈만 조회 가능
|
||||||
|
query = query.filter(Issue.reporter_id == current_user.id)
|
||||||
|
|
||||||
|
if status:
|
||||||
|
query = query.filter(Issue.status == status)
|
||||||
|
|
||||||
|
issues = query.offset(skip).limit(limit).all()
|
||||||
|
return issues
|
||||||
|
|
||||||
|
@router.get("/admin/all", response_model=List[schemas.Issue])
|
||||||
|
async def read_all_issues_admin(
|
||||||
|
skip: int = 0,
|
||||||
|
limit: int = 100,
|
||||||
|
status: Optional[IssueStatus] = None,
|
||||||
|
current_admin: User = Depends(get_current_admin),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""관리자 전용: 모든 부적합 조회"""
|
||||||
|
query = db.query(Issue)
|
||||||
|
|
||||||
if status:
|
if status:
|
||||||
query = query.filter(Issue.status == status)
|
query = query.filter(Issue.status == status)
|
||||||
@@ -73,7 +95,12 @@ async def read_issue(
|
|||||||
if not issue:
|
if not issue:
|
||||||
raise HTTPException(status_code=404, detail="Issue not found")
|
raise HTTPException(status_code=404, detail="Issue not found")
|
||||||
|
|
||||||
# 모든 사용자가 모든 이슈를 조회 가능 (협업을 위해)
|
# 권한별 조회 제한
|
||||||
|
if current_user.role != UserRole.admin and issue.reporter_id != current_user.id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail="본인이 등록한 부적합만 조회할 수 있습니다."
|
||||||
|
)
|
||||||
|
|
||||||
return issue
|
return issue
|
||||||
|
|
||||||
|
|||||||
324
backend/routers/page_permissions.py
Normal file
324
backend/routers/page_permissions.py
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
"""
|
||||||
|
페이지 권한 관리 API 라우터
|
||||||
|
사용자별 페이지 접근 권한을 관리하는 엔드포인트들
|
||||||
|
"""
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from typing import List, Optional
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from database.database import get_db
|
||||||
|
from database.models import User, UserPagePermission, UserRole
|
||||||
|
from routers.auth import get_current_user
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/api", tags=["page-permissions"])
|
||||||
|
|
||||||
|
# Pydantic 모델들
|
||||||
|
class PagePermissionRequest(BaseModel):
|
||||||
|
user_id: int
|
||||||
|
page_name: str
|
||||||
|
can_access: bool
|
||||||
|
notes: Optional[str] = None
|
||||||
|
|
||||||
|
class PagePermissionResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
user_id: int
|
||||||
|
page_name: str
|
||||||
|
can_access: bool
|
||||||
|
granted_by_id: Optional[int]
|
||||||
|
granted_at: str
|
||||||
|
notes: Optional[str]
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
class UserPagePermissionSummary(BaseModel):
|
||||||
|
user_id: int
|
||||||
|
username: str
|
||||||
|
full_name: Optional[str]
|
||||||
|
role: str
|
||||||
|
permissions: List[PagePermissionResponse]
|
||||||
|
|
||||||
|
# 기본 페이지 목록
|
||||||
|
DEFAULT_PAGES = {
|
||||||
|
'issues_create': {'title': '부적합 등록', 'default_access': True},
|
||||||
|
'issues_view': {'title': '부적합 조회', 'default_access': True},
|
||||||
|
'issues_manage': {'title': '부적합 관리', 'default_access': False},
|
||||||
|
'projects_manage': {'title': '프로젝트 관리', 'default_access': False},
|
||||||
|
'daily_work': {'title': '일일 공수', 'default_access': False},
|
||||||
|
'reports': {'title': '보고서', 'default_access': False}
|
||||||
|
}
|
||||||
|
|
||||||
|
@router.post("/page-permissions/grant")
|
||||||
|
async def grant_page_permission(
|
||||||
|
request: PagePermissionRequest,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""페이지 권한 부여/취소"""
|
||||||
|
|
||||||
|
# 관리자만 권한 설정 가능
|
||||||
|
if current_user.role != UserRole.admin:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="관리자만 권한을 설정할 수 있습니다."
|
||||||
|
)
|
||||||
|
|
||||||
|
# 대상 사용자 확인
|
||||||
|
target_user = db.query(User).filter(User.id == request.user_id).first()
|
||||||
|
if not target_user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="사용자를 찾을 수 없습니다."
|
||||||
|
)
|
||||||
|
|
||||||
|
# 유효한 페이지명 확인
|
||||||
|
if request.page_name not in DEFAULT_PAGES:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="유효하지 않은 페이지명입니다."
|
||||||
|
)
|
||||||
|
|
||||||
|
# 기존 권한 확인
|
||||||
|
existing_permission = db.query(UserPagePermission).filter(
|
||||||
|
UserPagePermission.user_id == request.user_id,
|
||||||
|
UserPagePermission.page_name == request.page_name
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if existing_permission:
|
||||||
|
# 기존 권한 업데이트
|
||||||
|
existing_permission.can_access = request.can_access
|
||||||
|
existing_permission.granted_by_id = current_user.id
|
||||||
|
existing_permission.notes = request.notes
|
||||||
|
db.commit()
|
||||||
|
db.refresh(existing_permission)
|
||||||
|
return {"message": "권한이 업데이트되었습니다.", "permission_id": existing_permission.id}
|
||||||
|
else:
|
||||||
|
# 새 권한 생성
|
||||||
|
new_permission = UserPagePermission(
|
||||||
|
user_id=request.user_id,
|
||||||
|
page_name=request.page_name,
|
||||||
|
can_access=request.can_access,
|
||||||
|
granted_by_id=current_user.id,
|
||||||
|
notes=request.notes
|
||||||
|
)
|
||||||
|
db.add(new_permission)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(new_permission)
|
||||||
|
return {"message": "권한이 설정되었습니다.", "permission_id": new_permission.id}
|
||||||
|
|
||||||
|
@router.get("/users/{user_id}/page-permissions", response_model=List[PagePermissionResponse])
|
||||||
|
async def get_user_page_permissions(
|
||||||
|
user_id: int,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""특정 사용자의 페이지 권한 목록 조회"""
|
||||||
|
|
||||||
|
# 관리자이거나 본인의 권한만 조회 가능
|
||||||
|
if current_user.role != UserRole.admin and current_user.id != user_id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="권한이 없습니다."
|
||||||
|
)
|
||||||
|
|
||||||
|
# 사용자 존재 확인
|
||||||
|
user = db.query(User).filter(User.id == user_id).first()
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="사용자를 찾을 수 없습니다."
|
||||||
|
)
|
||||||
|
|
||||||
|
# 사용자의 페이지 권한 조회
|
||||||
|
permissions = db.query(UserPagePermission).filter(
|
||||||
|
UserPagePermission.user_id == user_id
|
||||||
|
).all()
|
||||||
|
|
||||||
|
return permissions
|
||||||
|
|
||||||
|
@router.get("/page-permissions/check/{user_id}/{page_name}")
|
||||||
|
async def check_page_access(
|
||||||
|
user_id: int,
|
||||||
|
page_name: str,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""특정 사용자의 특정 페이지 접근 권한 확인"""
|
||||||
|
|
||||||
|
# 사용자 존재 확인
|
||||||
|
user = db.query(User).filter(User.id == user_id).first()
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="사용자를 찾을 수 없습니다."
|
||||||
|
)
|
||||||
|
|
||||||
|
# admin은 모든 페이지 접근 가능
|
||||||
|
if user.role == UserRole.admin:
|
||||||
|
return {"can_access": True, "reason": "admin_role"}
|
||||||
|
|
||||||
|
# 유효한 페이지명 확인
|
||||||
|
if page_name not in DEFAULT_PAGES:
|
||||||
|
return {"can_access": False, "reason": "invalid_page"}
|
||||||
|
|
||||||
|
# 개별 권한 확인
|
||||||
|
permission = db.query(UserPagePermission).filter(
|
||||||
|
UserPagePermission.user_id == user_id,
|
||||||
|
UserPagePermission.page_name == page_name
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if permission:
|
||||||
|
return {
|
||||||
|
"can_access": permission.can_access,
|
||||||
|
"reason": "explicit_permission",
|
||||||
|
"granted_at": permission.granted_at.isoformat() if permission.granted_at else None
|
||||||
|
}
|
||||||
|
|
||||||
|
# 기본 권한 확인
|
||||||
|
default_access = DEFAULT_PAGES[page_name]['default_access']
|
||||||
|
return {
|
||||||
|
"can_access": default_access,
|
||||||
|
"reason": "default_permission"
|
||||||
|
}
|
||||||
|
|
||||||
|
@router.get("/page-permissions/all-users", response_model=List[UserPagePermissionSummary])
|
||||||
|
async def get_all_users_permissions(
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""모든 사용자의 페이지 권한 요약 조회 (관리자용)"""
|
||||||
|
|
||||||
|
if current_user.role != UserRole.admin:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="관리자만 접근할 수 있습니다."
|
||||||
|
)
|
||||||
|
|
||||||
|
# 모든 사용자 조회
|
||||||
|
users = db.query(User).filter(User.is_active == True).all()
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for user in users:
|
||||||
|
# 각 사용자의 권한 조회
|
||||||
|
permissions = db.query(UserPagePermission).filter(
|
||||||
|
UserPagePermission.user_id == user.id
|
||||||
|
).all()
|
||||||
|
|
||||||
|
result.append(UserPagePermissionSummary(
|
||||||
|
user_id=user.id,
|
||||||
|
username=user.username,
|
||||||
|
full_name=user.full_name,
|
||||||
|
role=user.role.value,
|
||||||
|
permissions=permissions
|
||||||
|
))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@router.get("/page-permissions/available-pages")
|
||||||
|
async def get_available_pages(
|
||||||
|
current_user: User = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""사용 가능한 페이지 목록 조회"""
|
||||||
|
|
||||||
|
return {
|
||||||
|
"pages": DEFAULT_PAGES,
|
||||||
|
"total_count": len(DEFAULT_PAGES)
|
||||||
|
}
|
||||||
|
|
||||||
|
@router.delete("/page-permissions/{permission_id}")
|
||||||
|
async def delete_page_permission(
|
||||||
|
permission_id: int,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""페이지 권한 삭제 (기본값으로 되돌림)"""
|
||||||
|
|
||||||
|
if current_user.role != UserRole.admin:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="관리자만 권한을 삭제할 수 있습니다."
|
||||||
|
)
|
||||||
|
|
||||||
|
# 권한 조회
|
||||||
|
permission = db.query(UserPagePermission).filter(
|
||||||
|
UserPagePermission.id == permission_id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not permission:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="권한을 찾을 수 없습니다."
|
||||||
|
)
|
||||||
|
|
||||||
|
# 권한 삭제
|
||||||
|
db.delete(permission)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return {"message": "권한이 삭제되었습니다. 기본값이 적용됩니다."}
|
||||||
|
|
||||||
|
class BulkPermissionRequest(BaseModel):
|
||||||
|
user_id: int
|
||||||
|
permissions: List[dict] # [{"page_name": "issues_manage", "can_access": true}, ...]
|
||||||
|
|
||||||
|
@router.post("/page-permissions/bulk-grant")
|
||||||
|
async def bulk_grant_permissions(
|
||||||
|
request: BulkPermissionRequest,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""사용자의 여러 페이지 권한을 일괄 설정"""
|
||||||
|
|
||||||
|
if current_user.role != UserRole.admin:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="관리자만 권한을 설정할 수 있습니다."
|
||||||
|
)
|
||||||
|
|
||||||
|
# 대상 사용자 확인
|
||||||
|
target_user = db.query(User).filter(User.id == request.user_id).first()
|
||||||
|
if not target_user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="사용자를 찾을 수 없습니다."
|
||||||
|
)
|
||||||
|
|
||||||
|
updated_permissions = []
|
||||||
|
|
||||||
|
for perm_data in request.permissions:
|
||||||
|
page_name = perm_data.get('page_name')
|
||||||
|
can_access = perm_data.get('can_access', False)
|
||||||
|
|
||||||
|
# 유효한 페이지명 확인
|
||||||
|
if page_name not in DEFAULT_PAGES:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 기존 권한 확인
|
||||||
|
existing_permission = db.query(UserPagePermission).filter(
|
||||||
|
UserPagePermission.user_id == request.user_id,
|
||||||
|
UserPagePermission.page_name == page_name
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if existing_permission:
|
||||||
|
# 기존 권한 업데이트
|
||||||
|
existing_permission.can_access = can_access
|
||||||
|
existing_permission.granted_by_id = current_user.id
|
||||||
|
updated_permissions.append(existing_permission)
|
||||||
|
else:
|
||||||
|
# 새 권한 생성
|
||||||
|
new_permission = UserPagePermission(
|
||||||
|
user_id=request.user_id,
|
||||||
|
page_name=page_name,
|
||||||
|
can_access=can_access,
|
||||||
|
granted_by_id=current_user.id
|
||||||
|
)
|
||||||
|
db.add(new_permission)
|
||||||
|
updated_permissions.append(new_permission)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"message": f"{len(updated_permissions)}개의 권한이 설정되었습니다.",
|
||||||
|
"updated_count": len(updated_permissions)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user