"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = __importDefault(require("express"));
const mongodb_1 = require("mongodb");
const database_1 = require("../../config/database");
/**
* @namespace routes
* @description This module contains routes for administrative database operations.
*/
/**
* @description Router for admin-related database operations.
* @type {Router}
*/
const adminRouter = express_1.default.Router();
/**
* @description MongoDB client instance used to connect to the database.
* @type {MongoClient}
*/
const client = new mongodb_1.MongoClient(process.env.MONGO_URI);
/**
* @memberof routes
* @description Retrieves a list of all available databases in the MongoDB instance.
* @name GET /admin/databases
* @function
* @async
* @param {Request} req - Express request object.
* @param {Response} res - Express response object.
* @returns {void} Renders a list of databases or sends an error response.
* @example
* // Curl Example:
* curl -X GET http://localhost:3000/admin/databases
*
* @example
* // Response:
* {
* "databases": [
* { "name": "admin", "sizeOnDisk": 8192, "empty": false },
* { "name": "local", "sizeOnDisk": 4096, "empty": false }
* ]
* }
*/
adminRouter.get('/databases', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
// #swagger.summary = 'Retrieve a list of all databases'
// #swagger.description = 'Fetches all available databases in the system.'
try {
const admin = client.db().admin();
const databases = yield admin.listDatabases();
res.render('databases', { databases: databases.databases });
}
catch (error) {
console.error('Error fetching databases:', error.message);
res.status(500).send('Unable to fetch databases.');
}
}));
/**
* @memberof routes
* @description Creates a new database by providing a name and initializing it with a default collection.
* @name POST /admin/databases
* @function
* @async
* @param {Request} req - Express request object containing the database name in the body.
* @param {Response} res - Express response object.
* @returns {void} Sends a success or error response.
* @example
* // Curl Example:
* curl -X POST http://localhost:3000/admin/databases -H "Content-Type: application/json" -d '{"name": "test_db"}'
*
* @example
* // Response:
* {
* "success": true,
* "message": "Database created successfully",
* "name": "test_db"
* }
*/
adminRouter.post('/databases', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
// #swagger.summary = 'Create a new database'
// #swagger.description = 'Creates a new database by providing a name and initializing it with a default collection.'
try {
const { name } = req.body;
// Validate input
if (!name || typeof name !== 'string' || name.trim() === '') {
return res.status(400).json({ success: false, message: 'Invalid database name' });
}
// Sanitize the database name
const sanitizedName = name.trim().replace(/[^a-zA-Z0-9_]/g, '_');
// Check if the database already exists
const admin = client.db().admin();
const existingDatabases = yield admin.listDatabases();
const databaseExists = existingDatabases.databases.some((db) => db.name === sanitizedName);
if (databaseExists) {
return res.status(409).json({ success: false, message: 'Database already exists' });
}
// Create the new database by creating an initial collection
const db = client.db(sanitizedName);
yield db.createCollection('initial_collection');
res.status(201).json({ success: true, message: 'Database created successfully', name: sanitizedName });
}
catch (error) {
console.error('Error creating database:', error.message);
res.status(500).json({ success: false, message: 'Unable to create database' });
}
}));
/**
* @memberof routes
* @description Retrieves a list of collections in the specified database.
* @name GET /admin/databases/:dbName/collections
* @function
* @async
* @param {Request} req - Express request object containing the database name as a URL parameter.
* @param {Response} res - Express response object.
* @returns {void} Renders a list of collections or sends an error response.
* @example
* // Curl Example:
* curl -X GET http://localhost:3000/admin/databases/my_database/collections
*
* @example
* // Response:
* {
* "collections": [
* { "name": "users" },
* { "name": "products" }
* ]
* }
*/
adminRouter.get('/databases/:dbName/collections', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
const { dbName } = req.params;
try {
const collections = yield client.db(dbName).listCollections().toArray();
res.render('collections', { dbName, collections });
}
catch (error) {
console.error('Error fetching collections:', error.message);
res.status(500).json({ error: error.message });
}
}));
/**
* @memberof routes
* @description Adds a new collection to the specified database.
* @name POST /admin/databases/:dbName/collections
* @function
* @async
* @param {Request} req - Express request object containing the database name as a URL parameter and the collection name in the body.
* @param {Response} res - Express response object.
* @returns {void} Sends a success or error response.
* @example
* // Curl Example:
* curl -X POST http://localhost:3000/admin/databases/my_database/collections -H "Content-Type: application/json" -d '{"name": "my_collection"}'
*
* @example
* // Response:
* {
* "success": true,
* "message": "Collection created successfully",
* "redirectUrl": "/databases/my_database/collections/my_collection/newSchema"
* }
*/
adminRouter.post('/databases/:dbName/collections', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
const { dbName } = req.params;
const { name } = req.body;
try {
// Validate input
if (!name || typeof name !== 'string' || name.trim() === '') {
return res.status(400).json({ success: false, message: 'Invalid collection name' });
}
// Sanitize the collection name
const sanitizedName = name.trim().replace(/[^a-zA-Z0-9_]/g, '_');
// Check if the collection already exists
const existingCollections = yield client.db(dbName).listCollections().toArray();
const collectionExists = existingCollections.some((col) => col.name === sanitizedName);
if (collectionExists) {
return res.status(409).json({ success: false, message: 'Collection already exists' });
}
// Create the new collection
yield client.db(dbName).createCollection(sanitizedName);
// Redirect to the new collection's newSchema page
res.status(201).json({
success: true,
message: 'Collection created successfully',
redirectUrl: `/databases/${dbName}/collections/${sanitizedName}/newSchema`,
});
}
catch (error) {
console.error('Error creating collection:', error.message);
res.status(500).json({ success: false, message: 'Unable to create collection' });
}
}));
/**
* @memberof routes
* @description Deletes a collection in the specified database.
* @name DELETE /admin/databases/:dbName/collections/:collectionName
* @function
* @async
* @param {Request} req - Express request object containing database and collection names as URL parameters.
* @param {Response} res - Express response object.
* @returns {void} Sends a success or error response.
* @example
* // Curl Example:
* curl -X DELETE http://localhost:3000/admin/databases/my_database/collections/my_collection
*
* @example
* // Response:
* {
* "success": true,
* "message": "Collection deleted successfully"
* }
*/
adminRouter.delete('/databases/:dbName/collections/:collectionName', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
const { dbName, collectionName } = req.params;
try {
const existingCollections = yield client.db(dbName).listCollections().toArray();
const collectionExists = existingCollections.some((col) => col.name === collectionName);
if (!collectionExists) {
return res.status(404).json({ success: false, message: 'Collection not found' });
}
yield client.db(dbName).collection(collectionName).drop();
res.status(200).json({ success: true, message: 'Collection deleted successfully' });
}
catch (error) {
console.error('Error deleting collection:', error.message);
res.status(500).json({ success: false, message: 'Unable to delete collection' });
}
}));
/**
* @memberof routes
* @description Retrieves the schema for a collection in the specified database.
* @name GET /admin/databases/:dbName/collections/:collectionName/newSchema
* @function
* @async
* @param {Request} req - Express request object containing database and collection names as URL parameters.
* @param {Response} res - Express response object.
* @returns {void} Renders the schema or sends an error response.
* @example
* // Curl Example:
* curl -X GET http://localhost:3000/admin/databases/my_database/collections/my_collection/newSchema
*
* @example
* // Response:
* {
* "dbName": "my_database",
* "collectionName": "my_collection",
* "schema": {
* "field1": "value1",
* "field2": "value2"
* }
* }
*/
adminRouter.get('/databases/:dbName/collections/:collectionName/newSchema', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
const { dbName, collectionName } = req.params;
try {
const collection = client.db(dbName).collection(collectionName);
const schema = yield collection.findOne(); // Assuming one entry per collection
res.render('newSchema', { dbName, collectionName, schema });
}
catch (error) {
console.error('Error fetching schema:', error.message);
res.status(500).render('error', { message: 'Failed to load schema', error });
}
}));
/**
* @memberof routes
* @description Updates or creates a schema for the specified MongoDB collection in the database. Ensures proper handling of keys, data types, and dependencies on the `available_layers` database.
* @name POST /admin/databases/:dbName/collections/:collectionName/newSchema
* @function
* @async
* @param {Request} req - Express request object containing database and collection names as URL parameters, and the schema update details in the body.
* @param {Response} res - Express response object.
* @returns {void} Redirects to the schema page or renders an error response in case of failure.
* @example
* // Curl Example:
* curl -X POST http://localhost:3000/admin/databases/my_database/collections/my_collection/newSchema \
* -H "Content-Type: application/json" \
* -d '{
* "keys": {
* "booleans": { "isActive": "true", "isDeleted": "false" },
* "ownerNameKeys": ["ownerName1", "ownerName2"],
* "addressKeys": "addressKey1"
* },
* "baseUrls": {
* "key1": "https://example.com",
* "key2": "https://another.com"
* }
* }'
*
* @example
* // Response:
* HTTP/1.1 302 Found
* Location: /databases/my_database/collections/my_collection/schema
*/
adminRouter.post('/databases/:dbName/collections/:collectionName/newSchema', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
const { dbName, collectionName } = req.params;
console.log(req.body); // Log the request body to verify the input
try {
// Ensure 'ownerNameKeys' and 'addressKeys' are arrays or create them as empty arrays if they do not exist
if (req.body.keys) {
// Ensure booleans are handled first
if (req.body.keys.booleans) {
Object.keys(req.body.keys.booleans).forEach((key) => {
const value = req.body.keys.booleans[key];
if (value === "true") {
req.body.keys.booleans[key] = true;
}
else if (value === "false") {
req.body.keys.booleans[key] = false;
}
});
}
// Ensure 'ownerNameKeys' is an array or empty
if (req.body.keys.ownerNameKeys) {
if (!Array.isArray(req.body.keys.ownerNameKeys)) {
req.body.keys.ownerNameKeys = [req.body.keys.ownerNameKeys]; // Convert string to array
}
}
else {
// If 'ownerNameKeys' doesn't exist, create it as an empty array
req.body.keys.ownerNameKeys = [];
}
// Ensure 'addressKeys' is an array or empty
if (req.body.keys.addressKeys) {
if (!Array.isArray(req.body.keys.addressKeys)) {
req.body.keys.addressKeys = [req.body.keys.addressKeys]; // Convert string to array
}
}
else {
// If 'addressKeys' doesn't exist, create it as an empty array
req.body.keys.addressKeys = [];
}
}
// Extract different types of keys from the 'keys' object
const objectKeys = {};
const arrayKeys = {};
const primitiveKeys = {};
// Iterate over each key in req.body.keys and classify it
for (const key in req.body.keys) {
if (req.body.keys.hasOwnProperty(key)) {
const value = req.body.keys[key];
if (Array.isArray(value)) {
arrayKeys[key] = value; // Arrays are grouped here
}
else if (typeof value === 'object' && value !== null) {
objectKeys[key] = value; // Objects are grouped here
}
else {
primitiveKeys[key] = value; // Primitives (strings, numbers, etc.) go here
}
}
}
// Reconstruct the 'keys' object with sorted order: objects first, then arrays, and then primitives
req.body.keys = Object.assign(Object.assign(Object.assign({}, objectKeys), arrayKeys), primitiveKeys);
// Build the updated schema from the request body
const updatedSchema = Object.assign({}, req.body);
// Reconstruct baseUrls
updatedSchema.baseUrls = req.body.baseUrls || {};
// Loop through each base URL key-value pair in updatedSchema.baseUrls
for (const [baseUrlKey, baseUrlValue] of Object.entries(updatedSchema.baseUrls)) {
console.log('baseUrlKey:', baseUrlKey, 'baseUrlValue:', baseUrlValue); // Log the key-value pair
// Check if the 'available_layers' database exists and if 'layers' collection exists
const db = yield (0, database_1.connectToDatabase)('available_layers');
const availableLayersCollection = db.collection('layers');
// Check if the collection exists; if not, create it
const collections = yield db.listCollections().toArray();
const collectionExists = collections.some(collection => collection.name === 'layers');
if (!collectionExists) {
// Create the 'layers' collection if it doesn't exist
yield db.createCollection('layers');
console.log('Created "layers" collection');
// Insert the initial document with the default baseUrlKey value if the collection is just created
yield availableLayersCollection.insertOne({
[baseUrlKey]: `${baseUrlKey}-layer` // Default value format
});
console.log('Inserted initial document into the "layers" collection');
}
else {
// If collection exists, fetch the existing document
const existingDocument = yield availableLayersCollection.findOne({}); // Since there's only one document
if (existingDocument) {
// If the key doesn't exist in the document's 'layers' object, we need to update it
if (!existingDocument[baseUrlKey]) {
// If the baseUrlKey does not exist, add it with the default 'key-layer' format
yield availableLayersCollection.updateOne({}, // Match the only document
{ $set: { [baseUrlKey]: `${baseUrlKey}-layer` } } // Add or update the key-value pair
);
console.log(`Added new baseUrlKey: ${baseUrlKey}`);
}
}
}
yield (0, database_1.closeClient)(); // Close the database client after the operation
// Add the new base URL if it's valid and doesn't already exist
updatedSchema.baseUrls[baseUrlKey] = baseUrlValue;
}
const collection = client.db(dbName).collection(collectionName);
// Update the schema document or create it if it doesn't exist
yield collection.replaceOne({}, // Match condition (replace any document)
updatedSchema, // Replace the document with updatedSchema
{ upsert: true } // Create a document if none exists
);
// Redirect back to the schemas page
res.redirect(`/databases/${dbName}/collections/${collectionName}/schema`);
}
catch (error) {
console.error('Error saving schema:', error.message);
res.status(500).render('error', { message: 'Failed to save schema', error });
}
}));
/**
* @memberof routes
* @description Retrieves the schema for a specific collection in the specified database.
* @name GET /admin/databases/:dbName/collections/:collectionName/schema
* @function
* @async
* @param {Request} req - Express request object containing database and collection names as URL parameters.
* @param {Response} res - Express response object.
* @returns {void} Renders the schema or sends an error response in case of failure.
* @example
* // Curl Example:
* curl -X GET http://localhost:3000/admin/databases/my_database/collections/my_collection/schema
*
* @example
* // Response:
* {
* "dbName": "my_database",
* "collectionName": "my_collection",
* "schema": {
* "keys": {
* "booleans": { "isActive": true },
* "ownerNameKeys": ["owner1", "owner2"],
* "addressKeys": ["address1"]
* },
* "baseUrls": { "url1": "https://example.com" }
* }
* }
*/
adminRouter.get('/databases/:dbName/collections/:collectionName/schema', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
const { dbName, collectionName } = req.params;
try {
const collection = client.db(dbName).collection(collectionName);
const schema = yield collection.findOne({}); // Assuming one entry per collection
const existingBaseUrls = (schema === null || schema === void 0 ? void 0 : schema.baseUrls) || {};
console.log(existingBaseUrls); // Log the existing base URLs
res.render('schema', { dbName, collectionName, schema, existingBaseUrls });
}
catch (error) {
console.error('Error fetching schema:', error.message);
res.status(500).render('error', { message: 'Failed to load schema', error });
}
}));
/**
* @memberof routes
* @description Updates the schema for a specific collection in the specified database.
* @name PUT /admin/databases/:dbName/collections/:collectionName/schema
* @function
* @async
* @param {Request} req - Express request object containing database and collection names as URL parameters and the new schema in the body.
* @param {Response} res - Express response object.
* @returns {void} Sends a success message or an error response in case of failure.
* @example
* // Curl Example:
* curl -X PUT http://localhost:3000/admin/databases/my_database/collections/my_collection/schema \
* -H "Content-Type: application/json" \
* -d '{
* "keys": {
* "booleans": { "isActive": true },
* "ownerNameKeys": ["owner1"],
* "addressKeys": ["address1"]
* },
* "baseUrls": { "url1": "https://example.com" }
* }'
*
* @example
* // Response:
* {
* "message": "Schema updated successfully"
* }
*/
adminRouter.put('/databases/:dbName/collections/:collectionName/schema', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
const { dbName, collectionName } = req.params;
const newSchema = req.body;
try {
const collection = client.db(dbName).collection(collectionName);
yield collection.replaceOne({}, newSchema); // Replace the single document
res.json({ message: 'Schema updated successfully' });
}
catch (error) {
console.error('Error updating schema:', error.message);
res.status(500).json({ error: error.message });
}
}));
/**
* @memberof routes
* @description Updates or creates a schema for the specified collection in the given database. Handles various input validations, merges fields, and manages dependencies on the `available_layers` database.
* @name POST /admin/databases/:dbName/collections/:collectionName/schema
* @function
* @async
* @param {Request} req - Express request object containing database and collection names as URL parameters and the schema update details in the body.
* @param {Response} res - Express response object.
* @returns {void} Redirects to the schema page or renders an error response in case of failure.
* @example
* // Curl Example:
* curl -X POST http://localhost:3000/admin/databases/my_database/collections/my_collection/schema \
* -H "Content-Type: application/json" \
* -d '{
* "keys": {
* "booleans": { "isActive": "true", "isArchived": "false" },
* "ownerNameKeys": ["owner1", "owner2"],
* "addressKeys": "address1"
* },
* "baseUrls": {
* "key1": "https://example.com",
* "key2": "https://another.com"
* }
* }'
*
* @example
* // Response (Redirect):
* HTTP/1.1 302 Found
* Location: /databases/my_database/collections/my_collection/schema
*/
adminRouter.post('/databases/:dbName/collections/:collectionName/schema', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
const { dbName, collectionName } = req.params;
try {
if (req.body.keys) {
// Ensure booleans are handled first
if (req.body.keys.booleans) {
Object.keys(req.body.keys.booleans).forEach((key) => {
const value = req.body.keys.booleans[key];
if (value === "true") {
req.body.keys.booleans[key] = true;
}
else if (value === "false") {
req.body.keys.booleans[key] = false;
}
});
}
// Ensure 'ownerNameKeys' is an array or empty
if (req.body.keys.ownerNameKeys) {
if (!Array.isArray(req.body.keys.ownerNameKeys)) {
req.body.keys.ownerNameKeys = [req.body.keys.ownerNameKeys]; // Convert string to array
}
}
else {
// If 'ownerNameKeys' doesn't exist, create it as an empty array
req.body.keys.ownerNameKeys = [];
}
// Ensure 'addressKeys' is an array or empty
if (req.body.keys.addressKeys) {
if (!Array.isArray(req.body.keys.addressKeys)) {
req.body.keys.addressKeys = [req.body.keys.addressKeys]; // Convert string to array
}
}
else {
// If 'addressKeys' doesn't exist, create it as an empty array
req.body.keys.addressKeys = [];
}
}
// Extract different types of keys from the 'keys' object
const objectKeys = {};
const arrayKeys = {};
const primitiveKeys = {};
// Iterate over each key in req.body.keys and classify it
for (const key in req.body.keys) {
if (req.body.keys.hasOwnProperty(key)) {
const value = req.body.keys[key];
if (Array.isArray(value)) {
arrayKeys[key] = value; // Arrays are grouped here
}
else if (typeof value === 'object' && value !== null) {
objectKeys[key] = value; // Objects are grouped here
}
else {
primitiveKeys[key] = value; // Primitives (strings, numbers, etc.) go here
}
}
}
// Reconstruct the 'keys' object with sorted order: objects first, then arrays, and then primitives
req.body.keys = Object.assign(Object.assign(Object.assign({}, objectKeys), arrayKeys), primitiveKeys);
/**
* Updates the schema with the fields from the request body.
*
* @param {Object} req - The request object.
* @param {Object} req.body - The body of the request containing the fields to be merged.
*
* @returns {Object} The updated schema with merged fields from the request body.
*/
const updatedSchema = Object.assign({}, req.body);
// Reconstruct baseUrls
updatedSchema.baseUrls = req.body.baseUrls || {};
// Loop through each base URL key-value pair in updatedSchema.baseUrls
for (const [baseUrlKey, baseUrlValue] of Object.entries(updatedSchema.baseUrls)) {
console.log('baseUrlKey:', baseUrlKey, 'baseUrlValue:', baseUrlValue); // Log the key-value pair
// Check if the 'available_layers' database exists and if 'layers' collection exists
const db = yield (0, database_1.connectToDatabase)('available_layers');
const availableLayersCollection = db.collection('layers');
// Check if the collection exists; if not, create it
const collections = yield db.listCollections().toArray();
const collectionExists = collections.some(collection => collection.name === 'layers');
if (!collectionExists) {
// Create the 'layers' collection if it doesn't exist
yield db.createCollection('layers');
console.log('Created "layers" collection');
// Insert the initial document with the default baseUrlKey value if the collection is just created
yield availableLayersCollection.insertOne({
[baseUrlKey]: `${baseUrlKey}-layer` // Default value format
});
console.log('Inserted initial document into the "layers" collection');
}
else {
// If collection exists, fetch the existing document
const existingDocument = yield availableLayersCollection.findOne({}); // Since there's only one document
if (existingDocument) {
// If the key doesn't exist in the document's 'layers' object, we need to update it
if (!existingDocument[baseUrlKey]) {
// If the baseUrlKey does not exist, add it with the default 'key-layer' format
yield availableLayersCollection.updateOne({}, // Match the only document
{ $set: { [baseUrlKey]: `${baseUrlKey}-layer` } } // Add or update the key-value pair
);
console.log(`Added new baseUrlKey: ${baseUrlKey}`);
}
}
}
//await closeClient(); // Close the database client after the operation
// Add the new base URL if it's valid and doesn't already exist
updatedSchema.baseUrls[baseUrlKey] = baseUrlValue;
}
const collection = client.db(dbName).collection(collectionName);
yield collection.replaceOne({}, updatedSchema); // Replace the single document
res.redirect(`/databases/${dbName}/collections/${collectionName}/schema`);
}
catch (error) {
console.error('Error saving schema:', error.message);
res.status(500).render('error', { message: 'Failed to save schema', error });
}
finally {
yield (0, database_1.closeClient)();
}
}));
exports.default = adminRouter;