Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions Nabula-Template/Bot/api/logger.utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* @author Maik
* @version 1.0.0
*/
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LoggerUtils = void 0;
const winston_1 = require("winston");
class LoggerUtils {
static init() {
this.logger = (0, winston_1.createLogger)({
level: 'info',
format: winston_1.format.combine(winston_1.format.timestamp(), winston_1.format.json()),
defaultMeta: { service: 'nabula-bot' },
transports: [
new winston_1.transports.Console({
format: winston_1.format.combine(winston_1.format.colorize(), winston_1.format.simple())
}),
new winston_1.transports.File({
filename: 'logs/error.log',
level: 'error',
maxsize: 5242880, // 5MB
maxFiles: 5
}),
new winston_1.transports.File({
filename: 'logs/combined.log',
maxsize: 5242880, // 5MB
maxFiles: 5
})
]
});
// Handle uncaught exceptions
this.logger.exceptions.handle(new winston_1.transports.File({ filename: 'logs/exceptions.log' }));
}
static log(level, message, meta) {
if (!this.logger) {
this.init();
}
this.logger.log(level, message, meta);
}
static error(message, meta) {
this.log('error', message, meta);
}
static warn(message, meta) {
this.log('warn', message, meta);
}
static info(message, meta) {
this.log('info', message, meta);
}
static debug(message, meta) {
this.log('debug', message, meta);
}
}
exports.LoggerUtils = LoggerUtils;
80 changes: 80 additions & 0 deletions Nabula-Template/Bot/api/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* @author Maik
* @version 1.0.0
*/
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@nestjs/core");
const app_module_1 = require("./app.module");
const common_1 = require("@nestjs/common");
const swagger_1 = require("@nestjs/swagger");
const config_1 = require("@nestjs/config");
const helmet_1 = __importDefault(require("helmet"));
const compression_1 = __importDefault(require("compression"));
const logger = new common_1.Logger('Bootstrap');
async function bootstrap() {
try {
const app = await core_1.NestFactory.create(app_module_1.AppModule);
const configService = app.get(config_1.ConfigService);
// Configure middleware
app.use((0, helmet_1.default)());
app.use((0, compression_1.default)());
app.enableCors({
origin: configService.get('FRONTEND_URL', '*'),
credentials: true
});
// API Configuration
app.setGlobalPrefix('api/v1');
app.useGlobalPipes(new common_1.ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: { enableImplicitConversion: true },
validationError: { target: false }
}));
// Configure Swagger
const config = new swagger_1.DocumentBuilder()
.setTitle('Nabula Bot Dashboard API')
.setDescription('API for managing the Nabula Discord Bot through a web dashboard')
.setVersion('1.0.0')
.addServer('/api/v1')
.addTag('auth', 'Authentication endpoints')
.addTag('servers', 'Server management endpoints')
.addTag('modules', 'Feature module configuration endpoints')
.addTag('users', 'User management endpoints')
.addBearerAuth()
.build();
const document = swagger_1.SwaggerModule.createDocument(app, config);
swagger_1.SwaggerModule.setup('docs', app, document, {
swaggerOptions: {
persistAuthorization: true
},
customSiteTitle: 'Nabula API Documentation'
});
// Start server
const port = configService.get('PORT', 3000);
await app.listen(port);
logger.log(`Application started on http://localhost:${port}`);
logger.log(`API Documentation available at http://localhost:${port}/docs`);
// Setup graceful shutdown
const shutdown = async () => {
logger.log('Shutting down application gracefully...');
await app.close();
process.exit(0);
};
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
}
catch (error) {
logger.error('Failed to start application:', error);
process.exit(1);
}
}
// Start the application
bootstrap().catch(error => {
console.error('Failed to bootstrap application:', error);
process.exit(1);
});
35 changes: 35 additions & 0 deletions Nabula-Template/Bot/api/users.module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* @author Maik
* @version 1.0.0
*/
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UsersModule = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const user_entity_1 = require("./entities/user.entity");
const user_server_permission_entity_1 = require("./entities/user-server-permission.entity");
const users_service_1 = require("./users.service");
const users_controller_1 = require("./users.controller");
const user_repository_1 = require("./user.repository");
let UsersModule = class UsersModule {
};
exports.UsersModule = UsersModule;
exports.UsersModule = UsersModule = __decorate([
(0, common_1.Module)({
imports: [
typeorm_1.TypeOrmModule.forFeature([user_entity_1.User, user_server_permission_entity_1.UserServerPermission]),
],
controllers: [users_controller_1.UsersController],
providers: [
users_service_1.UsersService,
user_repository_1.UserRepository,
],
})
], UsersModule);
158 changes: 158 additions & 0 deletions Nabula-Template/Bot/dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* @author Maik
* @version 1.0.0
*/
// bot/api/dashboard.js
const axios = require('axios');
const config = require('../config');

// Custom error class for API errors
class DashboardAPIError extends Error {
constructor(message, statusCode, originalError) {
super(message);
this.name = 'DashboardAPIError';
this.statusCode = statusCode;
this.originalError = originalError;
}
}

// Retry configuration
const MAX_RETRIES = 3;
const RETRY_DELAY = 1000; // 1 second
const RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504];

// Helper function to implement delay
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

// Create an instance of axios with default configuration
const apiClient = axios.create({
baseURL: config.dashboardApiUrl,
timeout: 10000, // 10 second timeout
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});

// Add a request interceptor to add authentication headers
apiClient.interceptors.request.use(
(config) => {
// Add the API key to the headers
if (config.apiSecretKey) {
config.headers['X-API-Key'] = config.apiSecretKey;
}

return config;
},
(error) => {
return Promise.reject(new DashboardAPIError('Request configuration error', null, error));
}
);

// Add a response interceptor for error handling and retries
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
const { config, response } = error;

// Skip retry for specific error types
if (!config || !RETRY_STATUS_CODES.includes(response?.status)) {
throw new DashboardAPIError(
response?.data?.message || error.message,
response?.status,
error
);
}

// Initialize retry count
config.__retryCount = config.__retryCount || 0;

// Check if we should retry
if (config.__retryCount >= MAX_RETRIES) {
throw new DashboardAPIError(
`Failed after ${MAX_RETRIES} retries`,
response.status,
error
);
}

// Increment retry count
config.__retryCount += 1;

// Exponential backoff delay
const backoffDelay = RETRY_DELAY * Math.pow(2, config.__retryCount - 1);
await delay(backoffDelay);

// Retry request
return apiClient(config);
}
);

// API methods
const api = {
/**
* Get server configuration
* @param {string} guildId - Discord guild ID
* @returns {Promise<Object>} Server configuration
*/
async getServerConfig(guildId) {
try {
const response = await apiClient.get(`/servers/${guildId}/config`);
return response.data;
} catch (error) {
throw new DashboardAPIError(
`Failed to get server config for guild ${guildId}`,
error.statusCode,
error
);
}
},

/**
* Update server configuration
* @param {string} guildId - Discord guild ID
* @param {Object} config - New configuration
* @returns {Promise<Object>} Updated configuration
*/
async updateServerConfig(guildId, config) {
try {
const response = await apiClient.put(`/servers/${guildId}/config`, config);
return response.data;
} catch (error) {
throw new DashboardAPIError(
`Failed to update server config for guild ${guildId}`,
error.statusCode,
error
);
}
},

/**
* Log an event to the dashboard
* @param {string} guildId - Discord guild ID
* @param {string} eventType - Type of event
* @param {Object} eventData - Event data
* @returns {Promise<Object>} Logged event
*/
async logEvent(guildId, eventType, eventData) {
try {
const response = await apiClient.post(`/servers/${guildId}/logs`, {
type: eventType,
data: eventData,
timestamp: new Date().toISOString()
});
return response.data;
} catch (error) {
throw new DashboardAPIError(
`Failed to log event for guild ${guildId}`,
error.statusCode,
error
);
}
}
};

module.exports = {
api,
DashboardAPIError
};
Loading