commit 2ae6a66a4ec08698caafd2e50b9c207889763475 Author: david Date: Thu Aug 28 10:51:05 2025 +0200 commit inicial diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..51c91ae --- /dev/null +++ b/.env.development @@ -0,0 +1,38 @@ + + +ENVIRONMENT = development +LOCAL_TZ = Europe/Madrid +#LOG_PATH = ./app.log + +FACTUGES_HOST = 192.168.0.135 +FACTUGES_PORT = 3050 +FACTUGES_DATABASE = C:\Codigo\Output\Debug\Database\FACTUGES.FDB +FACTUGES_USER = sysdba +FACTUGES_PASSWORD = masterkey + +FACTUGES_ID_EMPRESA = 1 +FACTUGES_CONTRATO_ID_TIENDA = 1 +FACTUGES_CONTRATO_SITUACION = "PENDIENTE" +FACTUGES_CONTRATO_ENVIADA_REVISADA = 10 +FACTUGES_CONTRATO_TIPO_DETALLE = "Concepto" +FACTUGES_NOMBRE_TARIFA = TARIFA 2024 +FACTUGES_PRECIO_PUNTO = 3.31 + +UECKO_MYSQL_HOST = 192.168.0.116 +UECKO_MYSQL_PORT = 3306 +UECKO_MYSQL_DATABASE = uecko +UECKO_MYSQL_USER = rodax +UECKO_MYSQL_PASSWORD = rodax + +UECKO_DEFAULT_IVA = 2100 +UECKO_DEFAULT_CURRENCY_CODE = EUR +UECKO_DEFAULT_VALIDEZ = "30 días" +UECKO_DEFAULT_LOPD = "" +UECKO_DEFAULT_NOTAS = "" +UECKO_DEFAULT_FORMA_PAGO = "50% a la aceptación y 50% a la finalización" + +BREVO_API_KEY = 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 +BREVO_EMAIL_TEMPLATE = 1 +MAIL_FROM = 'no-reply@presupuestos.uecko.com' +MAIL_TO = 'soporte@rodax-software.com' + diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..f5c30c2 --- /dev/null +++ b/.env.production @@ -0,0 +1,36 @@ +ENVIRONMENT = production +LOCAL_TZ = Europe/Madrid +#LOG_PATH = /var/log/uecko_sync_app/uecko_sync_app.log + +FACTUGES_HOST = 83.48.36.69 +FACTUGES_PORT = 3050 +FACTUGES_DATABASE = D:\RODAX\FACTUGES\BD\FACTUGES_FABRICA.FDB +FACTUGES_USER = sysdba +FACTUGES_PASSWORD = abeto2010 + +FACTUGES_ID_EMPRESA = 1 +FACTUGES_CONTRATO_ID_TIENDA = 1 +FACTUGES_CONTRATO_SITUACION = "PENDIENTE" +FACTUGES_CONTRATO_ENVIADA_REVISADA = 10 +FACTUGES_CONTRATO_TIPO_DETALLE = "Concepto" +FACTUGES_NOMBRE_TARIFA = TARIFA 2024 +FACTUGES_PRECIO_PUNTO = 3.31 + +UECKO_MYSQL_HOST = mariadb +UECKO_MYSQL_PORT = 3306 +UECKO_MYSQL_DATABASE = uecko +UECKO_MYSQL_USER = uecko +UECKO_MYSQL_PASSWORD = u8Ax5Nw3%sjd + +UECKO_DEFAULT_IVA = 2100 +UECKO_DEFAULT_CURRENCY_CODE = EUR +UECKO_DEFAULT_VALIDEZ = "30 días" +UECKO_DEFAULT_LOPD = "" +UECKO_DEFAULT_NOTAS = "" +UECKO_DEFAULT_FORMA_PAGO = "50% a la aceptación y 50% a la finalización" + +BREVO_API_KEY = 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 +BREVO_EMAIL_TEMPLATE = 1 +MAIL_FROM = 'no-reply@presupuestos.uecko.com' +MAIL_TO = 'pedidos@uecko.com' + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7188996 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +venv/ +__pycache__ +.env +input/ +output/ +FACTUGES.FDB +last_execution*.txt +*.json +*.log \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..6dd71b2 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "bracketSpacing": true, + "useTabs": false, + "printWidth": 100, + "tabWidth": 4, + "semi": true, + "singleQuote": false, + "rcVerbose": true +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..89535bd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +# syntax=docker/dockerfile:1.4 + +# Usa una imagen base de Python +FROM python:3.12.6-slim-bookworm AS python_script + +RUN apt-get update +RUN apt-get install libfbclient2 -y + +# Establece el directorio de trabajo dentro del contenedor +WORKDIR /opt/uecko_sync_app + +# Copia los archivos del proyecto al contenedor +COPY . . +COPY ./.env.production ./.env + +# Instala las dependencias de Python +RUN pip install --no-cache-dir -r requirements.txt + +# Instala cron en el contenedor +RUN apt-get update && apt-get install -y cron nano + +# Copia el archivo de cron dentro del contenedor +COPY cronjob /etc/cron.d/cronjob + +# Da permisos de ejecución al archivo cronjob +RUN chmod 0644 /etc/cron.d/cronjob + +# Aplica la configuración de cronjob +RUN crontab /etc/cron.d/cronjob + +# Crea un archivo log para cron +RUN touch /var/log/cron.log + +#RUN mkdir -p /var/log/uecko_sync_app +#RUN touch /var/log/uecko_sync_app/uecko_sync_app.log + +# Comando para iniciar cron y mantener el contenedor en ejecución +CMD cron && tail -f /var/log/cron.log diff --git a/Dockerfile.firebird b/Dockerfile.firebird new file mode 100644 index 0000000..03a1baa --- /dev/null +++ b/Dockerfile.firebird @@ -0,0 +1,43 @@ +# syntax=docker/dockerfile:1.4 + +FROM debian:bookworm-slim AS build + +ENV FIREBIRD_PATH=/opt/firebird +ENV FIREBIRD_DB_PASSWORD=masterkey +ENV FIREBIRD_DB_PASSWORD_DEFAULT=masterkey + +RUN apt-get update +RUN apt-get install wget -y +RUN apt-get install libstdc++5 -y +RUN apt-get install xinetd -y +RUN wget http://sourceforge.net/projects/firebird/files/firebird-linux-amd64/2.1.7-Release/FirebirdCS-2.1.7.18553-0.amd64.tar.gz +RUN tar -vzxf FirebirdCS-2.1.7.18553-0.amd64.tar.gz +RUN rm FirebirdCS-2.1.7.18553-0.amd64.tar.gz +RUN rm FirebirdCS-2.1.7.18553-0.amd64/install.sh +RUN rm FirebirdCS-2.1.7.18553-0.amd64/scripts/postinstall.sh + +COPY ./firebird/install.sh FirebirdCS-2.1.7.18553-0.amd64 +COPY ./firebird/postinstall.sh FirebirdCS-2.1.7.18553-0.amd64/scripts + +# Otorgar permisos de ejecución al script install.sh +RUN chmod +x FirebirdCS-2.1.7.18553-0.amd64/install.sh +RUN chmod +x FirebirdCS-2.1.7.18553-0.amd64/scripts/postinstall.sh + +RUN cd FirebirdCS-2.1.7.18553-0.amd64 && ./install.sh ${FIREBIRD_DB_PASSWORD_DEFAULT} +RUN rm -r FirebirdCS-2.1.7.18553-0.amd64 + +COPY ./firebird/fbudflib2.so ${FIREBIRD_PATH}/UDF + +COPY ./firebird/launch.sh ${FIREBIRD_PATH} +RUN chmod +x ${FIREBIRD_PATH}/launch.sh + +RUN cd ${FIREBIRD_PATH} && mkdir DBA && chown firebird:firebird DBA && chmod -R 770 DBA +RUN cd / && mkdir dba && chown firebird:firebird dba && chmod -R 770 dba + +#RUN cp ${FIREBIRD_PATH}/lib/libfbclient.so.2.1.7 /usr/lib/libfbclient.so.2.1.7 +#RUN ln -s /usr/lib/libfbclient.so.2.1.7 /usr/lib/libfbclient.so.2 +#RUN ln -s /usr/lib//usr/lib/libfbclient.so.2 /usr/lib/libfbclient.so + +EXPOSE 3050/tcp +WORKDIR ${FIREBIRD_PATH} +ENTRYPOINT ${FIREBIRD_PATH}/launch.sh diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/__version__.py b/app/__version__.py new file mode 100644 index 0000000..1022a28 --- /dev/null +++ b/app/__version__.py @@ -0,0 +1 @@ +__version__ = "1.0.8" diff --git a/app/config/__init__.py b/app/config/__init__.py new file mode 100644 index 0000000..b3bddb7 --- /dev/null +++ b/app/config/__init__.py @@ -0,0 +1,3 @@ +from .settings import load_config +from .setup_logging import setup_logging +from .setup_brevo import setup_brevo \ No newline at end of file diff --git a/app/config/settings.py b/app/config/settings.py new file mode 100644 index 0000000..cf70f3e --- /dev/null +++ b/app/config/settings.py @@ -0,0 +1,46 @@ +import os +from os.path import join, dirname +from dotenv import load_dotenv + + +def load_config(): + dotenv_path = join(dirname(__file__), '../../.env') + load_dotenv(dotenv_path) + + return { + 'ENVIRONMENT': os.getenv('ENVIRONMENT'), + 'LOCAL_TZ': os.getenv('LOCAL_TZ', 'Europe/Madrid'), + # 'LOG_PATH': os.getenv('LOG_PATH', 'app.log'), + + 'FACTUGES_HOST': os.getenv('FACTUGES_HOST'), + 'FACTUGES_PORT': os.getenv('FACTUGES_PORT'), + 'FACTUGES_DATABASE': os.getenv('FACTUGES_DATABASE'), + 'FACTUGES_USER': os.getenv('FACTUGES_USER'), + 'FACTUGES_PASSWORD': os.getenv('FACTUGES_PASSWORD'), + + 'UECKO_MYSQL_HOST': os.getenv('UECKO_MYSQL_HOST'), + 'UECKO_MYSQL_PORT': os.getenv('UECKO_MYSQL_PORT', 3306), + 'UECKO_MYSQL_DATABASE': os.getenv('UECKO_MYSQL_DATABASE'), + 'UECKO_MYSQL_USER': os.getenv('UECKO_MYSQL_USER'), + 'UECKO_MYSQL_PASSWORD': os.getenv('UECKO_MYSQL_PASSWORD'), + + 'FACTUGES_ID_EMPRESA': os.getenv('FACTUGES_ID_EMPRESA'), + 'FACTUGES_PRECIO_PUNTO': os.getenv('FACTUGES_PRECIO_PUNTO'), + 'FACTUGES_NOMBRE_TARIFA': os.getenv('FACTUGES_NOMBRE_TARIFA'), + 'FACTUGES_CONTRATO_ID_TIENDA': os.getenv('FACTUGES_CONTRATO_ID_TIENDA'), + 'FACTUGES_CONTRATO_SITUACION': os.getenv('FACTUGES_CONTRATO_SITUACION'), + 'FACTUGES_CONTRATO_ENVIADA_REVISADA': os.getenv('FACTUGES_CONTRATO_ENVIADA_REVISADA'), + 'FACTUGES_CONTRATO_TIPO_DETALLE': os.getenv('FACTUGES_CONTRATO_TIPO_DETALLE'), + + 'UECKO_DEFAULT_IVA': os.getenv('UECKO_IVA', 2100), + 'UECKO_DEFAULT_CURRENCY_CODE': os.getenv('UECKO_CURRENCY_CODE', "EUR"), + 'UECKO_DEFAULT_VALIDEZ': os.getenv('UECKO_DEFAULT_VALIDEZ', ""), + 'UECKO_DEFAULT_LOPD': os.getenv('UECKO_DEFAULT_LOPD', ""), + 'UECKO_DEFAULT_NOTAS': os.getenv('UECKO_DEFAULT_NOTAS', ""), + 'UECKO_DEFAULT_FORMA_PAGO': os.getenv('UECKO_DEFAULT_FORMA_PAGO', ""), + + 'BREVO_API_KEY': os.getenv('BREVO_API_KEY'), + 'BREVO_EMAIL_TEMPLATE': os.getenv("BREVO_EMAIL_TEMPLATE"), + 'MAIL_FROM': os.getenv('MAIL_FROM'), + 'MAIL_TO': os.getenv('MAIL_TO'), + } diff --git a/app/config/setup_brevo.py b/app/config/setup_brevo.py new file mode 100644 index 0000000..d9d2db0 --- /dev/null +++ b/app/config/setup_brevo.py @@ -0,0 +1,10 @@ +from __future__ import print_function +import brevo_python + +def setup_brevo(config): + + # Configure API key authorization: api-key + configuration = brevo_python.Configuration() + configuration.api_key['api-key'] = config['BREVO_API_KEY'] + + return configuration diff --git a/app/config/setup_logging.py b/app/config/setup_logging.py new file mode 100644 index 0000000..b90a010 --- /dev/null +++ b/app/config/setup_logging.py @@ -0,0 +1,18 @@ +import logging +import sys +from logging.handlers import RotatingFileHandler + +def setup_logging(): + logging.basicConfig(stream=sys.stdout, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + + +def setup_rotating_logging(log_path): + # Rotación de logs con un tamaño máximo de 5 MB y mantiene 15 archivos de backup + handler = RotatingFileHandler(log_path, maxBytes=5*1024*1024, backupCount=15, encoding="utf8") + + # Configuración básica de logging + logging.basicConfig( + handlers=[handler], + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' + ) diff --git a/app/db/__init__.py b/app/db/__init__.py new file mode 100644 index 0000000..b927063 --- /dev/null +++ b/app/db/__init__.py @@ -0,0 +1,5 @@ +from .db_connection import get_factuges_connection +from .db_connection import get_mysql_connection +from .sync_catalog import sync_catalog +from .sync_dealers import sync_dealers +from .sync_orders import sync_orders diff --git a/app/db/db_connection.py b/app/db/db_connection.py new file mode 100644 index 0000000..c398d85 --- /dev/null +++ b/app/db/db_connection.py @@ -0,0 +1,44 @@ +import fdb +import mysql.connector +import logging + + +def get_factuges_connection(config): + try: + conn = fdb.connect( + host=config['FACTUGES_HOST'], + port=int(config['FACTUGES_PORT']), + database=config['FACTUGES_DATABASE'], + user=config['FACTUGES_USER'], + password=config['FACTUGES_PASSWORD'], + charset='UTF8' + ) + logging.info( + f"Conexión a la base de datos FactuGES establecida: {config['FACTUGES_HOST']} with database:{config['FACTUGES_DATABASE']} - using user:{config['FACTUGES_USER']}") + return conn + except Exception as e: + logging.error("Error al conectar a la base de datos FactuGES.") + logging.error( + f"(ERROR) Failed to establish connection to: {config['FACTUGES_HOST']} with database:{config['FACTUGES_DATABASE']} - using user:{config['FACTUGES_USER']}") + logging.error(str(e)) + raise e + + +def get_mysql_connection(config): + try: + conn = mysql.connector.connect( + host=config['UECKO_MYSQL_HOST'], + port=config['UECKO_MYSQL_PORT'], + database=config['UECKO_MYSQL_DATABASE'], + user=config['UECKO_MYSQL_USER'], + password=config['UECKO_MYSQL_PASSWORD'] + ) + logging.info( + f"Conexión a la base de datos MySQL establecida a: {config['UECKO_MYSQL_HOST']} with database:{config['UECKO_MYSQL_DATABASE']} - using user:{config['UECKO_MYSQL_USER']}") + return conn + except Exception as e: + logging.error("Error al conectar a la base de datos MySQL.") + logging.error( + f"(ERROR) Failed to establish connection to: {config['UECKO_MYSQL_HOST']} with database:{config['UECKO_MYSQL_DATABASE']} - using user:{config['UECKO_MYSQL_USER']}") + logging.error(str(e)) + raise e diff --git a/app/db/sync_catalog.py b/app/db/sync_catalog.py new file mode 100644 index 0000000..86ce471 --- /dev/null +++ b/app/db/sync_catalog.py @@ -0,0 +1,219 @@ +import logging +from uuid import uuid4 +from config import load_config + + +def sync_catalog(conn_factuges, conn_mysql, last_execution_date): + config = load_config() + + logging.info(f"Tarifa: {config['FACTUGES_NOMBRE_TARIFA']}") + logging.info(f"Precio punto: {config['FACTUGES_PRECIO_PUNTO']}") + + # Construir la consulta SQL con la condición de fecha de modificación + consulta_sql_art_modificados = ( + f"SELECT art.id || '' AS id, art.tarifa as tarifa, COALESCE(art.referencia,'') AS referencia, " + f"TRIM(COALESCE(art.familia, '') || ' ' || COALESCE(art.referencia_prov, '') || ' ' || COALESCE(art.descripcion, '')) as descripcion_es, " + f"TRIM(COALESCE(art_idioma_en.descripcion, '')) AS descripcion_en, " + f"TRUNC(art.precio_coste * 100) || '' AS puntos, " + f"TRUNC(ROUND(art.precio_coste * { + config['FACTUGES_PRECIO_PUNTO']}, 2) * 100) || '' AS pvp " + f"FROM articulos AS art " + f"LEFT JOIN ARTICULOS_IDIOMAS AS art_idioma_en ON art.id = art_idioma_en.id_articulo AND art_idioma_en.id_idioma = 2 " + f"WHERE " + f"(art.eliminado = 0) AND " + f"(art.tarifa = '{config['FACTUGES_NOMBRE_TARIFA']}') " + f"AND (art.FECHA_MODIFICACION > '{last_execution_date}')" + ) + + consulta_sql_all_tarifa = ( + f"SELECT art.id || '' AS id, art.tarifa as tarifa " + f"FROM articulos AS art " + f"WHERE " + f"(art.eliminado = 0) AND " + f"(art.tarifa = '{config['FACTUGES_NOMBRE_TARIFA']}') " + ) + + # Crear un cursor para ejecutar consultas SQL + cursor_FactuGES = None + try: + cursor_FactuGES = conn_factuges.cursor() + # Ejecutar la consulta de articulos modificados + cursor_FactuGES.execute(consulta_sql_art_modificados) + filas = cursor_FactuGES.fetchall() + except Exception as e: + if cursor_FactuGES is not None: + cursor_FactuGES.close() + logging.error(f"(ERROR) Failed to fetch from database:{ + config['FACTUGES_DATABASE']} - using user:{config['FACTUGES_USER']}") + logging.error(e) + raise e + + # Obtener los nombres de las columnas + columnas = [desc[0] for desc in cursor_FactuGES.description] + cursor_FactuGES.close() + + # Convertir las filas en diccionarios con nombres de columnas como claves + tuplas_seleccionadas = [] + for fila in filas: + tupla = dict(zip(columnas, fila)) + tuplas_seleccionadas.append(tupla) + + logging.info(f"Catalog rows to be processed: {len(tuplas_seleccionadas)}") + + # Verificar si hay filas en el resultado + if tuplas_seleccionadas: + insertar_datos(conn_mysql, tuplas_seleccionadas, config) + else: + logging.info( + "There are no new or modified catalog rows since the last run.") + + # Verificamos que en el catálogo de mysql solo hay los artículos del catálogo de FactuGES + # es decir, que si un artículo lo asignan a otra tarifa debe desaparecer del catálogo mysql + try: + cursor_FactuGES.execute(consulta_sql_all_tarifa) + filas = cursor_FactuGES.fetchall() + cursor_FactuGES.close() + + # Crear un conjunto con los IDs [0] de los artículos en FactuGES para una búsqueda rápida + ids_factuges = {str(fila[0]) for fila in filas} + logging.info(f"{config['FACTUGES_NOMBRE_TARIFA']} rows to be processed: { + len(ids_factuges)}") + + # Verificar si hay filas en el resultado + if ids_factuges: + eliminar_datos(conn_mysql, ids_factuges, config) + else: + logging.info(f"There are no rows in the { + config['FACTUGES_NOMBRE_TARIFA']}.") + except Exception as e: + if cursor_FactuGES is not None: + cursor_FactuGES.close() + logging.error(f"(ERROR) Failed to fetch from database:{ + config['FACTUGES_DATABASE']} - using user:{config['FACTUGES_USER']}") + logging.error(e) + raise e + + +def eliminar_datos(conn_mysql, ids_factuges, config): + # Recorrer todos los articulos del catálogo web para ver si estan en filas, si no están se eliminan + + select_all_catalog_query = ( + "SELECT catalog.id, catalog.id_article FROM catalog" + ) + + delete_catalog_query = ( + "DELETE FROM catalog WHERE catalog.id_article = %s" + ) + + cursorMySQL = None + try: + cursorMySQL = conn_mysql.cursor() + cursorMySQL.execute(select_all_catalog_query) + catalog_rows = cursorMySQL.fetchall() + logging.info( + f">>>>Comprobar que todos los artículos del catálogo existen en FactuGES") + + ids_a_eliminar = [ + catalog_row[1] # id_article + for catalog_row in catalog_rows + if str(catalog_row[1]) not in ids_factuges + ] + + if ids_a_eliminar: + logging.info(f"Deleting articles: {ids_a_eliminar}") + cursorMySQL.executemany(delete_catalog_query, [( + id_article,) for id_article in ids_a_eliminar]) + else: + logging.info("No articles to delete.") + + except Exception as e: + # Escribir el error en el archivo de errores + logging.error(str(e)) + raise e # Re-lanzar la excepción para detener el procesamiento + finally: + # Cerrar la conexión + if cursorMySQL is not None: + cursorMySQL.close() + + +def insertar_datos(conn_mysql, filas, config): + + insert_catalog_query = ( + "INSERT INTO catalog (id, catalog_name, id_article, points, retail_price, created_at, updated_at) " + "VALUES (%s, %s, %s, %s, %s, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)" + ) + + insert_translation_query = ( + "INSERT INTO catalog_translations (id, lang_code, catalog_id, description) " + "VALUES (%s, %s, %s, %s)" + ) + + update_catalog_query = ( + "UPDATE catalog set " + "points = %s, " + "retail_price = %s, " + "updated_at = Now() " + "WHERE id_article=%s" + ) + + update_translation_query = ( + "UPDATE catalog_translations SET " + "description = %s " + "WHERE lang_code = %s AND " + "catalog_id IN (SELECT catalog.id FROM catalog WHERE catalog.id_article = %s)" + ) + + select_catalog_query = ( + "SELECT count(catalog.id) FROM catalog WHERE catalog.id_article = %s" + ) + + cursorMySQL = None + try: + cursorMySQL = conn_mysql.cursor() + # Insertar datos en la tabla 'catalog' + for articulo in filas: + # Generar un ID único para la tabla catalog + id_catalog = str(uuid4()) + id_article = int(articulo['ID']) + points = int(articulo['PUNTOS']) + retail_price = int(articulo['PVP']) + tarifa = config['FACTUGES_NOMBRE_TARIFA'] + + cursorMySQL.execute(select_catalog_query, (id_article, )) + row_count = cursorMySQL.fetchone() + is_new = row_count[0] < 1 + + if is_new: + logging.info(f"Inserting article {id_article} {tarifa}") + cursorMySQL.execute( + insert_catalog_query, (id_catalog, tarifa, id_article, points, retail_price)) + else: + logging.info(f"Updating article {id_article} {tarifa}") + cursorMySQL.execute(update_catalog_query, + (points, retail_price, id_article)) + + # Insertar traducciones en la tabla 'catalog_translations' + for lang_code, desc_key in [('es', 'DESCRIPCION_ES'), ('en', 'DESCRIPCION_EN')]: + descripcion_traducida = articulo.get(desc_key, '') + if descripcion_traducida: + if (is_new): + logging.info(f"Inserting translation { + lang_code} {descripcion_traducida}") + # Generar un ID único para cada traducción + id_translation = str(uuid4()) + cursorMySQL.execute( + insert_translation_query, (id_translation, lang_code, id_catalog, descripcion_traducida)) + else: + logging.info(f"Updating translation { + lang_code} {descripcion_traducida}") + cursorMySQL.execute( + update_translation_query, (descripcion_traducida, lang_code, id_article)) + + except Exception as e: + # Escribir el error en el archivo de errores + logging.error(str(e)) + raise e # Re-lanzar la excepción para detener el procesamiento + finally: + # Cerrar la conexión + if cursorMySQL is not None: + cursorMySQL.close() diff --git a/app/db/sync_dealers.py b/app/db/sync_dealers.py new file mode 100644 index 0000000..f80d2c4 --- /dev/null +++ b/app/db/sync_dealers.py @@ -0,0 +1,320 @@ +import logging +from uuid import uuid4 +from config import load_config +from utils import hashPassword + + +def sync_dealers(conn_factuges, conn_mysql, last_execution_date): + config = load_config() + + consulta_factuges = ( + f"select V_CONTACTOS.ID as ID, V_CONTACTOS.NOMBRE, V_CONTACTOS.IDIOMA_ISO, " + f"CLIENTES_DATOS.DIST_EMAIL, CLIENTES_DATOS.DIST_PASSWORD, CLIENTES_DATOS.BLOQUEADO " + f"from V_CONTACTOS " + f"left OUTER JOIN CLIENTES_DATOS on (V_CONTACTOS.ID = CLIENTES_DATOS.ID_CLIENTE) " + f"where (V_CONTACTOS.ID_CATEGORIA = 1) " + f"and (V_CONTACTOS.ID_EMPRESA = '{config['FACTUGES_ID_EMPRESA']}') " + f"and (CLIENTES_DATOS.TIENDA_WEB = 1) " + f"and (V_CONTACTOS.FECHA_MODIFICACION is not null) " + f"and (V_CONTACTOS.FECHA_MODIFICACION > '{last_execution_date}')" + ) + + consulta_dealer_uecko = ( + "SELECT dealers.id, dealers.id_contact, dealers.user_id, dealers.status, dealers.updated_at " + "FROM dealers " + "WHERE dealers.id_contact = %s" + ) + + cursor_FactuGES = None + try: + cursor_FactuGES = conn_factuges.cursor() + # Ejecutar la consulta + cursor_FactuGES.execute(consulta_factuges) + contactos = cursor_FactuGES.fetchall() + except Exception as e: + if cursor_FactuGES is not None: + cursor_FactuGES.close() + logging.error(f"(ERROR) Failed to fetch from database:{ + config['FACTUGES_DATABASE']} - using user:{config['FACTUGES_USER']}") + logging.error(e) + raise e + + columnas_contacto = [desc[0] for desc in cursor_FactuGES.description] + cursor_FactuGES.close() + + contactos_seleccionados = [] + for contacto in contactos: + tupla = dict(zip(columnas_contacto, contacto)) + contactos_seleccionados.append(tupla) + + logging.info(f"Contacts rows to be processed: { + len(contactos_seleccionados)}") + + if contactos_seleccionados: + for contacto in contactos_seleccionados: + cursor_MySQL = None + try: + cursor_MySQL = conn_mysql.cursor() + cursor_MySQL.execute(consulta_dealer_uecko, (contacto['ID'],)) + dealer = cursor_MySQL.fetchone() + + if (dealer is None): + user_id = insert_user(conn_mysql, contacto, config) + insert_dealer(conn_mysql, user_id, contacto, config) + logging.info(f"Inserted user and dealer from contact { + contacto['ID']} {contacto['NOMBRE']}") + else: + # 0 => 'ID' + # 2 => 'USER_ID' + # Casos: + # - Cambio en el nombre del distribuidor + # - Distribuidor bloqueado / desbloqueado + # - Usuario con baja lógica + + id = dealer[0] + user_id = dealer[2] + + update_dealer(conn_mysql, id, contacto, config) + update_user(conn_mysql, user_id, contacto, config) + + except Exception as e: + # Escribir el error en el archivo de errores + logging.error(str(e)) + raise e # Re-lanzar la excepción para detener el procesamiento + finally: + # Cerrar la conexión + if cursor_MySQL is not None: + cursor_MySQL.close() + else: + logging.info( + "There are no new or modified contacts rows since the last run.") + + # Revisar todos los distribuidores dados de alta y + # comprobar si en FactuGES siguen estando activos (tienda_web = 1) + # + # - USUARIO DISABLED + dealers = fetch_all_dealers(conn_mysql, config) + for dealer in dealers: + # dealer[1] => id_contact + if (dealer[1] is not None) and (not is_valid_dealer(conn_factuges, dealer[1], config)): + user_id = dealer[7] # 7 => user_id + + # Desactivar el distribuidor + disable_dealer(conn_mysql, dealer[0], config) # 0 => id + + # Baja lógica del usuario del dealer + soft_delete_user(conn_mysql, user_id, config) + logging.info(f"Deleted dealer and user from contact {dealer[1]}") + + +def fetch_all_dealers(conn_mysql, config): + consulta = ( + f"SELECT dealers.id, dealers.id_contact, dealers.default_payment_method, dealers.default_notes, " + f"dealers.default_legal_terms, dealers.default_quote_validity, dealers.status, dealers.user_id " + f"FROM dealers " + ) + + cursor_MySQL = None + try: + cursor_MySQL = conn_mysql.cursor() + cursor_MySQL.execute(consulta) + return cursor_MySQL.fetchall() + except Exception as e: + # Escribir el error en el archivo de errores + logging.error(str(e)) + raise e # Re-lanzar la excepción para detener el procesamiento + finally: + if cursor_MySQL is not None: + cursor_MySQL.close() + + +def is_valid_dealer(conn_factuges, id_contact, config): + consulta = ( + f"select CLIENTES_DATOS.ID_CLIENTE from CLIENTES_DATOS where CLIENTES_DATOS.ID_CLIENTE = { + id_contact} and CLIENTES_DATOS.TIENDA_WEB = 1" + ) + + cursor_FactuGES = None + try: + cursor_FactuGES = conn_factuges.cursor() + cursor_FactuGES.execute(consulta) + exists = cursor_FactuGES.fetchone() + return exists is not None + + except Exception as e: + # Escribir el error en el archivo de errores + logging.error(str(e)) + raise e # Re-lanzar la excepción para detener el procesamiento + finally: + if cursor_FactuGES is not None: + cursor_FactuGES.close() + + +def insert_user(conn_mysql, data, config): + cursor_MySQL = None + try: + cursor_MySQL = conn_mysql.cursor() + id = str(uuid4()) + name = str(data['NOMBRE']) + email = str(data['DIST_EMAIL']) + password = hashPassword(str(data['DIST_PASSWORD'])) + lang_code = str(data['IDIOMA_ISO']) + + insert_data = ( + "INSERT INTO users (id, name, email, password, lang_code, roles, created_at, updated_at) VALUES (" + "%s, %s, %s, %s, %s, 'ROLE_USER', Now(), Now()" + ")" + ) + + cursor_MySQL.execute( + insert_data, (id, name, email, password, lang_code)) + return id + except Exception as e: + # Escribir el error en el archivo de errores + logging.error(str(e)) + raise e # Re-lanzar la excepción para detener el procesamiento + finally: + if cursor_MySQL is not None: + cursor_MySQL.close() + + +def update_user(conn_mysql, user_id, data, config): + cursor_MySQL = None + try: + cursor_MySQL = conn_mysql.cursor() + name = str(data['NOMBRE']) + email = str(data['DIST_EMAIL']) + password = hashPassword(str(data['DIST_PASSWORD'])) + lang_code = str(data['IDIOMA_ISO']) + + update_data = ( + "UPDATE users set " + "name = %s, " + "email = %s, " + "password = %s, " + "lang_code = %s, " + "updated_at = Now(), " + "deleted_at = NULL " + "WHERE id = %s" + ) + + cursor_MySQL.execute( + update_data, (name, email, password, lang_code, user_id)) + logging.info(f"Updated user from contact {data['ID']} {name}") + + except Exception as e: + # Escribir el error en el archivo de errores + logging.error(str(e)) + raise e # Re-lanzar la excepción para detener el procesamiento + finally: + if cursor_MySQL is not None: + cursor_MySQL.close() + + +def insert_dealer(conn_mysql, user_id, data, config): + cursor_MySQL = None + try: + cursor_MySQL = conn_mysql.cursor() + id = str(uuid4()) + id_contact = str(data['ID']) + name = str(data['NOMBRE']) + default_payment_method = str(config['UECKO_DEFAULT_FORMA_PAGO']) + default_notes = str(config['UECKO_DEFAULT_NOTAS']) + default_legal_terms = str(config['UECKO_DEFAULT_LOPD']) + default_quote_validity = str(config['UECKO_DEFAULT_VALIDEZ']) + default_tax = str(config["UECKO_DEFAULT_IVA"]) + lang_code = str(data['IDIOMA_ISO']) + currency_code = str(config["UECKO_DEFAULT_CURRENCY_CODE"]) + + insert_data = ( + "INSERT INTO dealers (id, id_contact, name, default_payment_method, default_notes, " + "default_legal_terms, default_quote_validity, default_tax, lang_code, " + "currency_code, user_id, status, created_at, updated_at ) values (" + "%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'actived', Now(), Now()" + ")" + ) + + cursor_MySQL.execute(insert_data, (id, id_contact, name, default_payment_method, default_notes, + default_legal_terms, default_quote_validity, default_tax, lang_code, currency_code, user_id)) + return id + except Exception as e: + # Escribir el error en el archivo de errores + logging.error(str(e)) + raise e # Re-lanzar la excepción para detener el procesamiento + finally: + if cursor_MySQL is not None: + cursor_MySQL.close() + + +def update_dealer(conn_mysql, dealer_id, data, config): + cursor_MySQL = None + try: + cursor_MySQL = conn_mysql.cursor() + name = str(data['NOMBRE']) + status = 'disabled' if data['BLOQUEADO'] == 1 else 'actived' + + insert_data = ( + "UPDATE dealers SET name = %s, status = %s WHERE dealers.id = %s" + ) + cursor_MySQL.execute(insert_data, (name, status, dealer_id)) + logging.info(f"Dealer with id = {dealer_id} name = {name} updated") + except Exception as e: + # Escribir el error en el archivo de errores + logging.error(str(e)) + raise e # Re-lanzar la excepción para detener el procesamiento + finally: + if cursor_MySQL is not None: + cursor_MySQL.close() + + +def soft_delete_user(conn_mysql, user_id, config): + consulta_sql = "UPDATE users SET users.deleted_at = NOW() WHERE users.id = %s AND users.roles = 'ROLE_USER'" + cursor_MySQL = None + + try: + cursor_MySQL = conn_mysql.cursor() + cursor_MySQL.execute(consulta_sql, (user_id, )) + logging.info(f"User with id = {id} soft delete") + except Exception as e: + # Escribir el error en el archivo de errores + logging.error(str(e)) + raise e # Re-lanzar la excepción para detener el procesamiento + finally: + if cursor_MySQL is not None: + cursor_MySQL.close() + + +def active_dealer(conn_mysql, id, config): + consulta_sql = "UPDATE dealers SET status = 'actived' WHERE dealers.id = %s" + cursor_MySQL = None + + try: + cursor_MySQL = conn_mysql.cursor() + cursor_MySQL.execute(consulta_sql, (id, )) + logging.info(f"Dealer with id = {id} actived") + + except Exception as e: + # Escribir el error en el archivo de errores + logging.error(str(e)) + raise e # Re-lanzar la excepción para detener el procesamiento + finally: + if cursor_MySQL is not None: + cursor_MySQL.close() + + +def disable_dealer(conn_mysql, id, config): + consulta_sql = "UPDATE dealers SET status = 'disabled' WHERE dealers.id = %s" + cursor_MySQL = None + + try: + cursor_MySQL = conn_mysql.cursor() + cursor_MySQL.execute(consulta_sql, (id, )) + logging.info(f"Dealer with id = {id} disabled") + + except Exception as e: + # Escribir el error en el archivo de errores + logging.error(str(e)) + raise e # Re-lanzar la excepción para detener el procesamiento + finally: + if cursor_MySQL is not None: + cursor_MySQL.close() diff --git a/app/db/sync_orders.py b/app/db/sync_orders.py new file mode 100644 index 0000000..8361ff5 --- /dev/null +++ b/app/db/sync_orders.py @@ -0,0 +1,216 @@ +import logging +from decimal import Decimal +from config import load_config +from utils import text_converter + + +def sync_orders(conn_factuges, conn_mysql): + config = load_config() + + consulta_quotes_uecko = ( + "SELECT quotes.id, quotes.date_sent, quotes.reference, quotes.customer_reference, " + "quotes.customer_information, quotes.dealer_id, dealers.id_contact, dealers.name " + "FROM quotes INNER JOIN dealers ON (dealers.id = quotes.dealer_id) " + "WHERE quotes.date_sent IS NOT NULL AND " + "quotes.id_contract IS NULL" + ) + + update_quotes_uecko = ( + "UPDATE quotes SET " + "id_contract = %s " + "WHERE id = %s" + ) + + inserted_orders = [] + cursor_MySQL = None + + try: + cursor_MySQL = conn_mysql.cursor() + cursor_MySQL.execute(consulta_quotes_uecko) + quotes = cursor_MySQL.fetchall() + + quote_columns = [desc[0] for desc in cursor_MySQL.description] + selected_quotes = [] + + for quote in quotes: + tupla = dict(zip(quote_columns, quote)) + selected_quotes.append(tupla) + + logging.info(f"Quotes rows to be processed: {len(selected_quotes)}") + if selected_quotes: + + for quote in selected_quotes: + logging.info(f"Quote reference: {quote['reference']}") + + if (quote['id_contact'] is None): + logging.info( + f"Error: Quote unprocesable (id_contact missing)") + continue + + items = fetch_quote_items(conn_mysql, quote['id']) + + id_contrato = insert_quote_to_factuges( + conn_factuges, quote, items, config) + cursor_MySQL.execute(update_quotes_uecko, + (int(id_contrato), str(quote['id']))) + + inserted_orders.append({ + "customer_reference": quote['customer_reference'], + "dealer_name": quote['name'], + }) + + cursor_MySQL.close() + return inserted_orders + + except Exception as e: + # Escribir el error en el archivo de errores + logging.error(msg=e, stack_info=True) + raise e # Re-lanzar la excepción para detener el procesamiento + finally: + # Cerrar la conexión + if cursor_MySQL is not None: + cursor_MySQL.close() + + +def fetch_quote_items(conn_mysql, quote_id): + consulta_quotes_items_uecko = ( + "SELECT quote_items.item_id, quote_items.id_article, quote_items.position, " + "quote_items.description, quote_items.quantity, quote_items.unit_price, " + "quote_items.discount, quote_items.total_price " + "FROM quote_items " + "WHERE quote_items.quote_id = %s " + "ORDER BY quote_items.position" + ) + + cursor_MySQL = None + try: + cursor_MySQL = conn_mysql.cursor() + cursor_MySQL.execute(consulta_quotes_items_uecko, (quote_id, )) + items = cursor_MySQL.fetchall() + + items_columns = [desc[0] for desc in cursor_MySQL.description] + cursor_MySQL.close() + + selected_items = [] + for item in items: + tupla = dict(zip(items_columns, item)) + selected_items.append(tupla) + return selected_items + except Exception as e: + # Escribir el error en el archivo de errores + logging.error(msg=e, stack_info=True) + raise e # Re-lanzar la excepción para detener el procesamiento + finally: + if cursor_MySQL is not None: + cursor_MySQL.close() + + +def insert_quote_to_factuges(conn_factuges, quote, items, config): + id_empresa = int(config['FACTUGES_ID_EMPRESA']) + situacion = str(config['FACTUGES_CONTRATO_SITUACION']) + id_tienda = int(config['FACTUGES_CONTRATO_ID_TIENDA']) + enviada_revisada = int(config['FACTUGES_CONTRATO_ENVIADA_REVISADA']) + id_cliente = int(quote['id_contact']) + # nombre_clliente = str(quote['name']) + fecha_presupuesto = quote['date_sent'].date() + persona_contacto = str(quote['customer_information']) + referencia_cliente = str(quote['customer_reference']) + + select_gen_id_contrato_cliente = ( + "select GEN_ID(GEN_CONTRATOS_CLI_ID, 1) from RDB$DATABASE" + ) + + select_gen_id_presupuesto_cliente = ( + "select GEN_ID(GEN_PRESUPUESTOS_CLI_ID, 1) from RDB$DATABASE" + ) + + insert_contrato_cliente_data = ( + "insert into CONTRATOS_CLIENTE (" + "ID, ID_EMPRESA, ID_TIENDA, ID_CLIENTE, NOMBRE, SITUACION, " + "NOTAS_ENVIO, REFERENCIA_CLIENTE, " + "ENVIADA_REVISADA, FECHA_CONTRATO " + ") values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + ) + + insert_presupuesto_cliente_data = ( + "insert into PRESUPUESTOS_CLIENTE (" + "ID, ID_EMPRESA, ID_TIENDA, ID_CLIENTE, SITUACION, " + "OBSERVACIONES, REFERENCIA_CLIENTE, " + "ENVIADA_REVISADA, FECHA_PRESUPUESTO " + ") values (?, ?, ?, ?, ?, ?, ?, ?, ?)" + ) + + insert_contrato_cliente_detalles_data = ( + "insert into CONTRATOS_CLIENTE_DETALLES (" + "ID, ID_CONTRATO, POSICION, ID_ARTICULO, TIPO_DETALLE, " + "CONCEPTO, CANTIDAD, IMPORTE_UNIDAD, " + "VALORADO, VISIBLE, FECHA_ALTA " + ") values (" + "GEN_ID(GEN_CONTRATOS_CLI_DETALLE_ID, 1), ?, ?, ?, ?, " + "?, ?, ?, " + "1, 1, CURRENT_TIMESTAMP" + ")" + ) + + insert_presupuesto_cliente_detalles_data = ( + "insert into PRESUPUESTOS_CLIENTE_DETALLES (" + "ID, ID_PRESUPUESTO, POSICION, ID_ARTICULO, TIPO_DETALLE, " + "CONCEPTO, CANTIDAD, IMPORTE_UNIDAD, " + "VALORADO, VISIBLE, FECHA_ALTA " + ") values (" + "GEN_ID(GEN_PRESUPUESTOS_CLI_DETALLE_ID, 1), ?, ?, ?, ?, " + "?, ?, ?, " + "1, 1, CURRENT_TIMESTAMP" + ")" + ) + + cursor_FactuGES = None + try: + cursor_FactuGES = conn_factuges.cursor() + cursor_FactuGES.execute(select_gen_id_presupuesto_cliente) + id_presupuesto = int(cursor_FactuGES.fetchone()[0]) + + logging.info( + f"Inserting quote on FactuGES -> id_preupuesto = {str(id_presupuesto)}") + logging.info(insert_presupuesto_cliente_data) + logging.info((id_presupuesto, id_empresa, id_tienda, id_cliente, + situacion, fecha_presupuesto, persona_contacto, + referencia_cliente, enviada_revisada, fecha_presupuesto)) + + cursor_FactuGES.execute(insert_presupuesto_cliente_data, + (id_presupuesto, id_empresa, id_tienda, id_cliente, + situacion, persona_contacto, + referencia_cliente, enviada_revisada, fecha_presupuesto)) + + logging.info( + f"Inserting items. Quote items length to be processed: {len(items)}") + for item in items: + descripcion_iso = text_converter( + item['description'], charset_destino='ISO8859_1', longitud_maxima=2000) + quantity = Decimal( + int(item['quantity'])) / Decimal(100) if item['quantity'] is not None else None + unit_price = Decimal(int( + item['unit_price'])) / Decimal(100) if item['unit_price'] is not None else None + # total_price = item['total_price'] + + logging.info(str(insert_presupuesto_cliente_detalles_data)) + logging.info(( + id_presupuesto, item['position'], item['id_article'], config['FACTUGES_CONTRATO_TIPO_DETALLE'], + descripcion_iso, quantity, unit_price + )) + + cursor_FactuGES.execute(insert_presupuesto_cliente_detalles_data, ( + id_presupuesto, item['position'], item['id_article'], config['FACTUGES_CONTRATO_TIPO_DETALLE'], + descripcion_iso, quantity, unit_price + )) + + cursor_FactuGES.close() + return id_presupuesto + + except Exception as e: + # Escribir el error en el archivo de errores + logging.error(msg=e, stack_info=True) + raise e # Re-lanzar la excepción para detener el procesamiento + finally: + if cursor_FactuGES is not None: + cursor_FactuGES.close() diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..efb71ec --- /dev/null +++ b/app/main.py @@ -0,0 +1,82 @@ +import sys +import logging +import __version__ + +from datetime import datetime +from dateutil import tz +from config import setup_logging, load_config +from db import get_mysql_connection, get_factuges_connection, sync_catalog, sync_dealers, sync_orders +from utils import obtener_fecha_ultima_ejecucion, actualizar_fecha_ultima_ejecucion, log_system_metrics, send_orders_mail + + +def main(): + + # Cargar la configuración + config = load_config() + + local_tz = tz.gettz(config['LOCAL_TZ']) + + # Logging + setup_logging() + + logging.info("== START ==") + logging.info(f"Version: {__version__.__version__}") + logging.info(f"Environment: {config['ENVIRONMENT']}") + log_system_metrics() + + conn_factuges = None + conn_mysql = None + try: + # Obtener la fecha de la última ejecución del programa + last_execution_date_utc = obtener_fecha_ultima_ejecucion() + last_execution_date_local_tz = last_execution_date_utc.astimezone( + tz=local_tz).strftime("%Y-%m-%d %H:%M:%S") + + logging.info("Last execution (UTC): %s", + last_execution_date_utc.strftime("%Y-%m-%d %H:%M:%S %Z")) + logging.info("Last execution (Local time): %s", + last_execution_date_local_tz) + + conn_factuges = get_factuges_connection(config) + conn_mysql = get_mysql_connection(config) + + # Sync catalog + sync_catalog(conn_factuges, conn_mysql, last_execution_date_local_tz) + sync_dealers(conn_factuges, conn_mysql, last_execution_date_local_tz) + inserted_orders = sync_orders( + conn_factuges, conn_mysql) + + actualizar_fecha_ultima_ejecucion() + + # Confirmar los cambios + conn_mysql.commit() + conn_factuges.commit() + + # Enviar email + send_orders_mail(inserted_orders) + + logging.info("== END (0) ==") + sys.exit(0) + + except Exception as e: + logging.error("Se ha producido un error en la última ejecución.") + logging.error(e) + logging.info("== END (1) ==") + + if conn_mysql is not None: + conn_mysql.rollback() + + if conn_factuges is not None: + conn_factuges.rollback() + + sys.exit(1) + + finally: + if conn_factuges: + conn_factuges.close() + if conn_mysql: + conn_mysql.close() + + +if __name__ == "__main__": + main() diff --git a/app/utils/__init__.py b/app/utils/__init__.py new file mode 100644 index 0000000..7f00760 --- /dev/null +++ b/app/utils/__init__.py @@ -0,0 +1,5 @@ +from .last_execution_helper import actualizar_fecha_ultima_ejecucion, obtener_fecha_ultima_ejecucion +from .log_system_metrics import log_system_metrics +from .password import hashPassword +from .send_orders_mail import send_orders_mail +from .text_converter import text_converter diff --git a/app/utils/last_execution_helper.py b/app/utils/last_execution_helper.py new file mode 100644 index 0000000..e1d3d92 --- /dev/null +++ b/app/utils/last_execution_helper.py @@ -0,0 +1,21 @@ +from datetime import datetime, timezone +from dateutil import tz + +# Función para obtener la fecha de la última ejecución del programa desde un archivo de texto + + +def obtener_fecha_ultima_ejecucion(): + try: + with open('./last_execution.txt', 'r', encoding="utf8") as f: + fecha_str = f.read().strip() + return datetime.strptime(fecha_str, '%Y-%m-%d %H:%M:%S').astimezone(tz=tz.UTC) + except FileNotFoundError: + # Si el archivo no existe, se asume que el programa nunca se ha ejecutado antes + return datetime(2024, 1, 1, 0, 0, 0).astimezone(tz=tz.UTC) + +# Función para actualizar la fecha de la última ejecución del programa en el archivo de texto + + +def actualizar_fecha_ultima_ejecucion(): + with open('./last_execution.txt', 'w', encoding="utf8") as f: + f.write(datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')) diff --git a/app/utils/log_system_metrics.py b/app/utils/log_system_metrics.py new file mode 100644 index 0000000..e200091 --- /dev/null +++ b/app/utils/log_system_metrics.py @@ -0,0 +1,9 @@ +import psutil +import logging + +def log_system_metrics(): + cpu_usage = psutil.cpu_percent(interval=1) + memory_usage = psutil.virtual_memory().percent + + logging.info(f'CPU Usage: {cpu_usage}%') + logging.info(f'Memory Usage: {memory_usage}%') diff --git a/app/utils/password.py b/app/utils/password.py new file mode 100644 index 0000000..2e2555a --- /dev/null +++ b/app/utils/password.py @@ -0,0 +1,5 @@ +import bcrypt + +def hashPassword(plain_text_password): + salt = bcrypt.gensalt(rounds=10) + return bcrypt.hashpw(plain_text_password.encode('utf-8'), salt) diff --git a/app/utils/send_orders_mail.py b/app/utils/send_orders_mail.py new file mode 100644 index 0000000..323bb6b --- /dev/null +++ b/app/utils/send_orders_mail.py @@ -0,0 +1,29 @@ +import logging +import brevo_python + +from brevo_python.rest import ApiException +from config import setup_brevo +from brevo_python.rest import ApiException +from config import load_config + +def send_orders_mail(inserted_orders): + config = load_config() + + try: + configuration = setup_brevo(config) + api_instance = brevo_python.TransactionalEmailsApi(brevo_python.ApiClient(configuration)) + + for order in inserted_orders: + send_smtp_email = brevo_python.SendSmtpEmail( + to=[{'email':config['MAIL_TO']}], + subject=f"Nuevo pedido del distribuidor {order["dealer_name"]}", + template_id=int(config["BREVO_EMAIL_TEMPLATE"]), + params={ + "customer_reference": order["customer_reference"], + "dealer_name": order["dealer_name"] + }, + ) + api_response = api_instance.send_transac_email(send_smtp_email) + logging.info(msg=api_response) + except ApiException as e: + logging.error(msg=e) diff --git a/app/utils/text_converter.py b/app/utils/text_converter.py new file mode 100644 index 0000000..8144200 --- /dev/null +++ b/app/utils/text_converter.py @@ -0,0 +1,38 @@ +import logging + + +def text_converter(texto, charset_destino='ISO8859_1', longitud_maxima=None): + """ + Convierte un texto al charset especificado, eliminando caracteres incompatibles. + + Args: + texto (str): El texto a convertir. + charset_destino (str): El charset de destino (por defecto 'ISO8859_1'). + longitud_maxima (int, opcional): La longitud máxima permitida para el texto convertido. + + Returns: + str: El texto convertido al charset de destino. + """ + if not texto: + return "" + + try: + # Convertir el texto al charset especificado + texto_convertido = texto.encode( + charset_destino, 'ignore').decode(charset_destino) + + # Si se especifica una longitud máxima, truncar el texto + if longitud_maxima and len(texto_convertido) > longitud_maxima: + logging.warning( + f"El texto ha sido truncado de {len(texto_convertido)} a {longitud_maxima} caracteres.") + texto_convertido = texto_convertido[:longitud_maxima] + + return texto_convertido + + except UnicodeEncodeError as e: + logging.error( + f"Error al convertir texto a {charset_destino}: {str(e)}") + return "" + except Exception as e: + logging.error(f"Error inesperado al convertir texto: {str(e)}") + return "" diff --git a/cronjob b/cronjob new file mode 100644 index 0000000..9272ab1 --- /dev/null +++ b/cronjob @@ -0,0 +1 @@ +*/5 * * * * /usr/local/bin/python /opt/uecko_sync_app/app/main.py >> /var/log/cron.log 2>&1 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1b7a400 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,17 @@ +services: + uecko_sync: + container_name: uecko_sync_app:v1.0.8 + env_file: ".env.production" + build: . + #volumes: + #- logs:/var/log/uecko_sync_app + networks: + - presupuestador-uecko_private + restart: unless-stopped + +#volumes: +# logs: + +networks: + presupuestador-uecko_private: + external: true diff --git a/firebird/fbudflib2.so b/firebird/fbudflib2.so new file mode 100644 index 0000000..ce38a16 Binary files /dev/null and b/firebird/fbudflib2.so differ diff --git a/firebird/install.sh b/firebird/install.sh new file mode 100644 index 0000000..6697cb1 --- /dev/null +++ b/firebird/install.sh @@ -0,0 +1,1171 @@ +#!/bin/sh +# posixLibrary.sh +#!/bin/sh + + +#------------------------------------------------------------------------ +# Adds parameter to $PATH if it's missing in it + +Add2Path() { + Dir=${1} + x=`echo :${PATH}: | grep :$Dir:` + if [ -z "$x" ] + then + PATH=$PATH:$Dir + export PATH + fi +} + +#------------------------------------------------------------------------ +# Global stuff init + +Answer="" +OrigPasswd="" +TmpFile="" +FBRootDir=/opt/firebird +export FBRootDir +FBBin=$FBRootDir/bin +export FBBin +SecurityDatabase=security2.fdb +ArchiveDateTag=`date +"%Y%m%d_%H%M"` +export ArchiveDateTag +ArchiveMainFile="${FBRootDir}_${ArchiveDateTag}" +export ArchiveMainFile +#this solves a problem with sudo env missing sbin +Add2Path /usr/sbin +Add2Path /sbin + +#------------------------------------------------------------------------ +# Create temporary file. In case mktemp failed, do something... + +MakeTemp() { + TmpFile=`mktemp $mktOptions /tmp/firebird_install.XXXXXX` + if [ $? -ne 0 ] + then + TmpFile=/tmp/firebird_install + touch $TmpFile + fi +} + +#------------------------------------------------------------------------ +# Prompt for response, store result in Answer + +AskQuestion() { + Test=$1 + DefaultAns=$2 + echo -n "${1}" + Answer="$DefaultAns" + read Answer + + if [ -z "$Answer" ] + then + Answer="$DefaultAns" + fi +} + + +#------------------------------------------------------------------------ +# Prompt for yes or no answer - returns non-zero for no + +AskYNQuestion() { + while echo -n "${*} (y/n): " + do + read answer rest + case $answer in + [yY]*) + return 0 + ;; + [nN]*) + return 1 + ;; + *) + echo "Please answer y or n" + ;; + esac + done +} + + +#------------------------------------------------------------------------ +# Run $1. If exit status is not zero, show output to user. + +runSilent() { + MakeTemp + $1 >$TmpFile 2>&1 + if [ $? -ne 0 ] + then + cat $TmpFile + echo "" + rm -f $TmpFile + return 1 + fi + rm -f $TmpFile + return 0 +} + + +#------------------------------------------------------------------------ +# Check for a user, running install, to be root + +checkRootUser() { + + if [ "`whoami`" != "root" ]; + then + echo "" + echo "--- Stop ----------------------------------------------" + echo "" + echo " You need to be 'root' user to do this change" + echo "" + exit 1 + fi +} + +#alias +checkInstallUser() { + checkRootUser +} + + +#------------------------------------------------------------------------ +# resetInetdServer +# Works for both inetd and xinetd + +resetInetdServer() { + pid=`ps -ef$psOptions | grep inetd | grep -v grep | awk '{print $2}'` + if [ "$pid" ] + then + kill -HUP $pid + fi +} + + +#------------------------------------------------------------------------ +# remove the xinetd config file(s) +# take into account possible pre-firebird xinetd services + +removeXinetdEntry() { + for i in `grep -l "service gds_db" /etc/xinetd.d/*` + do + rm -f $i + done +} + + +#------------------------------------------------------------------------ +# remove the line from inetd file + +removeInetdEntry() { + FileName=/etc/inetd.conf + oldLine=`grep "^gds_db" $FileName` + removeLineFromFile "$FileName" "$oldLine" +} + + +#------------------------------------------------------------------------ +# Remove (x)inetd service entry and restart the service. +# Check to see if we have xinetd installed or plain inetd. +# Install differs for each of them. + +removeInetdServiceEntry() { + if [ -d /etc/xinetd.d ] + then + removeXinetdEntry + elif [ -f /etc/inetd.conf ] + then + removeInetdEntry + fi + + # make [x]inetd reload configuration + resetInetdServer +} + + +#------------------------------------------------------------------------ +# check if it is running + +checkIfServerRunning() { + + stopSuperServerIfRunning + +# Check is server is being actively used. + + checkString=`ps -ef$psOptions | egrep "\b(fbserver|fbguard)\b" |grep -v grep` + + if [ ! -z "$checkString" ] + then + echo "An instance of the Firebird Super server seems to be running." + echo "Please quit all Firebird applications and then proceed." + exit 1 + fi + + checkString=`ps -ef$psOptions | egrep "\b(fb_inet_server|gds_pipe)\b" |grep -v grep` + + if [ ! -z "$checkString" ] + then + echo "An instance of the Firebird Classic server seems to be running." + echo "Please quit all Firebird applications and then proceed." + exit 1 + fi + + +# The following check for running interbase or firebird 1.0 servers. + + checkString=`ps -ef$psOptions | egrep "\b(ibserver|ibguard)\b" |grep -v grep` + + if [ ! -z "$checkString" ] + then + echo "An instance of the Firebird/InterBase Super server seems to be running." + echo "(the ibserver or ibguard process was detected running on your system)" + echo "Please quit all Firebird applications and then proceed." + exit 1 + fi + + checkString=`ps -ef$psOptions | egrep "\b(gds_inet_server|gds_pipe)\b" |grep -v grep` + + if [ ! -z "$checkString" ] + then + echo "An instance of the Firebird/InterBase Classic server seems to be running." + echo "(the gds_inet_server or gds_pipe process was detected running on your system)" + echo "Please quit all Firebird applications and then proceed." + exit 1 + fi + + removeInetdServiceEntry + +# Stop lock manager if it is the only thing running. + + for i in `ps -ef$psOptions | grep "fb_lock_mgr" | grep -v "grep" | awk '{print $2}' ` + do + kill $i + done + +} + + +#------------------------------------------------------------------------ +# ask user to enter CORRECT original DBA password + +askForOrigDBAPassword() { + OrigPasswd="" + while [ -z "$OrigPasswd" ] + do + AskQuestion "Please enter current password for SYSDBA user: " + OrigPasswd=$Answer + if ! runSilent "$FBBin/gsec -user sysdba -password $OrigPasswd -di" + then + OrigPasswd="" + fi + done +} + + +#------------------------------------------------------------------------ +# Modify DBA password to value, asked from user. +# $1 may be set to original DBA password +# !! This routine is interactive !! + +askUserForNewDBAPassword() { + + if [ -z $1 ] + then + askForOrigDBAPassword + else + OrigPasswd=$1 + fi + + NewPasswd="" + while [ -z "$NewPasswd" ] + do + AskQuestion "Please enter new password for SYSDBA user: " + NewPasswd=$Answer + if [ ! -z "$NewPasswd" ] + then + if ! runSilent "$FBBin/gsec -user sysdba -password $OrigPasswd -modify sysdba -pw $NewPasswd" + then + NewPasswd="" + fi + fi + done +} + + +#------------------------------------------------------------------------ +# add a line in the (usually) /etc/services or /etc/inetd.conf file +# Here there are three cases, not found => add +# found & different => replace +# found & same => do nothing +# + +replaceLineInFile() { + FileName="$1" + newLine="$2" + oldLine=`grep "$3" $FileName` + + if [ -z "$oldLine" ] + then + echo "$newLine" >> "$FileName" + elif [ "$oldLine" != "$newLine" ] + then + MakeTemp + grep -v "$oldLine" "$FileName" > "$TmpFile" + echo "$newLine" >> $TmpFile + cp $TmpFile $FileName && rm -f $TmpFile + echo "Updated $1" + fi +} + + +#------------------------------------------------------------------------ +# "edit" file $1 - replace line starting from $2 with $3 +# This should stop ed/ex/vim/"what else editor" battle. +# I hope awk is present in any posix system? AP. + +editFile() { + FileName=$1 + Starting=$2 + NewLine=$3 + + AwkProgram="(/^$Starting.*/ || \$1 == \"$Starting\") {\$0=\"$NewLine\"} {print \$0}" + MakeTemp + awk "$AwkProgram" <$FileName >$TmpFile && mv $TmpFile $FileName || rm -f $TmpFile +} + + +#------------------------------------------------------------------------ +# remove line from config file if it exists in it. + +removeLineFromFile() { + FileName=$1 + oldLine=$2 + + if [ ! -z "$oldLine" ] + then + cat $FileName | grep -v "$oldLine" > ${FileName}.tmp + cp ${FileName}.tmp $FileName && rm -f ${FileName}.tmp + fi +} + + +#------------------------------------------------------------------------ +# Write new password to the /opt/firebird/SYSDBA.password file + +writeNewPassword() { + NewPasswd=$1 + DBAPasswordFile=$FBRootDir/SYSDBA.password + + cat <$DBAPasswordFile +# Firebird generated password for user SYSDBA is: + +ISC_USER=sysdba +ISC_PASSWD=$NewPasswd + +EOT + + if [ $NewPasswd = "masterkey" ] + then + echo "# for install on `hostname` at time `date`" >> $DBAPasswordFile + echo "# You should change this password at the earliest oportunity" >> $DBAPasswordFile + else + echo "# generated on `hostname` at time `date`" >> $DBAPasswordFile + fi + + cat <>$DBAPasswordFile + +# Your password can be changed to a more suitable one using the +# /opt/firebird/bin/changeDBAPassword.sh script +EOT + + chmod u=r,go= $DBAPasswordFile + + + # Only if we have changed the password from the default do we need + # to update the entry in the database + + if [ $NewPasswd != "masterkey" ] + then + runSilent "$FBBin/gsec -user sysdba -password masterkey -modify sysdba -pw $NewPasswd" + fi +} + + +#------------------------------------------------------------------------ +# Generate new sysdba password - this routine is used only in the +# rpm file not in the install script. + +generateNewDBAPassword() { + # openssl generates random data. + openssl /dev/null 2&>/dev/null + if [ $? -eq 0 ] + then + # We generate 20 random chars, strip any '/''s and get the first 8 + NewPasswd=`openssl rand -base64 20 | tr -d '/' | cut -c1-8` + fi + + # mkpasswd is a bit of a hassle, but check to see if it's there + if [ -z "$NewPasswd" ] + then + if [ -f /usr/bin/mkpasswd ] + then + NewPasswd=`/usr/bin/mkpasswd -l 8` + fi + fi + + # On some systems the mkpasswd program doesn't appear and on others + # there is another mkpasswd which does a different operation. So if + # the specific one isn't available then keep the original password. + if [ -z "$NewPasswd" ] + then + NewPasswd="masterkey" + fi + + writeNewPassword $NewPasswd +} + + +#------------------------------------------------------------------------ +# Change sysdba password. + +changeDBAPassword() { + if [ -z "$InteractiveInstall" ] + then + generateNewDBAPassword + else + askUserForNewDBAPassword masterkey + fi +} + + +#------------------------------------------------------------------------ +# buildUninstallFile +# This will work only for the .tar.gz install and it builds an +# uninstall shell script. The RPM system, if present, takes care of it's own. + +buildUninstallFile() { + cd "$origDir" + + if [ ! -f manifest.txt ] # Only exists if we are a .tar.gz install + then + return + fi + + cp manifest.txt $FBRootDir/misc + + cp -r scripts $FBRootDir/misc/ + [ -f scripts/tarMainUninstall.sh ] && cp scripts/tarMainUninstall.sh $FBRootDir/bin/uninstall.sh + [ -f scripts/tarmainUninstall.sh ] && cp scripts/tarmainUninstall.sh $FBRootDir/bin/uninstall.sh + [ -f $FBRootDir/bin/uninstall.sh ] && chmod u=rx,go= $FBRootDir/bin/uninstall.sh +} + + +#------------------------------------------------------------------------ +# Remove if only a link + +removeIfOnlyAlink() { + Target=$1 + + if [ -L $Target ] + then + rm -f $Target + fi +} + + +#------------------------------------------------------------------------ +# re-link new file only if target is a link or missing + +safeLink() { + Source=$1 + Target=$2 + + removeIfOnlyAlink $Target + if [ ! -e $Target ] + then + ln -s $Source $Target + fi +} + + +#------------------------------------------------------------------------ +# createLinksForBackCompatibility +# Create links for back compatibility to InterBase and Firebird1.0 +# linked systems. + +createLinksForBackCompatibility() { + + # These two links are required for compatibility with existing ib programs + # If the program had been linked with libgds.so then this link is required + # to ensure it loads the fb equivalent. Eventually these should be + # optional and in a seperate rpm install. MOD 7-Nov-2002. + + if [ "$1" ] + then + # Use library name from parameter + newLibrary=$FBRootDir/lib/$1 + else + # Use DefaultLibrary, set by appropriate install library + newLibrary=$FBRootDir/lib/$DefaultLibrary.so + fi + + safeLink $newLibrary /usr/lib64/libgds.so + safeLink $newLibrary /usr/lib64/libgds.so.0 +} + + +#------------------------------------------------------------------------ +# removeLinksForBackCompatibility +# Remove links for back compatibility to InterBase and Firebird1.0 +# linked systems. + +removeLinksForBackCompatibility() { + removeIfOnlyAlink /usr/lib64/libgds.so + removeIfOnlyAlink /usr/lib64/libgds.so.0 +} + +#------------------------------------------------------------------------ +# For security reasons most files in firebird installation are +# root-owned and world-readable(executable) only (including firebird). + +# For some files RunUser (firebird) must have write access - +# lock and log for examples. + +MakeFileFirebirdWritable() { + FileName=$1 + chown $RunUser:$RunUser $FileName + chmod 0644 $FileName +} + + +#------------------------------------------------------------------------ +# Set correct permissions for $FbRoot/doc tree + +fixDocPermissions() { + cd $FBRootDir + + for i in `find doc -print`; do + chown root:root $i + if [ -d $i ]; then + chmod 0755 $i + else + chmod 0644 $i + fi + done +} + + +#------------------------------------------------------------------------ +# Run process and check status + +runAndCheckExit() { + Cmd=$* + + $Cmd + ExitCode=$? + + if [ $ExitCode -ne 0 ] + then + echo "Install aborted: The command $Cmd " + echo " failed with error code $ExitCode" + exit $ExitCode + fi +} + + +#------------------------------------------------------------------------ +# Display message if this is being run interactively. + +displayMessage() { + msgText=$1 + + if [ ! -z "$InteractiveInstall" ] + then + echo $msgText + fi +} + + +#------------------------------------------------------------------------ +# Archive any existing prior installed files. +# The 'cd' stuff is to avoid the "leading '/' removed message from tar. +# for the same reason the DestFile is specified without the leading "/" + +archivePriorInstallSystemFiles() { + if [ -z ${ArchiveMainFile} ] + then + echo "Variable ArchiveMainFile not set - exiting" + exit 1 + fi + + tarArc=${ArchiveMainFile}.$tarExt + + oldPWD=`pwd` + archiveFileList="" + + cd / + + DestFile=${FBRootDir#/} # strip off leading / + if [ -e "$DestFile" ] + then + echo "" + echo "" + echo "" + echo "--- Warning ----------------------------------------------" + echo " The installation target directory: $FBRootDir" + echo " Already contains a prior installation of InterBase/Firebird." + echo " This and files found in /usr/include and /usr/lib64 will be" + echo " archived in the file : ${tarArc}" + echo "" + + if [ ! -z "$InteractiveInstall" ] + then + AskQuestion "Press return to continue or ^C to abort" + fi + + if [ -e $DestFile ] + then + archiveFileList="$archiveFileList $DestFile" + fi + fi + + + for i in ibase.h ib_util.h + do + DestFile=usr/include/$i + if [ -e $DestFile ] + then + archiveFileList="$archiveFileList $DestFile" + fi + done + + for i in libib_util.so libfbclient.so* + do + for DestFile in usr/lib/$i + do + if [ -e $DestFile ] + then + archiveFileList="$archiveFileList $DestFile" + fi + done + done + +# for i in `cat manifest.txt` +# do +# if [ ! -d /$i ] # Ignore directories +# then +# if [ -e /$i ] +# then +# archiveFileList="$archiveFileList $i" +# fi +# fi +# done + + for i in usr/sbin/rcfirebird etc/init.d/firebird etc/rc.d/init.d/firebird + do + DestFile=./$i + if [ -e /$DestFile ] + then + archiveFileList="$archiveFileList $DestFile" + fi + done + + if [ ! -z "$archiveFileList" ] + then + displayMessage "Archiving..." + runAndCheckExit "tar -c${tarOptions}f $tarArc $archiveFileList" + displayMessage "Done." + + displayMessage "Deleting..." + for i in $archiveFileList + do + rm -rf $i + done + displayMessage "Done." + fi + + cd $oldPWD +} + + +#------------------------------------------------------------------------ +# removeInstalledFiles +# +removeInstalledFiles() { + + manifestFile=$FBRootDir/misc/manifest.txt + + if [ ! -f $manifestFile ] + then + return + fi + + origDir=`pwd` + + cd / + + for i in `cat $manifestFile` + do + if [ -f $i -o -L $i ] + then + rm -f $i + #echo $i + fi + done + + cd "$origDir" +} + + +#------------------------------------------------------------------------ +# removeUninstallFiles +# Under the install directory remove all the empty directories +# If some files remain then + +removeUninstallFiles() { + # remove the uninstall scripts files. + #echo $FBRootDir/misc/scripts + rm -rf $FBRootDir/misc/scripts + rm -f $FBRootDir/misc/manifest.txt + rm -f $FBRootDir/bin/uninstall.sh + +} + + +#------------------------------------------------------------------------ +# removeEmptyDirs +# Under the install directory remove all the empty directories +# If some files remain then +# This routine loops, since deleting a directory possibly makes +# the parent empty as well + +removeEmptyDirs() { + + dirContentChanged='yes' + while [ ! -z $dirContentChanged ] + do + dirContentChanged='' + for i in `find $FBRootDir -type d -print`; do + ls $i/* >/dev/null 2>&1 + if [ $? -ne 0 ]; then + rmdir $i + dirContentChanged=$i + fi + done + + if [ ! -d $FBRootDir ] # end loop if the FBRootDir was deleted. + then + dirContentChanged='' + fi + + done +} + +# classicLibrary.sh +#!/bin/sh + +#------------------------------------------------------------------------ +# init defaults +DefaultLibrary=libfbembed + +#------------------------------------------------------------------------ +# fixFilePermissions +# Change the permissions to restrict access to server programs to +# firebird group only. This is MUCH better from a saftey point of +# view than installing as root user, even if it requires a little +# more work. + + +fixFilePermissions() { + chown -R $RunUser:$RunGroup $FBRootDir + + # Turn other access off. + chmod -R o= $FBRootDir + + # Now fix up the mess. + + # fix up directories + for i in `find $FBRootDir -print` + do + FileName=$i + if [ -d $FileName ] + then + chmod o=rx $FileName + fi + done + + # set up the defaults for bin + cd $FBBin + for i in `ls` + do + chmod ug=rx,o= $i + done + + # User can run these programs, they need to talk to server though. + # and they cannot actually create a database. + chmod a=rx isql + chmod a=rx qli + + # Root SUID is still needed for group direct access. + # General users cannot run though. + for i in fb_lock_mgr + do + if [ -f $i ] + then + chown root $i + chmod ug=rx,o= $i + chmod ug+s $i + fi + done + + # set up libraries + cd $FBRootDir + cd lib + chmod a=rx lib* + + # set up include files + cd $FBRootDir + cd include + chmod a=r * + + # Fix lock files + cd $FBRootDir + for i in isc_init1 isc_lock1 isc_event1 isc_monitor1 + do + FileName=$i.`hostname` + touch $FileName + chown $RunUser:$RunUser $FileName + chmod ug=rw,o= $FileName + done + + # Fix the rest + touch firebird.log + chmod ug=rw,o= firebird.log + chmod a=r aliases.conf + chmod a=r firebird.conf + chmod a=r firebird.msg + chmod a=r help/help.fdb + chmod ug=rw,o= $SecurityDatabase + chmod a=r *License.txt + + if [ "$RunUser" = "root" ] + # In that case we must open databases to the world... + # That's a pity, but required if root RunUser choosen. + then + chmod a=rw $SecurityDatabase + fi + + # fix up examples' permissions + cd examples + + # set a default of read all files in examples + for i in `find . -name '*' -type f -print` + do + chmod a=r $i + done + + # set a default of read&search all dirs in examples + for i in `find . -name '*' -type d -print` + do + chmod a=rx $i + done + + # make examples db's writable by group + for i in `find . -name '*.fdb' -print` + do + chown $RunUser:$RunUser $i + chmod ug=rw,o= $i + done + + # fix up doc permissions + fixDocPermissions + + cd $FBRootDir +} + + +#------------------------------------------------------------------------ +# changeXinetdServiceUser +# Change the run user of the xinetd service + +changeXinetdServiceUser() { + InitFile=/etc/xinetd.d/firebird + if [ -f $InitFile ] + then + editFile $InitFile user "\tuser\t\t\t= $RunUser" + fi +} + + +#------------------------------------------------------------------------ +# Update inetd service entry +# This just adds/replaces the service entry line + +updateInetdEntry() { + newLine="gds_db stream tcp nowait.30000 $RunUser $FBBin/fb_inet_server fb_inet_server # Firebird Database Remote Server" + replaceLineInFile /etc/inetd.conf "$newLine" "^gds_db" +} + + +#------------------------------------------------------------------------ +# Update xinetd service entry + +updateXinetdEntry() { + cp $FBRootDir/misc/firebird.xinetd /etc/xinetd.d/firebird + changeXinetdServiceUser +} + + +#------------------------------------------------------------------------ +# Update inetd service entry +# Check to see if we have xinetd installed or plain inetd. +# Install differs for each of them. + +updateInetdServiceEntry() { + if [ -d /etc/xinetd.d ] + then + updateXinetdEntry + else + updateInetdEntry + fi +} + + +#------------------------------------------------------------------------ +# change init.d RunUser + +changeInitRunUser() { + # do nothing for CS + return 0 +} + + +#------------------------------------------------------------------------ +# start init.d service + +startService() { + # do nothing for CS + return 0 +} + +# linuxLibrary.sh +#!/bin/sh + +RunUser=firebird +export RunUser +RunGroup=firebird +export RunGroup +PidDir=/var/run/firebird +export PidDir + +#------------------------------------------------------------------------ +# Get correct options & misc. + +psOptions=ww +export psOptions +mktOptions=-q +export mktOptions +tarOptions=z +export tarOptions +tarExt=tar.gz +export tarExt + +#------------------------------------------------------------------------ +# Add new user and group + +TryAddGroup() { + + AdditionalParameter=$1 + testStr=`grep firebird /etc/group` + + if [ -z "$testStr" ] + then + groupadd $AdditionalParameter firebird + fi + +} + + +TryAddUser() { + + AdditionalParameter=$1 + testStr=`grep firebird /etc/passwd` + + if [ -z "$testStr" ] + then + useradd $AdditionalParameter -d $FBRootDir -s /bin/false \ + -c "Firebird Database Owner" -g firebird firebird + fi + +} + + +addFirebirdUser() { + + TryAddGroup "-g 84 -r" >/dev/null 2>&1 + TryAddGroup "-g 84" >/dev/null 2>&1 + TryAddGroup "-r" >/dev/null 2>&1 + TryAddGroup " " + + TryAddUser "-u 84 -r -M" >/dev/null 2>&1 + TryAddUser "-u 84 -M" >/dev/null 2>&1 + TryAddUser "-r -M" >/dev/null 2>&1 + TryAddUser "-M" + TryAddUser "-u 84 -r" >/dev/null 2>&1 + TryAddUser "-u 84" >/dev/null 2>&1 + TryAddUser "-r" >/dev/null 2>&1 + TryAddUser " " + +} + + +#------------------------------------------------------------------------ +# Detect Distribution. +# AP: very beautiful, but unused. Let's keep alive for a while. (2005) + +detectDistro() { + + # it's not provided... + if [ -z "$linuxDistro" ] + then + if [ -e /etc/SuSE-release ] + then + # SuSE + linuxDistro="SuSE" + elif [ -e /etc/mandrake-release ] + then + # Mandrake + linuxDistro="MDK" + elif [ -e /etc/debian_version ] + then + # Debian + linuxDistro="Debian" + elif [ -e /etc/gentoo-release ] + then + # Debian + linuxDistro="Gentoo" + elif [ -e /etc/rc.d/init.d/functions ] + then + # very likely Red Hat + linuxDistro="RH" + elif [ -d /etc/rc.d/init.d ] + then + # generic Red Hat + linuxDistro="G-RH" + elif [ -d /etc/init.d ] + then + # generic SuSE + linuxDistro="G-SuSE" + fi + fi +} + + +#------------------------------------------------------------------------ +# print location of init script + +getInitScriptLocation() { + if [ -f /etc/rc.d/init.d/firebird ] + then + echo -n /etc/rc.d/init.d/firebird + elif [ -f /etc/rc.d/rc.firebird ] + then + echo -n /etc/rc.d/rc.firebird + elif [ -f /etc/init.d/firebird ] + then + echo -n /etc/init.d/firebird + fi +} + + +#------------------------------------------------------------------------ +# stop super server if it is running + +stopSuperServerIfRunning() { + checkString=`ps -efww| egrep "\b(fbserver|fbguard)\b" |grep -v grep` + + if [ ! -z "$checkString" ] + then + init_d=`getInitScriptLocation` + + if [ -x "$init_d" ] + then + $init_d stop + sleep 1 + fi + fi +} + +#!/bin/sh +# +# This library is part of the FirebirdSQL project +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# You may obtain a copy of the Licence at +# http://www.gnu.org/licences/lgpl.html +# +# As a special exception this file can also be included in modules +# with other source code as long as that source code has been +# released under an Open Source Initiative certificed licence. +# More information about OSI certification can be found at: +# http://www.opensource.org +# +# This module is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public Licence for more details. +# +# This module was created by members of the firebird development +# team. All individual contributions remain the Copyright (C) of +# those individuals and all rights are reserved. Contributors to +# this file are either listed below or can be obtained from a CVS +# history command. +# +# Created by: Mark O'Donohue +# +# Contributor(s): +# +# +# $Id: tarMainInstall.sh.in,v 1.2 2005-08-16 10:04:11 alexpeshkoff Exp $ +# + +# Install script for FirebirdSQL database engine +# http://www.firebirdsql.org + + +InteractiveInstall=1 +export InteractiveInstall + + +checkInstallUser + +BuildVersion=2.1.7.18553 +PackageVersion=0 +CpuType=amd64 + +Version="$BuildVersion-$PackageVersion.$CpuType" + + +cat < Firebird running..." +done \ No newline at end of file diff --git a/firebird/postinstall.sh b/firebird/postinstall.sh new file mode 100644 index 0000000..909f52b --- /dev/null +++ b/firebird/postinstall.sh @@ -0,0 +1,1191 @@ +#!/bin/sh +# posixLibrary.sh +#!/bin/sh + + +#------------------------------------------------------------------------ +# Adds parameter to $PATH if it's missing in it + +Add2Path() { + Dir=${1} + x=`echo :${PATH}: | grep :$Dir:` + if [ -z "$x" ] + then + PATH=$PATH:$Dir + export PATH + fi +} + +#------------------------------------------------------------------------ +# Global stuff init + +Answer="" +OrigPasswd="" +TmpFile="" +FBRootDir=/opt/firebird +export FBRootDir +FBBin=$FBRootDir/bin +export FBBin +SecurityDatabase=security2.fdb +ArchiveDateTag=`date +"%Y%m%d_%H%M"` +export ArchiveDateTag +ArchiveMainFile="${FBRootDir}_${ArchiveDateTag}" +export ArchiveMainFile +#this solves a problem with sudo env missing sbin +Add2Path /usr/sbin +Add2Path /sbin + +#------------------------------------------------------------------------ +# Create temporary file. In case mktemp failed, do something... + +MakeTemp() { + TmpFile=`mktemp $mktOptions /tmp/firebird_install.XXXXXX` + if [ $? -ne 0 ] + then + TmpFile=/tmp/firebird_install + touch $TmpFile + fi +} + +#------------------------------------------------------------------------ +# Prompt for response, store result in Answer + +AskQuestion() { + Test=$1 + DefaultAns=$2 + echo -n "${1}" + Answer="$DefaultAns" + read Answer + + if [ -z "$Answer" ] + then + Answer="$DefaultAns" + fi +} + + +#------------------------------------------------------------------------ +# Prompt for yes or no answer - returns non-zero for no + +AskYNQuestion() { + while echo -n "${*} (y/n): " + do + read answer rest + case $answer in + [yY]*) + return 0 + ;; + [nN]*) + return 1 + ;; + *) + echo "Please answer y or n" + ;; + esac + done +} + + +#------------------------------------------------------------------------ +# Run $1. If exit status is not zero, show output to user. + +runSilent() { + MakeTemp + $1 >$TmpFile 2>&1 + if [ $? -ne 0 ] + then + cat $TmpFile + echo "" + rm -f $TmpFile + return 1 + fi + rm -f $TmpFile + return 0 +} + + +#------------------------------------------------------------------------ +# Check for a user, running install, to be root + +checkRootUser() { + + if [ "`whoami`" != "root" ]; + then + echo "" + echo "--- Stop ----------------------------------------------" + echo "" + echo " You need to be 'root' user to do this change" + echo "" + exit 1 + fi +} + +#alias +checkInstallUser() { + checkRootUser +} + + +#------------------------------------------------------------------------ +# resetInetdServer +# Works for both inetd and xinetd + +resetInetdServer() { + pid=`ps -ef$psOptions | grep inetd | grep -v grep | awk '{print $2}'` + if [ "$pid" ] + then + kill -HUP $pid + fi +} + + +#------------------------------------------------------------------------ +# remove the xinetd config file(s) +# take into account possible pre-firebird xinetd services + +removeXinetdEntry() { + for i in `grep -l "service gds_db" /etc/xinetd.d/*` + do + rm -f $i + done +} + + +#------------------------------------------------------------------------ +# remove the line from inetd file + +removeInetdEntry() { + FileName=/etc/inetd.conf + oldLine=`grep "^gds_db" $FileName` + removeLineFromFile "$FileName" "$oldLine" +} + + +#------------------------------------------------------------------------ +# Remove (x)inetd service entry and restart the service. +# Check to see if we have xinetd installed or plain inetd. +# Install differs for each of them. + +removeInetdServiceEntry() { + if [ -d /etc/xinetd.d ] + then + removeXinetdEntry + elif [ -f /etc/inetd.conf ] + then + removeInetdEntry + fi + + # make [x]inetd reload configuration + resetInetdServer +} + + +#------------------------------------------------------------------------ +# check if it is running + +checkIfServerRunning() { + + stopSuperServerIfRunning + +# Check is server is being actively used. + + checkString=`ps -ef$psOptions | egrep "\b(fbserver|fbguard)\b" |grep -v grep` + + if [ ! -z "$checkString" ] + then + echo "An instance of the Firebird Super server seems to be running." + echo "Please quit all Firebird applications and then proceed." + exit 1 + fi + + checkString=`ps -ef$psOptions | egrep "\b(fb_inet_server|gds_pipe)\b" |grep -v grep` + + if [ ! -z "$checkString" ] + then + echo "An instance of the Firebird Classic server seems to be running." + echo "Please quit all Firebird applications and then proceed." + exit 1 + fi + + +# The following check for running interbase or firebird 1.0 servers. + + checkString=`ps -ef$psOptions | egrep "\b(ibserver|ibguard)\b" |grep -v grep` + + if [ ! -z "$checkString" ] + then + echo "An instance of the Firebird/InterBase Super server seems to be running." + echo "(the ibserver or ibguard process was detected running on your system)" + echo "Please quit all Firebird applications and then proceed." + exit 1 + fi + + checkString=`ps -ef$psOptions | egrep "\b(gds_inet_server|gds_pipe)\b" |grep -v grep` + + if [ ! -z "$checkString" ] + then + echo "An instance of the Firebird/InterBase Classic server seems to be running." + echo "(the gds_inet_server or gds_pipe process was detected running on your system)" + echo "Please quit all Firebird applications and then proceed." + exit 1 + fi + + removeInetdServiceEntry + +# Stop lock manager if it is the only thing running. + + for i in `ps -ef$psOptions | grep "fb_lock_mgr" | grep -v "grep" | awk '{print $2}' ` + do + kill $i + done + +} + + +#------------------------------------------------------------------------ +# ask user to enter CORRECT original DBA password + +askForOrigDBAPassword() { + OrigPasswd="" + while [ -z "$OrigPasswd" ] + do + AskQuestion "Please enter current password for SYSDBA user: " + OrigPasswd=$Answer + if ! runSilent "$FBBin/gsec -user sysdba -password $OrigPasswd -di" + then + OrigPasswd="" + fi + done +} + + +#------------------------------------------------------------------------ +# Modify DBA password to value, asked from user. +# $1 may be set to original DBA password +# !! This routine is interactive !! + +askUserForNewDBAPassword() { + + if [ -z $1 ] + then + askForOrigDBAPassword + else + OrigPasswd=$1 + fi + + NewPasswd=$OrigPasswd + while [ -z "$NewPasswd" ] + do + AskQuestion "Please enter new password for SYSDBA user: " + NewPasswd=$Answer + if [ ! -z "$NewPasswd" ] + then + if ! runSilent "$FBBin/gsec -user sysdba -password $OrigPasswd -modify sysdba -pw $NewPasswd" + then + NewPasswd="" + fi + fi + done +} + + +#------------------------------------------------------------------------ +# add a line in the (usually) /etc/services or /etc/inetd.conf file +# Here there are three cases, not found => add +# found & different => replace +# found & same => do nothing +# + +replaceLineInFile() { + FileName="$1" + newLine="$2" + oldLine=`grep "$3" $FileName` + + if [ -z "$oldLine" ] + then + echo "$newLine" >> "$FileName" + elif [ "$oldLine" != "$newLine" ] + then + MakeTemp + grep -v "$oldLine" "$FileName" > "$TmpFile" + echo "$newLine" >> $TmpFile + cp $TmpFile $FileName && rm -f $TmpFile + echo "Updated $1" + fi +} + + +#------------------------------------------------------------------------ +# "edit" file $1 - replace line starting from $2 with $3 +# This should stop ed/ex/vim/"what else editor" battle. +# I hope awk is present in any posix system? AP. + +editFile() { + FileName=$1 + Starting=$2 + NewLine=$3 + + AwkProgram="(/^$Starting.*/ || \$1 == \"$Starting\") {\$0=\"$NewLine\"} {print \$0}" + MakeTemp + awk "$AwkProgram" <$FileName >$TmpFile && mv $TmpFile $FileName || rm -f $TmpFile +} + + +#------------------------------------------------------------------------ +# remove line from config file if it exists in it. + +removeLineFromFile() { + FileName=$1 + oldLine=$2 + + if [ ! -z "$oldLine" ] + then + cat $FileName | grep -v "$oldLine" > ${FileName}.tmp + cp ${FileName}.tmp $FileName && rm -f ${FileName}.tmp + fi +} + + +#------------------------------------------------------------------------ +# Write new password to the /opt/firebird/SYSDBA.password file + +writeNewPassword() { + NewPasswd=$1 + DBAPasswordFile=$FBRootDir/SYSDBA.password + + cat <$DBAPasswordFile +# Firebird generated password for user SYSDBA is: + +ISC_USER=sysdba +ISC_PASSWD=$NewPasswd + +EOT + + if [ $NewPasswd = "masterkey" ] + then + echo "# for install on `hostname` at time `date`" >> $DBAPasswordFile + echo "# You should change this password at the earliest oportunity" >> $DBAPasswordFile + else + echo "# generated on `hostname` at time `date`" >> $DBAPasswordFile + fi + + cat <>$DBAPasswordFile + +# Your password can be changed to a more suitable one using the +# /opt/firebird/bin/changeDBAPassword.sh script +EOT + + chmod u=r,go= $DBAPasswordFile + + + # Only if we have changed the password from the default do we need + # to update the entry in the database + + if [ $NewPasswd != "masterkey" ] + then + runSilent "$FBBin/gsec -user sysdba -password masterkey -modify sysdba -pw $NewPasswd" + fi +} + + +#------------------------------------------------------------------------ +# Generate new sysdba password - this routine is used only in the +# rpm file not in the install script. + +generateNewDBAPassword() { + # openssl generates random data. + openssl /dev/null 2&>/dev/null + if [ $? -eq 0 ] + then + # We generate 20 random chars, strip any '/''s and get the first 8 + NewPasswd=`openssl rand -base64 20 | tr -d '/' | cut -c1-8` + fi + + # mkpasswd is a bit of a hassle, but check to see if it's there + if [ -z "$NewPasswd" ] + then + if [ -f /usr/bin/mkpasswd ] + then + NewPasswd=`/usr/bin/mkpasswd -l 8` + fi + fi + + # On some systems the mkpasswd program doesn't appear and on others + # there is another mkpasswd which does a different operation. So if + # the specific one isn't available then keep the original password. + if [ -z "$NewPasswd" ] + then + NewPasswd="masterkey" + fi + + writeNewPassword $NewPasswd +} + + +#------------------------------------------------------------------------ +# Change sysdba password. + +changeDBAPassword() { + if [ -z "$InteractiveInstall" ] + then + generateNewDBAPassword + else + askUserForNewDBAPassword $FIREBIRD_DB_PASSWORD + fi +} + + +#------------------------------------------------------------------------ +# buildUninstallFile +# This will work only for the .tar.gz install and it builds an +# uninstall shell script. The RPM system, if present, takes care of it's own. + +buildUninstallFile() { + cd "$origDir" + + if [ ! -f manifest.txt ] # Only exists if we are a .tar.gz install + then + return + fi + + cp manifest.txt $FBRootDir/misc + + cp -r scripts $FBRootDir/misc/ + [ -f scripts/tarMainUninstall.sh ] && cp scripts/tarMainUninstall.sh $FBRootDir/bin/uninstall.sh + [ -f scripts/tarmainUninstall.sh ] && cp scripts/tarmainUninstall.sh $FBRootDir/bin/uninstall.sh + [ -f $FBRootDir/bin/uninstall.sh ] && chmod u=rx,go= $FBRootDir/bin/uninstall.sh +} + + +#------------------------------------------------------------------------ +# Remove if only a link + +removeIfOnlyAlink() { + Target=$1 + + if [ -L $Target ] + then + rm -f $Target + fi +} + + +#------------------------------------------------------------------------ +# re-link new file only if target is a link or missing + +safeLink() { + Source=$1 + Target=$2 + + removeIfOnlyAlink $Target + if [ ! -e $Target ] + then + ln -s $Source $Target + fi +} + + +#------------------------------------------------------------------------ +# createLinksForBackCompatibility +# Create links for back compatibility to InterBase and Firebird1.0 +# linked systems. + +createLinksForBackCompatibility() { + + # These two links are required for compatibility with existing ib programs + # If the program had been linked with libgds.so then this link is required + # to ensure it loads the fb equivalent. Eventually these should be + # optional and in a seperate rpm install. MOD 7-Nov-2002. + + if [ "$1" ] + then + # Use library name from parameter + newLibrary=$FBRootDir/lib/$1 + else + # Use DefaultLibrary, set by appropriate install library + newLibrary=$FBRootDir/lib/$DefaultLibrary.so + fi + + safeLink $newLibrary /usr/lib64/libgds.so + safeLink $newLibrary /usr/lib64/libgds.so.0 +} + + +#------------------------------------------------------------------------ +# removeLinksForBackCompatibility +# Remove links for back compatibility to InterBase and Firebird1.0 +# linked systems. + +removeLinksForBackCompatibility() { + removeIfOnlyAlink /usr/lib64/libgds.so + removeIfOnlyAlink /usr/lib64/libgds.so.0 +} + +#------------------------------------------------------------------------ +# For security reasons most files in firebird installation are +# root-owned and world-readable(executable) only (including firebird). + +# For some files RunUser (firebird) must have write access - +# lock and log for examples. + +MakeFileFirebirdWritable() { + FileName=$1 + chown $RunUser:$RunUser $FileName + chmod 0644 $FileName +} + + +#------------------------------------------------------------------------ +# Set correct permissions for $FbRoot/doc tree + +fixDocPermissions() { + cd $FBRootDir + + for i in `find doc -print`; do + chown root:root $i + if [ -d $i ]; then + chmod 0755 $i + else + chmod 0644 $i + fi + done +} + + +#------------------------------------------------------------------------ +# Run process and check status + +runAndCheckExit() { + Cmd=$* + + $Cmd + ExitCode=$? + + if [ $ExitCode -ne 0 ] + then + echo "Install aborted: The command $Cmd " + echo " failed with error code $ExitCode" + exit $ExitCode + fi +} + + +#------------------------------------------------------------------------ +# Display message if this is being run interactively. + +displayMessage() { + msgText=$1 + + if [ ! -z "$InteractiveInstall" ] + then + echo $msgText + fi +} + + +#------------------------------------------------------------------------ +# Archive any existing prior installed files. +# The 'cd' stuff is to avoid the "leading '/' removed message from tar. +# for the same reason the DestFile is specified without the leading "/" + +archivePriorInstallSystemFiles() { + if [ -z ${ArchiveMainFile} ] + then + echo "Variable ArchiveMainFile not set - exiting" + exit 1 + fi + + tarArc=${ArchiveMainFile}.$tarExt + + oldPWD=`pwd` + archiveFileList="" + + cd / + + DestFile=${FBRootDir#/} # strip off leading / + if [ -e "$DestFile" ] + then + echo "" + echo "" + echo "" + echo "--- Warning ----------------------------------------------" + echo " The installation target directory: $FBRootDir" + echo " Already contains a prior installation of InterBase/Firebird." + echo " This and files found in /usr/include and /usr/lib64 will be" + echo " archived in the file : ${tarArc}" + echo "" + + if [ ! -z "$InteractiveInstall" ] + then + AskQuestion "Press return to continue or ^C to abort" + fi + + if [ -e $DestFile ] + then + archiveFileList="$archiveFileList $DestFile" + fi + fi + + + for i in ibase.h ib_util.h + do + DestFile=usr/include/$i + if [ -e $DestFile ] + then + archiveFileList="$archiveFileList $DestFile" + fi + done + + for i in libib_util.so libfbclient.so* + do + for DestFile in usr/lib/$i + do + if [ -e $DestFile ] + then + archiveFileList="$archiveFileList $DestFile" + fi + done + done + +# for i in `cat manifest.txt` +# do +# if [ ! -d /$i ] # Ignore directories +# then +# if [ -e /$i ] +# then +# archiveFileList="$archiveFileList $i" +# fi +# fi +# done + + for i in usr/sbin/rcfirebird etc/init.d/firebird etc/rc.d/init.d/firebird + do + DestFile=./$i + if [ -e /$DestFile ] + then + archiveFileList="$archiveFileList $DestFile" + fi + done + + if [ ! -z "$archiveFileList" ] + then + displayMessage "Archiving..." + runAndCheckExit "tar -c${tarOptions}f $tarArc $archiveFileList" + displayMessage "Done." + + displayMessage "Deleting..." + for i in $archiveFileList + do + rm -rf $i + done + displayMessage "Done." + fi + + cd $oldPWD +} + + +#------------------------------------------------------------------------ +# removeInstalledFiles +# +removeInstalledFiles() { + + manifestFile=$FBRootDir/misc/manifest.txt + + if [ ! -f $manifestFile ] + then + return + fi + + origDir=`pwd` + + cd / + + for i in `cat $manifestFile` + do + if [ -f $i -o -L $i ] + then + rm -f $i + #echo $i + fi + done + + cd "$origDir" +} + + +#------------------------------------------------------------------------ +# removeUninstallFiles +# Under the install directory remove all the empty directories +# If some files remain then + +removeUninstallFiles() { + # remove the uninstall scripts files. + #echo $FBRootDir/misc/scripts + rm -rf $FBRootDir/misc/scripts + rm -f $FBRootDir/misc/manifest.txt + rm -f $FBRootDir/bin/uninstall.sh + +} + + +#------------------------------------------------------------------------ +# removeEmptyDirs +# Under the install directory remove all the empty directories +# If some files remain then +# This routine loops, since deleting a directory possibly makes +# the parent empty as well + +removeEmptyDirs() { + + dirContentChanged='yes' + while [ ! -z $dirContentChanged ] + do + dirContentChanged='' + for i in `find $FBRootDir -type d -print`; do + ls $i/* >/dev/null 2>&1 + if [ $? -ne 0 ]; then + rmdir $i + dirContentChanged=$i + fi + done + + if [ ! -d $FBRootDir ] # end loop if the FBRootDir was deleted. + then + dirContentChanged='' + fi + + done +} + +# classicLibrary.sh +#!/bin/sh + +#------------------------------------------------------------------------ +# init defaults +DefaultLibrary=libfbembed + +#------------------------------------------------------------------------ +# fixFilePermissions +# Change the permissions to restrict access to server programs to +# firebird group only. This is MUCH better from a saftey point of +# view than installing as root user, even if it requires a little +# more work. + + +fixFilePermissions() { + chown -R $RunUser:$RunGroup $FBRootDir + + # Turn other access off. + chmod -R o= $FBRootDir + + # Now fix up the mess. + + # fix up directories + for i in `find $FBRootDir -print` + do + FileName=$i + if [ -d $FileName ] + then + chmod o=rx $FileName + fi + done + + # set up the defaults for bin + cd $FBBin + for i in `ls` + do + chmod ug=rx,o= $i + done + + # User can run these programs, they need to talk to server though. + # and they cannot actually create a database. + chmod a=rx isql + chmod a=rx qli + + # Root SUID is still needed for group direct access. + # General users cannot run though. + for i in fb_lock_mgr + do + if [ -f $i ] + then + chown root $i + chmod ug=rx,o= $i + chmod ug+s $i + fi + done + + # set up libraries + cd $FBRootDir + cd lib + chmod a=rx lib* + + # set up include files + cd $FBRootDir + cd include + chmod a=r * + + # Fix lock files + cd $FBRootDir + for i in isc_init1 isc_lock1 isc_event1 isc_monitor1 + do + FileName=$i.`hostname` + touch $FileName + chown $RunUser:$RunUser $FileName + chmod ug=rw,o= $FileName + done + + # Fix the rest + touch firebird.log + chmod ug=rw,o= firebird.log + chmod a=r aliases.conf + chmod a=r firebird.conf + chmod a=r firebird.msg + chmod a=r help/help.fdb + chmod ug=rw,o= $SecurityDatabase + chmod a=r *License.txt + + if [ "$RunUser" = "root" ] + # In that case we must open databases to the world... + # That's a pity, but required if root RunUser choosen. + then + chmod a=rw $SecurityDatabase + fi + + # fix up examples' permissions + cd examples + + # set a default of read all files in examples + for i in `find . -name '*' -type f -print` + do + chmod a=r $i + done + + # set a default of read&search all dirs in examples + for i in `find . -name '*' -type d -print` + do + chmod a=rx $i + done + + # make examples db's writable by group + for i in `find . -name '*.fdb' -print` + do + chown $RunUser:$RunUser $i + chmod ug=rw,o= $i + done + + # fix up doc permissions + fixDocPermissions + + cd $FBRootDir +} + + +#------------------------------------------------------------------------ +# changeXinetdServiceUser +# Change the run user of the xinetd service + +changeXinetdServiceUser() { + InitFile=/etc/xinetd.d/firebird + if [ -f $InitFile ] + then + editFile $InitFile user "\tuser\t\t\t= $RunUser" + fi +} + + +#------------------------------------------------------------------------ +# Update inetd service entry +# This just adds/replaces the service entry line + +updateInetdEntry() { + newLine="gds_db stream tcp nowait.30000 $RunUser $FBBin/fb_inet_server fb_inet_server # Firebird Database Remote Server" + replaceLineInFile /etc/inetd.conf "$newLine" "^gds_db" +} + + +#------------------------------------------------------------------------ +# Update xinetd service entry + +updateXinetdEntry() { + cp $FBRootDir/misc/firebird.xinetd /etc/xinetd.d/firebird + changeXinetdServiceUser +} + + +#------------------------------------------------------------------------ +# Update inetd service entry +# Check to see if we have xinetd installed or plain inetd. +# Install differs for each of them. + +updateInetdServiceEntry() { + if [ -d /etc/xinetd.d ] + then + updateXinetdEntry + else + updateInetdEntry + fi +} + + +#------------------------------------------------------------------------ +# change init.d RunUser + +changeInitRunUser() { + # do nothing for CS + return 0 +} + + +#------------------------------------------------------------------------ +# start init.d service + +startService() { + # do nothing for CS + return 0 +} + +# linuxLibrary.sh +#!/bin/sh + +RunUser=firebird +export RunUser +RunGroup=firebird +export RunGroup +PidDir=/var/run/firebird +export PidDir + +#------------------------------------------------------------------------ +# Get correct options & misc. + +psOptions=ww +export psOptions +mktOptions=-q +export mktOptions +tarOptions=z +export tarOptions +tarExt=tar.gz +export tarExt + +#------------------------------------------------------------------------ +# Add new user and group + +TryAddGroup() { + + AdditionalParameter=$1 + testStr=`grep firebird /etc/group` + + if [ -z "$testStr" ] + then + groupadd $AdditionalParameter firebird + fi + +} + + +TryAddUser() { + + AdditionalParameter=$1 + testStr=`grep firebird /etc/passwd` + + if [ -z "$testStr" ] + then + useradd $AdditionalParameter -d $FBRootDir -s /bin/false \ + -c "Firebird Database Owner" -g firebird firebird + fi + +} + + +addFirebirdUser() { + + TryAddGroup "-g 84 -r" >/dev/null 2>&1 + TryAddGroup "-g 84" >/dev/null 2>&1 + TryAddGroup "-r" >/dev/null 2>&1 + TryAddGroup " " + + TryAddUser "-u 84 -r -M" >/dev/null 2>&1 + TryAddUser "-u 84 -M" >/dev/null 2>&1 + TryAddUser "-r -M" >/dev/null 2>&1 + TryAddUser "-M" + TryAddUser "-u 84 -r" >/dev/null 2>&1 + TryAddUser "-u 84" >/dev/null 2>&1 + TryAddUser "-r" >/dev/null 2>&1 + TryAddUser " " + +} + + +#------------------------------------------------------------------------ +# Detect Distribution. +# AP: very beautiful, but unused. Let's keep alive for a while. (2005) + +detectDistro() { + + # it's not provided... + if [ -z "$linuxDistro" ] + then + if [ -e /etc/SuSE-release ] + then + # SuSE + linuxDistro="SuSE" + elif [ -e /etc/mandrake-release ] + then + # Mandrake + linuxDistro="MDK" + elif [ -e /etc/debian_version ] + then + # Debian + linuxDistro="Debian" + elif [ -e /etc/gentoo-release ] + then + # Debian + linuxDistro="Gentoo" + elif [ -e /etc/rc.d/init.d/functions ] + then + # very likely Red Hat + linuxDistro="RH" + elif [ -d /etc/rc.d/init.d ] + then + # generic Red Hat + linuxDistro="G-RH" + elif [ -d /etc/init.d ] + then + # generic SuSE + linuxDistro="G-SuSE" + fi + fi +} + + +#------------------------------------------------------------------------ +# print location of init script + +getInitScriptLocation() { + if [ -f /etc/rc.d/init.d/firebird ] + then + echo -n /etc/rc.d/init.d/firebird + elif [ -f /etc/rc.d/rc.firebird ] + then + echo -n /etc/rc.d/rc.firebird + elif [ -f /etc/init.d/firebird ] + then + echo -n /etc/init.d/firebird + fi +} + + +#------------------------------------------------------------------------ +# stop super server if it is running + +stopSuperServerIfRunning() { + checkString=`ps -efww| egrep "\b(fbserver|fbguard)\b" |grep -v grep` + + if [ ! -z "$checkString" ] + then + init_d=`getInitScriptLocation` + + if [ -x "$init_d" ] + then + $init_d stop + sleep 1 + fi + fi +} + +#!/bin/sh +# +# This library is part of the Firebird project +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# You may obtain a copy of the Licence at +# http://www.gnu.org/licences/lgpl.html +# +# As a special exception this file can also be included in modules +# with other source code as long as that source code has been +# released under an Open Source Initiative certificed licence. +# More information about OSI certification can be found at: +# http://www.opensource.org +# +# This module is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public Licence for more details. +# +# This module was created by members of the firebird development +# team. All individual contributions remain the Copyright (C) of +# those individuals and all rights are reserved. Contributors to +# this file are either listed below or can be obtained from a CVS +# history command. +# +# Created by: Mark O'Donohue +# +# Contributor(s): +# +# +# + +# The post install script for Firebird Classic + + + + # Detect which linux distro we are installing on + detectDistro + + # Make sure the links are in place + if [ -z "$FirebirdInstallPrefix" ] + then + FirebirdInstallPrefix=/opt/firebird + fi + + origDir=`pwd` + + # Update /etc/services + + # The \n is needed, some /etc/services files are missing a trailing + # line feed - MOD 7-Nov-2002 + FileName=/etc/services + newLine="gds_db 3050/tcp # Firebird SQL Database Remote Protocol" + oldLine=`grep "^gds_db" $FileName` + if [ -z "$oldLine" ] + then + echo "" >> $FileName + echo $newLine >> $FileName + echo "" >> $FileName + # replaceLineInFile "$FileName" "$newLine" "$oldLine" + fi + + # add Firebird user + if [ $RunUser = "firebird" ] + then + addFirebirdUser + fi + + # Create log + cd $FBRootDir + touch firebird.log + + # Update ownership and SUID bits for programs. + chown -R $RunUser:$RunUser $FBRootDir + + fixFilePermissions + + createLinksForBackCompatibility + + buildUninstallFile + + # Update the /etc/inetd.conf or xinetd entry + updateInetdServiceEntry + + + # Get inetd to reread new init files. + resetInetdServer + + + cd $FBRootDir + + # Change sysdba password + changeDBAPassword \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..58856e1 --- /dev/null +++ b/readme.md @@ -0,0 +1,61 @@ +Crear el entorno por primera vez: +--------------------------------- +python3 -m venv venv +source venv/bin/activate <-- en linux +.\venv\Scripts\activat <-- en Windows +pip3 install -r requirements.txt + + +Lanzar el entorno para hacer pruebas del script: +----------------------------------------------- +source venv/bin/activate <-- en linux +.\venv\Scripts\activat <-- en Windows +python app\main.py + + + +---- + +git clone ssh://git@wopr.rodax-software.com:30001/uecko/presupuestador-web---scripts-sync.git uecko-sync-scripts +cd uecko-sync-scripts/ +cp .env-sample .env +pip install -r requirements.txt +python3 -m venv env +sudo apt install python3.11-venv +python3 -m venv env +source venv/bin/activate +pip3 install -r requirements.txt +python3 factuges_catalog_to_json_file.py + +> Reconstruir imagen docker +docker compose up --build -d + + +> Instalar Firebird 2.1 +1. Descargar paquete: https://master.dl.sourceforge.net/project/firebird/firebird-linux-amd64/2.1.7-Release/FirebirdSS-2.1.7.18553-0.amd64.tar.gz?viasf=1 +2. Descomprimir: tar -xvf +3. Lanzar instalación: sudo ./install.sh +4. Si da error, da igual. El caso es que en /opt/firebird estén los ficheros y en las librerías. +5. Crear enlaces simbólicos: + Busque la librería libfbclient.so.2.m.n (m.n es el nro. menor de versión más el nro. de actualización) en /opt/firebird/lib del equipo donde está instalado el servidor Firebird. Cópiela a /usr/lib en el cliente. + + Cree enlaces simbólicos usando los siguientes comandos: + + ln -s /usr/lib/libfbclient.so.2.m.n /usr/lib/libfbclient.so.2 + + ln -s /usr/lib/libfbclient.so.2 /usr/lib/libfbclient.so + + reemplazando 2.m.n con su número de versión, por ejemplo 2.1.7 + + Si Ud. está ejecutando aplicaciones que esperan que las librerías antiguas estén presentes, cree también los siguientes enlaces simbólicos: + + ln -s /usr/lib/libfbclient.so /usr/lib/libgds.so.0 + + ln -s /usr/lib/libfbclient.so /usr/lib/libgds.so + + Copie el archivo firebird.msg a /opt/firebird + + En el perfil por defecto del sistema, o usando setenv() desde una consola, cree la variable de entorno FIREBIRD y apúntela al directorio /opt/firebird, para permitir a las rutinas de la API localizar los mensajes. + Para ello, editar con sudo nano /etc/profile y añadir FIREBIRD=/opt/firebird + +6. sudo apt-get install libncurses5 diff --git a/requeriments.txt b/requeriments.txt new file mode 100644 index 0000000..4916e1f --- /dev/null +++ b/requeriments.txt @@ -0,0 +1,27 @@ +about-time==4.2.1 +alive-progress==3.1.5 +bcrypt==4.1.3 +black==24.8.0 +brevo-python==1.1.2 +certifi==2024.8.30 +cffi==1.16.0 +click==8.1.7 +colorama==0.4.6 +cryptography==42.0.8 +fdb==2.0.2 +future==0.18.3 +grapheme==0.6.0 +mypy-extensions==1.0.0 +mysql-connector-python==8.4.0 +packaging==24.1 +paramiko==3.4.0 +pathspec==0.12.1 +platformdirs==4.3.6 +psutil==6.0.0 +pycparser==2.22 +PyNaCl==1.5.0 +python-dateutil==2.9.0.post0 +python-dotenv==1.0.0 +six==1.16.0 +sshtunnel==0.4.0 +urllib3==2.2.3