Files
k42api/server.js
Rytek Digital Inc 2c46b8613b updates
2025-08-06 17:19:17 -03:00

302 lines
10 KiB
JavaScript

// Global error handler for uncaught exceptions
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
});
// Global handler for unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
// Required modules
const http = require('http');
const express = require('express');
const fs = require('fs');
const path = require('path');
const ejs = require('ejs');
const { MongoClient } = require('mongodb');
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
require('dotenv').config();
const ParseServer = require('parse-server').ParseServer;
const ParseDashboard = require('parse-dashboard');
const ParseAuditor = require('parse-auditor');
const cors = require('cors');
const nodemailer = require('nodemailer');
// Parse Dashboard configuration
const dashboard = new ParseDashboard({
apps: [
{
appId: process.env.appID,
masterKey: process.env.masterkey,
serverURL: process.env.NODE_ENV === 'dev' ? process.env.serverURL : process.env.serverURLProd,
appName: 'Dracattus',
production: process.env.NODE_ENV === 'prod'
}
],
users: [
{
user: process.env.DASHBOARD_USERNAME,
pass: process.env.DASHBOARD_USER_PASSWORD,
apps: [{ appId: process.env.appID }]
}
],
useEncryptedPasswords: false,
trustProxy: 1
}, {
allowInsecureHTTP: process.env.NODE_ENV === 'dev'
});
// Main server initialization
(async () => {
try {
const app = express();
const httpServer = http.createServer(app);
// Basic middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// CORS configuration
const corsOptions = {
origin: [
'http://localhost:4040',
'http://localhost:3000',
'https://dracattus.com',
'https://api.dracattus.com',
'*'
],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: [
'X-Parse-Application-Id',
'X-Parse-REST-API-Key',
'X-Parse-Session-Token',
'X-Parse-Master-Key',
'X-Parse-Javascript-Key',
'X-Parse-Installation-Id',
'X-Parse-Client-Version',
'X-Parse-Revocable-Session',
'Content-Type',
'Authorization',
'Sec-WebSocket-Protocol',
'Sec-WebSocket-Version',
'Sec-WebSocket-Key',
'access-control-expose-headers'
],
exposedHeaders: ['X-Parse-Session-Token'],
credentials: true,
optionsSuccessStatus: 200,
preflightContinue: false
};
// Apply CORS middleware
app.use(cors(corsOptions));
// Health check endpoint
app.get('/health', (req, res) => {
res.status(200).json({ status: 'OK' });
});
// Verify Parse Server configuration
console.log('Parse Server Configuration:');
console.log('- App ID:', process.env.appID);
console.log('- REST API Key:', process.env.rest_api_key);
// Parse Server configuration
const api = new ParseServer({
databaseURI: process.env.NODE_ENV === 'dev'
? process.env.DATABASE_URI // Local development database
: process.env.DATABASE_URI, // Production database
databaseOptions: {
useUnifiedTopology: true,
retryWrites: true,
w: 'majority',
readPreference: 'primaryPreferred',
maxPoolSize: 50,
minPoolSize: 5
},
cloud: './cloud/loader.js',
appId: process.env.appID,
appName: 'Koba42',
masterKey: process.env.masterkey,
restAPIKey: process.env.rest_api_key,
javascriptKey: process.env.rest_api_key,
encryptionKey: process.env.ENCRYPT_KEY,
serverURL: process.env.NODE_ENV === 'dev' ? process.env.serverURL : process.env.serverURLProd,
publicServerURL: process.env.serverURLProd,
verifyUserEmails: true,
emailVerifyTokenValidityDuration: 24 * 60 * 60,
preventLoginWithUnverifiedEmail: true,
enableAnonymousUsers: false,
enableNewUserRegistration: true,
allowClientClassCreation: process.env.NODE_ENV === 'prod' ? false : true,
sessionLength: 24 * 60 * 60,
requireRevocableSessions: true,
expireInactiveSessions: true,
logsFolder: './logs',
logLevel: 'warn',
maxUploadSize: '100mb',
allowExpiredAuthDataToken: false,
encodeParseObjectInCloudFunction: true,
auth: {
twitter: {
consumer_key: process.env.TWITTER_API_KEY,
consumer_secret: process.env.TWITTER_API_SECRET
},
},
emailAdapter: {
module: 'parse-server-api-mail-adapter',
options: {
sender: 'no-reply@dracattus.com',
templates: {
passwordResetEmail: {
subjectPath: './templates/emails/txt/password_reset_email_subject.txt',
textPath: './templates/emails/txt/password_reset_email.txt',
htmlPath: './templates/emails/html/password_reset_email.html'
},
verificationEmail: {
subjectPath: './templates/emails/txt/verification_email_subject.txt',
textPath: './templates/emails/txt/verification_email.txt',
htmlPath: './templates/emails/html/verification_email.html'
}
},
apiCallback: async ({ payload }) => {
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.GMAIL_ADDRESS,
pass: process.env.GMAIL_PASSWORD
}
});
const mailOptions = {
from: payload.from,
to: payload.to,
subject: payload.subject,
text: payload.text,
html: payload.html
};
try {
await transporter.sendMail(mailOptions);
console.log('Email sent successfully');
} catch (error) {
console.error('Error sending email:', error);
}
}
}
},
liveQuery: {
classNames: ['IOSC', 'DRACATTUS', 'BATTLES','QUESTS','BANK','OFFERS','NPC','RESOURCE_MARKERS'],
websocketTimeout: 100 * 1000,
websocketOptions: {
keepAlive: true,
keepAliveInitialDelay: 10000
},
logLevels: ['error', 'warn']
},
verbose: false,
startLiveQueryServer: true,
websocketTimeout: 100 * 1000
});
// Public metadata endpoint - MUST BE BEFORE Parse API mount
app.get('/metadata/:objectId', async (req, res) => {
try {
// Set CORS headers
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET');
res.header('Access-Control-Allow-Headers', 'Content-Type');
// Set cache headers for better performance
res.header('Cache-Control', 'public, max-age=300'); // Cache for 5 minutes
// Create a master key request to Parse Server
const metadata = await Parse.Cloud.run('getNFTMetadata',
{ objectId: req.params.objectId },
{ useMasterKey: true }
);
console.log("metadata", metadata);
res.json(metadata);
} catch (error) {
console.error('Metadata endpoint error:', error);
res.status(404).json({
error: error.message || 'Metadata not found'
});
}
});
// Start Parse Server
await api.start();
// Configure Parse Auditor to track changes to important classes
// This automatically creates _AUD classes that store all changes
ParseAuditor(
// Classes to track all changes (creates, updates, deletes)
['BANK', 'User', 'BATTLES', 'QUESTS', 'DRACATTUS', 'IOSC', 'STATS'],
// Classes to also track reads/views
['BANK'],
// Configuration
{
classPrefix: '',
classPostfix: '_AUDIT',
fieldPrefix: 'meta_',
fieldPostfix: '',
parseSDK: Parse,
useMasterKey: false,
clp: {}
}
);
console.log('Parse Auditor configured to track changes to important classes');
// Note: Scheduled jobs are handled by the cloud/scheduler module
console.log('Job scheduling is handled by the cloud/scheduler module');
// Mount Parse API AFTER the metadata endpoint
app.use('/v1', api.app);
// Mount Parse Dashboard
app.use('/dashboard', dashboard);
// Graceful shutdown handler
process.on('SIGTERM', () => {
console.log('SIGTERM signal received: closing HTTP server');
httpServer.close(() => {
console.log('HTTP server closed');
process.exit(0);
});
});
// Start server
httpServer.listen(process.env.httpPORT, () => {
console.log(`\n=== Server Configuration ===`);
console.log(`🚀 Parse Server running at: ${process.env.NODE_ENV === 'dev' ? process.env.serverURL : process.env.serverURLProd}`);
console.log(`📊 Parse Dashboard: ${process.env.NODE_ENV === 'dev' ? process.env.serverURL.replace('/v1', '/dashboard') : process.env.serverURLProd.replace('/v1', '/dashboard')}`);
console.log(`🌍 Environment: ${process.env.NODE_ENV}`);
console.log(`🔌 HTTP Port: ${process.env.httpPORT}`);
console.log(`🌐 Production Mode: ${process.env.NODE_ENV === 'prod'}`);
console.log(`=====================================\n`);
});
// Initialize Live Query Server with explicit websocket options
ParseServer.createLiveQueryServer(httpServer, {
websocketTimeout: 100 * 1000,
cacheTimeout: 100 * 1000,
websocketOptions: {
keepAlive: true,
keepAliveInitialDelay: 10000,
verifyClient: (info, callback) => {
console.log('New WebSocket connection attempt');
callback(true);
}
},
onLiveQueryEvent: (eventName, data) => {
console.log(`LiveQuery Event: ${eventName}`, data);
}
});
} catch (error) {
console.error('Error during startup:', error);
process.exit(1);
}
})();