From e345707db2cfd438deab2e1eaf1bc71d8c37d918 Mon Sep 17 00:00:00 2001 From: Kelly Date: Mon, 1 Dec 2025 13:59:01 -0700 Subject: [PATCH] Add store selection to API permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add store_id and store_name columns to wp_dutchie_api_permissions - Backend: Add /stores endpoint, require store_id when creating permissions - Frontend: Add store selector dropdown to API Permissions form - WordPress plugin v1.3.0: Remove store_id from shortcodes (store is tied to token) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- ...029_add_store_id_to_wp_api_permissions.sql | 13 ++++ backend/src/routes/api-permissions.ts | 38 ++++++++++-- frontend/public/wordpress/menus-v1.3.0.zip | Bin 0 -> 56720 bytes frontend/src/lib/api.ts | 6 +- frontend/src/pages/ApiPermissions.tsx | 57 +++++++++++++++++- wordpress-plugin/dutchie-menus.php | 5 +- 6 files changed, 109 insertions(+), 10 deletions(-) create mode 100644 backend/migrations/029_add_store_id_to_wp_api_permissions.sql create mode 100644 frontend/public/wordpress/menus-v1.3.0.zip diff --git a/backend/migrations/029_add_store_id_to_wp_api_permissions.sql b/backend/migrations/029_add_store_id_to_wp_api_permissions.sql new file mode 100644 index 00000000..c8d62892 --- /dev/null +++ b/backend/migrations/029_add_store_id_to_wp_api_permissions.sql @@ -0,0 +1,13 @@ +-- Migration: Add store_id to wp_dutchie_api_permissions +-- This allows API tokens to be associated with a specific store + +-- Add store_id column to wp_dutchie_api_permissions +ALTER TABLE wp_dutchie_api_permissions +ADD COLUMN IF NOT EXISTS store_id INTEGER REFERENCES stores(id); + +-- Add index for faster lookups +CREATE INDEX IF NOT EXISTS idx_wp_api_permissions_store_id ON wp_dutchie_api_permissions(store_id); + +-- Add store_name column to return store info without join +ALTER TABLE wp_dutchie_api_permissions +ADD COLUMN IF NOT EXISTS store_name VARCHAR(255); diff --git a/backend/src/routes/api-permissions.ts b/backend/src/routes/api-permissions.ts index ad5b2c69..b01944ca 100644 --- a/backend/src/routes/api-permissions.ts +++ b/backend/src/routes/api-permissions.ts @@ -27,6 +27,21 @@ router.get('/', requireRole('superadmin', 'admin'), async (req, res) => { } }); +// Get all stores for dropdown (must be before /:id to avoid route conflict) +router.get('/stores', requireRole('superadmin', 'admin'), async (req, res) => { + try { + const result = await pool.query(` + SELECT id, name + FROM stores + ORDER BY name + `); + res.json({ stores: result.rows }); + } catch (error) { + console.error('Error fetching stores:', error); + res.status(500).json({ error: 'Failed to fetch stores' }); + } +}); + // Get single API permission router.get('/:id', requireRole('superadmin', 'admin'), async (req, res) => { try { @@ -52,12 +67,23 @@ router.get('/:id', requireRole('superadmin', 'admin'), async (req, res) => { // Create new API permission router.post('/', requireRole('superadmin', 'admin'), async (req, res) => { try { - const { user_name, allowed_ips, allowed_domains } = req.body; + const { user_name, allowed_ips, allowed_domains, store_id } = req.body; if (!user_name) { return res.status(400).json({ error: 'User name is required' }); } + if (!store_id) { + return res.status(400).json({ error: 'Store is required' }); + } + + // Get store name for display + const storeResult = await pool.query('SELECT name FROM stores WHERE id = $1', [store_id]); + if (storeResult.rows.length === 0) { + return res.status(400).json({ error: 'Invalid store ID' }); + } + const storeName = storeResult.rows[0].name; + const apiKey = generateApiKey(); const result = await pool.query(` @@ -66,15 +92,19 @@ router.post('/', requireRole('superadmin', 'admin'), async (req, res) => { api_key, allowed_ips, allowed_domains, - is_active + is_active, + store_id, + store_name ) - VALUES ($1, $2, $3, $4, 1) + VALUES ($1, $2, $3, $4, 1, $5, $6) RETURNING * `, [ user_name, apiKey, allowed_ips || null, - allowed_domains || null + allowed_domains || null, + store_id, + storeName ]); res.status(201).json({ diff --git a/frontend/public/wordpress/menus-v1.3.0.zip b/frontend/public/wordpress/menus-v1.3.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..668e3d17487ff981f9299f275f48e12454993062 GIT binary patch literal 56720 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 dQ;*xr^uuTCKkA('/api/api-permissions'); } - async createApiPermission(data: { user_name: string; allowed_ips?: string; allowed_domains?: string }) { + async getApiPermissionStores() { + return this.request<{ stores: Array<{ id: number; name: string }> }>('/api/api-permissions/stores'); + } + + async createApiPermission(data: { user_name: string; store_id: number; allowed_ips?: string; allowed_domains?: string }) { return this.request<{ permission: any; message: string }>('/api/api-permissions', { method: 'POST', body: JSON.stringify(data), diff --git a/frontend/src/pages/ApiPermissions.tsx b/frontend/src/pages/ApiPermissions.tsx index 3e403ba4..a4e4670d 100644 --- a/frontend/src/pages/ApiPermissions.tsx +++ b/frontend/src/pages/ApiPermissions.tsx @@ -12,14 +12,23 @@ interface ApiPermission { is_active: number; created_at: string; last_used_at: string | null; + store_id: number | null; + store_name: string | null; +} + +interface Store { + id: number; + name: string; } export function ApiPermissions() { const [permissions, setPermissions] = useState([]); + const [stores, setStores] = useState([]); const [loading, setLoading] = useState(true); const [showAddForm, setShowAddForm] = useState(false); const [newPermission, setNewPermission] = useState({ user_name: '', + store_id: '', allowed_ips: '', allowed_domains: '', }); @@ -27,8 +36,18 @@ export function ApiPermissions() { useEffect(() => { loadPermissions(); + loadStores(); }, []); + const loadStores = async () => { + try { + const data = await api.getApiPermissionStores(); + setStores(data.stores); + } catch (error: any) { + console.error('Failed to load stores:', error); + } + }; + const loadPermissions = async () => { setLoading(true); try { @@ -49,10 +68,18 @@ export function ApiPermissions() { return; } + if (!newPermission.store_id) { + setNotification({ message: 'Store is required', type: 'error' }); + return; + } + try { - const result = await api.createApiPermission(newPermission); + const result = await api.createApiPermission({ + ...newPermission, + store_id: parseInt(newPermission.store_id), + }); setNotification({ message: result.message, type: 'success' }); - setNewPermission({ user_name: '', allowed_ips: '', allowed_domains: '' }); + setNewPermission({ user_name: '', store_id: '', allowed_ips: '', allowed_domains: '' }); setShowAddForm(false); loadPermissions(); } catch (error: any) { @@ -153,6 +180,26 @@ export function ApiPermissions() {

A friendly name to identify this API user

+
+ + +

The store this API token can access

+
+