some fixes to the auth and lockouts
This commit is contained in:
parent
a026af5b48
commit
24ce74d61c
@ -43,7 +43,6 @@ const checkTempUserExpiration = async (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const requireAuth = async (req, res, next) => {
|
const requireAuth = async (req, res, next) => {
|
||||||
// Check for both authentication patterns used in your app
|
|
||||||
const isAuthenticated = (req.session && req.session.authenticated) ||
|
const isAuthenticated = (req.session && req.session.authenticated) ||
|
||||||
(req.session && req.session.userId && req.session.userEmail);
|
(req.session && req.session.userId && req.session.userEmail);
|
||||||
|
|
||||||
@ -67,9 +66,22 @@ const requireAuth = async (req, res, next) => {
|
|||||||
logger.warn('Unauthorized access attempt', {
|
logger.warn('Unauthorized access attempt', {
|
||||||
ip: req.ip,
|
ip: req.ip,
|
||||||
path: req.path,
|
path: req.path,
|
||||||
userAgent: req.get('User-Agent')
|
userAgent: req.get('User-Agent'),
|
||||||
|
referer: req.get('Referer'), // Add referer to see where requests come from
|
||||||
|
method: req.method,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check if this is an auto-refresh request
|
||||||
|
if (req.headers['x-requested-with'] === 'XMLHttpRequest' ||
|
||||||
|
req.path.includes('/api/locations')) {
|
||||||
|
return res.status(401).json({
|
||||||
|
authenticated: false,
|
||||||
|
error: 'Session expired',
|
||||||
|
isAutoRefresh: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (req.xhr || req.headers.accept?.indexOf('json') > -1) {
|
if (req.xhr || req.headers.accept?.indexOf('json') > -1) {
|
||||||
res.status(401).json({
|
res.status(401).json({
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
@ -11,8 +11,12 @@ const keyGenerator = (req) => {
|
|||||||
// General API rate limiter
|
// General API rate limiter
|
||||||
const apiLimiter = rateLimit({
|
const apiLimiter = rateLimit({
|
||||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||||
max: 300, // Increased from 100 to 300 to accommodate auto-refresh and multiple users
|
max: 300, // Already increased
|
||||||
keyGenerator,
|
keyGenerator,
|
||||||
|
skip: (req) => {
|
||||||
|
// Skip rate limiting for authenticated users (or increase their limit)
|
||||||
|
return req.session?.authenticated === true;
|
||||||
|
},
|
||||||
standardHeaders: true,
|
standardHeaders: true,
|
||||||
legacyHeaders: false,
|
legacyHeaders: false,
|
||||||
trustProxy: true, // Explicitly trust proxy
|
trustProxy: true, // Explicitly trust proxy
|
||||||
|
|||||||
0
map/app/public/js/admin/auth.js
Normal file
0
map/app/public/js/admin/auth.js
Normal file
0
map/app/public/js/admin/navigation.js
Normal file
0
map/app/public/js/admin/navigation.js
Normal file
0
map/app/public/js/admin/shifts.js
Normal file
0
map/app/public/js/admin/shifts.js
Normal file
0
map/app/public/js/admin/startLocation.js
Normal file
0
map/app/public/js/admin/startLocation.js
Normal file
0
map/app/public/js/admin/users.js
Normal file
0
map/app/public/js/admin/users.js
Normal file
0
map/app/public/js/admin/utils.js
Normal file
0
map/app/public/js/admin/utils.js
Normal file
0
map/app/public/js/admin/walkSheet.js
Normal file
0
map/app/public/js/admin/walkSheet.js
Normal file
@ -96,6 +96,17 @@ function setupAutoRefresh() {
|
|||||||
|
|
||||||
refreshInterval = setInterval(async () => {
|
refreshInterval = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
|
// Check if user is still authenticated
|
||||||
|
const authResponse = await fetch('/api/auth/check');
|
||||||
|
const authData = await authResponse.json();
|
||||||
|
|
||||||
|
if (!authData.authenticated) {
|
||||||
|
// Stop auto-refresh if not authenticated
|
||||||
|
clearInterval(refreshInterval);
|
||||||
|
console.log('Auto-refresh stopped - user not authenticated');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await loadLocations();
|
await loadLocations();
|
||||||
consecutiveErrors = 0; // Reset error count on success
|
consecutiveErrors = 0; // Reset error count on success
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -111,6 +122,17 @@ function setupAutoRefresh() {
|
|||||||
const slowInterval = refreshInterval_ms * 2; // Double the interval
|
const slowInterval = refreshInterval_ms * 2; // Double the interval
|
||||||
refreshInterval = setInterval(async () => {
|
refreshInterval = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
|
// Check if user is still authenticated
|
||||||
|
const authResponse = await fetch('/api/auth/check');
|
||||||
|
const authData = await authResponse.json();
|
||||||
|
|
||||||
|
if (!authData.authenticated) {
|
||||||
|
// Stop auto-refresh if not authenticated
|
||||||
|
clearInterval(refreshInterval);
|
||||||
|
console.log('Auto-refresh stopped - user not authenticated');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await loadLocations();
|
await loadLocations();
|
||||||
consecutiveErrors = 0;
|
consecutiveErrors = 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -126,3 +126,16 @@ export function setViewportDimensions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add session expiry detection
|
||||||
|
function handleAuthError(error) {
|
||||||
|
if (error.status === 401) {
|
||||||
|
// Stop all intervals
|
||||||
|
if (typeof autoRefreshInterval !== 'undefined') {
|
||||||
|
clearInterval(autoRefreshInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to login with expiry message
|
||||||
|
window.location.href = '/login.html?expired=true';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,293 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Listmonk Integration Test Script
|
|
||||||
# This script validates the complete Listmonk integration
|
|
||||||
|
|
||||||
echo "🧪 Starting Listmonk Integration Tests..."
|
|
||||||
echo "======================================="
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
MAP_URL="http://localhost:3000"
|
|
||||||
LISTMONK_URL="http://localhost:9000"
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Test counter
|
|
||||||
TESTS_PASSED=0
|
|
||||||
TESTS_FAILED=0
|
|
||||||
|
|
||||||
# Helper functions
|
|
||||||
pass_test() {
|
|
||||||
echo -e "${GREEN}✅ PASS:${NC} $1"
|
|
||||||
((TESTS_PASSED++))
|
|
||||||
}
|
|
||||||
|
|
||||||
fail_test() {
|
|
||||||
echo -e "${RED}❌ FAIL:${NC} $1"
|
|
||||||
((TESTS_FAILED++))
|
|
||||||
}
|
|
||||||
|
|
||||||
warn_test() {
|
|
||||||
echo -e "${YELLOW}⚠️ WARN:${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
info_test() {
|
|
||||||
echo -e "${BLUE}ℹ️ INFO:${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Test 1: Environment Configuration
|
|
||||||
echo
|
|
||||||
echo "📋 Test 1: Environment Configuration"
|
|
||||||
echo "------------------------------------"
|
|
||||||
|
|
||||||
if grep -q "LISTMONK_ENABLED=true" .env 2>/dev/null; then
|
|
||||||
pass_test "Listmonk is enabled in environment"
|
|
||||||
else
|
|
||||||
fail_test "LISTMONK_ENABLED not set to true in .env"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -q "LISTMONK_URL=" .env 2>/dev/null; then
|
|
||||||
LISTMONK_ENV_URL=$(grep "LISTMONK_URL=" .env | cut -d'=' -f2)
|
|
||||||
pass_test "Listmonk URL configured: $LISTMONK_ENV_URL"
|
|
||||||
else
|
|
||||||
fail_test "LISTMONK_URL not configured in .env"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -q "LISTMONK_USERNAME=" .env 2>/dev/null; then
|
|
||||||
pass_test "Listmonk username configured"
|
|
||||||
else
|
|
||||||
fail_test "LISTMONK_USERNAME not configured in .env"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test 2: Service Connectivity
|
|
||||||
echo
|
|
||||||
echo "🌐 Test 2: Service Connectivity"
|
|
||||||
echo "------------------------------"
|
|
||||||
|
|
||||||
# Test Map application
|
|
||||||
if curl -s "$MAP_URL/health" > /dev/null 2>&1; then
|
|
||||||
pass_test "Map application is accessible at $MAP_URL"
|
|
||||||
else
|
|
||||||
fail_test "Map application not accessible at $MAP_URL"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test Listmonk service
|
|
||||||
if curl -s "$LISTMONK_URL/api/health" > /dev/null 2>&1; then
|
|
||||||
pass_test "Listmonk service is accessible at $LISTMONK_URL"
|
|
||||||
else
|
|
||||||
fail_test "Listmonk service not accessible at $LISTMONK_URL"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test 3: File Structure
|
|
||||||
echo
|
|
||||||
echo "📁 Test 3: Integration File Structure"
|
|
||||||
echo "------------------------------------"
|
|
||||||
|
|
||||||
REQUIRED_FILES=(
|
|
||||||
"app/services/listmonk.js"
|
|
||||||
"app/controllers/listmonkController.js"
|
|
||||||
"app/routes/listmonk.js"
|
|
||||||
"app/public/js/listmonk-status.js"
|
|
||||||
"app/public/js/listmonk-admin.js"
|
|
||||||
"app/public/css/modules/listmonk.css"
|
|
||||||
"listmonk-env-example.txt"
|
|
||||||
"instruct/LISTMONK_INTEGRATION_GUIDE.md"
|
|
||||||
)
|
|
||||||
|
|
||||||
for file in "${REQUIRED_FILES[@]}"; do
|
|
||||||
if [[ -f "$file" ]]; then
|
|
||||||
pass_test "Required file exists: $file"
|
|
||||||
else
|
|
||||||
fail_test "Missing required file: $file"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Test 4: Code Integration Points
|
|
||||||
echo
|
|
||||||
echo "🔗 Test 4: Code Integration Points"
|
|
||||||
echo "---------------------------------"
|
|
||||||
|
|
||||||
# Check if listmonk service is imported in server.js
|
|
||||||
if grep -q "listmonk" app/server.js 2>/dev/null; then
|
|
||||||
pass_test "Listmonk service integrated in server.js"
|
|
||||||
else
|
|
||||||
fail_test "Listmonk service not integrated in server.js"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if real-time sync hooks are in controllers
|
|
||||||
if grep -q "listmonkService" app/controllers/locationsController.js 2>/dev/null; then
|
|
||||||
pass_test "Real-time sync integrated in locations controller"
|
|
||||||
else
|
|
||||||
fail_test "Real-time sync missing from locations controller"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -q "listmonkService" app/controllers/usersController.js 2>/dev/null; then
|
|
||||||
pass_test "Real-time sync integrated in users controller"
|
|
||||||
else
|
|
||||||
fail_test "Real-time sync missing from users controller"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check admin panel integration
|
|
||||||
if grep -q "listmonk" app/public/admin.html 2>/dev/null; then
|
|
||||||
pass_test "Admin panel includes Listmonk section"
|
|
||||||
else
|
|
||||||
fail_test "Admin panel missing Listmonk integration"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check status indicator integration
|
|
||||||
if grep -q "listmonk-status" app/public/index.html 2>/dev/null; then
|
|
||||||
pass_test "Status indicator integrated in main page"
|
|
||||||
else
|
|
||||||
fail_test "Status indicator not integrated in main page"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test 5: API Endpoints (if services are running)
|
|
||||||
echo
|
|
||||||
echo "🔌 Test 5: API Endpoints"
|
|
||||||
echo "-----------------------"
|
|
||||||
|
|
||||||
# Try to test connection endpoint (requires authentication)
|
|
||||||
STATUS_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$MAP_URL/api/listmonk/status" 2>/dev/null)
|
|
||||||
|
|
||||||
if [[ "$STATUS_RESPONSE" == "401" ]]; then
|
|
||||||
pass_test "Listmonk status endpoint exists (requires auth)"
|
|
||||||
elif [[ "$STATUS_RESPONSE" == "200" ]]; then
|
|
||||||
pass_test "Listmonk status endpoint accessible"
|
|
||||||
else
|
|
||||||
warn_test "Listmonk status endpoint response: $STATUS_RESPONSE (may require auth)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test 6: Docker Configuration
|
|
||||||
echo
|
|
||||||
echo "🐳 Test 6: Docker Configuration"
|
|
||||||
echo "------------------------------"
|
|
||||||
|
|
||||||
# Check if services are running
|
|
||||||
if docker-compose ps | grep -q "map-viewer.*Up"; then
|
|
||||||
pass_test "Map application container is running"
|
|
||||||
else
|
|
||||||
fail_test "Map application container not running"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if docker-compose ps | grep -q "listmonk.*Up"; then
|
|
||||||
pass_test "Listmonk container is running"
|
|
||||||
else
|
|
||||||
fail_test "Listmonk container not running"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test 7: Documentation
|
|
||||||
echo
|
|
||||||
echo "📚 Test 7: Documentation"
|
|
||||||
echo "-----------------------"
|
|
||||||
|
|
||||||
if grep -q "Listmonk Integration" README.md 2>/dev/null; then
|
|
||||||
pass_test "README includes Listmonk integration documentation"
|
|
||||||
else
|
|
||||||
fail_test "README missing Listmonk integration documentation"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -q "files-explainer.md" app/ 2>/dev/null && grep -q "listmonk" files-explainer.md 2>/dev/null; then
|
|
||||||
pass_test "Files explainer includes Listmonk files"
|
|
||||||
else
|
|
||||||
warn_test "Files explainer may not include Listmonk files"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test 8: JavaScript Module Tests
|
|
||||||
echo
|
|
||||||
echo "🟨 Test 8: JavaScript Modules"
|
|
||||||
echo "----------------------------"
|
|
||||||
|
|
||||||
# Check for syntax errors in JavaScript files
|
|
||||||
JS_FILES=(
|
|
||||||
"app/public/js/listmonk-status.js"
|
|
||||||
"app/public/js/listmonk-admin.js"
|
|
||||||
)
|
|
||||||
|
|
||||||
for js_file in "${JS_FILES[@]}"; do
|
|
||||||
if [[ -f "$js_file" ]]; then
|
|
||||||
# Basic syntax check (requires node)
|
|
||||||
if command -v node > /dev/null; then
|
|
||||||
if node -c "$js_file" 2>/dev/null; then
|
|
||||||
pass_test "JavaScript syntax valid: $js_file"
|
|
||||||
else
|
|
||||||
fail_test "JavaScript syntax error: $js_file"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
info_test "Node.js not available for syntax checking: $js_file"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Test 9: CSS Validation
|
|
||||||
echo
|
|
||||||
echo "🎨 Test 9: CSS Validation"
|
|
||||||
echo "------------------------"
|
|
||||||
|
|
||||||
if [[ -f "app/public/css/modules/listmonk.css" ]]; then
|
|
||||||
# Check for basic CSS structure
|
|
||||||
if grep -q "@media" app/public/css/modules/listmonk.css; then
|
|
||||||
pass_test "CSS includes responsive design rules"
|
|
||||||
else
|
|
||||||
warn_test "CSS may not include responsive design"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -q "\.listmonk-" app/public/css/modules/listmonk.css; then
|
|
||||||
pass_test "CSS includes Listmonk-specific classes"
|
|
||||||
else
|
|
||||||
fail_test "CSS missing Listmonk-specific styling"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test 10: Log Analysis (if container is running)
|
|
||||||
echo
|
|
||||||
echo "📋 Test 10: Application Logs"
|
|
||||||
echo "---------------------------"
|
|
||||||
|
|
||||||
if docker-compose ps | grep -q "map-viewer.*Up"; then
|
|
||||||
# Check recent logs for Listmonk-related messages
|
|
||||||
RECENT_LOGS=$(docker-compose logs --tail=20 map-viewer 2>/dev/null | grep -i listmonk | head -5)
|
|
||||||
|
|
||||||
if [[ -n "$RECENT_LOGS" ]]; then
|
|
||||||
pass_test "Application logs contain Listmonk activity"
|
|
||||||
info_test "Recent Listmonk log entries:"
|
|
||||||
echo "$RECENT_LOGS" | sed 's/^/ /'
|
|
||||||
else
|
|
||||||
warn_test "No recent Listmonk activity in logs (may be normal)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check for error messages
|
|
||||||
ERROR_LOGS=$(docker-compose logs --tail=50 map-viewer 2>/dev/null | grep -i "error\|fail" | grep -i listmonk | head -3)
|
|
||||||
|
|
||||||
if [[ -n "$ERROR_LOGS" ]]; then
|
|
||||||
fail_test "Found Listmonk-related errors in logs:"
|
|
||||||
echo "$ERROR_LOGS" | sed 's/^/ /'
|
|
||||||
else
|
|
||||||
pass_test "No Listmonk-related errors in recent logs"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
warn_test "Cannot analyze logs - Map application container not running"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
echo
|
|
||||||
echo "📊 Test Summary"
|
|
||||||
echo "=============="
|
|
||||||
echo -e "Tests Passed: ${GREEN}$TESTS_PASSED${NC}"
|
|
||||||
echo -e "Tests Failed: ${RED}$TESTS_FAILED${NC}"
|
|
||||||
echo -e "Total Tests: $((TESTS_PASSED + TESTS_FAILED))"
|
|
||||||
|
|
||||||
if [[ $TESTS_FAILED -eq 0 ]]; then
|
|
||||||
echo -e "${GREEN}🎉 All tests passed! Listmonk integration appears to be working correctly.${NC}"
|
|
||||||
exit 0
|
|
||||||
elif [[ $TESTS_FAILED -le 3 ]]; then
|
|
||||||
echo -e "${YELLOW}⚠️ Most tests passed, but some issues were found. Check the failures above.${NC}"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo -e "${RED}❌ Multiple test failures detected. Please review the integration setup.${NC}"
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
// Test NocoDB API directly to debug the issue
|
|
||||||
const axios = require('axios');
|
|
||||||
require('dotenv').config();
|
|
||||||
|
|
||||||
async function testNocoDB() {
|
|
||||||
const apiUrl = process.env.NOCODB_API_URL;
|
|
||||||
const apiToken = process.env.NOCODB_API_TOKEN;
|
|
||||||
const projectId = process.env.NOCODB_PROJECT_ID || 'pp1ijipzj121aqq';
|
|
||||||
const shiftSignupsSheetId = process.env.NOCODB_SHIFT_SIGNUPS_SHEET || 'mocxv7kzcvyo4aa';
|
|
||||||
|
|
||||||
const client = axios.create({
|
|
||||||
baseURL: apiUrl,
|
|
||||||
timeout: 10000,
|
|
||||||
headers: {
|
|
||||||
'xc-token': apiToken,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test 1: Try to get table info
|
|
||||||
console.log('Test 1: Getting table info...');
|
|
||||||
try {
|
|
||||||
const url = `/db/data/v1/${projectId}/${shiftSignupsSheetId}?limit=1`;
|
|
||||||
console.log('Request URL:', apiUrl + url);
|
|
||||||
const response = await client.get(url);
|
|
||||||
console.log('Table info response:', JSON.stringify(response.data, null, 2));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting table info:', error.response?.data || error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 2: Try to create a minimal record
|
|
||||||
console.log('\nTest 2: Creating minimal record...');
|
|
||||||
try {
|
|
||||||
const testData = {
|
|
||||||
'Shift ID': 1,
|
|
||||||
'User Email': 'test@example.com',
|
|
||||||
'User Name': 'Test User',
|
|
||||||
'Status': 'Confirmed'
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('Test data:', JSON.stringify(testData, null, 2));
|
|
||||||
|
|
||||||
const url = `/db/data/v1/${projectId}/${shiftSignupsSheetId}`;
|
|
||||||
console.log('Create URL:', apiUrl + url);
|
|
||||||
|
|
||||||
const response = await client.post(url, testData);
|
|
||||||
console.log('Create response:', JSON.stringify(response.data, null, 2));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error creating record:');
|
|
||||||
console.error('Status:', error.response?.status);
|
|
||||||
console.error('Data:', JSON.stringify(error.response?.data, null, 2));
|
|
||||||
console.error('Headers:', JSON.stringify(error.response?.headers, null, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 3: Check what table fields exist
|
|
||||||
console.log('\nTest 3: Getting table schema...');
|
|
||||||
try {
|
|
||||||
const schemaUrl = `/db/meta/projects/${projectId}/tables`;
|
|
||||||
const response = await client.get(schemaUrl);
|
|
||||||
const tables = response.data.list || [];
|
|
||||||
const signupsTable = tables.find(t => t.id === shiftSignupsSheetId);
|
|
||||||
if (signupsTable) {
|
|
||||||
console.log('Signups table columns:');
|
|
||||||
signupsTable.columns?.forEach(col => {
|
|
||||||
console.log(` - ${col.title} (${col.column_name}) - ${col.uidt} - PK: ${col.pk}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting schema:', error.response?.data || error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testNocoDB().catch(console.error);
|
|
||||||
Loading…
x
Reference in New Issue
Block a user