diff --git a/backend/api/logbook.py b/backend/api/logbook.py index 3acac85..5a8f114 100644 --- a/backend/api/logbook.py +++ b/backend/api/logbook.py @@ -21,7 +21,7 @@ from models.logbook import ( ) # from models.products import Product # TODO: Add Product model -router = APIRouter(prefix="/logbook", tags=["Logbook"]) +router = APIRouter(prefix="/logbook", tags=["Logbook"], dependencies=[Depends(require_cap("logbook"))]) logger = logging.getLogger(__name__) diff --git a/backend/api/reports.py b/backend/api/reports.py index 2aa0a00..178ae9d 100644 --- a/backend/api/reports.py +++ b/backend/api/reports.py @@ -19,7 +19,7 @@ from models.newbook import NewbookDailyRevenue, NewbookGLAccount, NewbookDailyOc from models.cost_distribution import CostDistribution, CostDistributionEntry, DistributionStatus from auth import get_current_user, require_cap -router = APIRouter() +router = APIRouter(dependencies=[Depends(require_cap("view"))]) class RevenueEntryCreate(BaseModel): diff --git a/backend/api/search.py b/backend/api/search.py index 68eb7ec..20977ab 100644 --- a/backend/api/search.py +++ b/backend/api/search.py @@ -19,7 +19,7 @@ from models.settings import KitchenSettings from auth import get_current_user, require_cap from services.price_history import PriceHistoryService -router = APIRouter(prefix="/api/search", tags=["search"]) +router = APIRouter(prefix="/api/search", tags=["search"], dependencies=[Depends(require_cap("invoices"))]) # ============ Response Models ============ diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 2043203..a61fb51 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -106,7 +106,7 @@ interface RoomCategory { display_order: number } -type SettingsSection = 'account' | 'access' | 'display' | 'azure' | 'email' | 'inbox' | 'dext' | 'newbook' | 'resos' | 'sambapos' | 'kds' | 'budget' | 'kitchen' | 'suppliers' | 'search' | 'nextcloud' | 'backup' | 'food_flags' | 'allergen_keywords' | 'ingredient_categories' | 'recipe_sections' | 'dish_courses' | 'api_access' | 'llm' | 'data' +type SettingsSection = 'account' | 'display' | 'azure' | 'email' | 'inbox' | 'dext' | 'newbook' | 'resos' | 'sambapos' | 'kds' | 'budget' | 'kitchen' | 'suppliers' | 'search' | 'nextcloud' | 'backup' | 'food_flags' | 'allergen_keywords' | 'ingredient_categories' | 'recipe_sections' | 'dish_courses' | 'api_access' | 'llm' | 'data' interface SambaPOSSettingsData { sambapos_db_host: string | null @@ -274,7 +274,7 @@ interface ImapSyncStats { } export default function Settings() { - const { user, token, logout, restrictedPages: authRestrictedPages } = useAuth() + const { user, token, logout } = useAuth() const queryClient = useQueryClient() const [activeSection, setActiveSection] = useState('account') @@ -738,19 +738,6 @@ export default function Settings() { enabled: !!token, }) - // Fetch page restrictions - const { data: pageRestrictions } = useQuery<{ restricted_pages: string[] }>({ - queryKey: ['page-restrictions'], - queryFn: async () => { - const res = await fetch('/kitchen/api/settings/page-restrictions', { - headers: { Authorization: `Bearer ${token}` }, - }) - if (!res.ok) throw new Error('Failed to fetch page restrictions') - return res.json() - }, - enabled: !!token, - }) - // Fetch Nextcloud settings const { data: nextcloudSettings } = useQuery({ queryKey: ['nextcloud-settings'], @@ -869,17 +856,6 @@ export default function Settings() { enabled: !!token && !!user?.is_admin && activeSection === 'inbox', }) - // State for page restrictions - const [restrictedPages, setRestrictedPages] = useState>(new Set()) - const [accessSaveMessage, setAccessSaveMessage] = useState(null) - - // Populate restricted pages from settings - useEffect(() => { - if (pageRestrictions?.restricted_pages) { - setRestrictedPages(new Set(pageRestrictions.restricted_pages)) - } - }, [pageRestrictions]) - useEffect(() => { if (settings) { setAzureEndpoint(settings.azure_endpoint || '') @@ -1641,29 +1617,6 @@ export default function Settings() { }, }) - const savePageRestrictionsMutation = useMutation({ - mutationFn: async (pages: string[]) => { - const res = await fetch('/kitchen/api/settings/page-restrictions', { - method: 'PATCH', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ restricted_pages: pages }), - }) - if (!res.ok) throw new Error('Failed to save page restrictions') - return res.json() - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['page-restrictions'] }) - setAccessSaveMessage('Page restrictions saved successfully') - setTimeout(() => setAccessSaveMessage(null), 3000) - }, - onError: (error) => { - setAccessSaveMessage(`Error: ${error.message}`) - }, - }) - const reprocessMutation = useMutation({ mutationFn: async () => { const res = await fetch('/kitchen/api/invoices/reprocess-all', { @@ -2678,39 +2631,31 @@ export default function Settings() { return
Loading settings...
} - // Helper to check if a settings section is accessible - const isSectionAccessible = (restrictPath?: string) => { - if (!restrictPath) return true - if (user?.is_admin) return true - return !authRestrictedPages.includes(restrictPath) - } - - const sidebarItems: { id: SettingsSection; label: string; adminOnly?: boolean; href?: string; restrictPath?: string }[] = [ + const sidebarItems: { id: SettingsSection; label: string; adminOnly?: boolean; href?: string }[] = [ { id: 'account', label: 'Account' }, - { id: 'access', label: 'Access Control', adminOnly: true, restrictPath: '/settings-access' }, - { id: 'display', label: 'Display', restrictPath: '/settings-display' }, - { id: 'azure', label: 'Azure OCR', restrictPath: '/settings-azure' }, - { id: 'email', label: 'Email Configuration', restrictPath: '/settings-email' }, - { id: 'inbox', label: 'Email Inbox', adminOnly: true, restrictPath: '/settings-inbox' }, - { id: 'dext', label: 'Dext Integration', restrictPath: '/settings-dext' }, - { id: 'newbook', label: 'Newbook PMS', restrictPath: '/settings-newbook' }, - { id: 'resos', label: 'Resos Bookings', restrictPath: '/settings-resos' }, - { id: 'sambapos', label: 'SambaPOS EPOS', restrictPath: '/settings-sambapos' }, - { id: 'kds', label: 'Kitchen Display', restrictPath: '/settings-kds' }, - { id: 'budget', label: 'Spend Budget', restrictPath: '/settings-budget' }, - { id: 'kitchen', label: 'Kitchen Details', restrictPath: '/settings-kitchen' }, - { id: 'suppliers', label: 'Suppliers', restrictPath: '/settings-suppliers' }, - { id: 'search', label: 'Search & Pricing', restrictPath: '/settings-search' }, - { id: 'nextcloud', label: 'Nextcloud Storage', restrictPath: '/settings-nextcloud' }, - { id: 'backup', label: 'Backup & Restore', restrictPath: '/settings-backup' }, - { id: 'food_flags', label: 'Food Flags', restrictPath: '/settings-food-flags' }, - { id: 'allergen_keywords', label: 'Allergen Keywords', restrictPath: '/settings-food-flags' }, - { id: 'ingredient_categories', label: 'Ingredient Categories', restrictPath: '/settings-ingredient-categories' }, - { id: 'recipe_sections', label: 'Recipe Sections', restrictPath: '/settings-recipe-sections' }, - { id: 'dish_courses', label: 'Dish Courses', restrictPath: '/settings-dish-courses' }, - { id: 'api_access', label: 'API Access', restrictPath: '/settings-api-access' }, - { id: 'llm', label: 'AI Features', adminOnly: true, restrictPath: '/settings-llm' }, // LLM FEATURE - { id: 'data', label: 'Data Management', restrictPath: '/settings-data' }, + { id: 'display', label: 'Display' }, + { id: 'azure', label: 'Azure OCR' }, + { id: 'email', label: 'Email Configuration' }, + { id: 'inbox', label: 'Email Inbox', adminOnly: true }, + { id: 'dext', label: 'Dext Integration' }, + { id: 'newbook', label: 'Newbook PMS' }, + { id: 'resos', label: 'Resos Bookings' }, + { id: 'sambapos', label: 'SambaPOS EPOS' }, + { id: 'kds', label: 'Kitchen Display' }, + { id: 'budget', label: 'Spend Budget' }, + { id: 'kitchen', label: 'Kitchen Details' }, + { id: 'suppliers', label: 'Suppliers' }, + { id: 'search', label: 'Search & Pricing' }, + { id: 'nextcloud', label: 'Nextcloud Storage' }, + { id: 'backup', label: 'Backup & Restore' }, + { id: 'food_flags', label: 'Food Flags' }, + { id: 'allergen_keywords', label: 'Allergen Keywords' }, + { id: 'ingredient_categories', label: 'Ingredient Categories' }, + { id: 'recipe_sections', label: 'Recipe Sections' }, + { id: 'dish_courses', label: 'Dish Courses' }, + { id: 'api_access', label: 'API Access' }, + { id: 'llm', label: 'AI Features', adminOnly: true }, // LLM FEATURE + { id: 'data', label: 'Data Management' }, ] return ( @@ -2721,7 +2666,6 @@ export default function Settings() {