302 lines
10 KiB
JavaScript
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);
|
|
}
|
|
})();
|