From d2a9092f4628ad0d149e0181f4e04d2979b01413 Mon Sep 17 00:00:00 2001 From: Josako Date: Sat, 17 May 2025 18:46:17 +0200 Subject: [PATCH] Cleanup .pyc and .DS_Store, add new modules, remove legacy services --- .gitignore | 1 - common/.DS_Store | Bin 6148 -> 0 bytes common/__pycache__/__init__.cpython-312.pyc | Bin 190 -> 0 bytes common/__pycache__/extensions.cpython-312.pyc | Bin 1222 -> 0 bytes common/extensions.py | 2 - .../EveAIHistoryRetriever.cpython-312.pyc | Bin 2967 -> 0 bytes .../EveAIRetriever.cpython-312.pyc | Bin 5603 -> 0 bytes common/models/.DS_Store | Bin 6148 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 197 -> 0 bytes .../__pycache__/document.cpython-312.pyc | Bin 7956 -> 0 bytes .../__pycache__/interaction.cpython-312.pyc | Bin 4076 -> 0 bytes .../models/__pycache__/user.cpython-312.pyc | Bin 12318 -> 0 bytes common/models/entitlements.py | 421 +++++++++++++++++- common/services/entitlements/__init__.py | 9 + .../entitlements/license_period_services.py | 223 ++++++++++ .../license_tier_services.py} | 11 +- .../entitlements/license_usage_services.py | 143 ++++++ common/services/user/__init__.py | 5 + .../services/{ => user}/partner_services.py | 0 common/services/{ => user}/tenant_services.py | 0 common/services/{ => user}/user_services.py | 0 common/utils/.DS_Store | Bin 6148 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 196 -> 0 bytes .../__pycache__/celery_utils.cpython-312.pyc | Bin 3216 -> 0 bytes .../__pycache__/cors_utils.cpython-312.pyc | Bin 4861 -> 0 bytes .../__pycache__/database.cpython-312.pyc | Bin 5207 -> 0 bytes .../datetime_utils.cpython-312.pyc | Bin 812 -> 0 bytes .../__pycache__/debug_utils.cpython-312.pyc | Bin 1294 -> 0 bytes .../key_encryption.cpython-312.pyc | Bin 6007 -> 0 bytes .../__pycache__/middleware.cpython-312.pyc | Bin 1480 -> 0 bytes .../__pycache__/model_utils.cpython-312.pyc | Bin 6919 -> 0 bytes .../__pycache__/nginx_utils.cpython-312.pyc | Bin 1423 -> 0 bytes .../__pycache__/security.cpython-312.pyc | Bin 1566 -> 0 bytes .../security_utils.cpython-312.pyc | Bin 3188 -> 0 bytes .../simple_encryption.cpython-312.pyc | Bin 2607 -> 0 bytes .../template_filters.cpython-312.pyc | Bin 1532 -> 0 bytes .../view_assistants.cpython-312.pyc | Bin 2663 -> 0 bytes common/utils/asset_utils.py | 4 +- common/utils/cache/license_cache.py | 102 +++++ common/utils/document_utils.py | 23 +- common/utils/dynamic_field_utils.py | 44 ++ common/utils/eveai_exceptions.py | 62 +++ common/utils/mail_utils.py | 46 ++ common/utils/middleware.py | 4 +- common/utils/model_logging_utils.py | 1 - common/utils/security.py | 13 +- common/utils/security_utils.py | 46 +- common/utils/template_filters.py | 14 + config/.DS_Store | Bin 6148 -> 0 bytes config/__pycache__/__init__.cpython-312.pyc | Bin 190 -> 0 bytes config/__pycache__/config.cpython-312.pyc | Bin 8652 -> 0 bytes .../logging_config.cpython-312.pyc | Bin 1847 -> 0 bytes docker/.python-version | 1 + eveai_app/.DS_Store | Bin 6148 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 6165 -> 0 bytes eveai_app/__pycache__/errors.cpython-312.pyc | Bin 1872 -> 0 bytes eveai_app/templates/.DS_Store | Bin 6148 -> 0 bytes eveai_app/views/.DS_Store | Bin 6148 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 199 -> 0 bytes .../__pycache__/basic_forms.cpython-312.pyc | Bin 1971 -> 0 bytes .../__pycache__/basic_views.cpython-312.pyc | Bin 3646 -> 0 bytes .../document_forms.cpython-312.pyc | Bin 5479 -> 0 bytes .../document_views.cpython-312.pyc | Bin 36745 -> 0 bytes .../interaction_views.cpython-312.pyc | Bin 6498 -> 0 bytes .../security_forms.cpython-312.pyc | Bin 1639 -> 0 bytes .../security_views.cpython-312.pyc | Bin 10787 -> 0 bytes .../__pycache__/user_forms.cpython-312.pyc | Bin 7908 -> 0 bytes .../__pycache__/user_views.cpython-312.pyc | Bin 28996 -> 0 bytes eveai_app/views/document_views.py | 5 +- eveai_chat/.DS_Store | Bin 8196 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 3464 -> 0 bytes .../__pycache__/chat_handler.cpython-312.pyc | Bin 9897 -> 0 bytes eveai_chat/views/.DS_Store | Bin 6148 -> 0 bytes .../__pycache__/chat_views.cpython-312.pyc | Bin 3822 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 1754 -> 0 bytes .../__pycache__/tasks.cpython-312.pyc | Bin 12436 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 1823 -> 0 bytes .../__pycache__/tasks.cpython-312.pyc | Bin 21962 -> 0 bytes .../Zapier/eveai_integration/.gitignore | 63 +++ .../test/creates/add_document.test.js | 20 + .../eveai_integration/test/example.test.js | 7 + nginx/static/.DS_Store | Bin 8196 -> 0 bytes nginx/static/assets/.DS_Store | Bin 6148 -> 0 bytes nginx/static/assets/img/.DS_Store | Bin 8196 -> 0 bytes nginx/static/assets/js/.DS_Store | Bin 6148 -> 0 bytes .../SPIN_SPECIALIST_1.0.0_overview.svg | 299 +++++++++++++ ...CIE_VACATURE_SPECIALIST_1.0.0_overview.svg | 76 ++++ nginx/static/js/eveai-token-manager.js | 0 repopack stuff (deprecated)/.repopackignore | 55 +++ scripts/.DS_Store | Bin 6148 -> 0 bytes .../__pycache__/run_eveai_app.cpython-312.pyc | Bin 379 -> 0 bytes .../run_eveai_chat.cpython-312.pyc | Bin 376 -> 0 bytes .../run_eveai_workers.cpython-312.pyc | Bin 341 -> 0 bytes 93 files changed, 1620 insertions(+), 80 deletions(-) delete mode 100644 common/.DS_Store delete mode 100644 common/__pycache__/__init__.cpython-312.pyc delete mode 100644 common/__pycache__/extensions.cpython-312.pyc delete mode 100644 common/langchain/__pycache__/EveAIHistoryRetriever.cpython-312.pyc delete mode 100644 common/langchain/__pycache__/EveAIRetriever.cpython-312.pyc delete mode 100644 common/models/.DS_Store delete mode 100644 common/models/__pycache__/__init__.cpython-312.pyc delete mode 100644 common/models/__pycache__/document.cpython-312.pyc delete mode 100644 common/models/__pycache__/interaction.cpython-312.pyc delete mode 100644 common/models/__pycache__/user.cpython-312.pyc create mode 100644 common/services/entitlements/__init__.py create mode 100644 common/services/entitlements/license_period_services.py rename common/services/{entitlement_services.py => entitlements/license_tier_services.py} (94%) create mode 100644 common/services/entitlements/license_usage_services.py create mode 100644 common/services/user/__init__.py rename common/services/{ => user}/partner_services.py (100%) rename common/services/{ => user}/tenant_services.py (100%) rename common/services/{ => user}/user_services.py (100%) delete mode 100644 common/utils/.DS_Store delete mode 100644 common/utils/__pycache__/__init__.cpython-312.pyc delete mode 100644 common/utils/__pycache__/celery_utils.cpython-312.pyc delete mode 100644 common/utils/__pycache__/cors_utils.cpython-312.pyc delete mode 100644 common/utils/__pycache__/database.cpython-312.pyc delete mode 100644 common/utils/__pycache__/datetime_utils.cpython-312.pyc delete mode 100644 common/utils/__pycache__/debug_utils.cpython-312.pyc delete mode 100644 common/utils/__pycache__/key_encryption.cpython-312.pyc delete mode 100644 common/utils/__pycache__/middleware.cpython-312.pyc delete mode 100644 common/utils/__pycache__/model_utils.cpython-312.pyc delete mode 100644 common/utils/__pycache__/nginx_utils.cpython-312.pyc delete mode 100644 common/utils/__pycache__/security.cpython-312.pyc delete mode 100644 common/utils/__pycache__/security_utils.cpython-312.pyc delete mode 100644 common/utils/__pycache__/simple_encryption.cpython-312.pyc delete mode 100644 common/utils/__pycache__/template_filters.cpython-312.pyc delete mode 100644 common/utils/__pycache__/view_assistants.cpython-312.pyc create mode 100644 common/utils/cache/license_cache.py create mode 100644 common/utils/dynamic_field_utils.py create mode 100644 common/utils/mail_utils.py delete mode 100644 config/.DS_Store delete mode 100644 config/__pycache__/__init__.cpython-312.pyc delete mode 100644 config/__pycache__/config.cpython-312.pyc delete mode 100644 config/__pycache__/logging_config.cpython-312.pyc create mode 100644 docker/.python-version delete mode 100644 eveai_app/.DS_Store delete mode 100644 eveai_app/__pycache__/__init__.cpython-312.pyc delete mode 100644 eveai_app/__pycache__/errors.cpython-312.pyc delete mode 100644 eveai_app/templates/.DS_Store delete mode 100644 eveai_app/views/.DS_Store delete mode 100644 eveai_app/views/__pycache__/__init__.cpython-312.pyc delete mode 100644 eveai_app/views/__pycache__/basic_forms.cpython-312.pyc delete mode 100644 eveai_app/views/__pycache__/basic_views.cpython-312.pyc delete mode 100644 eveai_app/views/__pycache__/document_forms.cpython-312.pyc delete mode 100644 eveai_app/views/__pycache__/document_views.cpython-312.pyc delete mode 100644 eveai_app/views/__pycache__/interaction_views.cpython-312.pyc delete mode 100644 eveai_app/views/__pycache__/security_forms.cpython-312.pyc delete mode 100644 eveai_app/views/__pycache__/security_views.cpython-312.pyc delete mode 100644 eveai_app/views/__pycache__/user_forms.cpython-312.pyc delete mode 100644 eveai_app/views/__pycache__/user_views.cpython-312.pyc delete mode 100644 eveai_chat/.DS_Store delete mode 100644 eveai_chat/__pycache__/__init__.cpython-312.pyc delete mode 100644 eveai_chat/socket_handlers/__pycache__/chat_handler.cpython-312.pyc delete mode 100644 eveai_chat/views/.DS_Store delete mode 100644 eveai_chat/views/__pycache__/chat_views.cpython-312.pyc delete mode 100644 eveai_chat_workers/__pycache__/__init__.cpython-312.pyc delete mode 100644 eveai_chat_workers/__pycache__/tasks.cpython-312.pyc delete mode 100644 eveai_workers/__pycache__/__init__.cpython-312.pyc delete mode 100644 eveai_workers/__pycache__/tasks.cpython-312.pyc create mode 100644 integrations/Zapier/eveai_integration/.gitignore create mode 100644 integrations/Zapier/eveai_integration/test/creates/add_document.test.js create mode 100644 integrations/Zapier/eveai_integration/test/example.test.js delete mode 100644 nginx/static/.DS_Store delete mode 100644 nginx/static/assets/.DS_Store delete mode 100644 nginx/static/assets/img/.DS_Store delete mode 100644 nginx/static/assets/js/.DS_Store create mode 100644 nginx/static/assets/specialists/SPIN_SPECIALIST_1.0.0_overview.svg create mode 100644 nginx/static/assets/specialists/TRAICIE_VACATURE_SPECIALIST_1.0.0_overview.svg create mode 100755 nginx/static/js/eveai-token-manager.js create mode 100644 repopack stuff (deprecated)/.repopackignore delete mode 100644 scripts/.DS_Store delete mode 100644 scripts/__pycache__/run_eveai_app.cpython-312.pyc delete mode 100644 scripts/__pycache__/run_eveai_chat.cpython-312.pyc delete mode 100644 scripts/__pycache__/run_eveai_workers.cpython-312.pyc diff --git a/.gitignore b/.gitignore index 347cb00..c6cbd0d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ __pycache__ **/__pycache__ /.idea *.pyc -*.pyc common/.DS_Store common/__pycache__/__init__.cpython-312.pyc common/__pycache__/extensions.cpython-312.pyc diff --git a/common/.DS_Store b/common/.DS_Store deleted file mode 100644 index 59df3c20718841b5c7c994b5ee4399c10704fd13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK&1(}u6o1pE?ZzMkQb7=5!E1<#SVQS0jq%W%xT4|*F`Es|;$|mgH>M>7a`*cW z@FIBjt~bGhH&6Z<-aKd#eQ#!>v-v0@4SNDlGEfJwQHoFEso#ewVIlh)%$qs{p*7d zG&7mQ%xpT``^|pwd^E~^J&fIL>$9*iES$uw_Hb8gp*j)j!v)g*Hl+_ci)>w=Rp=nw zL(c%Bq$=#uVcet^uYBYtV(ALJyq#PB@?GRK#rc!>B2FS(+`rLQppUuwb7uSvNA>J9 zraOqbYDM0;-N29HV(~LuT28I3u32kV#=7UU`M`-AaX+dydUxpQI_H5qX*b*(err(A zu3zS1-0;IzPdNBq3n_PR`k}`MRo)N1NccK(z)D-`diKn4SlZku=Ix8+jZxknmdfQz zdAoF>xH%f7tyAaDZ(XZD2s$BuA`%296QzYs+EaYglA2nCx5-g`2_7Hp6h8eBOQ8Ad zKfL*jr2JQDHk$uKrxyl1+{NC>jEQlirUF};BaLc;83l|2$3X$wA8agy6@_DoV(Y+4 zTmcYM*ewj(^k;#5Y=sqtV~IF|#-uBvbY<#_!K6F(+lp6FIF=~gfvL*}Q{T+g4TXts z$N09O1FJ|hwNbz*kW^q;W;g diff --git a/common/__pycache__/extensions.cpython-312.pyc b/common/__pycache__/extensions.cpython-312.pyc deleted file mode 100644 index 29e10e54778694333c970c93401c2f9d41868861..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1222 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 FfiIm-eNq4b diff --git a/common/models/__pycache__/__init__.cpython-312.pyc b/common/models/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 11892cf519fc85752b3dd6f0f25f36d4bf7620fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 197 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 True transition + if current_value is True and allow_value is True: + # Already True, no change needed + return + elif allow_value is False: + raise ValueError(f"Cannot change {field_name} from {current_value} to False") + + # Update the field + setattr(self, field_name, True) + self.updated_at = dt.now(tz.utc) + if user_id: + self.updated_by = user_id + + @property + def prepaid_invoice(self): + """Get the prepaid invoice for this period""" + return Invoice.query.filter_by( + license_period_id=self.id, + invoice_type=PaymentType.PREPAID + ).first() + + @property + def overage_invoice(self): + """Get the overage invoice for this period""" + return Invoice.query.filter_by( + license_period_id=self.id, + invoice_type=PaymentType.POSTPAID + ).first() + + @property + def prepaid_payment(self): + """Get the prepaid payment for this period""" + return Payment.query.filter_by( + license_period_id=self.id, + payment_type=PaymentType.PREPAID + ).first() + + @property + def overage_payment(self): + """Get the overage payment for this period""" + return Payment.query.filter_by( + license_period_id=self.id, + payment_type=PaymentType.POSTPAID + ).first() + + @property + def all_invoices(self): + """Get all invoices for this period""" + return self.invoices + + @property + def all_payments(self): + """Get all payments for this period""" + return self.payments + + def transition_status(self, new_status: PeriodStatus, user_id: int = None): + """Transition to a new status with proper validation and logging""" + if not self.can_transition_to(new_status): + raise ValueError(f"Invalid status transition from {self.status} to {new_status}") + + self.status = new_status + self.updated_at = dt.now(tz.utc) + if user_id: + self.updated_by = user_id + + # Set appropriate timestamps + if new_status == PeriodStatus.ACTIVE and not self.prepaid_received_at: + self.prepaid_received_at = dt.now(tz.utc) + elif new_status == PeriodStatus.COMPLETED: + self.completed_at = dt.now(tz.utc) + elif new_status == PeriodStatus.INVOICED: + self.invoiced_at = dt.now(tz.utc) + elif new_status == PeriodStatus.CLOSED: + self.closed_at = dt.now(tz.utc) + + @property + def is_overdue(self): + """Check if a prepaid payment is overdue""" + return (self.status == PeriodStatus.PENDING and + self.period_start <= dt.now(tz.utc).date()) + + def can_transition_to(self, new_status: PeriodStatus) -> bool: + """Check if a status transition is valid""" + valid_transitions = { + PeriodStatus.UPCOMING: [PeriodStatus.ACTIVE, PeriodStatus.PENDING], + PeriodStatus.PENDING: [PeriodStatus.ACTIVE], + PeriodStatus.ACTIVE: [PeriodStatus.COMPLETED], + PeriodStatus.COMPLETED: [PeriodStatus.INVOICED, PeriodStatus.CLOSED], + PeriodStatus.INVOICED: [PeriodStatus.CLOSED], + PeriodStatus.CLOSED: [] + } + return new_status in valid_transitions.get(self.status, []) + + def __repr__(self): + return f'' + + +class LicenseUsage(db.Model): + __bind_key__ = 'public' + __table_args__ = {'schema': 'public'} + + id = db.Column(db.Integer, primary_key=True) + tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False) storage_mb_used = db.Column(db.Float, default=0) embedding_mb_used = db.Column(db.Float, default=0) embedding_prompt_tokens_used = db.Column(db.Integer, default=0) @@ -138,9 +386,170 @@ class LicenseUsage(db.Model): interaction_prompt_tokens_used = db.Column(db.Integer, default=0) interaction_completion_tokens_used = db.Column(db.Integer, default=0) interaction_total_tokens_used = db.Column(db.Integer, default=0) - period_start_date = db.Column(db.Date, nullable=False) - period_end_date = db.Column(db.Date, nullable=False) + license_period_id = db.Column(db.Integer, db.ForeignKey('public.license_period.id'), nullable=False) + + # Standard audit fields + created_at = db.Column(db.DateTime, server_default=db.func.now()) + updated_at = db.Column(db.DateTime, server_default=db.func.now(), onupdate=db.func.now()) + created_by = db.Column(db.Integer, db.ForeignKey('public.user.id')) + updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id')) + + license_period = db.relationship('LicensePeriod', back_populates='license_usage') + + def recalculate_storage(self): + Database(self.tenant_id).switch_schema() + # Perform a SUM operation to get the total file size from document_versions + total_storage = db.session.execute(text(f""" + SELECT SUM(file_size) + FROM document_version + """)).scalar() + + self.storage_mb_used = total_storage + + +class PaymentType(Enum): + PREPAID = "PREPAID" + POSTPAID = "POSTPAID" + + +class PaymentStatus(Enum): + PENDING = "PENDING" + PAID = "PAID" + FAILED = "FAILED" + CANCELLED = "CANCELLED" + + +class Payment(db.Model): + __bind_key__ = 'public' + __table_args__ = {'schema': 'public'} + + id = db.Column(db.Integer, primary_key=True) + license_period_id = db.Column(db.Integer, db.ForeignKey('public.license_period.id'), nullable=True) + tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False) + + # Payment details + payment_type = db.Column(db.Enum(PaymentType), nullable=False) + amount = db.Column(db.Numeric(10, 2), nullable=False) + currency = db.Column(db.String(3), nullable=False) + description = db.Column(db.Text, nullable=True) + + # Status tracking + status = db.Column(db.Enum(PaymentStatus), nullable=False, default=PaymentStatus.PENDING) + + # External provider information + external_payment_id = db.Column(db.String(255), nullable=True) + payment_method = db.Column(db.String(50), nullable=True) # credit_card, bank_transfer, etc. + provider_data = db.Column(JSONB, nullable=True) # Provider-specific data + + # Payment information + paid_at = db.Column(db.DateTime, nullable=True) + + # Standard audit fields + created_at = db.Column(db.DateTime, server_default=db.func.now()) + created_by = db.Column(db.Integer, db.ForeignKey('public.user.id')) + updated_at = db.Column(db.DateTime, server_default=db.func.now(), onupdate=db.func.now()) + updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id')) + + # Relationships + license_period = db.relationship('LicensePeriod', back_populates='payments') + invoice = db.relationship('Invoice', back_populates='payment', uselist=False) + + @property + def is_overdue(self): + """Check if payment is overdue""" + if self.status != PaymentStatus.PENDING: + return False + + # For prepaid payments, check if period start has passed + if (self.payment_type == PaymentType.PREPAID and + self.license_period_id): + return self.license_period.period_start <= dt.now(tz.utc).date() + + # For postpaid, check against due date (would be on invoice) + return False + + def __repr__(self): + return f'' + + +class InvoiceStatus(Enum): + DRAFT = "DRAFT" + SENT = "SENT" + PAID = "PAID" + OVERDUE = "OVERDUE" + CANCELLED = "CANCELLED" + + +class Invoice(db.Model): + __bind_key__ = 'public' + __table_args__ = {'schema': 'public'} + + id = db.Column(db.Integer, primary_key=True) + license_period_id = db.Column(db.Integer, db.ForeignKey('public.license_period.id'), nullable=False) + payment_id = db.Column(db.Integer, db.ForeignKey('public.payment.id'), nullable=True) + tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False) + + # Invoice details + invoice_type = db.Column(db.Enum(PaymentType), nullable=False) + invoice_number = db.Column(db.String(50), unique=True, nullable=False) + invoice_date = db.Column(db.Date, nullable=False) + due_date = db.Column(db.Date, nullable=False) + + # Financial details + amount = db.Column(db.Numeric(10, 2), nullable=False) + currency = db.Column(db.String(3), nullable=False) + tax_amount = db.Column(db.Numeric(10, 2), default=0) + + # Descriptive fields + description = db.Column(db.Text, nullable=True) + status = db.Column(db.Enum(InvoiceStatus), nullable=False, default=InvoiceStatus.DRAFT) + + # Timestamps + sent_at = db.Column(db.DateTime, nullable=True) + paid_at = db.Column(db.DateTime, nullable=True) + + # Standard audit fields + created_at = db.Column(db.DateTime, server_default=db.func.now()) + created_by = db.Column(db.Integer, db.ForeignKey('public.user.id')) + updated_at = db.Column(db.DateTime, server_default=db.func.now(), onupdate=db.func.now()) + updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id')) + + # Relationships + license_period = db.relationship('LicensePeriod', back_populates='invoices') + payment = db.relationship('Payment', back_populates='invoice') + + def __repr__(self): + return f'' + + +class LicenseChangeLog(db.Model): + """ + Log of changes to license configurations + Used for auditing and tracking when/why license details changed + """ + __bind_key__ = 'public' + __table_args__ = {'schema': 'public'} + + id = db.Column(db.Integer, primary_key=True) + license_id = db.Column(db.Integer, db.ForeignKey('public.license.id'), nullable=False) + changed_at = db.Column(db.DateTime, nullable=False, default=lambda: dt.now(tz.utc)) + + # What changed + field_name = db.Column(db.String(100), nullable=False) + old_value = db.Column(db.String(255), nullable=True) + new_value = db.Column(db.String(255), nullable=False) + + # Why it changed + reason = db.Column(db.Text, nullable=True) + + # Standard audit fields + created_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True) + + # Relationships + license = db.relationship('License', backref=db.backref('change_logs', order_by='LicenseChangeLog.changed_at')) + + def __repr__(self): + return f' {self.new_value}>' - license = db.relationship('License', back_populates='usages') diff --git a/common/services/entitlements/__init__.py b/common/services/entitlements/__init__.py new file mode 100644 index 0000000..80cd62c --- /dev/null +++ b/common/services/entitlements/__init__.py @@ -0,0 +1,9 @@ +from common.services.entitlements.license_period_services import LicensePeriodServices +from common.services.entitlements.license_usage_services import LicenseUsageServices +from common.services.entitlements.license_tier_services import LicenseTierServices + +__all__ = [ + 'LicensePeriodServices', + 'LicenseUsageServices', + 'LicenseTierServices' +] diff --git a/common/services/entitlements/license_period_services.py b/common/services/entitlements/license_period_services.py new file mode 100644 index 0000000..ef5dc0c --- /dev/null +++ b/common/services/entitlements/license_period_services.py @@ -0,0 +1,223 @@ +from dateutil.relativedelta import relativedelta +from datetime import datetime as dt, timezone as tz, timedelta +from flask import current_app + +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.sql.expression import and_ + +from common.extensions import db +from common.models.entitlements import LicensePeriod, License, PeriodStatus, LicenseUsage +from common.utils.eveai_exceptions import EveAILicensePeriodsExceeded, EveAIPendingLicensePeriod, EveAINoActiveLicense +from common.utils.model_logging_utils import set_logging_information, update_logging_information + + +class LicensePeriodServices: + @staticmethod + def find_current_license_period_for_usage(tenant_id: int) -> LicensePeriod: + """ + Find the current license period for a tenant. It ensures the status of the different license periods are adapted + when required, and a LicenseUsage object is created if required. + + Args: + tenant_id: The ID of the tenant to find the license period for + + Raises: + EveAIException: and derived classes + """ + current_date = dt.now(tz.utc).date() + license_period = (db.session.query(LicensePeriod) + .filter_by(tenant_id=tenant_id) + .filter(and_(LicensePeriod.period_start_date <= current_date, + LicensePeriod.period_end_date >= current_date)) + .first()) + if not license_period: + license_period = LicensePeriodServices._create_next_license_period_for_usage(tenant_id) + if license_period: + match license_period.status: + case PeriodStatus.UPCOMING: + LicensePeriodServices._complete_last_license_period() + LicensePeriodServices._activate_license_period(license_period) + if not license_period.license_usage: + new_license_usage = LicenseUsage() + new_license_usage.license_period = license_period + try: + db.session.add(new_license_usage) + db.session.commit() + except SQLAlchemyError as e: + db.session.rollback() + current_app.logger.error( + f"Error creating new license usage for license period {license_period.id}: {str(e)}") + raise e + if license_period.status == PeriodStatus.ACTIVE: + return license_period + else: + # Status is PENDING, so no prepaid payment received. There is no license period we can use. + # We allow for a delay of 5 days before raising an exception. + current_date = dt.now(tz.utc).date() + delta = abs(current_date - license_period.period_start_date) + if delta > timedelta(days=current_app.config.get('ENTITLEMENTS_MAX_PENDING_DAYS', 5)): + raise EveAIPendingLicensePeriod() + case PeriodStatus.ACTIVE: + return license_period + case PeriodStatus.PENDING: + return license_period + else: + raise EveAILicensePeriodsExceeded(license_id=None) + + @staticmethod + def _create_next_license_period_for_usage(tenant_id) -> LicensePeriod: + """ + Create a new period for this license using the current license configuration + + Args: + tenant_id: The ID of the tenant to create the period for + + Returns: + LicensePeriod: The newly created license period + """ + current_date = dt.now(tz.utc).date() + + # Zoek de actieve licentie voor deze tenant op de huidige datum + the_license = (db.session.query(License) + .filter_by(tenant_id=tenant_id) + .filter(License.start_date <= current_date) + .filter(License.end_date >= current_date) + .first()) + + if not the_license: + current_app.logger.error(f"No active license found for tenant {tenant_id} on date {current_date}") + raise EveAINoActiveLicense(tenant_id=tenant_id) + + next_period_number = 1 + if the_license.periods: + # If there are existing periods, get the next sequential number + next_period_number = max(p.period_number for p in the_license.periods) + 1 + + if next_period_number > the_license.max_periods: + raise EveAILicensePeriodsExceeded(license_id=the_license.id) + + new_license_period = LicensePeriod( + license_id=the_license.id, + tenant_id=tenant_id, + period_number=next_period_number, + period_start=the_license.start_date + relativedelta(months=next_period_number-1), + period_end=the_license.end_date + relativedelta(months=next_period_number, days=-1), + status=PeriodStatus.UPCOMING, + ) + set_logging_information(new_license_period, dt.now(tz.utc)) + new_license_usage = LicenseUsage( + license_period=new_license_period, + tenant_id=tenant_id, + ) + set_logging_information(new_license_usage, dt.now(tz.utc)) + + try: + db.session.add(new_license_period) + db.session.add(new_license_usage) + db.session.commit() + return new_license_period + except SQLAlchemyError as e: + db.session.rollback() + current_app.logger.error(f"Error creating next license period for tenant {tenant_id}: {str(e)}") + raise e + + @staticmethod + def _activate_license_period(license_period_id: int = None, license_period: LicensePeriod = None) -> LicensePeriod: + """ + Activate a license period + + Args: + license_period_id: The ID of the license period to activate (optional if license_period is provided) + license_period: The LicensePeriod object to activate (optional if license_period_id is provided) + + Returns: + LicensePeriod: The activated license period object + + Raises: + ValueError: If neither license_period_id nor license_period is provided + """ + if license_period is None and license_period_id is None: + raise ValueError("Either license_period_id or license_period must be provided") + + # Get a license period object if only ID was provided + if license_period is None: + license_period = LicensePeriod.query.get_or_404(license_period_id) + + if license_period.upcoming_at is not None: + license_period.pending_at.upcoming_at = dt.now(tz.utc) + license_period.status = PeriodStatus.PENDING + if license_period.prepaid_payment: + # There is a payment received for the given period + license_period.active_at = dt.now(tz.utc) + license_period.status = PeriodStatus.ACTIVE + + # Copy snapshot fields from the license to the period + the_license = License.query.get_or_404(license_period.license_id) + license_period.currency = the_license.currency + license_period.basic_fee = the_license.basic_fee + license_period.max_storage_mb = the_license.max_storage_mb + license_period.additional_storage_price = the_license.additional_storage_price + license_period.additional_storage_bucket = the_license.additional_storage_bucket + license_period.included_embedding_mb = the_license.included_embedding_mb + license_period.additional_embedding_price = the_license.additional_embedding_price + license_period.additional_embedding_bucket = the_license.additional_embedding_bucket + license_period.included_interaction_tokens = the_license.included_interaction_tokens + license_period.additional_interaction_token_price = the_license.additional_interaction_token_price + license_period.additional_interaction_bucket = the_license.additional_interaction_bucket + license_period.additional_storage_allowed = the_license.additional_storage_allowed + license_period.additional_embedding_allowed = the_license.additional_embedding_allowed + license_period.additional_interaction_allowed = the_license.additional_interaction_allowed + + update_logging_information(license_period, dt.now(tz.utc)) + + if not license_period.license_usage: + license_period.license_usage = LicenseUsage( + tenant_id=license_period.tenant_id, + license_period=license_period, + ) + + license_period.license_usage.recalculate_storage() + + try: + db.session.add(license_period) + db.session.add(license_period.license_usage) + db.session.commit() + except SQLAlchemyError as e: + db.session.rollback() + current_app.logger.error(f"Error activating license period {license_period_id}: {str(e)}") + raise e + + return license_period + + @staticmethod + def _complete_last_license_period(tenant_id) -> None: + """ + Complete the active or pending license period for a tenant. This is done by setting the status to COMPLETED. + + Args: + tenant_id: De ID van de tenant + """ + # Zoek de licenseperiode voor deze tenant met status ACTIVE of PENDING + active_period = ( + db.session.query(LicensePeriod) + .filter_by(tenant_id=tenant_id) + .filter(LicensePeriod.status.in_([PeriodStatus.ACTIVE, PeriodStatus.PENDING])) + .first() + ) + + # Als er geen actieve periode gevonden is, hoeven we niets te doen + if not active_period: + return + + # Zet de gevonden periode op COMPLETED + active_period.status = PeriodStatus.COMPLETED + active_period.completed_at = dt.now(tz.utc) + update_logging_information(active_period, dt.now(tz.utc)) + + try: + db.session.add(active_period) + db.session.commit() + except SQLAlchemyError as e: + db.session.rollback() + current_app.logger.error(f"Error completing period {active_period.id} for {tenant_id}: {str(e)}") + raise e diff --git a/common/services/entitlement_services.py b/common/services/entitlements/license_tier_services.py similarity index 94% rename from common/services/entitlement_services.py rename to common/services/entitlements/license_tier_services.py index 88312f3..d83ca3f 100644 --- a/common/services/entitlement_services.py +++ b/common/services/entitlements/license_tier_services.py @@ -1,17 +1,16 @@ -from flask import session, current_app, flash +from flask import session, flash, current_app +from datetime import datetime as dt, timezone as tz + from sqlalchemy.exc import SQLAlchemyError from common.extensions import db from common.models.entitlements import PartnerServiceLicenseTier -from common.models.user import Partner, PartnerTenant +from common.models.user import Partner from common.utils.eveai_exceptions import EveAINoManagementPartnerService from common.utils.model_logging_utils import set_logging_information -from datetime import datetime as dt, timezone as tz - -from common.utils.security_utils import current_user_has_role -class EntitlementServices: +class LicenseTierServices: @staticmethod def associate_license_tier_with_partner(license_tier_id): """Associate a license tier with a partner""" diff --git a/common/services/entitlements/license_usage_services.py b/common/services/entitlements/license_usage_services.py new file mode 100644 index 0000000..2859c83 --- /dev/null +++ b/common/services/entitlements/license_usage_services.py @@ -0,0 +1,143 @@ +from dateutil.relativedelta import relativedelta +from flask import session, current_app, flash +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.sql.expression import text + +from common.extensions import db, cache_manager +from common.models.entitlements import PartnerServiceLicenseTier, License, LicenseUsage, LicensePeriod, PeriodStatus +from common.models.user import Partner, PartnerTenant +from common.services.entitlements import LicensePeriodServices +from common.utils.database import Database +from common.utils.eveai_exceptions import EveAINoManagementPartnerService, EveAINoActiveLicense, \ + EveAIStorageQuotaExceeded, EveAIEmbeddingQuotaExceeded, EveAIInteractionQuotaExceeded, EveAILicensePeriodsExceeded, \ + EveAIException +from common.utils.model_logging_utils import set_logging_information, update_logging_information +from datetime import datetime as dt, timezone as tz + +from common.utils.security_utils import current_user_has_role + + +class LicenseUsageServices: + @staticmethod + def check_storage_and_embedding_quota(tenant_id: int, file_size_mb: float) -> None: + """ + Check if a tenant can add a new document without exceeding storage and embedding quotas + + Args: + tenant_id: ID of the tenant + file_size_mb: Size of the file in MB + + Raises: + EveAIStorageQuotaExceeded: If storage quota would be exceeded + EveAIEmbeddingQuotaExceeded: If embedding quota would be exceeded + EveAINoActiveLicense: If no active license is found + EveAIException: For other errors + """ + # Get active license period + license_period = LicensePeriodServices.find_current_license_period_for_usage(tenant_id) + # Early return if both overruns are allowed - no need to check usage at all + if license_period.additional_storage_allowed and license_period.additional_embedding_allowed: + return + + # Check storage quota only if overruns are not allowed + if not license_period.additional_storage_allowed: + LicenseUsageServices._validate_storage_quota(license_period, file_size_mb) + + # Check embedding quota only if overruns are not allowed + if not license_period.additional_embedding_allowed: + LicenseUsageServices._validate_embedding_quota(license_period, file_size_mb) + + @staticmethod + def check_embedding_quota(tenant_id: int, file_size_mb: float) -> None: + """ + Check if a tenant can re-embed a document without exceeding embedding quota + + Args: + tenant_id: ID of the tenant + file_size_mb: Size of the file in MB + + Raises: + EveAIEmbeddingQuotaExceeded: If embedding quota would be exceeded + EveAINoActiveLicense: If no active license is found + EveAIException: For other errors + """ + # Get active license period + license_period = LicensePeriodServices.find_current_license_period_for_usage(tenant_id) + # Early return if both overruns are allowed - no need to check usage at all + if license_period.additional_embedding_allowed: + return + + # Check embedding quota + LicenseUsageServices._validate_embedding_quota(license_period, file_size_mb) + + @staticmethod + def check_interaction_quota(tenant_id: int) -> None: + """ + Check if a tenant can execute a specialist without exceeding interaction quota. As it is impossible to estimate + the number of interaction tokens, we only check if the interaction quota are exceeded. So we might have a + limited overrun. + + Args: + tenant_id: ID of the tenant + + Raises: + EveAIInteractionQuotaExceeded: If interaction quota would be exceeded + EveAINoActiveLicense: If no active license is found + EveAIException: For other errors + """ + # Get active license period + license_period = LicensePeriodServices.find_current_license_period_for_usage(tenant_id) + # Early return if both overruns are allowed - no need to check usage at all + if license_period.additional_interaction_allowed: + return + + # Convert tokens to M tokens and check interaction quota + LicenseUsageServices._validate_interaction_quota(license_period) + + @staticmethod + def _validate_storage_quota(license_period: LicensePeriod, additional_mb: float) -> None: + """Check storage quota and raise exception if exceeded""" + current_storage = license_period.license_usage.storage_mb_used or 0 + projected_storage = current_storage + additional_mb + max_storage = license_period.max_storage_mb + + # Hard limit check (we only get here if overruns are NOT allowed) + if projected_storage > max_storage: + raise EveAIStorageQuotaExceeded( + current_usage=current_storage, + limit=max_storage, + additional=additional_mb + ) + + @staticmethod + def _validate_embedding_quota(license_period: LicensePeriod, additional_mb: float) -> None: + """Check embedding quota and raise exception if exceeded""" + current_embedding = license_period.license_usage.embedding_mb_used or 0 + projected_embedding = current_embedding + additional_mb + max_embedding = license_period.included_embedding_mb + + # Hard limit check (we only get here if overruns are NOT allowed) + if projected_embedding > max_embedding: + raise EveAIEmbeddingQuotaExceeded( + current_usage=current_embedding, + limit=max_embedding, + additional=additional_mb + ) + + @staticmethod + def _validate_interaction_quota(license_period) -> None: + """Check interaction quota and raise exception if exceeded (tokens in millions). We might have an overrun!""" + current_tokens = license_period.license_usage.interaction_total_tokens_used / 1_000_000 or 0 + max_tokens = license_period.included_interaction_tokens + + # Hard limit check (we only get here if overruns are NOT allowed) + if current_tokens > max_tokens: + raise EveAIInteractionQuotaExceeded( + current_usage=current_tokens, + limit=max_tokens + ) + + + + + diff --git a/common/services/user/__init__.py b/common/services/user/__init__.py new file mode 100644 index 0000000..adac945 --- /dev/null +++ b/common/services/user/__init__.py @@ -0,0 +1,5 @@ +from common.services.user.user_services import UserServices +from common.services.user.partner_services import PartnerServices +from common.services.user.tenant_services import TenantServices + +__all__ = ['UserServices', 'PartnerServices', 'TenantServices'] \ No newline at end of file diff --git a/common/services/partner_services.py b/common/services/user/partner_services.py similarity index 100% rename from common/services/partner_services.py rename to common/services/user/partner_services.py diff --git a/common/services/tenant_services.py b/common/services/user/tenant_services.py similarity index 100% rename from common/services/tenant_services.py rename to common/services/user/tenant_services.py diff --git a/common/services/user_services.py b/common/services/user/user_services.py similarity index 100% rename from common/services/user_services.py rename to common/services/user/user_services.py diff --git a/common/utils/.DS_Store b/common/utils/.DS_Store deleted file mode 100644 index 86dbb5e9a3b1815d26ed8a03070e55c59290d0e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKu};G<5IwgYDoUX+u^{pVl{&M8QjnPY0fi=2ThTUA(}KEXXJcXI4-g;0&ceqq zAb58+sC82T3qs(F&M$WS?7OGLj)_Rsr(un#LPSM4V_^+NhVgUuCCizX4WN=S`ZS>y zt}4Y#ylo>^x);;0A76+!`(Y8rWplv2Jcaa z$FW#f_4;N2YUFie?yv-{>9p=A*d9u(Xcc zVq7ic7vVamL{>LT9kq+T^XhCukq!;uV<^<3DOScl>e=ioi(Ljg0yFuP`D`&u6e8jY zybVtQ3c)r@yg8d diff --git a/common/utils/__pycache__/__init__.cpython-312.pyc b/common/utils/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index db92efbfd050c6e6bb1432d5f706112acb9274f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196 zcmX@j%ge<81Z&bu(?IlN5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!O4bj{&neAKE!OuB zcQ)}giZ|4EDatQM%CFG($}djL&Q~v1a7itLa`auxQXM_@T~Z5j@+)&w^GftnQp-|v z@(aN1{f7=D< zmzGE4Q@T{N z;P?~%Ep&|8RHZ9TO7r8Es+~RfdZkrqQ`%Mh_|&6{tv%EZn+aBHYvPuP!Q2X&jf0({i(>=b zeoPzTkc%hw1khdd&Y#c3=g;S;s#Dc0JmY2Lci5b)-VQ~M(_}saMh$jS(=xIO9?*Eg zRQ0S;Y&?hwbU$-~iiBNGm8p6mZ?^;D_i%+16vGONIUXP=Q@kX_go48_y!NSC6+*P1 zhQMXJNQ{9%h=)9HMj?nq| zv{@!X;tb|W&)KU0e1-_dd_Lde>tyg}x&~P_f)hUvy?gZ?xi2_O_1ug;9~{+xbr4GAQF*BB7~?7ZZTKsLaIY&=6`nR#S6qbqr+R+*iUBwo`=j?pzY?3*MM!GT!wX;Z zuk~9ON7m1cT0LV{%lM{v^#D`gq47m=t!VXsxE`Fe0#jB?bW@!E2PjP~r%Ku7to7Cp zt^Vjn>-1K~+0~wvp4Grg!1DjZ8v5l%$IY$NovZyT{i_2j16I$_lPepi$F|P;R%0u% z)mtmKthcXx>Do9uv0w7Z#V0@5=os5-ZYzy0kCr0K5v%*%zmSdQ%RH1_tABlSqxJo* z_JJ?o-ny23A>cur!_N_Jzk^>icv@VGS6{UvXZ`(&dlO%Y9qau6+#hcKT|DgU%trNGxx-2?5R*BZHD{o27Z%STa{MFs&k9!I63#2@z5bXwN4buy- zKzB>LyLnB~OMo!=-LZ4NnRuF1ZYvt%?C@>#LtaE**S1(U zwga@w()OKleaw5n$bLs&OP9;tJ3vQ)o5y0j1JiQ>WBe4IdWzbfqPPB8*Yum~i=LOR dPTcu&2q>*DC751FsPT+-Cb-eid(UeJ_h0y8@+<%V diff --git a/common/utils/__pycache__/cors_utils.cpython-312.pyc b/common/utils/__pycache__/cors_utils.cpython-312.pyc deleted file mode 100644 index ef730b63b2e5134e700bc6c188359475113a3b91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4861 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 diff --git a/common/utils/__pycache__/database.cpython-312.pyc b/common/utils/__pycache__/database.cpython-312.pyc deleted file mode 100644 index 93870c43def20f9abf482f0ebaf4da4df6aed7bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5207 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 diff --git a/common/utils/__pycache__/datetime_utils.cpython-312.pyc b/common/utils/__pycache__/datetime_utils.cpython-312.pyc deleted file mode 100644 index d792341fa2542ff45b6c7b44a772adbb79c11d88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 812 zcmYLHO=uHQ5T3U`&Bi8eHIWE0x@f_!U>C)c+CpdoDp)~5X)d8=*SaygFT8zee%KTd z54rY~LlJsZa`D)c*sESD5rj2@pcik!^dR-(d)sU~uruE`GjHE~v->F?KLN~o&#b4S z0PvHO&=?PFbdH5C7+|0Z*iZn(u~0z;WQebzAT$L{`psK4N)vwH%f2poJY%Z3#D2E#gbnCT;mjm?nH1zUbh(FYzOOG~XzhRcc|!m#f5bs3!Zd zu)F9|Lb!TAR;m$VI<&}3$1HdSGF`qamD_=#Kf;xoZMypX$4l4mT`OMIa|BnP;D&w| zyQ|OfC0EUv>tmwl*Uju5oh8f)uG;JZok5l2}b!Z=knQy UB0}gpWWK@VXkrQ_$tjln4{_hbHUIzs diff --git a/common/utils/__pycache__/debug_utils.cpython-312.pyc b/common/utils/__pycache__/debug_utils.cpython-312.pyc deleted file mode 100644 index 85c5fe725527c2f40094b94677a870dd433dbfae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1294 zcmb_c&1=*^6rV{p+t_wnQ0#7%Y7nKY&<%>87d1dUfaY-MdZgtXqs*kwLoN;@ZHbiFQi+*dC+!)qD zKy0__KoxXgK~i)b$F1nTSGJihLK=e4Rck>8pcruh4FTAQ0MF&QCPr;IWy`Y36O)qM zfbAAdn+@iU^1J}p*?HK0&!g{Z+soFa)-5%&sb=b@``3Fn`d0ha^m_kDy?gYddOHL= zw5L)2R_zgJMAh(Njq?Ah_6$vky7la>n_2P*-6giN(rmF6=?5v5HQ}F+B{9c z5u^`hL_9Kg_1{w|9Ix{TS=bI{GQXbQ=vnPqJ6q2W*SkkPsH2}c(~F(76RZJcM^waS z{eqNuuX)YN0zB;iV|tti;Un-+)1vR_Ivg%N2}XPtSU+q4sWFI>$F`L8rjiaNtc>AM zM#)S=u#JmC*R`p1g_5LXxGMdNa}mUrKq=WKo1r{%Gyw^(?2w>Ch@mCnPg3@3l(&P3 ryo`jWfViy=IIiW}WRRW$J)aiFC%qwKjK86CpHbTv)Eg%AI12X%mI^ix diff --git a/common/utils/__pycache__/key_encryption.cpython-312.pyc b/common/utils/__pycache__/key_encryption.cpython-312.pyc deleted file mode 100644 index a81d7e784f7b999460b09f475bbd622d060a0458..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6007 zcmbVQT~Hg>6}~HJ^|O)yg8^CLXZ;h~!Wim+iGRR0mSlplff%=`RWln|yI_fxR@q%J zFp}d3&s3gqVtdjSn4|;gWF~l=nL2G};y$(0$wNAQs1!LBv&PfRv=7Z2HEt$#UwZDY zRsyPKg73_pd+xdSp8L1=JLjH1Ih_=O^wH?}NYfUC{)P*BBJ<4Z&w!ahEMf^BMTr1G zVBWwRqQ-zRY6_UtvXLjF=73q{O}r&)4Oms4EW%EmNe&{uM5G2*o@Wm6(YG9)n`L)X~nx zLITUFEX!dQS{S?iLy%RBaxx#mGrj_@rJS*U)mu(FfhXzWf)* z@%vkD4*jHNrr}P*f@y)xZrS&RvpMZJe$5YLscdWNhqqt7EzL~cnOq>>c{5$JCvD%G zHtqfQ6C-k-ASAp^Z}0AQm_oo5QvN*9YA1+(3@6T^0KuYwA%#W=mbh*W7+C{n8iBLB zZVHgB={gFSSrVv)H3PM#&?FJC4Iz(3alqd6ogMP>Vc3Ipe-$3P9KO{Eke?E3|En9u z1bWMaP=W}fgy93?ZS|fd5TnlwZXJ{g|Cm~W_F7U)g}+WM75=;_beHIdX_3-+oD&r* z!-QjDnPC(wK808?$|;n35(3_KO)7?Cql%RmLP2~!3KbWH2p5u>FzYcXCW+%m6jM+f zmEhuCQF9x^gy0fM3?t!5!OwF|+zO2N0+8wIwL8+HPoNU2q(eZyfz!1*Z&)%`*X*ve z)w8hsbE{{`RhKf|bS}9YAS>5PYlLr=uR>p>7=!V6sIW^Q!7Io6wE8QcGq9t0w>PFp zVJC|<#k;+B-#5{}a6i|c)F%2H_4^I|3un8D{-!nk3x7ibMV!So>KsT5Bj_^t`${4f zNUg;-ItNnKdgx^>zn9kvE3oJ_u!0JmFXoY|BCm5SS$bZ8cZIzr@QHp`edf|DdTxD| z(tGjT`mCjQ@wxTcKwp`@o5lRO^-(g0VKqf+bgr1QcKwgrFha?{OU8(xrie@DiaG1R z*gJ(h&VHr5$IA?!8#wLvo@e^I&-xWR2+j<6yU+Iy^s9wF{{`k;Z-0;Cgc|R;3xnqe z7?4dn1~sUndwoafY3fWK!Z6>*P0{B7*noF_^Y@?c?LIT4Y|Ynu&kTIur_bNxRLmhE zHWD6HjNp8TG%QkV#H0a}dLT-95r3IDj3w4#shJl>M**=-;n;|v7z99DP{73|6bq2B zD8xJl5nK->?gk=Z2&8Ehbrq`$@c@h)pjQ{WafdE&WUiqlcl~_RT+@8ZTua(>Bg|XH_B(57vvA$eE-e#?w8Z9Ll3Qom)6uZEu8;w z`2FF9fwZgrq4nT*tlEUeFN=ev-R-|Q_sNBiFQkt%*@LgA+krH9tgm-`Rk<=iyj z8KVZ);^mSP4Oeo6u0s1T(BlXw!VIE)E4_KI11WQixB;IXNQ zq*(G^ykf+&F>5o>I(Fke75ZQzC&h)B#EHj2EFJ?Q;g=6MkXq94tXyxNS36y|&ZFE= z+k~}z0puF`ds)M*m@V6pGA?h~I#YY6cDCxh&^$lK!?Ph}UUJhpcU#8YcE9J7{*U{! z?Y^wrf1^A_-S)0nQ5`+sIoG*VzkPmqZumh}W^31q5!LNoMTR;mZFQ}f(Kb)c)s}I! zrJO4@sCG}TW?!aeU&;}e%?`bDELXKRQ?+-oHd}RI*}Ze&bk^OPb9ZFi9rv$f-N*lFH&;+A$ZVtj z7j>|!r=pGsw|Xt=B#0uG=(r(PfBW+^QF!3OV-%h~+dz5JP}=QD8s!4kKwATDTMAF$ z!3%hZlb$C{>w20t(UV-)liWm4^SYkqP4u+buxpH;ZW~g9ibtHqk&I_ zLWKckB{>M-Ct||}uS0+_2OpLpxWF^qM3C1o4Br{?6mDV3>k^OPlFdIE;?$@F1Vi8% zEUQ>4dU10fZcv$TuEHd&Bs?-Dz5*2ht=n;ZSssiq5C`R;wnpsFX$w7pxxyNi3SC

|fH+>Tc=J6`#$HoN21tm|CL zxm>Yrwq>z4<35mcAIi87J*dsPyR#K1Q{-}4?QGe?c*eCq=W5Tm+V8)Xb#-OSjsXY* z0$yF@GmS@cjVCjWCx8EPwy`f;eI`Y%m@PKfQsYhxk4xJ<59%|n9)MfA8Q_*KOFQZS zRI7I8Dw{Kv%_;9w3RPCGh4cF_KHPE$0KL+ctK65V+_!l8fiYXznX)XE*W9{#<7&3N zF>P)9=BX1^Y2u?KIrQW^e|cE+U4MyAY66e+3D48|5AHy3l1VG{)dCWiS-t zAW(v>EOrE9p{Z%lI&1cAO+%nk!Y2G00+VBNs>Z-W;iwLF86c`dF-%lo$9c><80L-f zAg|S^p%@WAn)emx2=4(XRRxBEF)E)2qJJTD+j E3kSUO1DywL--kCV-?1!1z zwPPztRmFh=2X3tdmAInz52yr(9xK6Nw`pauio}6(=nY7@^u)}r7YC@slXmv^X5PH_ z=Dpv{55-~uaJkv?*Ux7G_(dqGWJX}`bq=lq3>pASIAck6rjd~Z&RSVpZpgOMP`EB* z#md=gL*+R48EC-1T7iStmQ9Q;($gtH>n=s!20;tw7YhZUZ0HWQOs9oB;^>ZtDEY)E z%wvUzB6dZJCJmob;&__R2(4ryl@Z2F*NG<51-;XGC?&?Qu6ue@XT%V@Q&BnKM+19b zbF-_!OXv9k4s(n%V*C_r12*9ayI64chH%Q|isZ=&0-&8s(fD5nAHT!*OCC(;+Hpx! zE%~>L|I~$s<$az@|6TtIU}alsm!Gt9T~70pz!wWL@RZc5{Zxv^f1FFL*@1mAr^gm4 z+6J2^0HCwpq47+*w=D|wFS#+&CD1SItDd?Ic=m8#tS6r@e_ctvUy&*kgO}bVoE9YI zL7{-Su%77|8!1JYu|aG-UWjrDZK8~c2XCFx9mn-j>%=T!T+FQ+a@^DhChMj{Fftu9 z;?rPy(Lwg#Os$~{oNs7Q;0$Gmx)$O1BfrDv#KHjmPcSr4Ye-NQ4Gtpg62^mj+*g+% z-7q+X&sB<4&??HG-7-kW_26hSjF6;zLkNxihB0+kFEgoxzWuej0(Jtox^*Ya(`svoSLd3E^}?d58nx}B!G zRb6tKzUdxks7|^grFynY78a{@(y`pW%`amWlP=2?5qTsnx-%k!tFNLRZC^sRb*I;ffZnXu*ZG z;HZ8dHh;^CjgKIJ(?fMGROhZ;x~IN)(;Cj#!}IJa3$I}PrD)~s&H0&Ch7M0d5oo#CT z@$sqY^+(-|C*w{hErodWM8&;|%keFs;LXv*Z ZWF`5}0+3Gp3|{yZJiC{hlD4FH^IvsTZpQ!s diff --git a/common/utils/__pycache__/model_utils.cpython-312.pyc b/common/utils/__pycache__/model_utils.cpython-312.pyc deleted file mode 100644 index 15d652845b22bb3c322380c457d902cf2bd3089a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6919 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 diff --git a/common/utils/__pycache__/nginx_utils.cpython-312.pyc b/common/utils/__pycache__/nginx_utils.cpython-312.pyc deleted file mode 100644 index b5d239f61d7c3c8dcde363817f7acc1a45e448af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1423 zcmbVLO=ufO6rTNGOWrC!j_ahcyMg?P#$H;S7EIbuq8PW3G$oJ*5fE$dXf0=VXFIzq z%VGsO^k84=_7p)VxDcpHdPt#A9eWJ)BB2&HgCPAm`Bv~Pr_8Km#dUfpeXwud&-dQG znfKn$nwA2TzO$p^uRH*MF=daap3v?LJ?;SuSeye~F3-7qo_B@3z%d)|h^~~ETsbdO zUHA;-l{GOd?Xb|SKokr=jbIRx^iqT|^umJa`$UPbQz#;w<%vqgz;{X^Nl_{C5_ySB z{@iCOJ<{~s{gYbnfpF+r*{g790boVirwl}kv-pPtY>odP@xs0s`Q`k>-1~9=wKj2I zZg$fHG|VlANvD{5W%K}u*)lKNEGg#i151tt%9U8W55AHg4qlc6F}HH~Ut&~iWcW`h zJi3R7L6IxbmY@IS36L_}ObL0DD&1Tv>r zQy27LX&JhZ%IXDunR@4RGE#t*5Ms}C=66_McG!?)M=`Av5M>#f7Q_J&Y#0)`A5KIT zibV#H9c0@O6Ul;$kxc|2`IsfZY!t9USvE~n=vh80c{D@Wbm+trxnC4M#8B*|DP~Xw z(QN2JY=+P((4i-yZ-&c6qKksph+4#^?b1ylss~pK%oVWl*I&@HqHzm3kqZOkqff5P zUVE$Xrjf(QUqlt-Lll^I(YZj+!SX;cu9o5Z9~e34J7|q%HY`|%4)POu3Arxvj3_KQ zf#KODuTn_l8Gnr&>ffPXCRa2ST;5Jkb<$If@`KfH;;-XwdbXbHiJ5J2yd#cpizhqc$ws9q zZ&jMr*4fs?PZN(%KOS#S-uOlQ`1j1Q&FV(A@y1rv%}m!9dSkCO7CK`_Q>Ltb?GGi> z)6X|2yZSpVu63%dU+Son>*6~6;`VPxk2i8(zPqmWGRGTlZ_PF*9$jpmZ=bl*&E(o@ zjwWtsoy?iGdS;hi4qVTwL@GLFa5ov0WEhghlTq3c_k#vpl|Zvob6FrK39 nnR>}ZR^-6<@M)^D)eG20{Vi}D_Y@c%U_1rWyYegC*(B&W{XI}~ diff --git a/common/utils/__pycache__/security.cpython-312.pyc b/common/utils/__pycache__/security.cpython-312.pyc deleted file mode 100644 index a95c17739de3318b478c906c067b578d10a6c59f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1566 zcmb7E&2QX96d&8O-lV}vKWLJowlP^!YCtii5mKels$!v(s_B7Aq5 z?>E0$);P$g!JHihT6T8<{odD`@wqLOc+=or2s__hO0Z6_3#wnjfI z#|oLzHxTWb)-d~k%=`v@tFObFs%c0J^eluL>D61@Mv^B zgFZ2?8I41akBevX7%^tS1omi-?2dgboUz7tqq)_`DrBtZ^4RX!$1)YGjhy1#$5Zc7 zA?nSVEz-o;S>9~o3*W9r%#Jx_He^n(6KVEHazCL}KMsT!_|01E*QgxJ5gIO0LSR8J zj0g?nfjkfdVGnYKnaa2{gQ!-cOcn@TifgP07*olWg%4uN+H$a34+Lf25}3VxJvc(4 zT4!9yqKG_FuLx$SX@hFo$p5z<+}#pOlhY=F-LSfkG?zd0S&cJQ5?r0ehW4=Hz8?i~ zNV$7u;mw)LuX(53GK*SE(MRsP5%-s)7r9-gE1BcIwL;Hdbj!3AMC}kt+$D5{22m@u zD^VCmO*a(I@sUtH~4rNk<&i65?if4yUsHmujX)@)+UZdhk; zyuCGk{JY`X!yDr#S1)}2PJ$0_O`hydo=GOptX}9mb3DNlTaMdx&Lz${1xzLQ#8%1a zmQE+7(+ZgQ9oyS*8nm}we})L?HB%VqY7e~m#~?nUCcFWiO5@OH zmp;98YvAh_Ht|H}b@orLl{c}o<2paiY~ojU+|t_oJ^T_=BA#=KQ1gM$mwTn2O6Op& zx~5Z!@o81ksdUmBV(J7xp>%e7+J^(PK#T)=mOT$4l>)9V_qU;G+MnpeA86{I0aKgV LK8UrK(`o+#iS$f| diff --git a/common/utils/__pycache__/security_utils.cpython-312.pyc b/common/utils/__pycache__/security_utils.cpython-312.pyc deleted file mode 100644 index 842a2e91cdfd16ce422c4de71b0d85802196e4e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3188 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 diff --git a/common/utils/asset_utils.py b/common/utils/asset_utils.py index 5eb3815..9b600bd 100644 --- a/common/utils/asset_utils.py +++ b/common/utils/asset_utils.py @@ -5,7 +5,6 @@ from sqlalchemy.exc import SQLAlchemyError from common.extensions import cache_manager, minio_client, db from common.models.interaction import EveAIAsset, EveAIAssetVersion -from common.utils.document_utils import mark_tenant_storage_dirty from common.utils.model_logging_utils import set_logging_information @@ -55,7 +54,8 @@ def create_version_for_asset(asset, tenant_id): def add_asset_version_file(asset_version, field_name, file, tenant_id): object_name, file_size = minio_client.upload_file(asset_version.bucket_name, asset_version.id, field_name, file.content_type) - mark_tenant_storage_dirty(tenant_id) + # mark_tenant_storage_dirty(tenant_id) + # TODO - zorg ervoor dat de herberekening van storage onmiddellijk gebeurt! return object_name diff --git a/common/utils/cache/license_cache.py b/common/utils/cache/license_cache.py new file mode 100644 index 0000000..9c60db1 --- /dev/null +++ b/common/utils/cache/license_cache.py @@ -0,0 +1,102 @@ +# common/utils/cache/license_cache.py +from typing import Dict, Any, Optional +from datetime import datetime as dt, timezone as tz + +from flask import current_app +from sqlalchemy import and_ +from sqlalchemy.inspection import inspect + +from common.utils.cache.base import CacheHandler +from common.models.entitlements import License + + +class LicenseCacheHandler(CacheHandler[License]): + """Handles caching of active licenses for tenants""" + handler_name = 'license_cache' + + def __init__(self, region): + super().__init__(region, 'active_license') + self.configure_keys('tenant_id') + + def _to_cache_data(self, instance: License) -> Dict[str, Any]: + """Convert License instance to cache data using SQLAlchemy inspection""" + if not instance: + return {} + + # Get all column attributes from the SQLAlchemy model + mapper = inspect(License) + data = {} + + for column in mapper.columns: + value = getattr(instance, column.name) + + # Handle date serialization + if isinstance(value, dt): + data[column.name] = value.isoformat() + else: + data[column.name] = value + + return data + + def _from_cache_data(self, data: Dict[str, Any], **kwargs) -> License: + """Create License instance from cache data using SQLAlchemy inspection""" + if not data: + return None + + # Create a new License instance + license = License() + mapper = inspect(License) + + # Set all attributes dynamically + for column in mapper.columns: + if column.name in data: + value = data[column.name] + + # Handle date deserialization + if column.name.endswith('_date') and value: + if isinstance(value, str): + value = dt.fromisoformat(value).date() + + setattr(license, column.name, value) + + return license + + def _should_cache(self, value: License) -> bool: + """Validate if the license should be cached""" + return value is not None and value.id is not None + + def get_active_license(self, tenant_id: int) -> Optional[License]: + """ + Get the currently active license for a tenant + + Args: + tenant_id: ID of the tenant + + Returns: + License instance if found, None otherwise + """ + + def creator_func(tenant_id: int) -> Optional[License]: + from common.extensions import db + current_date = dt.now(tz=tz.utc).date() + + # TODO --> Active License via active Period? + + return (db.session.query(License) + .filter_by(tenant_id=tenant_id) + .filter(License.start_date <= current_date) + .last()) + + return self.get(creator_func, tenant_id=tenant_id) + + def invalidate_tenant_license(self, tenant_id: int): + """Invalidate cached license for specific tenant""" + self.invalidate(tenant_id=tenant_id) + + +def register_license_cache_handlers(cache_manager) -> None: + """Register license cache handlers with cache manager""" + cache_manager.register_handler( + LicenseCacheHandler, + 'eveai_model' # Use existing eveai_model region + ) diff --git a/common/utils/document_utils.py b/common/utils/document_utils.py index edf1551..7668bfd 100644 --- a/common/utils/document_utils.py +++ b/common/utils/document_utils.py @@ -16,9 +16,15 @@ from .eveai_exceptions import (EveAIInvalidLanguageException, EveAIDoubleURLExce EveAIInvalidCatalog, EveAIInvalidDocument, EveAIInvalidDocumentVersion, EveAIException) from ..models.user import Tenant from common.utils.model_logging_utils import set_logging_information, update_logging_information +from common.services.entitlements import LicenseUsageServices + +MB_CONVERTOR = 1_048_576 def create_document_stack(api_input, file, filename, extension, tenant_id): + # Precheck if we can add a document to the stack + LicenseUsageServices.check_storage_and_embedding_quota(tenant_id, len(file)/MB_CONVERTOR) + # Create the Document catalog_id = int(api_input.get('catalog_id')) catalog = Catalog.query.get(catalog_id) @@ -102,8 +108,6 @@ def create_version_for_document(document, tenant_id, url, sub_file_type, langua set_logging_information(new_doc_vers, dt.now(tz.utc)) - mark_tenant_storage_dirty(tenant_id) - return new_doc_vers @@ -124,7 +128,7 @@ def upload_file_for_version(doc_vers, file, extension, tenant_id): ) doc_vers.bucket_name = bn doc_vers.object_name = on - doc_vers.file_size = size / 1048576 # Convert bytes to MB + doc_vers.file_size = size / MB_CONVERTOR # Convert bytes to MB db.session.commit() current_app.logger.info(f'Successfully saved document to MinIO for tenant {tenant_id} for ' @@ -274,6 +278,9 @@ def refresh_document_with_info(doc_id, tenant_id, api_input): if not old_doc_vers.url: return None, "This document has no URL. Only documents with a URL can be refreshed." + # Precheck if we have enough quota for the new version + LicenseUsageServices.check_storage_and_embedding_quota(tenant_id, old_doc_vers.file_size) + new_doc_vers = create_version_for_document( doc, tenant_id, old_doc_vers.url, @@ -330,6 +337,9 @@ def refresh_document_with_content(doc_id: int, tenant_id: int, file_content: byt old_doc_vers = DocumentVersion.query.filter_by(doc_id=doc_id).order_by(desc(DocumentVersion.id)).first() + # Precheck if we have enough quota for the new version + LicenseUsageServices.check_storage_and_embedding_quota(tenant_id, len(file_content) / MB_CONVERTOR) + # Create new version with same file type as original extension = old_doc_vers.file_type @@ -377,13 +387,6 @@ def refresh_document(doc_id, tenant_id): return refresh_document_with_info(doc_id, tenant_id, api_input) -# Function triggered when a document_version is created or updated -def mark_tenant_storage_dirty(tenant_id): - tenant = db.session.query(Tenant).filter_by(id=int(tenant_id)).first() - tenant.storage_dirty = True - db.session.commit() - - def cope_with_local_url(url): parsed_url = urlparse(url) # Check if this is an internal WordPress URL (TESTING) and rewrite it diff --git a/common/utils/dynamic_field_utils.py b/common/utils/dynamic_field_utils.py new file mode 100644 index 0000000..fd10d70 --- /dev/null +++ b/common/utils/dynamic_field_utils.py @@ -0,0 +1,44 @@ +def create_default_config_from_type_config(type_config): + """ + Creëert een dictionary met standaardwaarden gebaseerd op een typedefinitie configuratie. + + Args: + type_config (dict): Het configuration-veld van een typedefinitie (bijv. uit processor_types). + + Returns: + dict: Een dictionary met de naam van ieder veld als sleutel en de standaardwaarde als waarde. + Alleen velden met een standaardwaarde of die verplicht zijn, worden opgenomen. + + Voorbeeld: + >>> config = PROCESSOR_TYPES["HTML_PROCESSOR"]["configuration"] + >>> create_default_config_from_type_def(config) + {'html_tags': 'p, h1, h2, h3, h4, h5, h6, li, table, thead, tbody, tr, td', + 'html_end_tags': 'p, li, table', + 'html_excluded_classes': '', + 'html_excluded_elements': 'header, footer, nav, script', + 'html_included_elements': 'article, main', + 'chunking_heading_level': 2} + """ + if not type_config: + return {} + + default_config = {} + + for field_name, field_def in type_config.items(): + # Als het veld een standaardwaarde heeft, voeg deze toe + if "default" in field_def: + default_config[field_name] = field_def["default"] + # Als het veld verplicht is maar geen standaardwaarde heeft, voeg een lege string toe + elif field_def.get("required", False): + # Kies een geschikte "lege" waarde op basis van het type + field_type = field_def.get("type", "string") + if field_type == "string": + default_config[field_name] = "" + elif field_type == "integer": + default_config[field_name] = 0 + elif field_type == "boolean": + default_config[field_name] = False + else: + default_config[field_name] = "" + + return default_config diff --git a/common/utils/eveai_exceptions.py b/common/utils/eveai_exceptions.py index 9622661..0c912c3 100644 --- a/common/utils/eveai_exceptions.py +++ b/common/utils/eveai_exceptions.py @@ -186,3 +186,65 @@ class EveAINoManagementPartnerForTenant(EveAIException): super().__init__(message, status_code, payload) +class EveAIQuotaExceeded(EveAIException): + """Base exception for quota-related errors""" + + def __init__(self, message, quota_type, current_usage, limit, additional=0, status_code=400, payload=None): + super().__init__(message, status_code, payload) + self.quota_type = quota_type + self.current_usage = current_usage + self.limit = limit + self.additional = additional + + +class EveAIStorageQuotaExceeded(EveAIQuotaExceeded): + """Raised when storage quota is exceeded""" + + def __init__(self, current_usage, limit, additional, status_code=400, payload=None): + message = (f"Storage quota exceeded. Current: {current_usage:.1f}MB, " + f"Additional: {additional:.1f}MB, Limit: {limit}MB") + super().__init__(message, "storage", current_usage, limit, additional, status_code, payload) + + +class EveAIEmbeddingQuotaExceeded(EveAIQuotaExceeded): + """Raised when embedding quota is exceeded""" + + def __init__(self, current_usage, limit, additional, status_code=400, payload=None): + message = (f"Embedding quota exceeded. Current: {current_usage:.1f}MB, " + f"Additional: {additional:.1f}MB, Limit: {limit}MB") + super().__init__(message, "embedding", current_usage, limit, additional, status_code, payload) + + +class EveAIInteractionQuotaExceeded(EveAIQuotaExceeded): + """Raised when the interaction token quota is exceeded""" + + def __init__(self, current_usage, limit, status_code=400, payload=None): + message = (f"Interaction token quota exceeded. Current: {current_usage:.2f}M tokens, " + f"Limit: {limit:.2f}M tokens") + super().__init__(message, "interaction", current_usage, limit, 0, status_code, payload) + + +class EveAIQuotaWarning(EveAIException): + """Warning when approaching quota limits (not blocking)""" + + def __init__(self, message, quota_type, usage_percentage, status_code=200, payload=None): + super().__init__(message, status_code, payload) + self.quota_type = quota_type + self.usage_percentage = usage_percentage + + +class EveAILicensePeriodsExceeded(EveAIException): + """Raised when no more license periods can be created for a given license""" + + def __init__(self, license_id, status_code=400, payload=None): + message = f"No more license periods can be created for license with ID {license_id}. " + super().__init__(message, status_code, payload) + + +class EveAIPendingLicensePeriod(EveAIException): + """Raised when a license period is pending""" + + def __init__(self, status_code=400, payload=None): + message = f"Basic Fee Payment has not been received yet. Please ensure payment has been made, and please wait for payment to be processed." + super().__init__(message, status_code, payload) + diff --git a/common/utils/mail_utils.py b/common/utils/mail_utils.py new file mode 100644 index 0000000..ff40848 --- /dev/null +++ b/common/utils/mail_utils.py @@ -0,0 +1,46 @@ +from scaleway import Client +from scaleway.tem.v1alpha1.api import TemV1Alpha1API +from scaleway.tem.v1alpha1.types import CreateEmailRequestAddress +from html2text import HTML2Text +from flask import current_app + + +def send_email(to_email, to_name, subject, html): + current_app.logger.debug(f"Sending email to {to_email} with subject {subject}") + access_key = current_app.config['SW_EMAIL_ACCESS_KEY'] + secret_key = current_app.config['SW_EMAIL_SECRET_KEY'] + default_project_id = current_app.config['SW_PROJECT'] + default_region = "fr-par" + current_app.logger.debug(f"Access Key: {access_key}\nSecret Key: {secret_key}\n" + f"Default Project ID: {default_project_id}\nDefault Region: {default_region}") + client = Client( + access_key=access_key, + secret_key=secret_key, + default_project_id=default_project_id, + default_region=default_region + ) + current_app.logger.debug(f"Scaleway Client Initialized") + tem = TemV1Alpha1API(client) + current_app.logger.debug(f"Tem Initialized") + from_ = CreateEmailRequestAddress(email=current_app.config['SW_EMAIL_SENDER'], + name=current_app.config['SW_EMAIL_NAME']) + to_ = CreateEmailRequestAddress(email=to_email, name=to_name) + + email = tem.create_email( + from_=from_, + to=[to_], + subject=subject, + text=html_to_text(html), + html=html, + project_id=default_project_id, + ) + current_app.logger.debug(f"Email sent to {to_email}") + + +def html_to_text(html_content): + """Convert HTML to plain text using html2text""" + h = HTML2Text() + h.ignore_images = True + h.ignore_emphasis = False + h.body_width = 0 # No wrapping + return h.handle(html_content) diff --git a/common/utils/middleware.py b/common/utils/middleware.py index 0301e27..f80e60f 100644 --- a/common/utils/middleware.py +++ b/common/utils/middleware.py @@ -4,11 +4,11 @@ for handling tenant requests """ from flask_security import current_user -from flask import session, current_app, redirect +from flask import session from .database import Database from .eveai_exceptions import EveAINoSessionTenant, EveAINoSessionPartner, EveAINoManagementPartnerService, \ EveAINoManagementPartnerForTenant -from ..services.user_services import UserServices +from common.services.user import UserServices def mw_before_request(): diff --git a/common/utils/model_logging_utils.py b/common/utils/model_logging_utils.py index 6578d51..94740c9 100644 --- a/common/utils/model_logging_utils.py +++ b/common/utils/model_logging_utils.py @@ -10,7 +10,6 @@ def set_logging_information(obj, timestamp): obj.created_by = user_id obj.updated_by = user_id - def update_logging_information(obj, timestamp): obj.updated_at = timestamp diff --git a/common/utils/security.py b/common/utils/security.py index 6d4c723..fd23676 100644 --- a/common/utils/security.py +++ b/common/utils/security.py @@ -39,11 +39,12 @@ def is_valid_tenant(tenant_id): raise EveAITenantInvalid(tenant_id) else: current_date = dt.now(tz=tz.utc).date() - active_license = (License.query.filter_by(tenant_id=tenant_id) - .filter(and_(License.start_date <= current_date, - License.end_date >= current_date)) - .one_or_none()) - if not active_license: - raise EveAINoActiveLicense(tenant_id) + # TODO -> Check vervangen door Active License Period! + # active_license = (License.query.filter_by(tenant_id=tenant_id) + # .filter(and_(License.start_date <= current_date, + # License.end_date >= current_date)) + # .one_or_none()) + # if not active_license: + # raise EveAINoActiveLicense(tenant_id) return True diff --git a/common/utils/security_utils.py b/common/utils/security_utils.py index 46f90cb..eae67ce 100644 --- a/common/utils/security_utils.py +++ b/common/utils/security_utils.py @@ -1,11 +1,10 @@ from flask import current_app, render_template from flask_security import current_user -from flask_mailman import EmailMessage from itsdangerous import URLSafeTimedSerializer -import socket from common.models.user import Role from common.utils.nginx_utils import prefixed_url_for +from common.utils.mail_utils import send_email def confirm_token(token, expiration=3600): @@ -18,14 +17,6 @@ def confirm_token(token, expiration=3600): return email -def send_email(to, subject, template): - msg = EmailMessage(subject=subject, - body=template, - to=[to]) - msg.content_subtype = "html" - msg.send() - - def generate_reset_token(email): serializer = URLSafeTimedSerializer(current_app.config['SECRET_KEY']) return serializer.dumps(email, salt=current_app.config['SECURITY_PASSWORD_SALT']) @@ -37,9 +28,6 @@ def generate_confirmation_token(email): def send_confirmation_email(user): - if not test_smtp_connection(): - raise Exception("Failed to connect to SMTP server") - token = generate_confirmation_token(user.email) confirm_url = prefixed_url_for('security_bp.confirm_email', token=token, _external=True) @@ -47,7 +35,7 @@ def send_confirmation_email(user): subject = "Please confirm your email" try: - send_email(user.email, "Confirm your email", html) + send_email(user.email, f"{user.first_name} {user.last_name}", "Confirm your email", html) current_app.logger.info(f'Confirmation email sent to {user.email}') except Exception as e: current_app.logger.error(f'Failed to send confirmation email to {user.email}. Error: {str(e)}') @@ -62,41 +50,13 @@ def send_reset_email(user): subject = "Reset Your Password" try: - send_email(user.email, "Reset Your Password", html) + send_email(user.email, f"{user.first_name} {user.last_name}", subject, html) current_app.logger.info(f'Reset email sent to {user.email}') except Exception as e: current_app.logger.error(f'Failed to send reset email to {user.email}. Error: {str(e)}') raise -def test_smtp_connection(): - try: - current_app.logger.info(f"Attempting to resolve google.com...") - google_ip = socket.gethostbyname('google.com') - current_app.logger.info(f"Successfully resolved google.com to {google_ip}") - except Exception as e: - current_app.logger.error(f"Failed to resolve google.com: {str(e)}") - - try: - smtp_server = current_app.config['MAIL_SERVER'] - current_app.logger.info(f"Attempting to resolve {smtp_server}...") - smtp_ip = socket.gethostbyname(smtp_server) - current_app.logger.info(f"Successfully resolved {smtp_server} to {smtp_ip}") - except Exception as e: - current_app.logger.error(f"Failed to resolve {smtp_server}: {str(e)}") - - try: - smtp_server = current_app.config['MAIL_SERVER'] - smtp_port = current_app.config['MAIL_PORT'] - sock = socket.create_connection((smtp_server, smtp_port), timeout=10) - sock.close() - current_app.logger.info(f"Successfully connected to SMTP server {smtp_server}:{smtp_port}") - return True - except Exception as e: - current_app.logger.error(f"Failed to connect to SMTP server: {str(e)}") - return False - - def get_current_user_roles(): """Get the roles of the currently authenticated user. diff --git a/common/utils/template_filters.py b/common/utils/template_filters.py index a14a2b2..f17ed3e 100644 --- a/common/utils/template_filters.py +++ b/common/utils/template_filters.py @@ -29,9 +29,23 @@ def time_difference(start_dt, end_dt): return "Ongoing" +def status_color(status_name): + """Return Bootstrap color class for status""" + colors = { + 'UPCOMING': 'secondary', + 'PENDING': 'warning', + 'ACTIVE': 'success', + 'COMPLETED': 'info', + 'INVOICED': 'primary', + 'CLOSED': 'dark' + } + return colors.get(status_name, 'secondary') + + def register_filters(app): """ Registers custom filters with the Flask app. """ app.jinja_env.filters['to_local_time'] = to_local_time app.jinja_env.filters['time_difference'] = time_difference + app.jinja_env.filters['status_color'] = status_color diff --git a/config/.DS_Store b/config/.DS_Store deleted file mode 100644 index 19a305de3b686b03a43c6771b742ac06084fc6c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}&BV5S~S1AS4D3CML$DXAdO!1eMeSXC45dNWm1A#Giy)?>>iT-@(L_Z=?tO zW_Kje3VJfe%rvv#?sR6l`|Y;dB_h+l8@7n*L{xz?mZ~Ve5Wde^kdjz9&bS;Cx}_ej zCd~`pYWRx`@Z8nhoK0v(>+`qhq&6O;b^s4v9_P^J(g|vxQFB^QOnvB>t~gIF>gi`u zY0~P(b}FmpaI`)D)JBo?s?s=peSUm@;hy<16nZnY!%9n(mZ}(; z1v4XXlel%AiGN24713MYOp3PFJdDK!FPzIEN6$AWz2%wCiVCK<%I#9SH0I&?R z6V&--@vbPon$9ru6y@ax`U*>TYf{NRU f;mcd`5!4Cn0at*bVCE4Pi2evT8njRbewBd_!A^el diff --git a/config/__pycache__/__init__.cpython-312.pyc b/config/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 381a0b7758813d2140f8ce0cb43c74b02eac238d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 190 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 T{L09CZ++SB>S}q%%{=pexg?bd diff --git a/config/__pycache__/logging_config.cpython-312.pyc b/config/__pycache__/logging_config.cpython-312.pyc deleted file mode 100644 index 8e971ca7284cf646c71bf0d935c691f5435407b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1847 zcmZ`&&2Jk;6yLRDuf4Vtf5-W(A+g93;-$18R1i|zv>_By)CwwrDl_p+HmmH1*;$9! zH~tE8h{QEW{3l#|wC04wsp8_Ox1M;jyWUJ(v4_$7{oZ@?-uu0szX}Cag6~hGfAo7< zlK$af`jsN%^*vdVev`0-Wt_lCJcm;_jTM}^NC!AOgVh{YyGB;xG#8|c;z)VzV#;Es2q zn(sn48yBe`q`{`vdIkDPk6CK>(Sz^yhMSga7^Y*ikCB5d;?edq*GE1uk4=kw8Ir>? zL{Xib5@dEzuh#}>O47H|E50w5p+3YR4{K?bvLMQ0sjh`6Wtk(m#6dP;85^BF82E&; z+#%|o^m`B8zT>m(k?YyW_lY;G3pIXmy%XLGP{WGAcaM=DkSjA}=j{yovxThgF|h|h zMlr~+k!jh;37AsAP}1#trXL1{wLD~W{Jz6gfskP>tYse(JWB`4J@-9=>_{pt=_#Oz zm*z(f4owYJ;JUFq*JIi%49^jtiIrkA_AUg}^%PNZGyyAw?x=efMqhUx-AIJ4nbLI~ z>RN=QL)C}3TU&^BebXiyZT{HW3UdfU1Is3qA_HhI&qqqK%=C8&24&fv z=k}0+V2=ep_&h%r_)+8sf4g3dAMmOrufmTi987s}|JlL*!5)Z|jsGrI2+e4vObub6 zQ~BDS3N}40D`iULInZRLeEoE94+i-|dJSRHo38%7YxO}k{ppK`cfa|lb6ej9i4NT} z{VSKE6ZbvZ+$Ex+W`}_JX=~?c;CXTOTnA385fel+bG7X(qy&ZK%ngHT@UPNm7>OzZZ(*W^0m`>TBcLXXC~PZCk%d?-4W z1jml(s0)sdMaR0}xRJ3cINldC#2VY8V_C@f->Y;nBP_l%uC4O9l)0*U^*nJPm(r7; z5>mD@&R0kE{Yf$b^Cssi>B+pbym?*-!q*`jhRRFJTcd^+Q_urYt@HVqSqzw+QKKDG zw*qQ=1fufF{3_^#Uv;@OYTSv*YCvk2+Ng0iCZQLI+`il&H9m>SsK})`YJ3`#AQVJy foae^1^{`4oy?QC%DW0I diff --git a/docker/.python-version b/docker/.python-version new file mode 100644 index 0000000..56bb660 --- /dev/null +++ b/docker/.python-version @@ -0,0 +1 @@ +3.12.7 diff --git a/eveai_app/.DS_Store b/eveai_app/.DS_Store deleted file mode 100644 index a049e9c7c8895e4284cddc5afc658e19b47c2c4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKJx>Bb5S>MgC=C_S?s^lVl?l~}SeV!tqOB+pApwsBzo@()B89(z#J^xnY>bty z|G>mT8%@+VyQ}WOVQe(U%#hi)?9J@Wd&ljvM5GG))p?=}5vAaa$tg53#@E@0EMZ4D zS?M_%v`3rxvFI9q;T{W^-qvvl0#iFe$HQVo8opn#77$qZZb)CRlR66J?mVL6oaYOh%O5LpFY1msv00tF9SlG4r{s z&Q2y0cTbvCOk{*nLNgkt13c#%9S$-XTN+^EL`E2>qccQ0SKZ3Xt26iOrt(O{=W}>D z-sqm1ik*mPrTl{v`(XvJXS1c!2Bj4RL;+FYLjhhNJUF9oF*K-`4mA1*08C<78~S?f z26{XI^eu)4F#=O26=+hGJz^-6j&aY%`4&TiCY_YNGv={(R`!IV?9QP)9Zt$OD6J?U z3PcqcGhZ{j|1Xa||3{PLnK*{c{L*#g`_26YV8^?tPbqX509qSHn d#nW(YxaM&O=vxd8Vgx3C1hfp&hyuT=z#9PYuXX?c diff --git a/eveai_app/__pycache__/__init__.cpython-312.pyc b/eveai_app/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 9e1c516aed0b37cf2d163a848c48f9b6593f59c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6165 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 MZ@rDn+a diff --git a/eveai_app/__pycache__/errors.cpython-312.pyc b/eveai_app/__pycache__/errors.cpython-312.pyc deleted file mode 100644 index ea652c7ae2143e2c91bdb73295190bb82ab80385..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1872 zcmd5+Noy2A6t14`S(2VCZb?*#9s*`urW2iji3^DFpkM?Ag*n)^Q#DD)UdC6|AtO2D z;2#i$_yar`^yt6vGMW*kiFokjO&m`-`Ko#*7>vtBw4lF!^=f-xy;tAMo}O-k?fGm} zT`~yyC_9~&Y8$PqXsi%QsOAx0D{FqLobvUu?i*!8lm3*K_A}*-vUM-(=gK*>jr*kB zMbn7gG=pf;EMlJK5PRxfh3-%CfPxYC0ShPtTQI-oIfBJmV0RD^}YR+AUKmYb*qp<2yFm>=Ta1IyOgp3_9ItG9QI21A&IDgiCVlsV}&e{`S#1l z?q;K!*wl_^ZA<&v+?0@es`lHtx{&Ye&*mE2&8?F{dIn6~0rnuc4={T7Ap)xG0I*q? zV__%hx96OIdRPhad4sHNkoSUms-qr!{~QLlU&!GA&L75)UqDzU?~LqQWB8RZ{Fpzv z(lkon^_wG;O=D_{<7CsA-0FB^Osa&;6nf)y)pPhArGFsr52P$BVT?^RWGi7!6m+N( z`hFM`BjI|y7@*)T+R9EubSpts0V?#>B>(EoZ{$w_;j^x3+6QuG ejhuN;M&6T&FBx4s{w3drW70a-${X603jG`G7;wJ; diff --git a/eveai_app/templates/.DS_Store b/eveai_app/templates/.DS_Store deleted file mode 100644 index bd2e771fae60083e03c5e4112c8358a327cc08ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHLJ!>055S>+|a72yDlxN5fi2s1Oh)Wp=s7Mp+=nFfb(+Nc)Hg@2^je{#UE{z(K zGDUjh!u|p2)7g#Pm_VA4CU0gJW%o`WoiSlY?7q9%ncb(ice`2Z|T-;P0~1S z_0kr+!N&I4)%wrN7x{?Jl8C{Uy+ZK1gT zAAkFCXXY{2m(6ZboXaa>Vr4v8+*@uIUwyXQY#y&q-}c}95gyZAd;E*TwTa5LyW^ho zxDVa1XR`%WjlT8@cm=!yQw4Z^2vHa#gPBHII#9_G09b}w4A-)!14DKIBZHYncwkDW z0(Gh~M+~LY;SWt*WH8gH(@Dw5=*Nt#%n3!w=At(gB;5JOwY7r@A1 TrV$>P{~;hU_{uAAuL}GH+xpmD diff --git a/eveai_app/views/.DS_Store b/eveai_app/views/.DS_Store deleted file mode 100644 index 0be32eac4cfe376ac8117a4da8b99ec805a351f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKu}%Xq47H)dNnJWJ<`+7%gzET$4vYuAB099XTNv2#3;l@x2oft2NPGnn5-Th` z+i9WV=z>%sWJ}3&96RTGr;8IJ;?ebVNHid#0!@%b=@2n@P90?D9*}j7nl7lO8C_D< zvCv;MN$!1QTTx3l$o(__YJN2w*Uh4}v>#u7_FOb&Ihi*Tcoa{|huhWr-N`QR>Bjr@ z?Ed)qZI^dD`;rCAcXDwCoB?OR8E^)E#Qehiy zuK++fMyo*AGb%8M0T?qTLs%eep+F1ee8pf3hdpG!m@yezII%t%>*Ob|FE8s8G6!`h zj)vYl1J1xU1BW&oNc}&>uhe{2VD%aL;uP3!Opq-F&{G~Ls>=a8V>Y>fD+=JGq48+J^-z8H!c7G diff --git a/eveai_app/views/__pycache__/__init__.cpython-312.pyc b/eveai_app/views/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 95ac753700816ddb44a6e651a5297383fc56d5ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 199 zcmX@j%ge<81Z&bu(_RDV#~=&b zKit{G*C^gl-=!$OASu5>-z&d3F*{$qSivQ=49d}WElYLu)OSfO$jPtFP0cIOPf0CH z&B-qSvw_US%=pBD0{ybg)be8e`1s7c%#!$cy@JYL95%W6DWy57c15f}YZ-yK7{vI< M%*e=C#0+Es0IPa7CjbBd diff --git a/eveai_app/views/__pycache__/basic_forms.cpython-312.pyc b/eveai_app/views/__pycache__/basic_forms.cpython-312.pyc deleted file mode 100644 index c57710c53485f6e26e6a312d6827f1515187daab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1971 zcmb_dO>ERg6rS~)a)cA0AeD$isd}k;tGH~Mts<;MJ(Nptl=O#k>O6a$fCN2tEWP>O zd-LA&`x*Ogb8`xbb+c40K8PXoD=^Vg8el~i;4(6iiEZRy9b>?fEjckg7T}mIJBqFZ zShnL%LQgoVt_HkfC!Lg@!bn1IA~SvwnF*@QideAp^q8t8e*>stKfx*ImRlCEsZeE% zmOWDClnFk0*f#jN!!C0)$xol~m{l$vwy13ihT62?1q>MG4bOO&p08Srngwtit#|{h zToK?ikq|{XHjyq#h{jATs65*^Qo=W-St(?6CD6y7tB(hI`MLUp(5R+T5(RiFLVUFd z$thICX5yl%r>4*atfx;PP4zdN2&<8&MWbqa9FiYTzAx&L9W)#&a!rjGwq+Wg%eXIt zVm$pGEgNMoI56JYu*|+?luK2kL`CTmg^yjUKsif`>I+(sEOhfCYrzOBWVcimGhg&1Gwwrji zk=1b9nlwVD6lujfu8*W^`~MUr7DdUUOSmAFP(k`=BSN3x3GocSuof>uQDuwLm(u5( zWN}`cMZ=pS{n}1eYvcWQ9pva8p5te=8)G6G4kC^sO&Rp#UNA$xTozO2w;8tWj#87@ zjUnQGs~Jw@sz_`06<)1S=BtEQWy>RkfqOp*x0eKdWHG0PuTaOZY(Evq1I%FN{8)*4 zemV>so>w3$W6&ekEQl8KWlrs)FT;!YNkR(Zi@*ut;2@iwd~D^0T)XN}o_ptHf8X1$ zkltLLxs|i-h1@Zh8|T~}p3T#d29p~Yp$CuV^0Z>RV~+UJ!#P$)%K;%or|5l^PRmy(lXsrS38!nJ=fA#)7OtIWcMy+_swVb-FaytdvHo# zRJR0Pw%^zGeBFMd{niHy+WtlD(7bl2)<3kMo%}0?wNv=}uIpX5UA4 zAO!2}^Xm4x+I_!!*R|7EPuF_;7rOJ2{AYE?KMxf+$>Bo((6hDwRat9-hC=%Y@l%92 zu35DKHxY8aYS^KMfdNy2z+|&6K(+x%00}w>1@oI5Ju6V#CPc$5BcvQ|>jBmYN8w`- z5OBs%3G(P7y4Tu1bA0Z@+h=Nh`FiWX6ozf}{z+yDRo diff --git a/eveai_app/views/__pycache__/basic_views.cpython-312.pyc b/eveai_app/views/__pycache__/basic_views.cpython-312.pyc deleted file mode 100644 index fbf1ed78c3eb9701b2bd2c9fbe1db7e682d27fe2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3646 zcmc&$U1%KF6}~e&`=^n#`m=r_OW9PhtZ{8G38ju@S;UUqI0=zkv`Lfg!!X)=R~mU{ zXM1N>wYCbVP+D3F)msEfLG($%dB|f5f%eg*h0d}r>kfr9G^H5&AfwU;KlPluJG-m3 z1tl-NFn7+(`MKww@0@e*ANu-w2#ovx{9)lgF+%>1AK{2LiKBl>guG8Ep^`;xDJR)- zPPQYth^^!lJDQ8yv0O|-zHG(qL@r?`b4mD)P{r!8)to93nXm|rvfjIL!)C6J^=+IE z7izz0?8oG8^D~zMnRJ^MQZ(KnGYB$gLQeZl6EwLdecThSN7W9U;S+RvyL9?GbVg3l z>F?4>b?BTrL1%y_>EN2&_70+xq(gMLJr4|d~oPEE{TrWs*R-}yU91Nc@bD{Ivd^(WQT8zg*I*3`$+8X0Ql(N+a&SO4_J@87t0 z)t`Fz&q|J;0*!n#I6xADAr^rB8i@ZFU^KE#HhR&W;{%|KwV~W5D@0QO%xeXY@fMcH zz~m{gP*Kwt(kq_fmEHNY78ls^0buY!oN&8*2&Z9~f|x5LAxh1CvRf7>q^HF3E zA^--i*oyxo{O^ayy#~|&8vl3lKDFx9ty0&|d;V1UzOwm19p1SmFnKIxg2<~kvfxF$ zk#KGvjJF2*EMOW30$^%ENTo195FB~A(qlq9xN~vcv#pOLu7ETj!3lwHY$z%u_;c_B zIc^_JunF~JH>*@ZJpVMY3a`GujbICNa!bw2@iQ1*6#LipnF|D z15y+4xEOTdsz3QsO}*?Zmm9tgcbPAM!(YwYsi%31=1%r^=(xWq%<<%#?(%rPre5)t zD`9k-y;-NtrWz>UzO|CA^sJOiklb&%j8}TItY{Ry^fhXm#bf(HTqE!t{T!ywbO=wS z_Xt!JN_35o&!>QoR=UPUtNFGWUaJjLX#swFYRIGVHu*Rbo{pkAgMsFTbjv8Nm5nu4 zX=Xt8hMPGWDA1X0=)hS2!O*5*nH0`3-6`sBdDS*OK8Fkiei|ITYb3P>RA-qEberec z9A{AKDoi`%-!anrY)wt?Ed4F6*5lLF`1JjF`oZkE`s|hJ?3McL%hlPJe-ZZwzE(}l zKs7*$ed^s;mwaWZ0ohQ-Wu9IJV06#4S;Z-`W0>G!CNODs(pHH+?dS_}5I8;c7!1q$ zJ!xCw6WblfB9#RScmjw4-BY5GcY~82U8GboIAX7D%%W1y6AZ65_dLD%4ky@8=31u0+_oH3N6|= zP7`YoM7)6n5uh4?Vt#?r`EU&v?+WR#|2`3QehJtY;c?%A3H!lNU7f6|lQnf}XX!y| z_?LHnc4x1rmYUhQ`9(bSKzjjUJyT80{xvnauYEdR9lKsjz4E&iKXubj-24)Lp7p=^ zW_4`2mb&GCC+nxOej?idJ)T)-hUKjvL%LOcX;{Cr`lv)@v(=DkM8sKH7SRi2=Qe*4 z6f}wBhP&Hdtg#TE2Od5yp7LUIyEpf!KRi=YpWn~>s^%-2@NHR}2%-hcaBnwGH1vvN zDm8+N7aFlp&@Y5^m%%O2^tL!A2FHt2pf)ZWSb%P${t~)gG;F5p9FK*d|0y?*pzq1k zFo<%e3`Zm$DxzJB4k$`cpe1(Iwd2Q$1>2!zi!Ji&poZn*F2Z~mktFGJlC6{M=j8G~ z$oVhSk=@+>cuhUGlQ@hKxu-6TSEcbsvLt;^I*Jf^;!u!>3BenyN@I^?SsFps_#sk< zJ;3?t^pB=@BYSM`-JR*0bQaY$6q!S9O(>&?KQME^4us4ll$l4Fmr!QDL*~pU(St3t za~Wk8P-Y%w7CL0W&L?jl=qPpdFH&l^cduBJE%ibx;} diff --git a/eveai_app/views/__pycache__/document_forms.cpython-312.pyc b/eveai_app/views/__pycache__/document_forms.cpython-312.pyc deleted file mode 100644 index 3238be96c87d7261e54e6f82dd98073cdf432700..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5479 zcmeHLU2Ggz6~42xKeIdg*<-duPu*-@WI|TZu%Bz~_5!zMSt%67nbPc%M+Mv34ICuM&-Df=&!U z5d>@p^nf8MB5#X&&rUNK*~cZCu(0()I@F19Kgj&i8QXjNn8pYFP!d2+io2p0zHInas#U z9eL7bh2qp$f$EwQA2v;$W{Yl5o-AE33bxycj%ID@$*E3ro}A0Z3ziM@!j!H_;wyC4 z9%3}>cBOM?$9XFgbmA~k_AGs&RA5wd!sE0!WnXlp)6+IQT~>EQu+4}tSwZ*K!{(5# zn^#~!96R3doaN@%c&f;Of_reLF~?9U?Hl>E((gC?P&U8BCfN)j|FEvCs$ z7IA-uHqf+d32%L+;~L2eTJox-bY3H~g3>ifGASoDq-mpOu4K@njXU53(M8}waazke zqCIOn!Hc$`R|F?Elhq4a);5{t1ShfveNTg*3<}Z6&N|X~wm4PFPSHxhiR3Ptg&eim z17NTbKW9;v9x;nHZ0knsJkFBNGt=1Enuir1$$^HMdK#vt(}mn+%3z8>LF1#Hup39Y zn%lXPk)6njwVwcdUwD<+T#Rr%@FMvR=sl+j#P+yME`@zFzoGdXddUj`Uq@iWJ@<{0 zSA<+(isS;%!z?ceFT!6w@ZG?-r69xfg-b|lY^?E+avbb=Pu^XxG+h^lH1$Z4sn zg#*&*@`gTm-qc}pt-;e@9r?nk$JD)pqs*MXV9pMnFs?7!iXSouRrp&D+q-7S!zY^x#Z^Ua5I^WY2QG2{!(FxkM zlK}V2|UquK@Rdr&jYM5F{$97y*UnpgD zSHn8Mp5c?QE&wN(hiGRw2}|)lnz1PM+Yr#NERLFZ38GlVScNENxYsp8s2Iu&gU1P5 z{LJHia1yoM$WdKa)r!FOfr*7-e3b16WLrGI<*aW4+`mdz<*tSEm2GeDJGq=0U(M`W z+VQ?9WS&?jLi|8^^j&$&!c1lVk8?MVR6l!gMLtv>T}|~Z9=O47&)%88rLUw;mXEJ? zreE7}`*zQzrND0mQTE!=vnMpPV6j?-3yAL zGs61>aSfireX<<3ypKnmwLgNS_37vl4o;oMb1~3#E{dR+wBXHo)GW82-?? z81YXv_gs{kPQCgJ?ztG%VoesZKZAQN#=Z4x=VD^hxtMff5XE@!Kxnge_|a?|!ghom z2oECkBkV-@48lVQyAd8n$N>B=9b4>k_^b9HJPzQUF>Ww(>UY+i=Chu==ZiS#O8}K_ z_5`*MARI(UAVB&>xSJiZ#Pm8J*8~NnhZR=(S z`cyj#u~nxUdkVYFch3Y05s;FNchBL9zEr%OyF;swjIP9v{nzdue3sUPxC)w9<*$ZC zZmkM~dn4#4cJA#H(s<$9@@Bz1C9FvwW8o%#QEz@lo{ME<^+rp-jaDY$6kHKKeo+=GnfRUGOA=C;OO5I1;aNRT%E)6cyqgB0Ot9gQF3~;NLn9 zfJys@)AE){_iCzVv9CHX^y}HT=6?m*`g7&uULO99F?SK1deg?>)rIg&&rk9TsRYPwl;3SdCKq|5KoDD=F5{_+=BV!iW#lbQ5&_V{?f$FaX*@KSVhed^YTn6vV$l%F6x__z<6 zoU^>6~@ t3x#!p-|Oi(_IlR|ey{J~z09WGvtBPG>{^&xC-Al22C!K80NZ@u{s~O9jXVGV diff --git a/eveai_app/views/__pycache__/document_views.cpython-312.pyc b/eveai_app/views/__pycache__/document_views.cpython-312.pyc deleted file mode 100644 index 9c290fd46ba9edbce5280eb09393d791f4658d7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36745 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 S2ThEF$$eO|hDjoH>Hh%0p4+bg diff --git a/eveai_app/views/__pycache__/interaction_views.cpython-312.pyc b/eveai_app/views/__pycache__/interaction_views.cpython-312.pyc deleted file mode 100644 index fe83f8cf718a30c25f0b6cffed8c63b0ee662f1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6498 zcmc&2U2Id=`CR+jaqJsA!TAA_IEI9PNn)2a{ee;-kc7f$_({UgE!1^p95XB@s+Tn);TOK1|)qzH{tr zUx!epzU;_8_d7q|ch33#&-qJTof|<}oJvpL_agKKX_&=P0v`U=hR_E{KmsEnzW8N$ z2HG~sCfj(s%<>MV*ze?>&~KM&WEby}Yx!E)&AVj}?~ys4lk508*~@$BTvqbQ^?beD zz&FsgL)swwc|V1nQls3&H&M7oYL;9077Dwhjq)ab6NPJ~&2lT>N?}H7N-OU$+-| z$Ui4OHs_CjSD=!Id!+5;j`q?0F2GO_n*&!<;!2yYdw_3#~%ilx?y7 zI>?h0Az;DU`xvYaz+oylaJqUSC~PUy*jk}cKFLduq17q0ekZN2b!hE-46SWXpw+z& zt^LCGw-J9}o(b*vk}zN-WHVesOcS+)EE+CSXOyHEsxfM?_(ob(HN%BPA%VrXW;iCL zm^y9vu$UA?9MwcQB>}2Ydr(S?DV#`ZMvW?}YC=gGo_HF=JS`earC<#&RwPl4#^P}? zrHO*UnZs#S#39CD1yPN|*FB>olA<6fF#%eh;e;fPY6^}`iO}?^BA|)UN$5?+WZE@) z^4NeBpBCl$A&eD5$~8KAtPH2Il!{?hge`auO~1qOjR0OozQT!$*$J#-s$u1}H`+V{m4{Xo0zK9xQEcD?!PMh+ zEu8|R5gN42xJ;<>{Rn)dWd`-Nl~DqHgnq$ZC(MV!pm`Lsk7TwSqFNlhr1B1aW9c2U``1_#C2QX?`nD-Ilq42mg9nJ3B+5yV+hQc@HKFqR;? z9hpstbE=iwQQ8wu%^MqFV{0@3_rXru;jcao)dG6J`U-4wj%_ZmZ8^40?>O+smOll4 zAJ7k+&`+FPI(4pa>O$_+1^wh}dVBN^`#SCcw0MbI03t+qP9^v;0RJxu3id3T;mCHX z3&_@4k!==@f`x%}2PQ#T%fekpx_BGV$#{Z&!BH)yrPV#bPz@C}?uG%pom525cn7IM zP#I2@&IDZthoz>JBzPjiry%Yn!wp0hF|w{{jCdFHZ-u{l2&zX!@wvNa@$hd(u8rtD zFW%hsyEC7?`su4TU%B0?k6$Q^3%PMY9}{)gW9V|zkJ;97)9O6hY;|@;8)F6p9!H^phJ2PsBE|Hi6 zPPC#xhC?L50@slV4jP)MPC>s6j@Dplbg>zXXs~pYh$mRmByY7($9rImXrTHds20#F z>vVXRU7PN>TK~Q5Y5W?or0FlXHs@TM3$9?!71TS&^zqaB*cpAx*(KMx<;Gy4u{+n; z{WW50&oVzAT;^H|Tp-5<3S3u?>$;j=JYVSBpX=MN_a3_LzH4;}}=8E+&G5inYc(5hiSJTzvo zCytMffzi|y9j00p9f*lh-|{Ba-|rJ0tlodoZf1j^;Xt}r{^v-;3XV!7vCv|)a@?6I zYo;f*i}1jcIfDahDT;Lu3-m*nKb zAv*@EMtA@!qn0XpR7qxaWv2Nni*>5 zZ%1`sN8Y!4;pGR!M%!~-dx7iDaoxYt7JpFa9m@3%<+l&#xx))X_Zyl&2>d*7b?nB= zw;k^V@(m*kM?d#86_;5UeBjx7wg2y-blx*d8k_aj7jJdxqZgJuuj%Y-G?Zilk?B}c zfIQE-f3RN(l5y~78TV)!#2+NCFT;&NN}SY!1A?4LRv9tj1~uYu0C-Y@ATUG+!90oS z)3ROw$|BPs)(u7FAOzvXB(rWpDcI?1U{nHo5kfg`)<{N2i&i#U!I5Paou-+XE03ae z=hm3x<`+!IHDYi@u0fIw3TikYvrdbKvzY%I4J2c$VsZvzjWwSRKQiMQTt}+Jk`OX& zYmlttDkLz3rv^(>d&3@sn-wR4YU7v`6=6U47>r^fEZhNA$ZoV+@xfXl2Te>S;?%4R zM_eTad;rz`c~?Ub;Dz=Y|)C9@I6yd;ZG#4|nA1Iv0lTyX)`P1(*G;OKrV+-*G)Q zxzsk5_fPBGG*IOlmm0hDZHMxWgL%)8&JGn-x{Kz^RlJVuU31QTRrxJB997@506a;3 z*Sn!qloiym28hs-WlRO3cMfJG#8RI`J&NH5KNW?`RsU1{MuR%7RBK1bhIt?Ymso2& zwd4qx?3Gr;*Qg}Pq^xf2DEPZ_{;q;QlJiGy`11Y(3&)n(ruU>fY-f={eUCB-9HSYu zLFAFWrdv)3kKxoTtoQvmwOHoLOqo+atW?aL75G@rK-cimHWFg+Fw74*EbOd>8#~Fo zC*}=qGJ#c-w>VB_+9(1R09GwbG&&wJx@u0~X}}|yOkbifNedwvQ;bb;OP%C$fTy?AWWeto8gK^ z$!#hc#l#KL+Y)^QG3p+@jgz}r8XN`jnlw@s9VxN%qDJl{P!FldrT$yjUf^9?68GVE zpb2*rq+WpgRSK`RS>`@>tNE6T&OGuD#`hlgVKUG35u9O)6DCU>WmZ|zQH8ko wR{GWyMI5mr+L$haLh+6R9<*28mi3186XUn%DBhXJ;hinwJ#^ccIfjbrzr~jVPyhe` diff --git a/eveai_app/views/__pycache__/security_forms.cpython-312.pyc b/eveai_app/views/__pycache__/security_forms.cpython-312.pyc deleted file mode 100644 index f1ea3981dea617dca7ef03ee890a9ce4a2a1c958..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1639 zcmc&!O>Y}T7@qZdzwF)E38iVFr4a(P+oD=6l@J6Wq1vV(a8XLAGzwX5vSWAK9j`mP zc5qHb>JO;6G=Cv-FNhQ3ggA0>l}IQfRXuRxh7|Xb6Z4L}#^Hd}6C?TA_iJbUzRxrB zo8uIaSWjBrW}+hW2RPZ%CdT3S!g!1V6etm*3RV=bb5V|}Se3RK<*9}>s$-oR*pRV& zWKs)Tw15k=h>O(5Hg&K==kT0@a_AEjXb(}K6LlcI3k#QI)DTf~CR&zJOGFDZ(fNMS zwf}_IT!j}myNr=`>iM0HaIJdeC->?xqpr%GFa0Fhidj$(Nfhwnt?mX5)67X(*ltP3 z)wx~uQ-6ir?}m&7T>qT3Tj?FH)uFZc8hTHW+x5fwjXh$cE9UKU&Wj7 zSF&YHjKjOac#H_bN`P=Khlm;|0`r4OK}&g(8|20g)?{9t%?n!(@&jdD(aOzK8zN^0 zT9(6BUv~{&x<%4y!(ss3%)DKN7kYja27Vf|C zqb?;$<>u|{*S@&wU8z)A+}Vh~t$Y?I{=N8ovQ#C#iBqZd$j6^ns-zReeJV(=1f)lz zxFc<0`XR`m(hJE}Qb~xAMws@!rWi(YxzlGQ@kt3lQu4llhv=YmW(WVU_T&2ST6Mov zdvN1m@vY(8@BOm&>-y8xwc$6dz12IzYPi36cbGqM;GEidW6!zpU7elD_BzHs^a$(l zhJgQKUofaml`zY#E;2@-PPsMvc~|3QkS0me6_St?)Xm`NvUvg9CnvcnZA-GM;acn@ z6fFXr0)V#1Ji*&sBcN`{3wsSxuLH=&&VqF=10XbcQ-Bzky|`oSt_{|ITK)O<@SWPe zU4Kv$V;P>lvgcgQ<^pdXo6G-qD(2-dGNsGWm7y;f9eV`;G`l?~%cdi2uva1VuYte_ z7{G$~B_9ZQdHoDeyL-;XY*4US!(HH76Rs@=w;{Kr?QNw^mIvPu7ASap%s^q1NWL(M zU;gK4kIA*5MCzjmmJI+Dj`c5Dm7N^#?#TRbe7rwm=S4BxJIS(uzf?t0o}shP(7ESm r`6*g{j_~gYk91XWAB7_%9wR%ay!)s>LgFzhsLG`s>xGD4l3My3cV>{O diff --git a/eveai_app/views/__pycache__/security_views.cpython-312.pyc b/eveai_app/views/__pycache__/security_views.cpython-312.pyc deleted file mode 100644 index e59c7bf7eca5460291eee2663f415ba24a10c778..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10787 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 z 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 N`Y&w;x}MD4{|hCS@TmX* diff --git a/eveai_app/views/document_views.py b/eveai_app/views/document_views.py index 422c494..2f8fcf3 100644 --- a/eveai_app/views/document_views.py +++ b/eveai_app/views/document_views.py @@ -17,6 +17,7 @@ from common.models.interaction import Specialist, SpecialistRetriever from common.utils.document_utils import create_document_stack, start_embedding_task, process_url, \ edit_document, \ edit_document_version, refresh_document, clean_url +from common.utils.dynamic_field_utils import create_default_config_from_type_config from common.utils.eveai_exceptions import EveAIInvalidLanguageException, EveAIUnsupportedFileType, \ EveAIDoubleURLException, EveAIException from config.type_defs.processor_types import PROCESSOR_TYPES @@ -159,6 +160,8 @@ def processor(): new_processor = Processor() form.populate_obj(new_processor) new_processor.catalog_id = form.catalog.data.id + new_processor.configuration = create_default_config_from_type_config( + PROCESSOR_TYPES[new_processor.type]["configuration"]) set_logging_information(new_processor, dt.now(tz.utc)) @@ -181,7 +184,7 @@ def processor(): @document_bp.route('/processor/', methods=['GET', 'POST']) @roles_accepted('Super User', 'Partner Admin', 'Tenant Admin') def edit_processor(processor_id): - """Edit an existing processorr configuration.""" + """Edit an existing processor configuration.""" # Get the processor or return 404 processor = Processor.query.get_or_404(processor_id) diff --git a/eveai_chat/.DS_Store b/eveai_chat/.DS_Store deleted file mode 100644 index e306a2f516f8d04a078ccd8e71cc7379123ce34e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHMOK;Oa5T0$))=_vwqKFEFWQl7OVnm55E+&Knf)hqO1SrI@)mU-7QS3B|qKF)N zLvY|naHKzg0~dPW$b}yR;xYS3upL5rDXPRywL9zG`F3Z%UE3KK07$j(7yz39pkZOU zkjLr|!p2#dlNH%>4mCkMfCmxO@vj8kIcwFS7*Gr-1{4E|0mZ;^U;y9QOtb~>eKD$P z#eibqzhrWI_1IS^B5RVxM*1M>{9+&#~8b`OF?{yx0z)28iH z8!gyPbpuQ3Cl_o@QC%296B<}r;IesAsQtsC{2jw;(vJLUkT)04-=Vdd^h!Jb;l$&Q z-wv2(B8E9{I@Fn2eNZWoum2M@FS` zt*9GU%G={{PCI+)^7VW5w%-ZpD+~&S$R{tH6xXx*HMW)nn%v$~8_N;#Z63be8@fOE z#w}SVzTJmMCAVE?fh}2Q*Xjj64R+9yv9gATB*KFc;SJPG9f_AX9=~l8wn<*JJL$RN zEIhgkZw|y^Ps|aId67*rLVmDUVHdr1VURw=D=ezG$1%&Kzoi#w85@mzn}K}$lkhfS z@%wiCd?tLiKWCBj)Ku?(q*#e!;3yc#h)FkD{(o!n_y40`!-6MH)ujJJ`qz>ID`EEjcn+=zk-(QM#8gC}t{Ym`Tx7&7 zR#Y?MrVeGEdQBs@h%MEi&cd=qeT%v&=QI@;Eh^>=Vp5NZO;a_BVCb1n-@G}Qx(Q@w z?snFaZ)b~&h6%h6wpZXjzNwYxxK##4 zys64S&L}RZ`Dk;xj+f$`LH&3MXH}VBQQDJ9WHOWKlsuJ~li!$4+?c#oam7k%F`u(T zvgckPj@XsDk05g#DJqtXV*U{#Atqp2U;i@p3qvdG*o>v$j=wf@QT}1f`jF18kb>glO^hU;GcyT0H0_PwLK-qGr}E(@2!g{S9UTZ?Zb*OS%Y*!kT2JkZBfH|rAM!PE{6HMoa@NGtUv!_| z$~?UF;MV)*PG!5Y*L`sMbl9Gt2r!uAL+P|*v1f>h zVI3bQdunU0phzG^`!nhIRN~Fav^)bid+laI{`p&pw-R)u#gC_FGxD|R>Ga!)>vDQ_ z5;}+s?QMy#W#&?Gc_w{5L4z$OYcvwGbg;!pq`)izUK)~$FV^T=qN)LnOaK7c%;vES z9Y-0{zD5_KX~r^EWP_-HHzxIE&6Q$K)(r)dZ^IYTATELctA=cYNGWUGu6SasaKz42 zk8LTOhYqKMVACiQKvXCnOe5kaEH|i^<2QpEmI#CuH0sP_i?AC(SYJR`HqgLxZ4qH- zPgq(;1o9o2OxD{p5Q>a48GA&S;IHriXcFBUQ|nWkfFykfV&}dX+7&~ax%$|-o#b|M zC$*ibzB;uxHeJ6E{qXXm%O6fWny6l$+q)pu&y4L1Zx8Q`ZjV-fkk~tOqdpRTC_RuK zzVqOn>i4ehjZDi8IdVPAcw?>pE2YQ diff --git a/eveai_chat/socket_handlers/__pycache__/chat_handler.cpython-312.pyc b/eveai_chat/socket_handlers/__pycache__/chat_handler.cpython-312.pyc deleted file mode 100644 index b5c180340ccbd273a240f059ce882ef7336c3c03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9897 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 M`?<4;Y{SF;KY+cV;Q#;t diff --git a/eveai_chat/views/.DS_Store b/eveai_chat/views/.DS_Store deleted file mode 100644 index 2e89e7eaed020e02149f94fd5ed8180ee0302236..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKy-EW?5T4N#2T76AM%Y)#8=T<;`#ivDE@Fy=yT-z9A(p;@58+b?3citz-~1R2 zo@oRTnStGJc6MfO?mKR0mxy?Jy_gV=mxoexHqJB0)T!9Xw&3lfkPXPrT(AdSL(gw?}o%E7zhUb83R11X4Mog<+b(8>q)ImXy<4m;+I8%K-W?X k)KZLr>?21yY4b^R%$JVYP*xGUh6CdupoByh4EzEEZ-7TMo&W#< diff --git a/eveai_chat/views/__pycache__/chat_views.cpython-312.pyc b/eveai_chat/views/__pycache__/chat_views.cpython-312.pyc deleted file mode 100644 index c0d2e28a22497ce0179a80f3d0fdafdeb717e1a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3822 zcmbUkOKcm*b(UO`OG^AmvLsr+tZdg36<72}2@)qrV#`0djhxy_T5pVOSDcZw)Gn8q zUCJg)7fPEV&MD5x23i9;y08yMPd@bEqk&$QkXoW!1!_@Wa#P>}1>8&D47sEf*g)U~ zIQ!&S{}PYK5In#8-zN(PIuQCd+xSnY2|WF?fY2?35f%(&B1JF*N>E_?fFYV8 zC1i$`uo+Pzrld$_REe4~C1!Rg9cHJ}$?1Yd+)OA5GpQual#=2&(daU}m2M7)j2?53 zvd8RIdd)tikK@8dznNCj=3Zs5IiL)1T*Meu20^c644K2qusNcPaD3Ew#grA<992fm zeab$Liy8aP1IhsocNjl14=M-EF=fmgSH?N6)5w?;%7lOdh(vLmyowX|llOv+Z&nVG zL-~(=L$u#Gg}WAo``z~f4IJ+I9NlXkE0Z8;EdNQH+K2ESklpL^09CKggZtY2K2ACO zLOJ~}l#_mroFjPe=SVqPj${VDWmm}rJPB(KadeY-5?d=)fn+2vLdmBkVmqEh3Dzme zIbNhh4RyhyUXqdmCRBBZSu}vzi=H$}q)7Dw5ZT1Gb*tcY0zVe6Pk+PH(_uLNID9AQOA(OM z)jEw-1A6P5u(S}kgkC~Nl}DCB`y20K(K6=cl z8SMoj%e*8P&GNA!oigiF=p4uk8`k0?cK1EiVo9yWoa$+V62x3@86Y?tyAYfqC zbd@Dlc10&wZJu8$2PcbVR>gK@7M%k1J@B(1!m^5+$4F}Zn5DDxs^mghl34>RZpEO7gT-$fdsWwUF_^cbvOVo57tgQ!g*^esT{gP)y+ zWfg&2`VOx5j;+paipjdzR}=f{;!sT-x_x!+*AJF!V<+8%(~pk3!?S-CFVNS3haQ3@ z*D?fb!tmopx&gp*jU>F3k%YF9SYf%-Du7VBTICUoIE2HxfFt)Me+Cf{7&1Cb--P=# zjMydH5b?4mH#7IR%sRGRv{Sl zu-kj&;oRTEliVq}RuF<@<_qqRKLG&#fVJ`b(DqhEe(Am*Xg8?5~~A7_Mm9i?J!iqUzM)DK7}V@H!2h zO&Nwtps0F%q>!U!HV5%C2BQ@3AM_O_B8dq<^s(T_R#)-DjePOC+7g<~URRS3gf>w& zD+W+_o8F}?f%tM10Q)>Ft7tRc^VO9vuhioQYViYWm)GOj)iayv!FqbUmL9*`SI?ZO zWzMXp&$?polk~oN`e-eE)ID};J$)K5|LhoAi_|*CTycy$HFJzIH^PMEP*z&JfK%~8 z3x;Md^X@`f;!##$D02;EIYF5_8U=#25ih~XRhvMCfbotpe^BNM%7z3lVSj39jj3^x zT+7iELv}ZK*jPxefrT)^jb_!&_*eyMiEWN;JPl|sJMLr4DMr(svrN+}Oqv!ZhCNw= zsgd@xQ|{WmyH8;#_QyubDkB|W%OI1}x0@1MIcRzXhYqpBuI3@XD4VElI?Omn*un## zw@FcQbhrgebv95^nQ=?1%IdzV(owbzQL6;kU>(j2SOs1ydD3z>{}DCnA1kZRq_^R- zN(Om{vi~})8rtiyYzGBF*h0s)&|)1euA{{*bgqWZeS;>p(8U_MxP@M?q1XR`V*d>z z;m{VEtD(7X5C9AfRR4NzZm{(*iDY!J#+Vs)!}tv>bpRP z@SgBAhyt%|b8veI1>&peF9tswym1-&t1$6*A^zKu?*akg15TG>#IJ6PY`-0Q0kJ4d O?;_^=L|7Q-AN_yFRIQKz diff --git a/eveai_chat_workers/__pycache__/__init__.cpython-312.pyc b/eveai_chat_workers/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 2cee1d27dfa9b5d0180ed913d12d71b4cc9c773c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1754 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�h-@a)zW*+Ui4xAsMx!Sc&MZye{0)ngP?NcLfF^WYE$fQK71_8uTvea~z`) zf~o|(&@CN;{?<{?rqVu)eMGa3)>>pVrlg0y8#Er`QmL;+*5+FMYFB@`iKmEW!$qv$ zqEn*)m8!U!>1wBon6CDx&b`pPNm1BO{tsrJBfTSBp?cltR0D!_aSCS zj_t}ILBK6J)Q3V`D72-A5`4-3(ThzK95yx>>QHh^>YGoU*@vZu9NI$%cD~1a&zawT zzo$}3z}7Qbsr?!Q;14cDO9+|c24n669q7;m<$yw;3Z`Ji$}z}wF%wzwa@-QjqAw#e zVM%4l=i}y(l`JP2FX`f4SeEs~T~Hnt0G7AIBww*?fJ#x^*JhQY(>|rc5#P^Fhaz1g9dUiC z3z20TYNz?=NMz9-=!0ha+Qzwv@6*?JxgFl+x3k$7k6}xV&WtyvNmG}SerJNxg%ATZY zcE!S)=7~Wso?Ii2r8$cYT%}&Zbn121iI>!kDh+xh9KYUB9n%}^5EE}?uxE{Xa3#TY z!=;#Lc!gryH5}U|{BQ6OPe|gN1%#v06GIMh4?S=d$QZ%==6^CRs;^(t|ii_VHV=eU(6=b~F+PZJ8NfBh;`VAE+@*rqCD zugvnjVrX~#XK__c^9O=fY_59AD!~DuU4AmCxYI)`hjMyT{O0o=bnckd4E{09`0M+_tjL(bVb(YuX51%OgJWhD zJb)!dAR;^RB>%41^(c_=-__-aFOYGz6FwCD?Ja)5hY7niByL>qUf+y8MrX++H@Lty zB||cdc#hc%>DHNgxQx8$c)`HyK*Piz2(RI8zotUtpET3 diff --git a/eveai_workers/__pycache__/tasks.cpython-312.pyc b/eveai_workers/__pycache__/tasks.cpython-312.pyc deleted file mode 100644 index ae58524f057d89391fd4b4859d47b449a0d83914..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21962 zcmcJ1c~BhLnP+C5SqBBh4aHF$0u>+-0$m6sAr65g(1p}3)ar7HEJT$G3TGvfU3L2qlr%*;j%_Rg+lcekx>$A&WvimFMYuy#j|*pB^&R%^o^ z^~TJ8-^PqD3jk4|D( zAMfV_qkf6Oa4J0J_s~+>5x;MO4-8L)&W0z(A;A>kpKkIzjtWF7Z`)|oQ@G6=Nsbv+<^ZEe|9_+;2{nfnJ0W>S0FPm)^%gZKh6ij zSUeL__)}bXbetdXkB*0+Ge}K8d-_CsXlTSgI@!f>VN(6vScK;$hWH82&vt|YFcK(e zia>9E7GliV&f`j19eWvOnBEy4niz$0=lxs+dXp?&qnG_G8yLHSH3$4-KGKFmnb?p& z`k(J<=|d9T4_eV0T2h?GCX`C+i$5X@@Acg64J6T_FB zi5$PuTST&IacI&o=7VK7etp6};U7voXYz@3Cs@{hWw&xJJSZfP)YsLp;?l~^}U3$8_p+J{?-Y1d$d7FS}? zq_t$B7jLGL!&fV@;C3aZUHKX%9;{Ph+Ep1hw5P5VOTk^fQij~A>-2(g?^bGQ*QC|n zucRi=#R9zrTa+B_dV^-2S7AOJP-ebONssEI2B?jDAAU47NwJxvl!HGcr%}}AGO*s- z&dEyMAM95L*;Cp0#NII}EAu8@LGkKZS5jR4v?=L*OZ8q9DjWcBP z{s22x-J^WFvQ|;`xdHdjFT|bv&$D-rC_Mzbl$dtqyOnsbM~P|Iy9Li%drx7%ly>Su zM^F2iK5u8&k#k2mq~Ve!6dv-0M#2$3@azMM8t~{P!+4nEB}as3!xQ{K_^hA1=I6-0 zpW~GKsnQPQH^95x8ZU7VhdDP-_?Y`yjypW&W)m#W4NV}=b3dc&r_3^|b5B{_V__aD zO^mTqPK7(FMW?z*7k;Vq8Fd5UAe*ME9pVn z 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 { + it('should run', async () => { + const bundle = { inputData: {} }; + + const results = await appTester( + App.creates['add_document'].operation.perform, + bundle + ); + expect(results).toBeDefined(); + // TODO: add more assertions + }); +}); diff --git a/integrations/Zapier/eveai_integration/test/example.test.js b/integrations/Zapier/eveai_integration/test/example.test.js new file mode 100644 index 0000000..3e8b74d --- /dev/null +++ b/integrations/Zapier/eveai_integration/test/example.test.js @@ -0,0 +1,7 @@ +/* globals describe, it, expect */ + +describe('addition ', () => { + it('should work', () => { + expect(1 + 1).toEqual(2); + }); +}); diff --git a/nginx/static/.DS_Store b/nginx/static/.DS_Store deleted file mode 100644 index 6b60b017f93ea9d9cad10729433bf8bec68a2c36..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHMNpBM|6n>th?L;jQBoLJV$r9I4l882~xC|j2fD2*}l~|N!QxZBEM_I@riZmxK z2*j0N!I1;p5Qsm(a_7pC3m4!$+ccAzR!Bu+5q4yM9{YV>KHFox2>`I-u$}{01^^o) zVR{jhT>{lpj0H=V97ANt2N*#Ak|%0aPgG;probp*6fg=H1&jhlfxkfkyt66Td))h~ z)}%%OqriWu0KFeLMnXqppisU#Fp(|*ViJpGz&!c^lEXDR8UuxriftdJ{~eDKJBjrx z-(-;F8Qbcp-_^eU^J>>?_XpYRJ7y)4bMp)Kg1uaee0urbdVM}_Q~#+=w-xHge@xxYjN_i$^F84=v7E-9fEq1R=mQJA z>!|1O0%@y1y)?Ov)og~JEv)|X(&!0=7X@JM<5ZCFa%?_3^ycc^Kj;KpR;e9_8noSMG1AQR=$s7^8KwBjnf|EP)RK>Udtu z(2=}aFYrY7VX9gTrPItdue{jYQ|sZn6R3M3S9nf(8Ydw>7mZ}Wy{6fg?> z0}8NY$t@Lep!#l&4Vipxj&Tbk1+ohiN(v^@9Y>VzIO5J9hM4DQN*qUHpb$$im=6I` M29p>CeyalC0Wcvw*#H0l diff --git a/nginx/static/assets/.DS_Store b/nginx/static/assets/.DS_Store deleted file mode 100644 index 3b894deee3ab8c5c3ef5b14b2db2acf3a59aa2ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKF^|(Q6n;)~T*?XPa3Cavh{S|c9c_o}omfIC0~;a;20*>0ExlZmxN2I8R8{JZ zVBjaPAhGZR7?IdmVMUDS7JdOd+pntA9*idr`APO?`@Ls>{?f!X0I=GqT?QxsfP+P# zeHyDHO!!4vF^fhnATlurZv_z#uqUQ7W)v_A{ObyccQ*$uNFluwW!~SL=-K)uO>Tms zf^piXxBNrUkHU0VDt%#A&R#pU?yNf-&J%y1kNshDI7(~H!6VVT&v_Kg+s)uX*cmqp zo40r}Y=%i^AQQrN2SXk|43jn=*Z3%Dr!v=94kz#A8-+`gNu{(?ExMJR?P<}Sl&jV4 zqFb(%cBj+4bN=e}8}}N$C{FlGr8~pVlC(2~b9jXf+ECOj`z;>#rK|Y%aN&u0y-r(o zKKGt~{Azs?qEKGU+kf%)_w`x7iODJNnLkJ(o^)|Uuv>$tz#)O*$^&?hUi|@Rkr~c5 z{K(Fu^Y*U%ob%T5b?!$;Wcb^6aRKBXLJZyP&xBoGVo6atL+hILeUY!Td-vvW&W`eQ zW~*MEm9-^L84C`Um18fKILR2D5uEM6#uz!S^YQn3b3Wu-JmY1b&IQg%g>cG~Wb|@4 z8z+gh;c65x3LJ|9;``uY5$F-l6v|HrR`LpfXydRnY|GCwqALavJ;Iqn96@6$6;Y)! zx?(W8J1~`wevjg33ROBVxiZc}D>J&GFu6L8yD}V@r_j_!0i!@xft>0V#QA^w&-edq zlIa-*i~|3a0?e-a^%`!8&eoO9iL=(lvWG>Oh?^;tDOk~QEF0n|zKBH{`draK^ay7P SaRkl$5s)&N$|!KG3j79aL*% zqJMypLPaYP#3I3ez(2sk@6F8W%%*s|MN@qu0sOh94gEEQ(;y~U3uHE}-fm*2B6SsbE)FAg|y4Xk-F(2%f&JNCde!nT^)*j4lt~mLJ^|v~}TZIqM8dH-RINtCy?u~zB z4ecCP-`3Pormq96UWtZadMhz$*tuo>c;bQ^|LJM#r0gl)okYOwaVWg?englcRSxQJ>D9w zmLiU)qjJqU2B0H`4!*}G3a*v#F=W+taI3h!{fW-wdI}$YN^H zY~Zs0vm4UHfv0nzWKxfE{XbLu{{QI&N@j6D9Qf}JsB(3#I*l}DYtHB6T02CaN9V@8 o)SwQ*pv!TfF2{j)e;DF81eIeVi>X1JL6aW>+6EcKfk$=V7O&|S9{>OV diff --git a/nginx/static/assets/js/.DS_Store b/nginx/static/assets/js/.DS_Store deleted file mode 100644 index ff05f2ed3ca0a41aaf4cd66b10f4c427820f51ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKOHRWu5FLj|6qQAnEPaLEAXMQ5y+D-~6;OhsqHa+Z6>E;cJ&?Ep*Wrz4C^4Zd z5rP?M{G9pN&P$ZZ5Rsd0S5u-15sjdX*$IX(g#D}o$<)G5$7|H<>3kWQb`WWX-^u_# zyPTF3XhWCD^UHQmeP0vGvZ_M`+vwqb@%);-oTvWPxBm93_ikj5NTp+HsirNh>6&io zF4=utg<(Yh47<(E`x!p1(}a(c9F9uPbL#m#CdPmmc)_y1{kDb;g+4E!hqZ##lZs{jB1 diff --git a/nginx/static/assets/specialists/SPIN_SPECIALIST_1.0.0_overview.svg b/nginx/static/assets/specialists/SPIN_SPECIALIST_1.0.0_overview.svg new file mode 100644 index 0000000..c374ee4 --- /dev/null +++ b/nginx/static/assets/specialists/SPIN_SPECIALIST_1.0.0_overview.svg @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + EveAI Agent + + + + + + + + + + + + + + + + + + + + Identification Agent + + + + + + + + + + + + + + + + SPIN Sales Assistant + + + + + + + + + + + + + + + + SPIN Sales Specialist + + + + + + + + + + + + + + + + + + + + Identification Agent + + + + + + + + + + + + + + + + Consolidation Agent + + + + + + + + + + + + + + + + RAG Agent + + + + + + + + + + + + + + + + + EveAI Task + + + + + + + + + + + + + RAG Task + + + + + + + + + + + + + + + SPIN Detection + + + + + + + + + + + + + + + + + + + Identification Gathering + + + + + + + + + + + + + + + + + + + Identification Questions + + + + + + + + + + + + + + + Consolidate Q&A + + + + + + + + + + + + + + + SPIN Questions + + + + + + + + + + + + + EveAI Tool + + + + + + + + + + + + + + + + RAG Task + + + + + + + + + + + + + Retrieval + + + + + + + Q&A Processing + + + + + + + + + + + + + + + + + diff --git a/nginx/static/assets/specialists/TRAICIE_VACATURE_SPECIALIST_1.0.0_overview.svg b/nginx/static/assets/specialists/TRAICIE_VACATURE_SPECIALIST_1.0.0_overview.svg new file mode 100644 index 0000000..53f0227 --- /dev/null +++ b/nginx/static/assets/specialists/TRAICIE_VACATURE_SPECIALIST_1.0.0_overview.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + EveAI Agent + + + + + + + + + + + + + + + + + EveAI Task + + + + + + + + + + + + + EveAI Tool + + + + + + + + + + + + + + + + Retrieval + + + + + + + + + + + + + + + + + diff --git a/nginx/static/js/eveai-token-manager.js b/nginx/static/js/eveai-token-manager.js new file mode 100755 index 0000000..e69de29 diff --git a/repopack stuff (deprecated)/.repopackignore b/repopack stuff (deprecated)/.repopackignore new file mode 100644 index 0000000..639b115 --- /dev/null +++ b/repopack stuff (deprecated)/.repopackignore @@ -0,0 +1,55 @@ +# Add patterns to ignore here, one per line +# Example: +# *.log +# tmp/ +db_backups/ +logs/ +nginx/static/assets/fonts/ +nginx/static/assets/img/ +nginx/static/assets/js/ +nginx/static/scss/ +patched_packages/ +migrations/ +*material* +*nucleo* +*package* +*.svg +nginx/mime.types +*.gitignore* +.python-version +.repopackignore* +repopack.config.json +*repo.txt +temp_requirements/ +tests/ +docker/eveai_logs/ +docker/logs/ +patched_packages/docker/ +eveai_api/ +eveai_beat/ +eveai_chat/ +eveai_chat_workers/ +eveai_entitlements/ +eveai_workers/ +eveai_client/ +instance/ +integrations/ +migrations/ +nginx/ +scripts/ +common/models/entitlements.py +common/models/interaction.py +common/models/document.py +config/agents/ +config/prompts/ +config/specialists/ +config/tasks/ +config/tools/ +eveai_app/templates/administration/ +eveai_app/templates/entitlements/ +eveai_app/templates/interaction/ +eveai_app/templates/document/ +eveai_app/views/administration* +eveai_app/views/entitlements* +eveai_app/views/interaction* +eveai_app/views/document* \ No newline at end of file diff --git a/scripts/.DS_Store b/scripts/.DS_Store deleted file mode 100644 index 0392ff2d7eaf9349a5c3cba75a3bb15e5438b926..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKu};J=47K5eDs{P$1u37v{XsaDFSrjVTn}+WPwj#b5<^Bt{sf7CVB&Y!Sa^=j zK}ETN0U=~d@pBS8ai7#ACL*3+t!6}1A}Y`XS&R{p;nAr*bDjV>uhGysHMFJ+sz(<3 zi$juoiEL}?=?1xfMNaoPmGF08gq#HOEKU-TLM8bGQp diff --git a/scripts/__pycache__/run_eveai_chat.cpython-312.pyc b/scripts/__pycache__/run_eveai_chat.cpython-312.pyc deleted file mode 100644 index f619f7f04c643558d48f33da9da65215315a6f58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 376 zcmX@j%ge<81fRdhrDX!?#~=<2Fhd!i{eX<=3@HpLj5!Rsj8Tk?3``8EjH{v2QB0}K zDNNZK3=BoWsZ3d%FzpCBg?SCjYDNg3k)e{cl2w!KC5WrZc#A8!C^fMpH9oPR;1)-G zd~RZ9UVMBAP=YljHK{b+Pm}c)S87>mVrG1DMq)`3$W6DHp^Ebob5rBvZ!s5@=B;G- z3^MRnyna}IPHApxvA%z}vx%=!yrI5JQGP*EeuchQesN-UzIw5OOKKUEqwiXl>gcHt z6iCgJE71osfg;7pMVSR9#ri;#;}N#&6;%G>u*uC&Da}c>E8+#(2l7<07LfSB y%*e=imqGC%n?Q3;1M>rB?vAQU%#t@)*xTJ3-6yCtxL;)v|G><^B2~l^hOxYR?3`Igr z45^G+Y%nbddNq{E$WX~#$*jrp62#YJyv3HBnv+^od5a@HJ~uHlFFxK+ljRm~YFTPx zW_)>mQFdxkaS=06DM&$HVs2`D{4LhvlEk8tl?sROg34bUHo5sJr8%i~MchCu8G*Q12}pckW@Kc%%b@Uqje&)yqv{f~q V2Df&fMxR!{56lcKQbn9VWdI8AS=9gl