feat(api): complete unified v2 mobile surface
This commit is contained in:
111
backend/command-api/src/services/actor-context.js
Normal file
111
backend/command-api/src/services/actor-context.js
Normal file
@@ -0,0 +1,111 @@
|
||||
import { AppError } from '../lib/errors.js';
|
||||
import { query } from './db.js';
|
||||
|
||||
export async function loadActorContext(uid) {
|
||||
const [userResult, tenantResult, businessResult, vendorResult, staffResult] = await Promise.all([
|
||||
query(
|
||||
`
|
||||
SELECT id AS "userId", email, display_name AS "displayName", phone, status
|
||||
FROM users
|
||||
WHERE id = $1
|
||||
`,
|
||||
[uid]
|
||||
),
|
||||
query(
|
||||
`
|
||||
SELECT tm.id AS "membershipId",
|
||||
tm.tenant_id AS "tenantId",
|
||||
tm.base_role AS role,
|
||||
t.name AS "tenantName",
|
||||
t.slug AS "tenantSlug"
|
||||
FROM tenant_memberships tm
|
||||
JOIN tenants t ON t.id = tm.tenant_id
|
||||
WHERE tm.user_id = $1
|
||||
AND tm.membership_status = 'ACTIVE'
|
||||
ORDER BY tm.created_at ASC
|
||||
LIMIT 1
|
||||
`,
|
||||
[uid]
|
||||
),
|
||||
query(
|
||||
`
|
||||
SELECT bm.id AS "membershipId",
|
||||
bm.business_id AS "businessId",
|
||||
bm.business_role AS role,
|
||||
b.business_name AS "businessName",
|
||||
b.slug AS "businessSlug",
|
||||
bm.tenant_id AS "tenantId"
|
||||
FROM business_memberships bm
|
||||
JOIN businesses b ON b.id = bm.business_id
|
||||
WHERE bm.user_id = $1
|
||||
AND bm.membership_status = 'ACTIVE'
|
||||
ORDER BY bm.created_at ASC
|
||||
LIMIT 1
|
||||
`,
|
||||
[uid]
|
||||
),
|
||||
query(
|
||||
`
|
||||
SELECT vm.id AS "membershipId",
|
||||
vm.vendor_id AS "vendorId",
|
||||
vm.vendor_role AS role,
|
||||
v.company_name AS "vendorName",
|
||||
v.slug AS "vendorSlug",
|
||||
vm.tenant_id AS "tenantId"
|
||||
FROM vendor_memberships vm
|
||||
JOIN vendors v ON v.id = vm.vendor_id
|
||||
WHERE vm.user_id = $1
|
||||
AND vm.membership_status = 'ACTIVE'
|
||||
ORDER BY vm.created_at ASC
|
||||
LIMIT 1
|
||||
`,
|
||||
[uid]
|
||||
),
|
||||
query(
|
||||
`
|
||||
SELECT s.id AS "staffId",
|
||||
s.tenant_id AS "tenantId",
|
||||
s.full_name AS "fullName",
|
||||
s.email,
|
||||
s.phone,
|
||||
s.primary_role AS "primaryRole",
|
||||
s.onboarding_status AS "onboardingStatus",
|
||||
s.status,
|
||||
s.metadata,
|
||||
w.id AS "workforceId",
|
||||
w.vendor_id AS "vendorId",
|
||||
w.workforce_number AS "workforceNumber"
|
||||
FROM staffs s
|
||||
LEFT JOIN workforce w ON w.staff_id = s.id
|
||||
WHERE s.user_id = $1
|
||||
ORDER BY s.created_at ASC
|
||||
LIMIT 1
|
||||
`,
|
||||
[uid]
|
||||
),
|
||||
]);
|
||||
|
||||
return {
|
||||
user: userResult.rows[0] || null,
|
||||
tenant: tenantResult.rows[0] || null,
|
||||
business: businessResult.rows[0] || null,
|
||||
vendor: vendorResult.rows[0] || null,
|
||||
staff: staffResult.rows[0] || null,
|
||||
};
|
||||
}
|
||||
|
||||
export async function requireClientContext(uid) {
|
||||
const context = await loadActorContext(uid);
|
||||
if (!context.user || !context.tenant || !context.business) {
|
||||
throw new AppError('FORBIDDEN', 'Client business context is required for this route', 403, { uid });
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export async function requireStaffContext(uid) {
|
||||
const context = await loadActorContext(uid);
|
||||
if (!context.user || !context.tenant || !context.staff) {
|
||||
throw new AppError('FORBIDDEN', 'Staff context is required for this route', 403, { uid });
|
||||
}
|
||||
return context;
|
||||
}
|
||||
Reference in New Issue
Block a user