Sergei 7d9e7e37bf Remove console.log statements and add structured logging
Created a centralized logger utility (src/utils/logger.js) that provides:
- Structured logging with context labels
- Log levels (ERROR, WARN, INFO, DEBUG)
- Environment-based log level control via LOG_LEVEL env variable
- Consistent timestamp and JSON data formatting

Removed console.log/error/warn statements from:
- All service files (mqtt, notifications, legacyAPI, email, storage, subscription-sync)
- All route handlers (auth, beneficiaries, deployments, webhook, admin, etc)
- Controllers (dashboard, auth, alarm)
- Database connection handler
- Main server file (index.js)

Preserved:
- Critical startup validation error for JWT_SECRET in index.js

Benefits:
- Production-ready logging that can be easily integrated with log aggregation services
- Reduced noise in production logs
- Easier debugging with structured context and data
- Configurable log levels per environment

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-29 11:58:06 -08:00

825 lines
21 KiB
JavaScript

const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const { supabase } = require('../config/supabase');
const Stripe = require('stripe');
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
// JWT-based admin auth middleware
// Verifies Bearer token and checks user role is 'admin'
const adminAuth = async (req, res, next) => {
try {
// Check for Bearer token
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Unauthorized' });
}
const token = authHeader.split(' ')[1];
// Verify JWT token
let decoded;
try {
decoded = jwt.verify(token, process.env.JWT_SECRET);
} catch (err) {
return res.status(401).json({ error: 'Invalid token' });
}
// Get user from database and check role
const { data: user, error } = await supabase
.from('users')
.select('id, email, role')
.eq('id', decoded.userId)
.single();
if (error || !user) {
return res.status(401).json({ error: 'User not found' });
}
// Check if user has admin role
if (user.role !== 'admin') {
return res.status(403).json({ error: 'Access denied. Admin only.' });
}
// Attach user to request
req.user = user;
next();
} catch (error) {
return res.status(401).json({ error: 'Unauthorized' });
}
};
// Apply admin auth to all routes
router.use(adminAuth);
// ============ DASHBOARD STATS ============
/**
* GET /api/admin/stats
* Dashboard statistics
*/
router.get('/stats', async (req, res) => {
try {
// Get order counts by status
const { data: orders, error: ordersError } = await supabase
.from('orders')
.select('status, created_at');
if (ordersError) throw ordersError;
const today = new Date();
today.setHours(0, 0, 0, 0);
const stats = {
orders: {
total: orders.length,
today: orders.filter(o => new Date(o.created_at) >= today).length,
byStatus: {
paid: orders.filter(o => o.status === 'paid').length,
preparing: orders.filter(o => o.status === 'preparing').length,
shipped: orders.filter(o => o.status === 'shipped').length,
delivered: orders.filter(o => o.status === 'delivered').length,
installed: orders.filter(o => o.status === 'installed').length
}
}
};
// Subscription stats are derived from Stripe (no local DB storage).
// Get beneficiary stats
const { data: beneficiaries } = await supabase
.from('beneficiaries')
.select('status');
if (beneficiaries) {
stats.beneficiaries = {
total: beneficiaries.length,
active: beneficiaries.filter(b => b.status === 'active').length,
awaitingSensors: beneficiaries.filter(b => b.status === 'awaiting_sensors').length
};
}
res.json(stats);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// ============ ORDERS ============
/**
* POST /api/admin/orders
* Create a new order manually
*/
router.post('/orders', async (req, res) => {
try {
const {
user_id,
customer_email,
customer_name,
shipping_address,
total_amount,
status,
notes
} = req.body;
// Generate order number
const orderNumber = `ORD-${Date.now().toString(36).toUpperCase()}`;
const { data: order, error } = await supabase
.from('orders')
.insert({
user_id: user_id || null,
customer_email: customer_email || null,
customer_name: customer_name || null,
shipping_address,
total_amount: total_amount || 29900,
status: status || 'paid',
order_number: orderNumber,
notes: notes || null,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
})
.select()
.single();
if (error) throw error;
res.json(order);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/admin/orders
* List orders with pagination (optimized)
*/
router.get('/orders', async (req, res) => {
try {
const { status } = req.query;
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 50;
const offset = (page - 1) * limit;
let query = supabase
.from('orders')
.select('*', { count: 'exact' })
.order('created_at', { ascending: false })
.range(offset, offset + limit - 1);
if (status) {
query = query.eq('status', status);
}
const { data: orders, error, count: totalCount } = await query;
if (error) throw error;
// Get all user IDs from orders
const userIds = [...new Set((orders || []).map(o => o.user_id).filter(Boolean))];
// Single query to get all users
let usersMap = {};
if (userIds.length > 0) {
const { data: users } = await supabase
.from('users')
.select('id, email')
.in('id', userIds);
usersMap = (users || []).reduce((acc, u) => {
acc[u.id] = u;
return acc;
}, {});
}
// Combine data
const result = (orders || []).map(order => ({
...order,
user: usersMap[order.user_id] || null
}));
res.json({
orders: result,
pagination: {
page,
limit,
total: totalCount || 0,
pages: Math.ceil((totalCount || 0) / limit)
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/admin/orders/:id
* Get single order details
*/
router.get('/orders/:id', async (req, res) => {
try {
const { data: order, error } = await supabase
.from('orders')
.select('*')
.eq('id', req.params.id)
.single();
if (error) throw error;
if (!order) return res.status(404).json({ error: 'Order not found' });
// Get related data
let beneficiary = null;
if (order.beneficiary_id) {
const { data: b } = await supabase
.from('users')
.select('*')
.eq('id', order.beneficiary_id)
.single();
beneficiary = b;
}
res.json({ ...order, beneficiary });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
/**
* PATCH /api/admin/orders/:id
* Update order status, add tracking, etc.
*/
router.patch('/orders/:id', async (req, res) => {
try {
const { status, tracking_number, carrier, estimated_delivery } = req.body;
const updates = { updated_at: new Date().toISOString() };
if (status) {
updates.status = status;
// Set timestamps based on status
if (status === 'shipped') {
updates.shipped_at = new Date().toISOString();
} else if (status === 'delivered') {
updates.delivered_at = new Date().toISOString();
}
}
if (tracking_number) updates.tracking_number = tracking_number;
if (carrier) updates.carrier = carrier;
if (estimated_delivery) updates.estimated_delivery = estimated_delivery;
const { data, error } = await supabase
.from('orders')
.update(updates)
.eq('id', req.params.id)
.select()
.single();
if (error) throw error;
// TODO: Send email notification when status changes
if (status === 'shipped') {
} else if (status === 'delivered') {
}
res.json(data);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// ============ USERS ============
/**
* GET /api/admin/users
* List users with pagination and relationships (optimized)
*/
router.get('/users', async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 50;
const offset = (page - 1) * limit;
const filter = req.query.filter; // 'beneficiaries', 'caretakers', 'admins'
// Get all user_access relationships first (for filtering)
const { data: accessRecords } = await supabase
.from('user_access')
.select('beneficiary_id, accessor_id, role');
// Create lookup sets
const beneficiaryIds = new Set((accessRecords || []).map(a => a.beneficiary_id));
const caretakerIds = new Set((accessRecords || []).map(a => a.accessor_id));
// Build query
let query = supabase
.from('users')
.select('*', { count: 'exact' })
.order('created_at', { ascending: false });
// Apply filter if needed
if (filter === 'admins') {
query = query.eq('role', 'admin');
}
// Get users
const { data: allUsers, error, count: totalCount } = await query;
if (error) throw error;
// Apply client-side filtering for beneficiaries/caretakers
let filteredUsers = allUsers || [];
if (filter === 'beneficiaries') {
filteredUsers = filteredUsers.filter(u => beneficiaryIds.has(u.id));
} else if (filter === 'caretakers') {
filteredUsers = filteredUsers.filter(u => caretakerIds.has(u.id));
}
// Apply pagination after filtering
const paginatedUsers = filteredUsers.slice(offset, offset + limit);
// Create user email lookup
const userEmailMap = (allUsers || []).reduce((acc, u) => {
acc[u.id] = u.email;
return acc;
}, {});
// Enrich paginated users with relationship info
const enrichedUsers = paginatedUsers.map(user => {
const watches = (accessRecords || [])
.filter(a => a.accessor_id === user.id)
.map(a => ({ ...a, beneficiary_email: userEmailMap[a.beneficiary_id] }));
const watchedBy = (accessRecords || [])
.filter(a => a.beneficiary_id === user.id)
.map(a => ({ ...a, accessor_email: userEmailMap[a.accessor_id] }));
return {
...user,
watches,
watched_by: watchedBy,
is_beneficiary: watchedBy.length > 0,
is_caretaker: watches.length > 0,
};
});
res.json({
users: enrichedUsers,
pagination: {
page,
limit,
total: filteredUsers.length,
pages: Math.ceil(filteredUsers.length / limit)
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/admin/users/:id
* Get user with full details: relationships, devices, orders, subscriptions
*/
router.get('/users/:id', async (req, res) => {
try {
const { data: user, error: userError } = await supabase
.from('users')
.select('*')
.eq('id', req.params.id)
.single();
if (userError) throw userError;
if (!user) return res.status(404).json({ error: 'User not found' });
// Get all users for email lookup
const { data: allUsers } = await supabase
.from('users')
.select('id, email, first_name, last_name');
// Get relationships: who this user watches
const { data: watchesAccess } = await supabase
.from('user_access')
.select('*')
.eq('accessor_id', req.params.id);
const watches = (watchesAccess || []).map(a => {
const beneficiary = allUsers?.find(u => u.id === a.beneficiary_id);
return { ...a, user: beneficiary };
});
// Get relationships: who watches this user
const { data: watchedByAccess } = await supabase
.from('user_access')
.select('*')
.eq('beneficiary_id', req.params.id);
const watched_by = (watchedByAccess || []).map(a => {
const accessor = allUsers?.find(u => u.id === a.accessor_id);
return { ...a, user: accessor };
});
// Get deployments where user is owner
const { data: deployments } = await supabase
.from('deployments')
.select('*')
.eq('owner_user_id', req.params.id);
// Get devices from user's deployments
let devices = [];
if (deployments && deployments.length > 0) {
const deploymentIds = deployments.map(d => d.deployment_id);
const { data: devs } = await supabase
.from('devices')
.select('*')
.in('well_id', deploymentIds);
devices = devs || [];
}
// Get orders
const { data: orders } = await supabase
.from('orders')
.select('*')
.eq('user_id', req.params.id)
.order('created_at', { ascending: false });
// Get subscriptions
const subscriptions = [];
res.json({
...user,
watches,
watched_by,
is_beneficiary: watched_by.length > 0,
is_caretaker: watches.length > 0,
deployments: deployments || [],
devices,
orders: orders || [],
subscriptions
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// ============ SUBSCRIPTIONS ============
/**
* GET /api/admin/subscriptions
* List all subscriptions with pagination (optimized)
*/
router.get('/subscriptions', async (req, res) => {
try {
res.json({
subscriptions: [],
pagination: {
page: 1,
limit: 0,
total: 0,
pages: 0
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// ============ REFUNDS ============
/**
* POST /api/admin/refund
* Process a refund via Stripe
*/
router.post('/refund', async (req, res) => {
try {
const { orderId, amount, reason } = req.body;
// Get order
const { data: order, error: orderError } = await supabase
.from('orders')
.select('*')
.eq('id', orderId)
.single();
if (orderError || !order) {
return res.status(404).json({ error: 'Order not found' });
}
// Get payment intent from Stripe session
const session = await stripe.checkout.sessions.retrieve(order.stripe_session_id);
const paymentIntentId = session.payment_intent;
// Create refund
const refund = await stripe.refunds.create({
payment_intent: paymentIntentId,
amount: amount ? amount : undefined, // Full refund if no amount specified
reason: reason || 'requested_by_customer'
});
// Update order status
await supabase
.from('orders')
.update({
status: 'canceled',
updated_at: new Date().toISOString()
})
.eq('id', orderId);
res.json({
success: true,
refund: {
id: refund.id,
amount: refund.amount / 100,
status: refund.status
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// ============ DEPLOYMENTS ============
/**
* GET /api/admin/deployments
* List deployments with pagination (optimized - single queries)
*/
router.get('/deployments', async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 50;
const offset = (page - 1) * limit;
// Get deployments with count
const { data: deployments, error, count: totalCount } = await supabase
.from('deployments')
.select('*', { count: 'exact' })
.order('deployment_id', { ascending: false })
.range(offset, offset + limit - 1);
if (error) throw error;
// Get all owner IDs
const ownerIds = [...new Set((deployments || []).map(d => d.owner_user_id).filter(Boolean))];
// Single query to get all owners
let ownersMap = {};
if (ownerIds.length > 0) {
const { data: owners } = await supabase
.from('users')
.select('id, email, first_name, last_name')
.in('id', ownerIds);
ownersMap = (owners || []).reduce((acc, u) => {
acc[u.id] = u;
return acc;
}, {});
}
// Get deployment IDs
const deploymentIds = (deployments || []).map(d => d.deployment_id);
// Single query to get device counts
let deviceCounts = {};
if (deploymentIds.length > 0) {
const { data: devices } = await supabase
.from('devices')
.select('well_id')
.in('well_id', deploymentIds);
// Count devices per deployment
(devices || []).forEach(d => {
deviceCounts[d.well_id] = (deviceCounts[d.well_id] || 0) + 1;
});
}
// Combine data
const result = (deployments || []).map(dep => ({
...dep,
owner: ownersMap[dep.owner_user_id] || null,
device_count: deviceCounts[dep.deployment_id] || 0
}));
res.json({
deployments: result,
pagination: {
page,
limit,
total: totalCount || 0,
pages: Math.ceil((totalCount || 0) / limit)
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/admin/deployments/:id
* Get single deployment with devices, users with access, events
*/
router.get('/deployments/:id', async (req, res) => {
try {
const { data: deployment, error } = await supabase
.from('deployments')
.select('*')
.eq('deployment_id', req.params.id)
.single();
if (error) throw error;
if (!deployment) return res.status(404).json({ error: 'Deployment not found' });
// Get devices for this deployment
const { data: devices } = await supabase
.from('devices')
.select('*')
.eq('well_id', deployment.deployment_id);
// Get owner info
let owner = null;
if (deployment.owner_user_id) {
const { data: u } = await supabase
.from('users')
.select('id, email, first_name, last_name, phone')
.eq('id', deployment.owner_user_id)
.single();
owner = u;
}
// Get all users with access to this deployment's beneficiary
// First find beneficiary (owner is also beneficiary in most cases)
let users_with_access = [];
if (deployment.owner_user_id) {
const { data: accessRecords } = await supabase
.from('user_access')
.select('*')
.eq('beneficiary_id', deployment.owner_user_id);
if (accessRecords && accessRecords.length > 0) {
const accessorIds = accessRecords.map(a => a.accessor_id);
const { data: accessors } = await supabase
.from('users')
.select('id, email, first_name, last_name')
.in('id', accessorIds);
users_with_access = accessRecords.map(a => {
const user = accessors?.find(u => u.id === a.accessor_id);
return { ...a, user };
});
}
}
// Get recent events/alerts for this deployment (if events table exists)
let events = [];
try {
const { data: eventsData } = await supabase
.from('events')
.select('*')
.eq('deployment_id', deployment.deployment_id)
.order('created_at', { ascending: false })
.limit(20);
events = eventsData || [];
} catch (e) {
// events table might not exist
}
res.json({
...deployment,
devices: devices || [],
owner,
users_with_access,
events
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// ============ DEVICES ============
/**
* GET /api/admin/devices
* List all devices
*/
router.get('/devices', async (req, res) => {
try {
const { well_id } = req.query;
let query = supabase
.from('devices')
.select('*')
.order('device_id', { ascending: false });
if (well_id) {
query = query.eq('well_id', parseInt(well_id));
}
const { data: devices, error } = await query;
if (error) throw error;
res.json({ devices: devices || [] });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/admin/devices/:id
* Get single device
*/
router.get('/devices/:id', async (req, res) => {
try {
const { data: device, error } = await supabase
.from('devices')
.select('*')
.eq('device_id', req.params.id)
.single();
if (error) throw error;
if (!device) return res.status(404).json({ error: 'Device not found' });
// Get deployment info if exists
let deployment = null;
if (device.well_id) {
const { data: d } = await supabase
.from('deployments')
.select('*')
.eq('deployment_id', device.well_id)
.single();
deployment = d;
}
res.json({ ...device, deployment });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// ============ BENEFICIARIES ============
/**
* GET /api/admin/beneficiaries
* List all beneficiaries (optimized)
*/
router.get('/beneficiaries', async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 50;
const offset = (page - 1) * limit;
// Get all users who are beneficiaries (have user_access records where they are beneficiary_id)
const { data: accessRecords, error } = await supabase
.from('user_access')
.select('beneficiary_id')
.order('granted_at', { ascending: false });
if (error) throw error;
// Get unique beneficiary IDs
const beneficiaryIds = [...new Set((accessRecords || []).map(a => a.beneficiary_id))];
// Single query to get all beneficiary users
let beneficiaries = [];
if (beneficiaryIds.length > 0) {
const { data: users } = await supabase
.from('users')
.select('*')
.in('id', beneficiaryIds);
beneficiaries = users || [];
}
// Apply pagination
const paginatedBeneficiaries = beneficiaries.slice(offset, offset + limit);
res.json({
beneficiaries: paginatedBeneficiaries,
pagination: {
page,
limit,
total: beneficiaries.length,
pages: Math.ceil(beneficiaries.length / limit)
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;