commit 67dce4c1e62e5cfac0c71e062f61e27c02ae7cdf Author: Equipe de Desenvolvimento Sothis Date: Sat Aug 30 22:04:35 2025 +0000 Initial commit - SNGLPI diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1bfdd8b --- /dev/null +++ b/.env.example @@ -0,0 +1,29 @@ +# Contents of the file: /servicenow-glpi-sync/servicenow-glpi-sync/.env.example + +# This is an example of the environment variables needed for the application. + +# ServiceNow API Configuration +SERVICENOW_URL=https://your-instance.service-now.com/api/now/table/ +SERVICENOW_USERNAME=your_servicenow_username +SERVICENOW_PASSWORD=your_servicenow_password + +# GLPI API Configuration +GLPI_URL=https://your-glpi-instance/apirest.php/ +GLPI_APP_TOKEN=your_glpi_app_token +GLPI_USER_TOKEN=your_glpi_user_token + +# Database Configuration +DB_HOST=localhost +DB_PORT=3306 # or 5432 for PostgreSQL +DB_USER=your_database_user +DB_PASSWORD=your_database_password +DB_NAME=your_database_name + +# Logging Configuration +LOG_LEVEL=info +LOG_FILE_PATH=logs/app.log +ERROR_LOG_FILE_PATH=logs/error.log + +# Other Configuration +PORT=3000 +NODE_ENV=development \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3622f3a --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +logs/*.log +.env +config/*.json +.DS_Store +*.tmp diff --git a/README.md b/README.md new file mode 100644 index 0000000..8be3499 --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# servicenow-glpi-sync/README.md + +# ServiceNow and GLPI Sync + +This project is designed to facilitate the integration between ServiceNow and GLPI, allowing for the synchronization of tickets between the two platforms. It utilizes Node.js and follows the MVC architecture pattern. + +## Table of Contents + +- [Prerequisites](#prerequisites) +- [Installation](#installation) +- [Configuration](#configuration) +- [Running the Application](#running-the-application) +- [Endpoints](#endpoints) +- [Logging](#logging) +- [Contributing](#contributing) +- [License](#license) + +## Prerequisites + +- Node.js (version 14 or higher) +- npm (Node Package Manager) +- PostgreSQL or MySQL database +- Access to ServiceNow and GLPI APIs + +## Installation + +1. Clone the repository: + + ``` + git clone https://github.com/yourusername/servicenow-glpi-sync.git + cd servicenow-glpi-sync + ``` + +2. Install the dependencies: + + ``` + npm install + ``` + +3. Copy the `.env.example` file to `.env` and fill in the required environment variables: + + ``` + cp .env.example .env + ``` + +## Configuration + +- Update the `.env` file with your ServiceNow and GLPI API credentials, database connection details, and any other necessary configurations. + +## Running the Application + +To start the application, run: + +``` +node server.js +``` + +The application will listen on the port specified in the `.env` file. + +## Endpoints + +- `POST /tickets/sync` - Synchronize tickets from ServiceNow to GLPI. +- `GET /tickets/:id` - Retrieve details of a specific ticket. +- `POST /tickets` - Create a new ticket in GLPI based on ServiceNow data. +- `PUT /tickets/:id` - Update an existing ticket in GLPI. + +## Logging + +The application uses Winston for logging. Logs are stored in the `logs` directory: + +- `app.log` - Logs application events. +- `error.log` - Logs errors that occur during execution. + +## Contributing + +Contributions are welcome! Please open an issue or submit a pull request for any improvements or bug fixes. + +## License + +This project is licensed under the MIT License. See the LICENSE file for details. \ No newline at end of file diff --git a/config/databaseConfig.js b/config/databaseConfig.js new file mode 100644 index 0000000..7ce29f4 --- /dev/null +++ b/config/databaseConfig.js @@ -0,0 +1,28 @@ +const { Sequelize } = require('sequelize'); +require('dotenv').config(); + +// Database connection configuration +const dbConfig = { + host: process.env.DB_HOST, + dialect: process.env.DB_DIALECT || 'mysql', // Default to MySQL if not specified + logging: false, // Disable logging; set to console.log to enable +}; + +// Create a new Sequelize instance +const sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASSWORD, dbConfig); + +// Test the database connection +const testConnection = async () => { + try { + await sequelize.authenticate(); + console.log('Connection to the database has been established successfully.'); + } catch (error) { + console.error('Unable to connect to the database:', error); + } +}; + +// Export the sequelize instance and the test connection function +module.exports = { + sequelize, + testConnection, +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..adbdcd2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,202 @@ +{ + "name": "servicenow-glpi-sync", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "servicenow-glpi-sync", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "axios": "^0.21.1", + "dotenv": "^10.0.0", + "pg": "^8.7.1" + } + }, + "node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/pg": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2baeeb9 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "servicenow-glpi-sync", + "version": "1.0.0", + "description": "A Node.js application for integrating ServiceNow and GLPI.", + "main": "app.js", + "scripts": { + "start": "node server.js", + "dev": "nodemon server.js", + "test": "echo \"No tests specified\" && exit 0" + }, + "dependencies": { + "axios": "^0.21.1", + "dotenv": "^10.0.0", + "pg": "^8.7.1" + }, + "author": "Your Name", + "license": "MIT" +} diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..8265be9 --- /dev/null +++ b/src/app.js @@ -0,0 +1,29 @@ +const express = require('express'); +const bodyParser = require('body-parser'); +const cors = require('cors'); +const routes = require('./src/routes/index'); +const { logger } = require('./utils/logger'); + +const app = express(); + +// Middleware setup +app.use(cors()); // Enable CORS for all routes +app.use(bodyParser.json()); // Parse JSON request bodies +app.use(bodyParser.urlencoded({ extended: true })); // Parse URL-encoded request bodies + +// Logger middleware +app.use((req, res, next) => { + logger.info(`${req.method} ${req.url}`); + next(); +}); + +// Routes setup +app.use('/api', routes); + +// Error handling middleware +app.use((err, req, res, next) => { + logger.error(err.message); + res.status(500).json({ error: 'Internal Server Error' }); +}); + +module.exports = app; \ No newline at end of file diff --git a/src/controllers/glpiController.js b/src/controllers/glpiController.js new file mode 100644 index 0000000..e69de29 diff --git a/src/controllers/hubsoftController.js b/src/controllers/hubsoftController.js new file mode 100644 index 0000000..e69de29 diff --git a/src/controllers/servicenowController.js b/src/controllers/servicenowController.js new file mode 100644 index 0000000..e69de29 diff --git a/src/data/database.js b/src/data/database.js new file mode 100644 index 0000000..e69de29 diff --git a/src/models/databaseModel.js b/src/models/databaseModel.js new file mode 100644 index 0000000..0db0d7c --- /dev/null +++ b/src/models/databaseModel.js @@ -0,0 +1,28 @@ +const { Sequelize } = require('sequelize'); +require('dotenv').config(); + +// Database connection configuration +const dbConfig = { + host: process.env.DB_HOST, + dialect: process.env.DB_DIALECT || 'mysql', // Default to MySQL if not specified + logging: false, // Disable logging; enable for debugging +}; + +// Create a new Sequelize instance +const sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASSWORD, dbConfig); + +// Test the database connection +const testConnection = async () => { + try { + await sequelize.authenticate(); + console.log('Connection to the database has been established successfully.'); + } catch (error) { + console.error('Unable to connect to the database:', error); + } +}; + +// Export the sequelize instance and the test function +module.exports = { + sequelize, + testConnection, +}; \ No newline at end of file diff --git a/src/models/ticketGlpiModel.js b/src/models/ticketGlpiModel.js new file mode 100644 index 0000000..e69de29 diff --git a/src/models/ticketSnModel.js b/src/models/ticketSnModel.js new file mode 100644 index 0000000..c186989 --- /dev/null +++ b/src/models/ticketSnModel.js @@ -0,0 +1,44 @@ +// src/models/ticketModel.js + +const { DataTypes } = require('sequelize'); +const db = require('./db'); + +// Define the Ticket model +const Ticket = db.define('Ticket', { + id: { + type: DataTypes.STRING, + primaryKey: true, + allowNull: false, + }, + short_description: { + type: DataTypes.STRING, + allowNull: false, + }, + description: { + type: DataTypes.TEXT, + allowNull: true, + }, + state: { + type: DataTypes.STRING, + allowNull: false, + }, + priority: { + type: DataTypes.STRING, + allowNull: true, + }, + created_at: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW, + }, + updated_at: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW, + }, +}, { + timestamps: false, // Disable automatic timestamps +}); + +// Export the Ticket model +module.exports = Ticket; \ No newline at end of file diff --git a/src/services/glpiService.js b/src/services/glpiService.js new file mode 100644 index 0000000..ca7cac3 --- /dev/null +++ b/src/services/glpiService.js @@ -0,0 +1,57 @@ +// src/services/glpiService.js + +const axios = require('axios'); +const { GLPI_URL, GLPI_APP_TOKEN, GLPI_USER_TOKEN } = process.env; + +// Function to create a ticket in GLPI +const createTicketInGLPI = async (ticketData) => { + try { + const response = await axios.post(`${GLPI_URL}/ticket`, ticketData, { + headers: { + 'Content-Type': 'application/json', + 'App-Token': GLPI_APP_TOKEN, + 'Authorization': `Bearer ${GLPI_USER_TOKEN}` + } + }); + return response.data; + } catch (error) { + throw new Error(`Error creating ticket in GLPI: ${error.message}`); + } +}; + +// Function to update a ticket in GLPI +const updateTicketInGLPI = async (ticketId, ticketData) => { + try { + const response = await axios.put(`${GLPI_URL}/ticket/${ticketId}`, ticketData, { + headers: { + 'Content-Type': 'application/json', + 'App-Token': GLPI_APP_TOKEN, + 'Authorization': `Bearer ${GLPI_USER_TOKEN}` + } + }); + return response.data; + } catch (error) { + throw new Error(`Error updating ticket in GLPI: ${error.message}`); + } +}; + +// Function to fetch ticket details from GLPI +const getTicketFromGLPI = async (ticketId) => { + try { + const response = await axios.get(`${GLPI_URL}/ticket/${ticketId}`, { + headers: { + 'App-Token': GLPI_APP_TOKEN, + 'Authorization': `Bearer ${GLPI_USER_TOKEN}` + } + }); + return response.data; + } catch (error) { + throw new Error(`Error fetching ticket from GLPI: ${error.message}`); + } +}; + +module.exports = { + createTicketInGLPI, + updateTicketInGLPI, + getTicketFromGLPI +}; \ No newline at end of file diff --git a/src/services/hubsoftService.js b/src/services/hubsoftService.js new file mode 100644 index 0000000..e69de29 diff --git a/src/services/servicenowService.js b/src/services/servicenowService.js new file mode 100644 index 0000000..d77c008 --- /dev/null +++ b/src/services/servicenowService.js @@ -0,0 +1,56 @@ +// src/services/servicenowService.js + +const axios = require('axios'); +const { logError, logInfo } = require('../utils/logger'); + +// Configurações da API do ServiceNow +const serviceNowConfig = { + baseURL: process.env.SERVICENOW_BASE_URL, + auth: { + username: process.env.SERVICENOW_USERNAME, + password: process.env.SERVICENOW_PASSWORD, + }, +}; + +// Função para buscar um ticket pelo ID +const getTicketById = async (ticketId) => { + try { + const response = await axios.get(`/api/now/table/ticket/${ticketId}`, serviceNowConfig); + logInfo(`Ticket ${ticketId} fetched successfully.`); + return response.data.result; + } catch (error) { + logError(`Error fetching ticket ${ticketId}: ${error.message}`); + throw error; + } +}; + +// Função para criar um novo ticket no ServiceNow +const createTicket = async (ticketData) => { + try { + const response = await axios.post('/api/now/table/ticket', ticketData, serviceNowConfig); + logInfo(`Ticket created successfully with ID: ${response.data.result.sys_id}`); + return response.data.result; + } catch (error) { + logError(`Error creating ticket: ${error.message}`); + throw error; + } +}; + +// Função para atualizar um ticket existente +const updateTicket = async (ticketId, ticketData) => { + try { + const response = await axios.put(`/api/now/table/ticket/${ticketId}`, ticketData, serviceNowConfig); + logInfo(`Ticket ${ticketId} updated successfully.`); + return response.data.result; + } catch (error) { + logError(`Error updating ticket ${ticketId}: ${error.message}`); + throw error; + } +}; + +// Exporta as funções para uso em outros módulos +module.exports = { + getTicketById, + createTicket, + updateTicket, +}; \ No newline at end of file diff --git a/src/services/syncService.js b/src/services/syncService.js new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/logger.js b/src/utils/logger.js new file mode 100644 index 0000000..9c81785 --- /dev/null +++ b/src/utils/logger.js @@ -0,0 +1,39 @@ +const winston = require('winston'); +const path = require('path'); + +// Create a logger instance with specified settings +const logger = winston.createLogger({ + level: 'info', + format: winston.format.combine( + winston.format.timestamp(), + winston.format.json() + ), + transports: [ + // Log to a file for application events + new winston.transports.File({ filename: path.join(__dirname, '../../logs/app.log') }), + // Log to a file for error events + new winston.transports.File({ filename: path.join(__dirname, '../../logs/error.log'), level: 'error' }), + ], +}); + +// If not in production, log to the console as well +if (process.env.NODE_ENV !== 'production') { + logger.add(new winston.transports.Console({ + format: winston.format.simple(), + })); +} + +// Utility function to log errors +const logError = (error) => { + logger.error(error); +}; + +// Utility function to log info messages +const logInfo = (message) => { + logger.info(message); +}; + +module.exports = { + logError, + logInfo, +}; \ No newline at end of file