From 9c453bfe07f5af0de229915efbfc7694d966460a Mon Sep 17 00:00:00 2001 From: Chandini Date: Mon, 15 Sep 2025 14:55:50 +0530 Subject: [PATCH] backend changes --- Jenkinsfile | 1 + docker-compose.yml | 112 +++++----- .../src/models/custom_feature.js | 59 ++++-- services/template-manager/src/routes/admin.js | 200 ++++++++++++++++++ 4 files changed, 294 insertions(+), 78 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 00700c3..077eb94 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -196,6 +196,7 @@ pipeline { docker compose ps ' """ + } } } diff --git a/docker-compose.yml b/docker-compose.yml index e76a2d3..3e99e69 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: container_name: pipeline_postgres environment: POSTGRES_USER: pipeline_admin - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_PASSWORD: secure_pipeline_2024 POSTGRES_DB: dev_pipeline volumes: - postgres_data:/var/lib/postgresql/data @@ -26,7 +26,7 @@ services: redis: image: redis:7-alpine container_name: pipeline_redis - command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD} + command: redis-server --appendonly yes --requirepass redis_secure_2024 volumes: - redis_data:/data ports: @@ -45,8 +45,8 @@ services: image: mongo:7 container_name: pipeline_mongodb environment: - MONGO_INITDB_ROOT_USERNAME: pipeline_user - MONGO_INITDB_ROOT_PASSWORD: ${MONGODB_PASSWORD} + MONGO_INITDB_ROOT_USERNAME: pipeline_admin + MONGO_INITDB_ROOT_PASSWORD: mongo_secure_2024 volumes: - mongodb_data:/data/db ports: @@ -62,7 +62,7 @@ services: container_name: pipeline_rabbitmq environment: RABBITMQ_DEFAULT_USER: pipeline_admin - RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD} + RABBITMQ_DEFAULT_PASS: rabbit_secure_2024 volumes: - rabbitmq_data:/var/lib/rabbitmq - rabbitmq_logs:/var/log/rabbitmq @@ -93,12 +93,12 @@ services: - POSTGRES_PORT=5432 - POSTGRES_DB=dev_pipeline - POSTGRES_USER=pipeline_admin - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_PASSWORD=secure_pipeline_2024 - REDIS_HOST=redis - REDIS_PORT=6379 - - REDIS_PASSWORD=${REDIS_PASSWORD} + - REDIS_PASSWORD=redis_secure_2024 - NODE_ENV=development - - DATABASE_URL=postgresql://pipeline_admin:${POSTGRES_PASSWORD}@postgres:5432/dev_pipeline + - DATABASE_URL=postgresql://pipeline_admin:secure_pipeline_2024@postgres:5432/dev_pipeline entrypoint: ["/bin/sh", "-c", "chmod +x ./scripts/migrate-all.sh && ./scripts/migrate-all.sh"] depends_on: postgres: @@ -240,20 +240,20 @@ services: - POSTGRES_PORT=5432 - POSTGRES_DB=dev_pipeline - POSTGRES_USER=pipeline_admin - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_PASSWORD=secure_pipeline_2024 # Cache and message queue - REDIS_HOST=redis - REDIS_PORT=6379 - - REDIS_PASSWORD=${REDIS_PASSWORD} + - REDIS_PASSWORD=redis_secure_2024 - RABBITMQ_HOST=rabbitmq - RABBITMQ_PORT=5672 - RABBITMQ_USER=pipeline_admin - - RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD} + - RABBITMQ_PASSWORD=rabbit_secure_2024 # JWT configuration - JWT_ACCESS_SECRET=access-secret-key-2024-tech4biz-secure_pipeline_2024 - - JWT_SECRET=access-secret-key-2024-tech4biz-secure_pipeline_2024 - # - JWT_ACCESS_SECRET=access-secret-key-2024-tech4biz-${POSTGRES_PASSWORD} - # - JWT_REFRESH_SECRET=refresh-secret-key-2024-tech4biz-${POSTGRES_PASSWORD} + - JWT_SECRET=ultra_secure_jwt_secret_2024 + # - JWT_ACCESS_SECRET=access-secret-key-2024-tech4biz-secure_pipeline_2024 + # - JWT_REFRESH_SECRET=refresh-secret-key-2024-tech4biz-secure_pipeline_2024 # Service URLs - USER_AUTH_URL=http://user-auth:8011 - TEMPLATE_MANAGER_URL=http://template-manager:8009 @@ -299,10 +299,10 @@ services: - POSTGRES_PORT=5432 - POSTGRES_DB=dev_pipeline - POSTGRES_USER=pipeline_admin - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_PASSWORD=secure_pipeline_2024 - REDIS_HOST=redis - REDIS_PORT=6379 - - REDIS_PASSWORD=${REDIS_PASSWORD} + - REDIS_PASSWORD=redis_secure_2024 - MONGODB_HOST=mongodb - MONGODB_PORT=27017 - NEO4J_URI=bolt://neo4j:7687 @@ -310,7 +310,7 @@ services: - NEO4J_PASSWORD=password - CHROMA_HOST=chromadb - CHROMA_PORT=8000 - - REDIS_URL=redis://redis:6379 + - REDIS_URL=redis://:redis_secure_2024@redis:6379 networks: - pipeline_network depends_on: @@ -333,10 +333,10 @@ services: - POSTGRES_PORT=5432 - POSTGRES_DB=dev_pipeline - POSTGRES_USER=pipeline_admin - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_PASSWORD=secure_pipeline_2024 - REDIS_HOST=redis - REDIS_PORT=6379 - - REDIS_PASSWORD=${REDIS_PASSWORD} + - REDIS_PASSWORD=redis_secure_2024 networks: - pipeline_network depends_on: @@ -355,13 +355,13 @@ services: environment: - PORT=8003 - HOST=0.0.0.0 - - CLAUDE_API_KEY=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 - - ANTHROPIC_API_KEY=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 + - CLAUDE_API_KEY=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 + - ANTHROPIC_API_KEY=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 - POSTGRES_HOST=postgres - POSTGRES_PORT=5432 - POSTGRES_DB=dev_pipeline - POSTGRES_USER=pipeline_admin - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_PASSWORD=secure_pipeline_2024 - MONGODB_HOST=mongodb - MONGODB_PORT=27017 networks: @@ -391,13 +391,13 @@ services: - POSTGRES_PORT=5432 - POSTGRES_DB=dev_pipeline - POSTGRES_USER=pipeline_admin - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_PASSWORD=secure_pipeline_2024 - MONGODB_HOST=mongodb - MONGODB_PORT=27017 - REDIS_HOST=redis - REDIS_PORT=6379 - - REDIS_PASSWORD=${REDIS_PASSWORD} - - CLAUDE_API_KEY=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 + - REDIS_PASSWORD=redis_secure_2024 + - CLAUDE_API_KEY=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 - OPENAI_API_KEY=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 # - NEO4J_URI=bolt://neo4j:7687 # - NEO4J_USER=neo4j @@ -440,10 +440,10 @@ services: - POSTGRES_PORT=5432 - POSTGRES_DB=dev_pipeline - POSTGRES_USER=pipeline_admin - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_PASSWORD=secure_pipeline_2024 - REDIS_HOST=redis - REDIS_PORT=6379 - - REDIS_PASSWORD=${REDIS_PASSWORD} + - REDIS_PASSWORD=redis_secure_2024 networks: - pipeline_network depends_on: @@ -464,13 +464,13 @@ services: - POSTGRES_PORT=5432 - POSTGRES_DB=dev_pipeline - POSTGRES_USER=pipeline_admin - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_PASSWORD=secure_pipeline_2024 - MONGODB_HOST=mongodb - MONGODB_PORT=27017 - RABBITMQ_HOST=rabbitmq - RABBITMQ_PORT=5672 - RABBITMQ_USER=pipeline_admin - - RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD} + - RABBITMQ_PASSWORD=rabbit_secure_2024 networks: - pipeline_network depends_on: @@ -496,25 +496,25 @@ services: - POSTGRES_PORT=5432 - POSTGRES_DB=dev_pipeline - POSTGRES_USER=pipeline_admin - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_PASSWORD=secure_pipeline_2024 - REDIS_HOST=redis - REDIS_PORT=6379 - - REDIS_PASSWORD=${REDIS_PASSWORD} + - REDIS_PASSWORD=redis_secure_2024 - JWT_ACCESS_SECRET=access-secret-key-2024-tech4biz-secure_pipeline_2024 - - JWT_REFRESH_SECRET=refresh-secret-key-2024-tech4biz-${POSTGRES_PASSWORD} + - JWT_REFRESH_SECRET=refresh-secret-key-2024-tech4biz-secure_pipeline_2024 - JWT_ACCESS_EXPIRY=24h - JWT_ADMIN_ACCESS_EXPIRY=7d - JWT_REFRESH_EXPIRY=7d - FRONTEND_URL=* # Email Configuration - - SMTP_HOST=${SMTP_HOST:-smtp.gmail.com} - - SMTP_PORT=${SMTP_PORT:-587} - - SMTP_SECURE=${SMTP_SECURE:-false} - - SMTP_USER=${SMTP_USER:-frontendtechbiz@gmail.com} - - SMTP_PASS=${SMTP_PASS:-oidhhjeasgzbqptq} - - SMTP_FROM=${SMTP_FROM:-frontendtechbiz@gmail.com} - - GMAIL_USER=${GMAIL_USER:-frontendtechbiz@gmail.com} - - GMAIL_APP_PASSWORD=${GMAIL_APP_PASSWORD:-oidhhjeasgzbqptq} + - SMTP_HOST=smtp.gmail.com + - SMTP_PORT=587 + - SMTP_SECURE=false + - SMTP_USER=frontendtechbiz@gmail.com + - SMTP_PASS=oidhhjeasgzbqptq + - SMTP_FROM=frontendtechbiz@gmail.com + - GMAIL_USER=frontendtechbiz@gmail.com + - GMAIL_APP_PASSWORD=oidhhjeasgzbqptq - AUTH_PUBLIC_URL=* - TEMPLATE_MANAGER_URL=http://template-manager:8009 networks: @@ -551,10 +551,10 @@ services: - POSTGRES_PORT=5432 - POSTGRES_DB=dev_pipeline - POSTGRES_USER=pipeline_admin - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_PASSWORD=secure_pipeline_2024 - REDIS_HOST=redis - REDIS_PORT=6379 - - REDIS_PASSWORD=${REDIS_PASSWORD} + - REDIS_PASSWORD=redis_secure_2024 - NODE_ENV=development - JWT_ACCESS_SECRET=access-secret-key-2024-tech4biz-secure_pipeline_2024 networks: @@ -584,16 +584,16 @@ services: environment: - PORT=8021 - HOST=0.0.0.0 - - CLAUDE_API_KEY=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 + - CLAUDE_API_KEY=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 - POSTGRES_HOST=postgres - POSTGRES_PORT=5432 - POSTGRES_DB=dev_pipeline - POSTGRES_USER=pipeline_admin - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_PASSWORD=secure_pipeline_2024 - REDIS_HOST=redis - REDIS_PORT=6379 - - REDIS_PASSWORD=${REDIS_PASSWORD} - - JWT_ACCESS_SECRET=access-secret-key-2024-tech4biz-${POSTGRES_PASSWORD} + - REDIS_PASSWORD=redis_secure_2024 + - JWT_ACCESS_SECRET=access-secret-key-2024-tech4biz-secure_pipeline_2024 - USER_AUTH_SERVICE_URL=http://user-auth:8011 - FLASK_ENV=development networks: @@ -622,10 +622,10 @@ services: - POSTGRES_PORT=5432 - POSTGRES_DB=dev_pipeline - POSTGRES_USER=pipeline_admin - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_PASSWORD=secure_pipeline_2024 - REDIS_HOST=redis - REDIS_PORT=6379 - - REDIS_PASSWORD=${REDIS_PASSWORD} + - REDIS_PASSWORD=redis_secure_2024 - NODE_ENV=development - GITHUB_REDIRECT_URI=* - ATTACHED_REPOS_DIR=/tmp/attached-repos @@ -657,9 +657,9 @@ services: environment: - PORT=8007 - HOST=0.0.0.0 - - DATABASE_URL=postgresql://pipeline_admin:${POSTGRES_PASSWORD}@postgres:5432/dev_pipeline - - CLAUDE_API_KEY=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 - - REDIS_URL=redis://pipeline_redis:6379 + - DATABASE_URL=postgresql://pipeline_admin:secure_pipeline_2024@postgres:5432/dev_pipeline + - CLAUDE_API_KEY=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 + - REDIS_URL=redis://:redis_secure_2024@pipeline_redis:6379 - SERVICE_PORT=8007 - LOG_LEVEL=INFO - DEFAULT_TARGET_QUALITY=0.85 @@ -697,8 +697,8 @@ services: environment: - NODE_ENV=production - PORT=8008 - - DATABASE_URL=postgresql://pipeline_admin:${POSTGRES_PASSWORD}@postgres:5432/dev_pipeline - - REDIS_URL=redis://pipeline_redis:6379 + - DATABASE_URL=postgresql://pipeline_admin:secure_pipeline_2024@postgres:5432/dev_pipeline + - REDIS_URL=redis://:redis_secure_2024@pipeline_redis:6379 - API_GATEWAY_URL=http://pipeline_api_gateway:8000 - CODE_GENERATOR_URL=http://pipeline_code_generator:8004 - SELF_IMPROVING_URL=http://pipeline_self_improving_generator:8007 @@ -738,8 +738,8 @@ services: - "5678:5678" environment: - N8N_BASIC_AUTH_ACTIVE=true - - N8N_BASIC_AUTH_USER=pipeline_admin - - N8N_BASIC_AUTH_PASSWORD=pipeline_n8n_2024 + - N8N_BASIC_AUTH_USER=admin + - N8N_BASIC_AUTH_PASSWORD=admin_n8n_2024 - N8N_HOST=localhost - N8N_PORT=5678 - N8N_PROTOCOL=http @@ -750,7 +750,7 @@ services: - DB_POSTGRESDB_PORT=5432 - DB_POSTGRESDB_DATABASE=n8n - DB_POSTGRESDB_USER=pipeline_admin - - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD} + - DB_POSTGRESDB_PASSWORD=secure_pipeline_2024 volumes: - n8n_data:/home/node/.n8n - ./orchestration/n8n/workflows:/home/node/.n8n/workflows diff --git a/services/template-manager/src/models/custom_feature.js b/services/template-manager/src/models/custom_feature.js index 67c7066..39a0a97 100644 --- a/services/template-manager/src/models/custom_feature.js +++ b/services/template-manager/src/models/custom_feature.js @@ -230,31 +230,46 @@ class CustomFeature { const updated = await CustomFeature.update(id, updates); // If approved, ensure a mirrored entry exists/updates in template_features + // Only mirror if the template_id exists in the main templates table if (updated && status === 'approved') { try { - const Feature = require('./feature'); - const featureId = `custom_${updated.id}`; - const existingMirror = await Feature.getByFeatureId(updated.template_id, featureId); - if (existingMirror) { - await Feature.update(existingMirror.id, { - name: updated.name, - description: updated.description, - complexity: updated.complexity, - feature_type: 'custom', - is_default: false - }); + // Check if template_id exists in main templates table + const templateCheck = await database.query( + 'SELECT id FROM templates WHERE id = $1 AND is_active = true', + [updated.template_id] + ); + + if (templateCheck.rows.length > 0) { + // Template exists in main templates table, safe to mirror + const Feature = require('./feature'); + const featureId = `custom_${updated.id}`; + const existingMirror = await Feature.getByFeatureId(updated.template_id, featureId); + if (existingMirror) { + await Feature.update(existingMirror.id, { + name: updated.name, + description: updated.description, + complexity: updated.complexity, + feature_type: 'custom', + is_default: false + }); + console.log('✅ Updated mirrored feature in template_features for approved custom feature'); + } else { + await Feature.create({ + template_id: updated.template_id, + feature_id: featureId, + name: updated.name, + description: updated.description, + feature_type: 'custom', + complexity: updated.complexity, + display_order: 999, + is_default: false, + created_by_user: true + }); + console.log('✅ Created mirrored feature in template_features for approved custom feature'); + } } else { - await Feature.create({ - template_id: updated.template_id, - feature_id: featureId, - name: updated.name, - description: updated.description, - feature_type: 'custom', - complexity: updated.complexity, - display_order: 999, - is_default: false, - created_by_user: true - }); + // Template is likely a custom template, don't mirror to template_features + console.log('ℹ️ Custom feature approved but template_id references custom template, skipping mirror to template_features'); } } catch (mirrorErr) { console.error('⚠️ Failed to mirror approved custom feature into template_features:', mirrorErr.message); diff --git a/services/template-manager/src/routes/admin.js b/services/template-manager/src/routes/admin.js index 1ce8c74..6a97433 100644 --- a/services/template-manager/src/routes/admin.js +++ b/services/template-manager/src/routes/admin.js @@ -48,6 +48,206 @@ const requireAdmin = (req, res, next) => { // Apply admin middleware to all routes router.use(requireAdmin); +// GET /api/admin/custom-features - Proxy to existing custom features functionality +router.get('/custom-features', async (req, res) => { + try { + const limit = parseInt(req.query.limit) || 50; + const offset = parseInt(req.query.offset) || 0; + const status = req.query.status; + + console.log(`Admin: Fetching custom features (status: ${status || 'all'}, limit: ${limit}, offset: ${offset})`); + + let features; + if (status) { + features = await CustomFeature.getFeaturesByStatus(status, limit, offset); + } else { + features = await CustomFeature.getAllFeatures(limit, offset); + } + + res.json({ + success: true, + data: features, + count: features.length, + message: `Found ${features.length} custom features` + }); + } catch (error) { + console.error('Error fetching custom features:', error.message); + res.status(500).json({ + success: false, + error: 'Failed to fetch custom features', + message: error.message + }); + } +}); + +// POST /api/admin/custom-features/:id/review - Review custom feature +router.post('/custom-features/:id/review', async (req, res) => { + try { + const { id } = req.params; + const { status, admin_notes, admin_reviewed_by } = req.body; + + const validStatuses = ['approved', 'rejected', 'duplicate']; + if (!validStatuses.includes(status)) { + return res.status(400).json({ + success: false, + error: 'Invalid status', + message: `Status must be one of: ${validStatuses.join(', ')}` + }); + } + + console.log(`🔍 Admin: Reviewing custom feature ${id} with status: ${status}`); + + const feature = await CustomFeature.getById(id); + if (!feature) { + return res.status(404).json({ + success: false, + error: 'Feature not found', + message: 'The specified feature does not exist' + }); + } + + const reviewData = { + status, + admin_notes, + admin_reviewed_by: admin_reviewed_by || req.user.id + }; + + const updatedFeature = await CustomFeature.reviewFeature(id, reviewData); + + try { + await AdminNotification.notifyFeatureReviewed(id, feature.name, status); + } catch (notifError) { + console.warn('⚠️ Failed to create notification:', notifError.message); + } + + res.json({ + success: true, + data: updatedFeature, + message: `Feature "${feature.name}" has been ${status}` + }); + } catch (error) { + console.error('❌ Error reviewing custom feature:', error.message); + res.status(500).json({ + success: false, + error: 'Failed to review custom feature', + message: error.message + }); + } +}); + +// GET /api/admin/custom-templates - Get custom templates +router.get('/custom-templates', async (req, res) => { + try { + const limit = parseInt(req.query.limit) || 50; + const offset = parseInt(req.query.offset) || 0; + const status = req.query.status; + + console.log(`Admin: Fetching custom templates (status: ${status || 'all'}, limit: ${limit}, offset: ${offset})`); + + let templates; + if (status) { + templates = await CustomTemplate.getTemplatesByStatus(status, limit, offset); + } else { + templates = await CustomTemplate.getAllTemplates(limit, offset); + } + + res.json({ + success: true, + data: templates, + count: templates.length, + message: `Found ${templates.length} custom templates` + }); + } catch (error) { + console.error('Error fetching custom templates:', error.message); + res.status(500).json({ + success: false, + error: 'Failed to fetch custom templates', + message: error.message + }); + } +}); + +// POST /api/admin/custom-templates/:id/review - Review custom template +router.post('/custom-templates/:id/review', async (req, res) => { + try { + const { id } = req.params; + const { status, admin_notes, admin_reviewed_by } = req.body; + + const validStatuses = ['approved', 'rejected', 'duplicate']; + if (!validStatuses.includes(status)) { + return res.status(400).json({ + success: false, + error: 'Invalid status', + message: `Status must be one of: ${validStatuses.join(', ')}` + }); + } + + console.log(`🔍 Admin: Reviewing custom template ${id} with status: ${status}`); + + const template = await CustomTemplate.getById(id); + if (!template) { + return res.status(404).json({ + success: false, + error: 'Template not found', + message: 'The specified template does not exist' + }); + } + + const reviewData = { + status, + admin_notes, + admin_reviewed_by: admin_reviewed_by || req.user.id + }; + + const updatedTemplate = await CustomTemplate.reviewTemplate(id, reviewData); + + try { + await AdminNotification.notifyTemplateReviewed(id, template.title, status); + } catch (notifError) { + console.warn('⚠️ Failed to create notification:', notifError.message); + } + + res.json({ + success: true, + data: updatedTemplate, + message: `Template "${template.title}" has been ${status}` + }); + } catch (error) { + console.error('❌ Error reviewing custom template:', error.message); + res.status(500).json({ + success: false, + error: 'Failed to review custom template', + message: error.message + }); + } +}); + +// GET /api/admin/templates/stats - Get template statistics +router.get('/templates/stats', async (req, res) => { + try { + console.log('📊 Admin: Fetching template statistics...'); + + const stats = await CustomTemplate.getTemplateStats(); + const notificationCounts = await AdminNotification.getCounts(); + + res.json({ + success: true, + data: { + templates: stats, + notifications: notificationCounts + }, + message: 'Template statistics retrieved successfully' + }); + } catch (error) { + console.error('❌ Error fetching template stats:', error.message); + res.status(500).json({ + success: false, + error: 'Failed to fetch template statistics', + message: error.message + }); + } +}); + // GET /api/admin/features/pending - Get pending features for review router.get('/features/pending', async (req, res) => { try {