Pushing Cuts to repo. Still bugs however decently stable.
This commit is contained in:
363
map/app/controllers/cutsController.js
Normal file
363
map/app/controllers/cutsController.js
Normal file
@@ -0,0 +1,363 @@
|
||||
const nocodbService = require('../services/nocodb');
|
||||
const logger = require('../utils/logger');
|
||||
const config = require('../config');
|
||||
|
||||
class CutsController {
|
||||
/**
|
||||
* Get all cuts - filter by public visibility for non-admins
|
||||
*/
|
||||
async getAll(req, res) {
|
||||
try {
|
||||
// Check if cuts table is configured
|
||||
if (!config.nocodb.cutsSheetId) {
|
||||
// Return empty list if cuts table is not configured
|
||||
return res.json({ list: [] });
|
||||
}
|
||||
|
||||
const { isAdmin } = req.user || {};
|
||||
|
||||
// For NocoDB v2 API, we need to get all records and filter in memory
|
||||
// since the where clause syntax may be different
|
||||
const response = await nocodbService.getAll(
|
||||
config.nocodb.cutsSheetId
|
||||
);
|
||||
|
||||
// Ensure response has list property
|
||||
if (!response || !response.list) {
|
||||
return res.json({ list: [] });
|
||||
}
|
||||
|
||||
// Filter results based on user permissions
|
||||
if (!isAdmin) {
|
||||
response.list = response.list.filter(cut => {
|
||||
const isPublic = cut.is_public || cut.Is_public || cut['Public Visibility'];
|
||||
return isPublic === true || isPublic === 1 || isPublic === '1';
|
||||
});
|
||||
}
|
||||
|
||||
logger.info(`Retrieved ${response.list?.length || 0} cuts for ${isAdmin ? 'admin' : 'user'}`);
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
logger.error('Error fetching cuts:', error);
|
||||
// Log more details about the error
|
||||
if (error.response) {
|
||||
logger.error('Error response:', error.response.data);
|
||||
logger.error('Error status:', error.response.status);
|
||||
}
|
||||
res.status(500).json({
|
||||
error: 'Failed to fetch cuts',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get single cut by ID
|
||||
*/
|
||||
async getById(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { isAdmin } = req.user || {};
|
||||
|
||||
const response = await nocodbService.getById(
|
||||
config.nocodb.cutsSheetId,
|
||||
id
|
||||
);
|
||||
|
||||
if (!response) {
|
||||
return res.status(404).json({ error: 'Cut not found' });
|
||||
}
|
||||
|
||||
// Non-admins can only access public cuts
|
||||
if (!isAdmin) {
|
||||
const isPublic = response.is_public || response.Is_public || response['Public Visibility'];
|
||||
if (!(isPublic === true || isPublic === 1 || isPublic === '1')) {
|
||||
return res.status(403).json({ error: 'Access denied' });
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Retrieved cut: ${response.name} (ID: ${id})`);
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
logger.error('Error fetching cut:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to fetch cut',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new cut - admin only
|
||||
*/
|
||||
async create(req, res) {
|
||||
try {
|
||||
const { isAdmin, email } = req.user || {};
|
||||
|
||||
if (!isAdmin) {
|
||||
return res.status(403).json({ error: 'Admin access required' });
|
||||
}
|
||||
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
color = '#3388ff',
|
||||
opacity = 0.3,
|
||||
category,
|
||||
is_public = false,
|
||||
is_official = false,
|
||||
geojson,
|
||||
bounds
|
||||
} = req.body;
|
||||
|
||||
// Validate required fields
|
||||
if (!name || !geojson) {
|
||||
return res.status(400).json({
|
||||
error: 'Name and geojson are required'
|
||||
});
|
||||
}
|
||||
|
||||
// Validate GeoJSON
|
||||
try {
|
||||
const parsedGeoJSON = JSON.parse(geojson);
|
||||
if (parsedGeoJSON.type !== 'Polygon' && parsedGeoJSON.type !== 'MultiPolygon') {
|
||||
return res.status(400).json({
|
||||
error: 'GeoJSON must be a Polygon or MultiPolygon'
|
||||
});
|
||||
}
|
||||
} catch (parseError) {
|
||||
return res.status(400).json({
|
||||
error: 'Invalid GeoJSON format'
|
||||
});
|
||||
}
|
||||
|
||||
// Validate opacity range
|
||||
if (opacity < 0 || opacity > 1) {
|
||||
return res.status(400).json({
|
||||
error: 'Opacity must be between 0 and 1'
|
||||
});
|
||||
}
|
||||
|
||||
const cutData = {
|
||||
name,
|
||||
description,
|
||||
color,
|
||||
opacity,
|
||||
category,
|
||||
is_public,
|
||||
is_official,
|
||||
geojson,
|
||||
bounds,
|
||||
created_by: email,
|
||||
};
|
||||
|
||||
const response = await nocodbService.create(
|
||||
config.nocodb.cutsSheetId,
|
||||
cutData
|
||||
);
|
||||
|
||||
logger.info(`Created cut: ${name} by ${email}`);
|
||||
res.status(201).json(response);
|
||||
} catch (error) {
|
||||
logger.error('Error creating cut:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to create cut',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update cut - admin only
|
||||
*/
|
||||
async update(req, res) {
|
||||
try {
|
||||
const { isAdmin, email } = req.user || {};
|
||||
const { id } = req.params;
|
||||
|
||||
if (!isAdmin) {
|
||||
return res.status(403).json({ error: 'Admin access required' });
|
||||
}
|
||||
|
||||
// Check if cut exists
|
||||
const existingCut = await nocodbService.getById(
|
||||
config.nocodb.cutsSheetId,
|
||||
id
|
||||
);
|
||||
|
||||
if (!existingCut) {
|
||||
return res.status(404).json({ error: 'Cut not found' });
|
||||
}
|
||||
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
color,
|
||||
opacity,
|
||||
category,
|
||||
is_public,
|
||||
is_official,
|
||||
geojson,
|
||||
bounds
|
||||
} = req.body;
|
||||
|
||||
// Validate GeoJSON if provided
|
||||
if (geojson) {
|
||||
try {
|
||||
const parsedGeoJSON = JSON.parse(geojson);
|
||||
if (parsedGeoJSON.type !== 'Polygon' && parsedGeoJSON.type !== 'MultiPolygon') {
|
||||
return res.status(400).json({
|
||||
error: 'GeoJSON must be a Polygon or MultiPolygon'
|
||||
});
|
||||
}
|
||||
} catch (parseError) {
|
||||
return res.status(400).json({
|
||||
error: 'Invalid GeoJSON format'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Validate opacity if provided
|
||||
if (opacity !== undefined && (opacity < 0 || opacity > 1)) {
|
||||
return res.status(400).json({
|
||||
error: 'Opacity must be between 0 and 1'
|
||||
});
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Only include fields that are provided
|
||||
if (name !== undefined) updateData.name = name;
|
||||
if (description !== undefined) updateData.description = description;
|
||||
if (color !== undefined) updateData.color = color;
|
||||
if (opacity !== undefined) updateData.opacity = opacity;
|
||||
if (category !== undefined) updateData.category = category;
|
||||
if (is_public !== undefined) updateData.is_public = is_public;
|
||||
if (is_official !== undefined) updateData.is_official = is_official;
|
||||
if (geojson !== undefined) updateData.geojson = geojson;
|
||||
if (bounds !== undefined) updateData.bounds = bounds;
|
||||
|
||||
const response = await nocodbService.update(
|
||||
config.nocodb.cutsSheetId,
|
||||
id,
|
||||
updateData
|
||||
);
|
||||
|
||||
logger.info(`Updated cut: ${existingCut.name} (ID: ${id}) by ${email}`);
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
logger.error('Error updating cut:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to update cut',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete cut - admin only
|
||||
*/
|
||||
async delete(req, res) {
|
||||
try {
|
||||
const { isAdmin, email } = req.user || {};
|
||||
const { id } = req.params;
|
||||
|
||||
if (!isAdmin) {
|
||||
return res.status(403).json({ error: 'Admin access required' });
|
||||
}
|
||||
|
||||
// Check if cut exists
|
||||
const existingCut = await nocodbService.getById(
|
||||
config.nocodb.cutsSheetId,
|
||||
id
|
||||
);
|
||||
|
||||
if (!existingCut) {
|
||||
return res.status(404).json({ error: 'Cut not found' });
|
||||
}
|
||||
|
||||
await nocodbService.delete(
|
||||
config.nocodb.cutsSheetId,
|
||||
id
|
||||
);
|
||||
|
||||
logger.info(`Deleted cut: ${existingCut.name} (ID: ${id}) by ${email}`);
|
||||
res.json({ message: 'Cut deleted successfully' });
|
||||
} catch (error) {
|
||||
logger.error('Error deleting cut:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to delete cut',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get public cuts for map display
|
||||
*/
|
||||
async getPublic(req, res) {
|
||||
try {
|
||||
// Check if cuts table is configured
|
||||
if (!config.nocodb.cutsSheetId) {
|
||||
logger.warn('Cuts table not configured - NOCODB_CUTS_SHEET not set');
|
||||
return res.json({ list: [] });
|
||||
}
|
||||
|
||||
logger.info(`Fetching public cuts from table ID: ${config.nocodb.cutsSheetId}`);
|
||||
|
||||
// Use the same pattern as getAll method that's known to work
|
||||
const response = await nocodbService.getAll(
|
||||
config.nocodb.cutsSheetId
|
||||
);
|
||||
|
||||
logger.info(`Raw response from nocodbService.getAll:`, {
|
||||
hasResponse: !!response,
|
||||
hasList: !!(response && response.list),
|
||||
listLength: response?.list?.length || 0,
|
||||
sampleData: response?.list?.[0] || null,
|
||||
allFields: response?.list?.[0] ? Object.keys(response.list[0]) : []
|
||||
});
|
||||
|
||||
// Ensure response has list property
|
||||
if (!response || !response.list) {
|
||||
logger.warn('No cuts found or invalid response structure');
|
||||
return res.json({ list: [] });
|
||||
}
|
||||
|
||||
// Log all cuts before filtering
|
||||
logger.info(`All cuts found: ${response.list.length}`);
|
||||
response.list.forEach((cut, index) => {
|
||||
// Check multiple possible field names for is_public
|
||||
const isPublic = cut.is_public || cut.Is_public || cut['Public Visibility'];
|
||||
logger.info(`Cut ${index}: ${cut.name || cut.Name} - is_public: ${isPublic} (type: ${typeof isPublic})`);
|
||||
logger.info(`Available fields:`, Object.keys(cut));
|
||||
});
|
||||
|
||||
// Filter to only public cuts - handle multiple possible field names
|
||||
const originalCount = response.list.length;
|
||||
response.list = response.list.filter(cut => {
|
||||
const isPublic = cut.is_public || cut.Is_public || cut['Public Visibility'];
|
||||
return isPublic === true || isPublic === 1 || isPublic === '1';
|
||||
});
|
||||
const publicCount = response.list.length;
|
||||
|
||||
logger.info(`Filtered ${originalCount} total cuts to ${publicCount} public cuts`);
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
logger.error('Error fetching public cuts:', error);
|
||||
// Log more details about the error
|
||||
if (error.response) {
|
||||
logger.error('Error response:', error.response.data);
|
||||
logger.error('Error status:', error.response.status);
|
||||
}
|
||||
res.status(500).json({
|
||||
error: 'Failed to fetch public cuts',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new CutsController();
|
||||
Reference in New Issue
Block a user