Auth: assignable-users endpoint + maintenance app seed row
GET /api/auth/users?app=<slug> — non-admin user list for task allocation (requester must have access to the app; returns active users with access, minimal fields). Seed row for the maintenance app (LXC 121). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
608ed4ce19
commit
a03b065c43
2 changed files with 55 additions and 1 deletions
|
|
@ -125,7 +125,8 @@ export async function initDb() {
|
|||
('hk-planner', 'HK Planner', 'Housekeeping workload and hours planning', '/hk-planner', 'CalendarClock', '#2d6a4f', 'Housekeeping', '10.10.10.118', 3080),
|
||||
('twin-optimiser', 'Twin Optimiser', 'Identify twin room opportunities from booking grid', '/twin-optimiser', 'LayoutGrid', '#c9841a', 'Housekeeping', '10.10.10.119', 3080),
|
||||
('forecasting', 'Forecasting', 'Revenue forecasting and reporting', '/forecast', 'TrendingUp', '#0077b6', 'Finance', '10.10.10.113', 3080),
|
||||
('rates', 'Rate Scraper', 'Competitor rate monitoring', '/rates', 'Tag', '#7b4f00', 'Finance', '10.10.10.115', 3080)
|
||||
('rates', 'Rate Scraper', 'Competitor rate monitoring', '/rates', 'Tag', '#7b4f00', 'Finance', '10.10.10.115', 3080),
|
||||
('maintenance', 'Maintenance', 'Maintenance log book — faults, recurring tasks, assets and contractors', '/maintenance', 'Wrench', '#b45309', 'Operations', '10.10.10.121', 3080)
|
||||
ON CONFLICT (slug) DO UPDATE SET
|
||||
icon = EXCLUDED.icon,
|
||||
category = EXCLUDED.category,
|
||||
|
|
|
|||
|
|
@ -227,4 +227,57 @@ export async function authRoutes(app) {
|
|||
caps,
|
||||
}
|
||||
})
|
||||
|
||||
// GET /api/auth/users?app=slug
|
||||
// Assignable-user list for apps (e.g. maintenance task allocation). Any user
|
||||
// with access to the requested app may call it; returns the active users who
|
||||
// can access that app (minimal fields only — this is not the admin list).
|
||||
app.get('/users', async (request, reply) => {
|
||||
const token = request.cookies?.hnf_session
|
||||
if (!token) return reply.status(401).send({ error: 'Not authenticated' })
|
||||
|
||||
let payload
|
||||
try { payload = await verifyToken(token) }
|
||||
catch { return reply.status(401).send({ error: 'Invalid session' }) }
|
||||
|
||||
const { app: appSlug } = request.query
|
||||
if (!appSlug) return reply.status(400).send({ error: 'app query parameter required' })
|
||||
|
||||
// Requester must themselves have access to the app
|
||||
const { rows: [requester] } = await pool.query(
|
||||
`SELECT u.id FROM users u
|
||||
WHERE u.id = $1 AND u.active = true
|
||||
AND (
|
||||
u.is_admin = true
|
||||
OR EXISTS (SELECT 1 FROM user_app_perms p
|
||||
JOIN apps a ON a.id = p.app_id
|
||||
WHERE p.user_id = u.id AND a.slug = $2 AND a.active = true)
|
||||
OR EXISTS (SELECT 1 FROM user_roles ur
|
||||
JOIN role_app_perms rap ON rap.role_id = ur.role_id
|
||||
JOIN apps a ON a.id = rap.app_id
|
||||
WHERE ur.user_id = u.id AND a.slug = $2 AND a.active = true)
|
||||
)`,
|
||||
[payload.user_id, appSlug]
|
||||
)
|
||||
if (!requester) return reply.status(403).send({ error: 'Access denied' })
|
||||
|
||||
const { rows: users } = await pool.query(
|
||||
`SELECT DISTINCT u.id, u.email, u.name
|
||||
FROM users u
|
||||
WHERE u.active = true
|
||||
AND (
|
||||
u.is_admin = true
|
||||
OR EXISTS (SELECT 1 FROM user_app_perms p
|
||||
JOIN apps a ON a.id = p.app_id
|
||||
WHERE p.user_id = u.id AND a.slug = $1 AND a.active = true)
|
||||
OR EXISTS (SELECT 1 FROM user_roles ur
|
||||
JOIN role_app_perms rap ON rap.role_id = ur.role_id
|
||||
JOIN apps a ON a.id = rap.app_id
|
||||
WHERE ur.user_id = u.id AND a.slug = $1 AND a.active = true)
|
||||
)
|
||||
ORDER BY u.name`,
|
||||
[appSlug]
|
||||
)
|
||||
return users
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue