Cleanup .pyc and .DS_Store, add new modules, remove legacy services
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,7 +14,6 @@ __pycache__
|
||||
**/__pycache__
|
||||
/.idea
|
||||
*.pyc
|
||||
*.pyc
|
||||
common/.DS_Store
|
||||
common/__pycache__/__init__.cpython-312.pyc
|
||||
common/__pycache__/extensions.cpython-312.pyc
|
||||
|
||||
BIN
common/.DS_Store
vendored
BIN
common/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -2,7 +2,6 @@ from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_migrate import Migrate
|
||||
from flask_bootstrap import Bootstrap
|
||||
from flask_security import Security
|
||||
from flask_mailman import Mail
|
||||
from flask_login import LoginManager
|
||||
from flask_cors import CORS
|
||||
from flask_jwt_extended import JWTManager
|
||||
@@ -23,7 +22,6 @@ migrate = Migrate()
|
||||
bootstrap = Bootstrap()
|
||||
csrf = CSRFProtect()
|
||||
security = Security()
|
||||
mail = Mail()
|
||||
login_manager = LoginManager()
|
||||
cors = CORS()
|
||||
jwt = JWTManager()
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
common/models/.DS_Store
vendored
BIN
common/models/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,13 @@
|
||||
from sqlalchemy.sql.expression import text
|
||||
|
||||
from common.extensions import db
|
||||
from datetime import datetime as dt, timezone as tz
|
||||
from enum import Enum
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from common.utils.database import Database
|
||||
|
||||
|
||||
class BusinessEventLog(db.Model):
|
||||
@@ -41,7 +50,7 @@ class License(db.Model):
|
||||
tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False)
|
||||
tier_id = db.Column(db.Integer, db.ForeignKey('public.license_tier.id'),nullable=False) # 'small', 'medium', 'custom'
|
||||
start_date = db.Column(db.Date, nullable=False)
|
||||
end_date = db.Column(db.Date, nullable=True)
|
||||
nr_of_periods = db.Column(db.Integer, nullable=False)
|
||||
currency = db.Column(db.String(20), nullable=False)
|
||||
yearly_payment = db.Column(db.Boolean, nullable=False, default=False)
|
||||
basic_fee = db.Column(db.Float, nullable=False)
|
||||
@@ -56,6 +65,9 @@ class License(db.Model):
|
||||
additional_interaction_bucket = db.Column(db.Integer, nullable=False)
|
||||
overage_embedding = db.Column(db.Float, nullable=False, default=0)
|
||||
overage_interaction = db.Column(db.Float, nullable=False, default=0)
|
||||
additional_storage_allowed = db.Column(db.Boolean, nullable=False, default=False)
|
||||
additional_embedding_allowed = db.Column(db.Boolean, nullable=False, default=False)
|
||||
additional_interaction_allowed = db.Column(db.Boolean, nullable=False, default=False)
|
||||
|
||||
# Versioning Information
|
||||
created_at = db.Column(db.DateTime, nullable=True, server_default=db.func.now())
|
||||
@@ -65,7 +77,58 @@ class License(db.Model):
|
||||
|
||||
tenant = db.relationship('Tenant', back_populates='licenses')
|
||||
license_tier = db.relationship('LicenseTier', back_populates='licenses')
|
||||
usages = db.relationship('LicenseUsage', order_by='LicenseUsage.period_start_date', back_populates='license')
|
||||
periods = db.relationship('LicensePeriod', back_populates='license',
|
||||
order_by='LicensePeriod.period_number',
|
||||
cascade='all, delete-orphan')
|
||||
|
||||
@hybrid_property
|
||||
def end_date(self):
|
||||
"""
|
||||
Berekent de einddatum van de licentie op basis van start_date en nr_of_periods.
|
||||
Elke periode is 1 maand, dus einddatum = startdatum + nr_of_periods maanden - 1 dag
|
||||
"""
|
||||
if self.start_date and self.nr_of_periods:
|
||||
return self.start_date + relativedelta(months=self.nr_of_periods) - relativedelta(days=1)
|
||||
return None
|
||||
|
||||
@end_date.expression
|
||||
def end_date(cls):
|
||||
"""
|
||||
SQL expressie versie van de end_date property voor gebruik in queries
|
||||
"""
|
||||
return db.func.date_add(
|
||||
db.func.date_add(
|
||||
cls.start_date,
|
||||
db.text(f'INTERVAL cls.nr_of_periods MONTH')
|
||||
),
|
||||
db.text('INTERVAL -1 DAY')
|
||||
)
|
||||
|
||||
def update_configuration(self, **changes):
|
||||
"""
|
||||
Update license configuration
|
||||
These changes will only apply to future periods, not existing ones
|
||||
|
||||
Args:
|
||||
**changes: Dictionary of changes to apply to the license
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
allowed_fields = [
|
||||
'tier_id', 'currency', 'basic_fee', 'max_storage_mb',
|
||||
'additional_storage_price', 'additional_storage_bucket',
|
||||
'included_embedding_mb', 'additional_embedding_price', 'additional_embedding_bucket',
|
||||
'included_interaction_tokens', 'additional_interaction_token_price',
|
||||
'additional_interaction_bucket', 'overage_embedding', 'overage_interaction',
|
||||
'additional_storage_allowed', 'additional_embedding_allowed',
|
||||
'additional_interaction_allowed'
|
||||
]
|
||||
|
||||
# Apply only allowed changes
|
||||
for key, value in changes.items():
|
||||
if key in allowed_fields:
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
class LicenseTier(db.Model):
|
||||
@@ -123,13 +186,198 @@ class PartnerServiceLicenseTier(db.Model):
|
||||
partner_service = db.relationship('PartnerService', back_populates='license_tiers')
|
||||
|
||||
|
||||
class LicenseUsage(db.Model):
|
||||
class PeriodStatus(Enum):
|
||||
UPCOMING = "UPCOMING" # The period is still in the future
|
||||
PENDING = "PENDING" # The period is active, but prepaid is not yet received
|
||||
ACTIVE = "ACTIVE" # The period is active and prepaid has been received
|
||||
COMPLETED = "COMPLETED" # The period has been completed, but not yet invoiced
|
||||
INVOICED = "INVOICED" # The period has been completed and invoiced, but overage payment still pending
|
||||
CLOSED = "CLOSED" # The period has been closed, invoiced and fully paid
|
||||
|
||||
|
||||
class LicensePeriod(db.Model):
|
||||
__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)
|
||||
tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False)
|
||||
|
||||
# Period identification
|
||||
period_number = db.Column(db.Integer, nullable=False)
|
||||
period_start = db.Column(db.Date, nullable=False)
|
||||
period_end = db.Column(db.Date, nullable=False)
|
||||
|
||||
# License configuration snapshot - copied from license when period is created
|
||||
currency = db.Column(db.String(20), nullable=False)
|
||||
basic_fee = db.Column(db.Float, nullable=False)
|
||||
max_storage_mb = db.Column(db.Integer, nullable=False)
|
||||
additional_storage_price = db.Column(db.Float, nullable=False)
|
||||
additional_storage_bucket = db.Column(db.Integer, nullable=False)
|
||||
included_embedding_mb = db.Column(db.Integer, nullable=False)
|
||||
additional_embedding_price = db.Column(db.Numeric(10, 4), nullable=False)
|
||||
additional_embedding_bucket = db.Column(db.Integer, nullable=False)
|
||||
included_interaction_tokens = db.Column(db.Integer, nullable=False)
|
||||
additional_interaction_token_price = db.Column(db.Numeric(10, 4), nullable=False)
|
||||
additional_interaction_bucket = db.Column(db.Integer, nullable=False)
|
||||
|
||||
# Allowance flags - can be changed from False to True within a period
|
||||
additional_storage_allowed = db.Column(db.Boolean, nullable=False, default=False)
|
||||
additional_embedding_allowed = db.Column(db.Boolean, nullable=False, default=False)
|
||||
additional_interaction_allowed = db.Column(db.Boolean, nullable=False, default=False)
|
||||
|
||||
# Status tracking
|
||||
status = db.Column(db.Enum(PeriodStatus), nullable=False, default=PeriodStatus.UPCOMING)
|
||||
|
||||
# State transition timestamps
|
||||
upcoming_at = db.Column(db.DateTime, nullable=True)
|
||||
pending_at = db.Column(db.DateTime, nullable=True)
|
||||
active_at = db.Column(db.DateTime, nullable=True)
|
||||
completed_at = db.Column(db.DateTime, nullable=True)
|
||||
invoiced_at = db.Column(db.DateTime, nullable=True)
|
||||
closed_at = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
# 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'))
|
||||
|
||||
# Relationships
|
||||
license = db.relationship('License', back_populates='periods')
|
||||
license_usage = db.relationship('LicenseUsage',
|
||||
uselist=False, # This makes it one-to-one
|
||||
back_populates='license_period',
|
||||
cascade='all, delete-orphan')
|
||||
payments = db.relationship('Payment', back_populates='license_period')
|
||||
invoices = db.relationship('Invoice', back_populates='license_period',
|
||||
cascade='all, delete-orphan')
|
||||
|
||||
def update_allowance(self, allowance_type, allow_value, user_id=None):
|
||||
"""
|
||||
Update an allowance flag within a period
|
||||
Only allows transitioning from False to True
|
||||
|
||||
Args:
|
||||
allowance_type: One of 'storage', 'embedding', or 'interaction'
|
||||
allow_value: The new value (must be True)
|
||||
user_id: User ID performing the update
|
||||
|
||||
Raises:
|
||||
ValueError: If trying to change from True to False, or invalid allowance type
|
||||
"""
|
||||
field_name = f"additional_{allowance_type}_allowed"
|
||||
|
||||
# Verify valid field
|
||||
if not hasattr(self, field_name):
|
||||
raise ValueError(f"Invalid allowance type: {allowance_type}")
|
||||
|
||||
# Get current value
|
||||
current_value = getattr(self, field_name)
|
||||
|
||||
# Only allow False -> 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'<LicensePeriod {self.id}: License {self.license_id}, Period {self.period_number}>'
|
||||
|
||||
|
||||
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'<Payment {self.id}: {self.payment_type} {self.amount} {self.currency}>'
|
||||
|
||||
|
||||
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'<Invoice {self.invoice_number}: {self.amount} {self.currency}>'
|
||||
|
||||
|
||||
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'<LicenseChangeLog: {self.license_id} {self.field_name} {self.old_value} -> {self.new_value}>'
|
||||
|
||||
license = db.relationship('License', back_populates='usages')
|
||||
|
||||
|
||||
|
||||
9
common/services/entitlements/__init__.py
Normal file
9
common/services/entitlements/__init__.py
Normal file
@@ -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'
|
||||
]
|
||||
223
common/services/entitlements/license_period_services.py
Normal file
223
common/services/entitlements/license_period_services.py
Normal file
@@ -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
|
||||
@@ -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"""
|
||||
143
common/services/entitlements/license_usage_services.py
Normal file
143
common/services/entitlements/license_usage_services.py
Normal file
@@ -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
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
5
common/services/user/__init__.py
Normal file
5
common/services/user/__init__.py
Normal file
@@ -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']
|
||||
BIN
common/utils/.DS_Store
vendored
BIN
common/utils/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
|
||||
|
||||
|
||||
102
common/utils/cache/license_cache.py
vendored
Normal file
102
common/utils/cache/license_cache.py
vendored
Normal file
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
|
||||
44
common/utils/dynamic_field_utils.py
Normal file
44
common/utils/dynamic_field_utils.py
Normal file
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
46
common/utils/mail_utils.py
Normal file
46
common/utils/mail_utils.py
Normal file
@@ -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)
|
||||
@@ -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():
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
config/.DS_Store
vendored
BIN
config/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1
docker/.python-version
Normal file
1
docker/.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.12.7
|
||||
BIN
eveai_app/.DS_Store
vendored
BIN
eveai_app/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
eveai_app/templates/.DS_Store
vendored
BIN
eveai_app/templates/.DS_Store
vendored
Binary file not shown.
BIN
eveai_app/views/.DS_Store
vendored
BIN
eveai_app/views/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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/<int:processor_id>', 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)
|
||||
|
||||
|
||||
BIN
eveai_chat/.DS_Store
vendored
BIN
eveai_chat/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
eveai_chat/views/.DS_Store
vendored
BIN
eveai_chat/views/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
63
integrations/Zapier/eveai_integration/.gitignore
vendored
Normal file
63
integrations/Zapier/eveai_integration/.gitignore
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/
|
||||
dist/
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# environment variables file
|
||||
.env
|
||||
.environment
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
@@ -0,0 +1,20 @@
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
// Use this to make test calls into your app:
|
||||
const App = require('../../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
// read the `.env` file into the environment, if available
|
||||
zapier.tools.env.inject();
|
||||
|
||||
describe('creates.add_document', () => {
|
||||
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
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
/* globals describe, it, expect */
|
||||
|
||||
describe('addition ', () => {
|
||||
it('should work', () => {
|
||||
expect(1 + 1).toEqual(2);
|
||||
});
|
||||
});
|
||||
BIN
nginx/static/.DS_Store
vendored
BIN
nginx/static/.DS_Store
vendored
Binary file not shown.
BIN
nginx/static/assets/.DS_Store
vendored
BIN
nginx/static/assets/.DS_Store
vendored
Binary file not shown.
BIN
nginx/static/assets/img/.DS_Store
vendored
BIN
nginx/static/assets/img/.DS_Store
vendored
Binary file not shown.
BIN
nginx/static/assets/js/.DS_Store
vendored
BIN
nginx/static/assets/js/.DS_Store
vendored
Binary file not shown.
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 387 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 812 KiB |
0
nginx/static/js/eveai-token-manager.js
Executable file
0
nginx/static/js/eveai-token-manager.js
Executable file
55
repopack stuff (deprecated)/.repopackignore
Normal file
55
repopack stuff (deprecated)/.repopackignore
Normal file
@@ -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*
|
||||
BIN
scripts/.DS_Store
vendored
BIN
scripts/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user