Introduction of Partner Model, adding code to Tenant model
This commit is contained in:
@@ -20,6 +20,7 @@ class Tenant(db.Model):
|
||||
|
||||
# company Information
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
code = db.Column(db.String(50), unique=True, nullable=True)
|
||||
name = db.Column(db.String(80), unique=True, nullable=False)
|
||||
website = db.Column(db.String(255), nullable=True)
|
||||
timezone = db.Column(db.String(50), nullable=True, default='UTC')
|
||||
@@ -99,6 +100,7 @@ class User(db.Model, UserMixin):
|
||||
|
||||
# User Information
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False)
|
||||
user_name = db.Column(db.String(80), unique=True, nullable=False)
|
||||
email = db.Column(db.String(255), unique=True, nullable=False)
|
||||
password = db.Column(db.String(255), nullable=True)
|
||||
@@ -120,7 +122,6 @@ class User(db.Model, UserMixin):
|
||||
|
||||
# Relations
|
||||
roles = db.relationship('Role', secondary=RolesUsers.__table__, backref=db.backref('users', lazy='dynamic'))
|
||||
tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False)
|
||||
|
||||
def __repr__(self):
|
||||
return '<User %r>' % self.user_name
|
||||
@@ -176,3 +177,91 @@ class TenantProject(db.Model):
|
||||
|
||||
def __repr__(self):
|
||||
return f"<TenantProject {self.id}: {self.name}>"
|
||||
|
||||
|
||||
class Partner(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, unique=True)
|
||||
code = db.Column(db.String(50), unique=True, nullable=False)
|
||||
|
||||
# Basic information
|
||||
logo_url = db.Column(db.String(255), nullable=True)
|
||||
active = db.Column(db.Boolean, default=True)
|
||||
|
||||
# Versioning Information
|
||||
created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
|
||||
created_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)
|
||||
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())
|
||||
updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)
|
||||
|
||||
# Relationships
|
||||
services = db.relationship('PartnerService', back_populates='partner')
|
||||
tenant = db.relationship('Tenant', backref=db.backref('partner', uselist=False))
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'tenant_id': self.tenant_id,
|
||||
'code': self.code,
|
||||
'logo_url': self.logo_url,
|
||||
'active': self.active,
|
||||
'name': self.tenant.name
|
||||
}
|
||||
|
||||
|
||||
class PartnerService(db.Model):
|
||||
__bind_key__ = 'public'
|
||||
__table_args__ = {'schema': 'public'}
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
partner_id = db.Column(db.Integer, db.ForeignKey('public.partner.id'), nullable=False)
|
||||
|
||||
# Basic info
|
||||
name = db.Column(db.String(50), nullable=False)
|
||||
description = db.Column(db.Text, nullable=True)
|
||||
|
||||
# Service type with versioning (similar to your specialist/retriever pattern)
|
||||
type = db.Column(db.String(50), nullable=False) # REFERRAL, KNOWLEDGE, SPECIALIST, IMPLEMENTATION, WHITE_LABEL
|
||||
type_version = db.Column(db.String(20), nullable=False, default="1.0.0")
|
||||
|
||||
# Status
|
||||
active = db.Column(db.Boolean, default=True)
|
||||
|
||||
# Dynamic configuration specific to this service - using JSONB like your other models
|
||||
configuration = db.Column(db.JSON, nullable=True)
|
||||
|
||||
# For services that need to track shared resources
|
||||
system_metadata = db.Column(db.JSON, nullable=True)
|
||||
user_metadata = db.Column(db.JSON, nullable=True)
|
||||
|
||||
# Relationships
|
||||
partner = db.relationship('Partner', back_populates='services')
|
||||
|
||||
# Versioning Information
|
||||
created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
|
||||
created_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)
|
||||
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())
|
||||
updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)
|
||||
|
||||
|
||||
class PartnerTenant(db.Model):
|
||||
__bind_key__ = 'public'
|
||||
__table_args__ = {'schema': 'public'}
|
||||
|
||||
partner_service_id = db.Column(db.Integer, db.ForeignKey('public.partner_service.id'), primary_key=True)
|
||||
tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), primary_key=True)
|
||||
|
||||
# Relationship type
|
||||
relationship_type = db.Column(db.String(20), nullable=False) # REFERRED, MANAGED, WHITE_LABEL
|
||||
|
||||
# JSONB for flexible configuration specific to this relationship
|
||||
configuration = db.Column(db.JSON, nullable=True)
|
||||
|
||||
# Tracking
|
||||
created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
|
||||
created_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)
|
||||
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())
|
||||
updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)
|
||||
|
||||
14
common/utils/cache/config_cache.py
vendored
14
common/utils/cache/config_cache.py
vendored
@@ -7,7 +7,7 @@ from flask import current_app
|
||||
|
||||
from common.utils.cache.base import CacheHandler, CacheKey
|
||||
from config.type_defs import agent_types, task_types, tool_types, specialist_types, retriever_types, prompt_types, \
|
||||
catalog_types
|
||||
catalog_types, partner_service_types
|
||||
|
||||
|
||||
def is_major_minor(version: str) -> bool:
|
||||
@@ -422,7 +422,6 @@ PromptConfigCacheHandler, PromptConfigVersionTreeCacheHandler, PromptConfigTypes
|
||||
config_type='prompts',
|
||||
config_dir='config/prompts',
|
||||
types_module=prompt_types.PROMPT_TYPES
|
||||
|
||||
))
|
||||
|
||||
CatalogConfigCacheHandler, CatalogConfigVersionTreeCacheHandler, CatalogConfigTypesCacheHandler = (
|
||||
@@ -430,7 +429,14 @@ CatalogConfigCacheHandler, CatalogConfigVersionTreeCacheHandler, CatalogConfigTy
|
||||
config_type='catalogs',
|
||||
config_dir='config/catalogs',
|
||||
types_module=catalog_types.CATALOG_TYPES
|
||||
))
|
||||
|
||||
# Add to common/utils/cache/config_cache.py
|
||||
PartnerServiceConfigCacheHandler, PartnerServiceConfigVersionTreeCacheHandler, PartnerServiceConfigTypesCacheHandler = (
|
||||
create_config_cache_handlers(
|
||||
config_type='partner_services',
|
||||
config_dir='config/partner_services',
|
||||
types_module=partner_service_types.PARTNER_SERVICE_TYPES
|
||||
))
|
||||
|
||||
|
||||
@@ -459,6 +465,9 @@ def register_config_cache_handlers(cache_manager) -> None:
|
||||
cache_manager.register_handler(AgentConfigCacheHandler, 'eveai_config')
|
||||
cache_manager.register_handler(AgentConfigTypesCacheHandler, 'eveai_config')
|
||||
cache_manager.register_handler(AgentConfigVersionTreeCacheHandler, 'eveai_config')
|
||||
cache_manager.register_handler(PartnerServiceConfigCacheHandler, 'eveai_config')
|
||||
cache_manager.register_handler(PartnerServiceConfigTypesCacheHandler, 'eveai_config')
|
||||
cache_manager.register_handler(PartnerServiceConfigVersionTreeCacheHandler, 'eveai_config')
|
||||
|
||||
cache_manager.agents_config_cache.set_version_tree_cache(cache_manager.agents_version_tree_cache)
|
||||
cache_manager.tasks_config_cache.set_version_tree_cache(cache_manager.tasks_version_tree_cache)
|
||||
@@ -466,3 +475,4 @@ def register_config_cache_handlers(cache_manager) -> None:
|
||||
cache_manager.specialists_config_cache.set_version_tree_cache(cache_manager.specialists_version_tree_cache)
|
||||
cache_manager.retrievers_config_cache.set_version_tree_cache(cache_manager.retrievers_version_tree_cache)
|
||||
cache_manager.prompts_config_cache.set_version_tree_cache(cache_manager.prompts_version_tree_cache)
|
||||
cache_manager.partner_services_config_cache.set_version_tree_cache(cache_manager.partner_services_version_tree_cache)
|
||||
|
||||
@@ -136,3 +136,14 @@ class EveAIInvalidEmbeddingModel(EveAIException):
|
||||
# Construct the message dynamically
|
||||
message = f"Tenant with ID '{tenant_id}' has no or an invalid embedding model in Catalog {catalog_id}."
|
||||
super().__init__(message, status_code, payload)
|
||||
|
||||
|
||||
class EveAIDoublePartner(EveAIException):
|
||||
"""Raised when there is already a partner defined for a given tenant (while registering a partner)"""
|
||||
|
||||
def __init__(self, tenant_id, status_code=400, payload=None):
|
||||
self.tenant_id = tenant_id
|
||||
# Construct the message dynamically
|
||||
message = f"Tenant with ID '{tenant_id}' is already defined as a Partner."
|
||||
super().__init__(message, status_code, payload)
|
||||
|
||||
|
||||
11
common/utils/form_assistants.py
Normal file
11
common/utils/form_assistants.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import json
|
||||
|
||||
from wtforms.validators import ValidationError
|
||||
|
||||
|
||||
def validate_json(form, field):
|
||||
if field.data:
|
||||
try:
|
||||
json.loads(field.data)
|
||||
except json.JSONDecodeError:
|
||||
raise ValidationError('Invalid JSON format')
|
||||
57
common/utils/log_utils.py
Normal file
57
common/utils/log_utils.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import pandas as pd
|
||||
from sqlalchemy import inspect
|
||||
from typing import Any, List, Union, Optional
|
||||
|
||||
|
||||
def format_query_results(query_results: Any) -> str:
|
||||
"""
|
||||
Format query results as a readable string using pandas
|
||||
|
||||
Args:
|
||||
query_results: SQLAlchemy query, query results, or model instance(s)
|
||||
|
||||
Returns:
|
||||
Formatted string representation of the query results
|
||||
"""
|
||||
try:
|
||||
# If it's a query object, execute it
|
||||
if hasattr(query_results, 'all'):
|
||||
results = query_results.all()
|
||||
elif not isinstance(query_results, list):
|
||||
results = [query_results]
|
||||
else:
|
||||
results = query_results
|
||||
|
||||
# Handle different types of results
|
||||
if results and hasattr(results[0], '__table__'):
|
||||
# SQLAlchemy ORM objects
|
||||
data = []
|
||||
for item in results:
|
||||
row = {}
|
||||
for column in inspect(item).mapper.column_attrs:
|
||||
row[column.key] = getattr(item, column.key)
|
||||
data.append(row)
|
||||
df = pd.DataFrame(data)
|
||||
elif results and isinstance(results[0], tuple):
|
||||
# Join query results (tuples)
|
||||
if hasattr(results[0], '_fields'): # Named tuples
|
||||
df = pd.DataFrame(results)
|
||||
else:
|
||||
# Regular tuples - try to get column names from query
|
||||
if hasattr(query_results, 'statement'):
|
||||
columns = query_results.statement.columns.keys()
|
||||
df = pd.DataFrame(results, columns=columns)
|
||||
else:
|
||||
df = pd.DataFrame(results)
|
||||
else:
|
||||
# Fallback for other types
|
||||
df = pd.DataFrame(results)
|
||||
|
||||
# Format the output with pandas
|
||||
with pd.option_context('display.max_rows', 20, 'display.max_columns', None,
|
||||
'display.width', 1000):
|
||||
formatted_output = f"Query returned {len(df)} results:\n{df}"
|
||||
|
||||
return formatted_output
|
||||
except Exception as e:
|
||||
return f"Error formatting query results: {str(e)}"
|
||||
@@ -44,4 +44,5 @@ def perform_startup_invalidation(app):
|
||||
except Exception as e:
|
||||
app.logger.error(f"Error during startup invalidation: {e}")
|
||||
# In case of error, we don't want to block the application startup
|
||||
pass
|
||||
pass
|
||||
|
||||
|
||||
15
config/partner_services/MANAGEMENT_SERVICE/1.0.0.yaml
Normal file
15
config/partner_services/MANAGEMENT_SERVICE/1.0.0.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
version: "1.0.0"
|
||||
name: "Management Service"
|
||||
configuration:
|
||||
admin_roles:
|
||||
name: "Administrative Roles"
|
||||
type: "enum"
|
||||
description: "Administrative Roles that can be assigned to the partner"
|
||||
required: true
|
||||
allowed_values: ["Partner Admin"]
|
||||
default: "Partner Admin"
|
||||
metadata:
|
||||
author: "Josako"
|
||||
date_added: "2025-04-02"
|
||||
changes: "Initial version"
|
||||
description: "Initial definition of the management service"
|
||||
24
config/type_defs/partner_service_types.py
Normal file
24
config/type_defs/partner_service_types.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# config/type_defs/partner_service_types.py
|
||||
PARTNER_SERVICE_TYPES = {
|
||||
"REFERRAL_SERVICE": {
|
||||
"name": "Referral Service",
|
||||
"description": "Partner referring new customers",
|
||||
},
|
||||
"KNOWLEDGE_SERVICE": {
|
||||
"name": "Knowledge Service",
|
||||
"description": "Partner providing catalog content",
|
||||
},
|
||||
"SPECIALIST_SERVICE": {
|
||||
"name": "Specialist Service",
|
||||
"description": "Partner providing specialist solutions",
|
||||
},
|
||||
"MANAGEMENT_SERVICE": {
|
||||
"name": "Management Service",
|
||||
"description": "Partner managing customer instances",
|
||||
},
|
||||
"WHITE_LABEL_SERVICE": {
|
||||
"name": "White Label Service",
|
||||
"description": "Partner reselling under their own brand",
|
||||
}
|
||||
}
|
||||
|
||||
18
eveai_app/templates/administration/edit_partner.html
Normal file
18
eveai_app/templates/administration/edit_partner.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "macros.html" import render_field %}
|
||||
{% block title %}Update Partner{% endblock %}
|
||||
|
||||
{% block content_title %}Update Partner{% endblock %}
|
||||
{% block content_description %}Update partner{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{{ form.hidden_tag() }}
|
||||
{% set disabled_fields = ['tenant', 'code'] %}
|
||||
{% set exclude_fields = [] %}
|
||||
{% for field in form %}
|
||||
{{ render_field(field, disabled_fields, exclude_fields) }}
|
||||
{% endfor %}
|
||||
<button type="submit" class="btn btn-primary">Update Partner</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
18
eveai_app/templates/administration/edit_partner_service.html
Normal file
18
eveai_app/templates/administration/edit_partner_service.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "macros.html" import render_field %}
|
||||
{% block title %}Register Partner Service{% endblock %}
|
||||
|
||||
{% block content_title %}Register Partner Service{% endblock %}
|
||||
{% block content_description %}Register Partner Service{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{{ form.hidden_tag() }}
|
||||
{% set disabled_fields = ['type'] %}
|
||||
{% set exclude_fields = [] %}
|
||||
{% for field in form %}
|
||||
{{ render_field(field, disabled_fields, exclude_fields) }}
|
||||
{% endfor %}
|
||||
<button type="submit" class="btn btn-primary">Register Partner Service</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
18
eveai_app/templates/administration/partner_service.html
Normal file
18
eveai_app/templates/administration/partner_service.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "macros.html" import render_field %}
|
||||
{% block title %}Register Partner Service{% endblock %}
|
||||
|
||||
{% block content_title %}Register Partner Service{% endblock %}
|
||||
{% block content_description %}Register Partner Service{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{{ form.hidden_tag() }}
|
||||
{% set disabled_fields = [] %}
|
||||
{% set exclude_fields = [] %}
|
||||
{% for field in form %}
|
||||
{{ render_field(field, disabled_fields, exclude_fields) }}
|
||||
{% endfor %}
|
||||
<button type="submit" class="btn btn-primary">Register Partner Service</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
26
eveai_app/templates/administration/partner_services.html
Normal file
26
eveai_app/templates/administration/partner_services.html
Normal file
@@ -0,0 +1,26 @@
|
||||
{% extends 'base.html' %}
|
||||
{% from 'macros.html' import render_selectable_table, render_pagination %}
|
||||
|
||||
{% block title %}Partner Services{% endblock %}
|
||||
|
||||
{% block content_title %}Partner Services{% endblock %}
|
||||
{% block content_description %}View Partner Services for active Partner{% endblock %}
|
||||
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<form method="POST" action="{{ url_for('administration_bp.handle_partner_service_selection') }}" id="partnerServicesForm">
|
||||
{{ render_selectable_table(headers=["Partner Service ID", "Name", "Type"], rows=rows, selectable=True, id="retrieversTable") }}
|
||||
<div class="form-group mt-3 d-flex justify-content-between">
|
||||
<div>
|
||||
<button type="submit" name="action" value="edit_partner_service" class="btn btn-primary" onclick="return validateTableSelection('partnerServicesForm')">Edit Partner Service</button>
|
||||
</div>
|
||||
<button type="submit" name="action" value="create_partner_service" class="btn btn-success">Register Partner Service</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content_footer %}
|
||||
{{ render_pagination(pagination, 'document_bp.retrievers') }}
|
||||
{% endblock %}
|
||||
26
eveai_app/templates/administration/partners.html
Normal file
26
eveai_app/templates/administration/partners.html
Normal file
@@ -0,0 +1,26 @@
|
||||
{% extends 'base.html' %}
|
||||
{% from 'macros.html' import render_selectable_table, render_pagination %}
|
||||
|
||||
{% block title %}Partners{% endblock %}
|
||||
|
||||
{% block content_title %}Partners{% endblock %}
|
||||
{% block content_description %}View Partners{% endblock %}
|
||||
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<form method="POST" action="{{ url_for('administration_bp.handle_partner_selection') }}" id="partnersForm">
|
||||
{{ render_selectable_table(headers=["Partner ID", "Name"], rows=rows, selectable=True, id="partnersTable") }}
|
||||
<div class="form-group mt-3 d-flex justify-content-between">
|
||||
<div>
|
||||
<button type="submit" name="action" value="edit_partner" class="btn btn-primary" onclick="return validateTableSelection('partnersForm')">Edit Partner</button>
|
||||
<button type="submit" name="action" value="set_session_partner" class="btn btn-primary" onclick="return validateTableSelection('partnersForm')">Set Session Partner</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content_footer %}
|
||||
{{ render_pagination(pagination, 'document_bp.retrievers') }}
|
||||
{% endblock %}
|
||||
@@ -8,6 +8,7 @@
|
||||
<!-- Trigger action Form -->
|
||||
<form method="POST" action="{{ url_for('administration_bp.handle_trigger_action') }}">
|
||||
<div class="form-group mt-3">
|
||||
<button type="submit" name="action" value="register_partner" class="btn btn-secondary">Register Partner</button>
|
||||
<button type="submit" name="action" value="update_usages" class="btn btn-secondary">Update Usages</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{{ form.hidden_tag() }}
|
||||
{% set disabled_fields = ['user_name', 'user_email', 'tenant_name'] %}
|
||||
{% set disabled_fields = ['user_name', 'user_email', 'tenant_name', 'partner_name'] %}
|
||||
{% set exclude_fields = [] %}
|
||||
{% for field in form %}
|
||||
{{ render_field(field, disabled_fields, exclude_fields) }}
|
||||
|
||||
@@ -101,6 +101,8 @@
|
||||
{'name': 'Trigger Actions', 'url': '/administration/trigger_actions', 'roles': ['Super User']},
|
||||
{'name': 'Licenses', 'url': '/entitlements/view_licenses', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'Usage', 'url': '/entitlements/view_usages', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'Partners', 'url': '/administration/partners', 'roles': ['Super User']},
|
||||
{'name': 'Partner Services', 'url': '/administration/partner_services', 'roles': ['Super User']},
|
||||
]) }}
|
||||
{% endif %}
|
||||
{% if current_user.is_authenticated %}
|
||||
@@ -123,6 +125,13 @@
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% if current_user.has_roles('Super User') and 'partner' in session %}
|
||||
<li class="nav-item mt-2">
|
||||
<a href="/session_defaults" class="btn btn-sm bg-gradient-success mb-0">
|
||||
PARTNER {{ session['partner'].get('id', 'None') }}: {{ session['partner'].get('name', 'None') }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{{ form.hidden_tag() }}
|
||||
{% set disabled_fields = ['name', 'llm_model'] %}
|
||||
{% set disabled_fields = ['name', 'code', 'llm_model'] %}
|
||||
{% set exclude_fields = [] %}
|
||||
{% for field in form %}
|
||||
{{ render_field(field, disabled_fields, exclude_fields) }}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{{ form.hidden_tag() }}
|
||||
{% set disabled_fields = [] %}
|
||||
{% set disabled_fields = ['code'] %}
|
||||
{% set exclude_fields = [] %}
|
||||
{% for field in form %}
|
||||
{{ render_field(field, disabled_fields, exclude_fields) }}
|
||||
|
||||
@@ -1,7 +1,46 @@
|
||||
from flask import current_app
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms.fields.simple import SubmitField
|
||||
from wtforms.fields.choices import SelectField
|
||||
from wtforms.fields.simple import SubmitField, StringField, BooleanField, TextAreaField
|
||||
from wtforms.validators import DataRequired, Optional, Length
|
||||
|
||||
from common.extensions import cache_manager
|
||||
from common.utils.form_assistants import validate_json
|
||||
from eveai_app.views.dynamic_form_base import DynamicFormBase
|
||||
|
||||
|
||||
class TriggerActionForm(FlaskForm):
|
||||
submit = SubmitField('Submit')
|
||||
|
||||
|
||||
class EditPartnerForm(FlaskForm):
|
||||
tenant = StringField('Tenant', validators=[DataRequired()], render_kw={'readonly': True})
|
||||
code = StringField('Code', description="Referral Code", validators=[DataRequired()], render_kw={'readonly': True})
|
||||
logo_url = StringField('Logo URL', validators=[Optional(), Length(max=255)])
|
||||
active = BooleanField('Active', validators=[DataRequired()], default=True)
|
||||
|
||||
|
||||
class PartnerServiceForm(FlaskForm):
|
||||
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
|
||||
description = TextAreaField('Description', validators=[Optional()])
|
||||
type = SelectField('Partner Service Type', validators=[DataRequired()])
|
||||
active = BooleanField('Active', validators=[DataRequired()], default=True)
|
||||
|
||||
user_metadata = TextAreaField('User Metadata', validators=[Optional(), validate_json])
|
||||
system_metadata = TextAreaField('System Metadata', validators=[Optional(), validate_json])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Dynamically populate the 'type' field
|
||||
types_dict = cache_manager.partner_services_types_cache.get_types()
|
||||
self.type.choices = [(key, value['name']) for key, value in types_dict.items()]
|
||||
|
||||
|
||||
class EditPartnerServiceForm(DynamicFormBase):
|
||||
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
|
||||
description = TextAreaField('Description', validators=[Optional()])
|
||||
type = StringField('Partner Service Type', validators=[DataRequired()], render_kw={'readonly': True})
|
||||
active = BooleanField('Active', validators=[DataRequired()], default=True)
|
||||
|
||||
user_metadata = TextAreaField('User Metadata', validators=[Optional(), validate_json])
|
||||
system_metadata = TextAreaField('System Metadata', validators=[Optional(), validate_json])
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import ast
|
||||
import uuid
|
||||
from datetime import datetime as dt, timezone as tz
|
||||
from flask import request, redirect, flash, render_template, Blueprint, session, current_app, jsonify
|
||||
@@ -5,10 +6,15 @@ from flask_security import hash_password, roles_required, roles_accepted, curren
|
||||
from itsdangerous import URLSafeTimedSerializer
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from common.extensions import db, cache_manager
|
||||
from common.models.user import Partner, Tenant, PartnerService
|
||||
from common.utils.celery_utils import current_celery
|
||||
from common.utils.eveai_exceptions import EveAIException
|
||||
from common.utils.log_utils import format_query_results
|
||||
from common.utils.model_logging_utils import update_logging_information, set_logging_information
|
||||
from common.utils.view_assistants import prepare_table_for_macro, form_validation_failed
|
||||
from common.utils.nginx_utils import prefixed_url_for
|
||||
from .administration_forms import TriggerActionForm
|
||||
from .administration_forms import TriggerActionForm, EditPartnerForm, PartnerServiceForm, EditPartnerServiceForm
|
||||
|
||||
administration_bp = Blueprint('administration_bp', __name__, url_prefix='/administration')
|
||||
|
||||
@@ -35,5 +41,215 @@ def handle_trigger_action():
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Failed to trigger usage update task: {str(e)}")
|
||||
flash(f'Failed to trigger usage update: {str(e)}', 'danger')
|
||||
case 'register_partner':
|
||||
try:
|
||||
partner_id = register_partner_from_tenant(session['tenant']['id'])
|
||||
return redirect(prefixed_url_for('administration_bp.edit_partner', partner_id=partner_id, ))
|
||||
except EveAIException as e:
|
||||
current_app.logger.error(f'Error registering partner for tenant {session['tenant']['id']}: {str(e)}')
|
||||
flash('Error Registering Partner for Selected Tenant', 'danger')
|
||||
return redirect(prefixed_url_for('user_bp.select_tenant'))
|
||||
|
||||
return redirect(prefixed_url_for('administration_bp.trigger_actions'))
|
||||
|
||||
|
||||
@administration_bp.route('/partner/<int:partner_id>', methods=['GET', 'POST'])
|
||||
@roles_accepted('Super User')
|
||||
def edit_partner(partner_id):
|
||||
partner = Partner.query.get_or_404(partner_id) # This will return a 404 if no partner is found
|
||||
tenant = Tenant.query.get_or_404(partner.tenant_id)
|
||||
form = EditPartnerForm(obj=partner)
|
||||
form.tenant.data = tenant.name
|
||||
|
||||
if form.validate_on_submit():
|
||||
# Populate the user with form data
|
||||
form.populate_obj(partner)
|
||||
update_logging_information(partner, dt.now(tz.utc))
|
||||
db.session.commit()
|
||||
flash('Partner updated successfully.', 'success')
|
||||
return redirect(
|
||||
prefixed_url_for('administration_bp.edit_partner',
|
||||
partner_id=partner.id)) # Assuming there's a user profile view to redirect to
|
||||
else:
|
||||
form_validation_failed(request, form)
|
||||
|
||||
return render_template('administration/edit_partner.html', form=form, partner_id=partner_id)
|
||||
|
||||
|
||||
@administration_bp.route('/partners', methods=['GET', 'POST'])
|
||||
@roles_accepted('Super User')
|
||||
def partners():
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 10, type=int)
|
||||
|
||||
query = (db.session.query(
|
||||
Partner.id,
|
||||
Partner.code,
|
||||
Partner.active,
|
||||
Partner.logo_url,
|
||||
# Include all needed Partner columns here
|
||||
Tenant.name.label('name')
|
||||
).join(Tenant, Partner.tenant_id == Tenant.id).order_by(Partner.id))
|
||||
|
||||
current_app.logger.debug(f'{format_query_results(query)}')
|
||||
|
||||
pagination = query.paginate(page=page, per_page=per_page)
|
||||
the_partners = pagination.items
|
||||
|
||||
# prepare table data
|
||||
rows = prepare_table_for_macro(the_partners, [('id', ''), ('name', '')])
|
||||
|
||||
# Render the catalogs in a template
|
||||
return render_template('administration/partners.html', rows=rows, pagination=pagination)
|
||||
|
||||
|
||||
@administration_bp.route('/handle_partner_selection', methods=['POST'])
|
||||
@roles_accepted('Super User')
|
||||
def handle_partner_selection():
|
||||
action = request.form['action']
|
||||
partner_identification = request.form.get('selected_row')
|
||||
partner_id = ast.literal_eval(partner_identification).get('value')
|
||||
partner = Partner.query.get_or_404(partner_id)
|
||||
|
||||
if action == 'set_session_partner':
|
||||
current_app.logger.info(f"Setting session partner: {partner.id}")
|
||||
session['partner'] = partner.to_dict()
|
||||
elif action == 'edit_partner':
|
||||
return redirect(prefixed_url_for('administration_bp.edit_partner', partner_id=partner_id))
|
||||
|
||||
return redirect(prefixed_url_for('administration_bp.partners'))
|
||||
|
||||
|
||||
@administration_bp.route('/partner_service', methods=['GET', 'POST'])
|
||||
@roles_accepted('Super User')
|
||||
def partner_service():
|
||||
form = PartnerServiceForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
partner_id = session.get('partner_id', None)
|
||||
if not partner_id:
|
||||
flash('No partner has been selected. Set partner before adding services.', 'warning')
|
||||
return redirect(prefixed_url_for('administration_bp.partners'))
|
||||
new_partner_service = PartnerService()
|
||||
form.populate_obj(new_partner_service)
|
||||
set_logging_information(new_partner_service, dt.now(tz.utc))
|
||||
new_partner_service.partner_id = partner_id
|
||||
|
||||
try:
|
||||
db.session.add(new_partner_service)
|
||||
db.session.commit()
|
||||
flash('Partner Service successfully added!', 'success')
|
||||
current_app.logger.info(f"Partner Service {new_partner_service.name} added successfully for {partner_id}")
|
||||
# Step 2 of the creation process (depending on type)
|
||||
return redirect(prefixed_url_for('administration_bp.partner_service',
|
||||
partner_service_id=new_partner_service.id))
|
||||
except SQLAlchemyError as e:
|
||||
db.session.rollback()
|
||||
flash(f'Failed to add Partner Service: {str(e)}', 'danger')
|
||||
current_app.logger.error(f"Failed to add Partner Service {new_partner_service.name} "
|
||||
f"for partner {partner_id}. Error: {str(e)}")
|
||||
return render_template('administration/partner_service.html', form=form)
|
||||
|
||||
|
||||
@administration_bp.route('/partner_service/<int:partner_service_id>', methods=['GET', 'POST'])
|
||||
@roles_accepted('Super User')
|
||||
def edit_partner_service(partner_service_id):
|
||||
partner_service = PartnerService.query.get_or_404(partner_service_id)
|
||||
partner_id = session.get('partner_id', None)
|
||||
|
||||
form = EditPartnerServiceForm(obj=partner_service)
|
||||
partner_service_config = cache_manager.partner_services_config_cache.get_config(partner_service.type,
|
||||
partner_service.type_version)
|
||||
configuration_config = partner_service_config.get('configuration')
|
||||
current_app.logger.debug(f"Configuration config for {partner_service.type} {partner_service.type_version}: "
|
||||
f"{configuration_config}")
|
||||
form.add_dynamic_fields("configuration", configuration_config, partner_service.configuration)
|
||||
|
||||
if form.validate_on_submit():
|
||||
form.populate_obj(partner_service)
|
||||
partner_service.configuration = form.get_dynamic_data('configuration')
|
||||
|
||||
# update partner relationship
|
||||
partner_service.partner_id = partner_id
|
||||
|
||||
update_logging_information(partner_service, dt.now(tz.utc))
|
||||
|
||||
try:
|
||||
db.session.add(partner_service)
|
||||
db.session.commit()
|
||||
flash('Partner Service updated successfully.', 'success')
|
||||
current_app.logger.info(f"Partner Service {partner_service.name} updated successfully! ")
|
||||
except SQLAlchemyError as e:
|
||||
db.session.rollback()
|
||||
flash(f'Failed to update Partner Service: {str(e)}', 'danger')
|
||||
current_app.logger.error(f"Failed to update Partner Service {partner_service.id} for partner {partner_id}. "
|
||||
f"Error: {str(e)} ")
|
||||
return render_template('administration/edit_partner_service.html', form=form,
|
||||
partner_service_id=partner_service_id)
|
||||
|
||||
return redirect(prefixed_url_for('administration_bp.partner_services'))
|
||||
else:
|
||||
form_validation_failed(request, form)
|
||||
|
||||
return render_template('administration/edit_partner_service.html', form=form,
|
||||
partner_service_id=partner_service_id)
|
||||
|
||||
|
||||
@administration_bp.route('/partner_services', methods=['GET', 'POST'])
|
||||
@roles_accepted('Super User')
|
||||
def partner_services():
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 10, type=int)
|
||||
partner_id = session.get('partner_id', None)
|
||||
if not partner_id:
|
||||
flash('No partner has been selected. Set partner before adding services.', 'warning')
|
||||
return redirect(prefixed_url_for('administration_bp.partners'))
|
||||
|
||||
query = PartnerService.query.filter(PartnerService.partner_id == partner_id)
|
||||
|
||||
pagination = query.paginate(page=page, per_page=per_page)
|
||||
the_partner_services = pagination.items
|
||||
|
||||
# prepare table data
|
||||
rows = prepare_table_for_macro(the_partner_services, [('id', ''), ('name', ''), ('type', '')])
|
||||
|
||||
return render_template('administration/partner_services.html', rows=rows, pagination=pagination)
|
||||
|
||||
|
||||
@administration_bp.route('/handle_partner_service_selection', methods=['POST'])
|
||||
@roles_accepted('Super User')
|
||||
def handle_partner_service_selection():
|
||||
action = request.form['action']
|
||||
if action == 'create_partner_service':
|
||||
return redirect(prefixed_url_for('administration_bp.partner_service'))
|
||||
|
||||
partner_service_identification = request.form.get('selected_row')
|
||||
partner_service_id = ast.literal_eval(partner_service_identification).get('value')
|
||||
|
||||
if action == 'edit_partner_service':
|
||||
return redirect(prefixed_url_for('administration_bp.edit_partner_service',
|
||||
partner_service_id=partner_service_id))
|
||||
|
||||
return redirect(prefixed_url_for('administration_bp.partner_services'))
|
||||
|
||||
|
||||
def register_partner_from_tenant(tenant_id):
|
||||
# check if there is already a partner defined for the tenant
|
||||
partner = Partner.query.filter_by(tenant_id=tenant_id).first()
|
||||
if partner:
|
||||
return partner.id
|
||||
|
||||
try:
|
||||
partner = Partner(
|
||||
tenant_id=tenant_id,
|
||||
code=f"PART-{str(uuid.uuid4())}",
|
||||
)
|
||||
set_logging_information(partner, dt.now(tz.utc))
|
||||
db.session.add(partner)
|
||||
db.session.commit()
|
||||
return partner.id
|
||||
except SQLAlchemyError as e:
|
||||
db.session.rollback()
|
||||
raise EveAIException(f"Failed to register partner for tenant {tenant_id}. Error: {str(e)}")
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ class SessionDefaultsForm(FlaskForm):
|
||||
tenant_name = StringField('Tenant Name', validators=[DataRequired()])
|
||||
default_language = SelectField('Default Language', choices=[], validators=[DataRequired()])
|
||||
|
||||
# Partner Defaults
|
||||
partner_name = StringField('Partner Name', validators=[DataRequired()])
|
||||
|
||||
# Default Catalog - initialize as a regular SelectField
|
||||
catalog = SelectField('Catalog', choices=[], validators=[Optional()])
|
||||
|
||||
@@ -28,6 +31,10 @@ class SessionDefaultsForm(FlaskForm):
|
||||
self.user_name.data = current_user.user_name
|
||||
self.user_email.data = current_user.email
|
||||
self.tenant_name.data = session.get('tenant').get('name')
|
||||
if session.get('partner', None):
|
||||
self.partner_name.data = session.get('partner').get('name')
|
||||
else:
|
||||
self.partner_name.data = ""
|
||||
self.default_language.choices = [(lang, lang.lower()) for lang in
|
||||
session.get('tenant').get('allowed_languages')]
|
||||
self.default_language.data = session.get('default_language')
|
||||
|
||||
@@ -10,21 +10,13 @@ from wtforms_sqlalchemy.fields import QuerySelectField
|
||||
|
||||
from common.extensions import cache_manager
|
||||
from common.models.document import Catalog
|
||||
from common.utils.form_assistants import validate_json
|
||||
|
||||
from config.type_defs.catalog_types import CATALOG_TYPES
|
||||
from config.type_defs.processor_types import PROCESSOR_TYPES
|
||||
from config.type_defs.retriever_types import RETRIEVER_TYPES
|
||||
from .dynamic_form_base import DynamicFormBase
|
||||
|
||||
|
||||
def validate_json(form, field):
|
||||
if field.data:
|
||||
try:
|
||||
json.loads(field.data)
|
||||
except json.JSONDecodeError:
|
||||
raise ValidationError('Invalid JSON format')
|
||||
|
||||
|
||||
class CatalogForm(FlaskForm):
|
||||
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
|
||||
description = TextAreaField('Description', validators=[Optional()])
|
||||
|
||||
@@ -29,7 +29,6 @@ from common.utils.view_assistants import form_validation_failed, prepare_table_f
|
||||
from .document_list_view import DocumentListView
|
||||
from .document_version_list_view import DocumentVersionListView
|
||||
from config.type_defs.catalog_types import CATALOG_TYPES
|
||||
from config.type_defs.retriever_types import RETRIEVER_TYPES
|
||||
|
||||
document_bp = Blueprint('document_bp', __name__, url_prefix='/document')
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ class DynamicFormBase(FlaskForm):
|
||||
|
||||
# Required validator
|
||||
if field_def.get('required', False):
|
||||
validators_list.append(validators.InputRequired())
|
||||
validators_list.append(validators.DataRequired())
|
||||
else:
|
||||
validators_list.append(validators.Optional())
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from config.type_defs.service_types import SERVICE_TYPES
|
||||
|
||||
class TenantForm(FlaskForm):
|
||||
name = StringField('Name', validators=[DataRequired(), Length(max=80)])
|
||||
code = StringField('Code', validators=[DataRequired()], render_kw={'readonly': True})
|
||||
type = SelectField('Tenant Type', validators=[Optional()], default='Active')
|
||||
website = StringField('Website', validators=[DataRequired(), Length(max=255)])
|
||||
# language fields
|
||||
@@ -125,3 +126,6 @@ class EditTenantProjectForm(FlaskForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Initialize choices for the services field
|
||||
self.services.choices = [(key, value['description']) for key, value in SERVICE_TYPES.items()]
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from itsdangerous import URLSafeTimedSerializer
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
import ast
|
||||
|
||||
from common.models.user import User, Tenant, Role, TenantDomain, TenantProject
|
||||
from common.models.user import User, Tenant, Role, TenantDomain, TenantProject, Partner
|
||||
from common.extensions import db, security, minio_client, simple_encryption
|
||||
from common.utils.security_utils import send_confirmation_email, send_reset_email
|
||||
from config.type_defs.service_types import SERVICE_TYPES
|
||||
@@ -18,6 +18,8 @@ from common.utils.database import Database
|
||||
from common.utils.view_assistants import prepare_table_for_macro, form_validation_failed
|
||||
from common.utils.simple_encryption import generate_api_key
|
||||
from common.utils.nginx_utils import prefixed_url_for
|
||||
from common.utils.eveai_exceptions import EveAIDoublePartner, EveAIException
|
||||
from common.utils.document_utils import set_logging_information, update_logging_information
|
||||
|
||||
user_bp = Blueprint('user_bp', __name__, url_prefix='/user')
|
||||
|
||||
@@ -36,6 +38,10 @@ def log_after_request(response):
|
||||
@roles_required('Super User')
|
||||
def tenant():
|
||||
form = TenantForm()
|
||||
if request.method == 'GET':
|
||||
code = f"TENANT-{str(uuid.uuid4())}"
|
||||
form.code.data = code
|
||||
|
||||
if form.validate_on_submit():
|
||||
# Handle the required attributes
|
||||
new_tenant = Tenant()
|
||||
@@ -244,6 +250,7 @@ def handle_tenant_selection():
|
||||
return redirect(prefixed_url_for('user_bp.tenant_overview'))
|
||||
case 'new_tenant':
|
||||
return redirect(prefixed_url_for('user_bp.tenant'))
|
||||
|
||||
# Add more conditions for other actions
|
||||
return redirect(prefixed_url_for('select_tenant'))
|
||||
|
||||
|
||||
BIN
migrations/.DS_Store
vendored
BIN
migrations/.DS_Store
vendored
Binary file not shown.
BIN
migrations/public/.DS_Store
vendored
BIN
migrations/public/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,82 @@
|
||||
"""Initialize empty Tenant codes
|
||||
|
||||
Revision ID: 867deef0888b
|
||||
Revises: cab899dbb213
|
||||
Create Date: 2025-04-03 09:01:20.446536
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import uuid
|
||||
from sqlalchemy.sql import table, column, select, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '867deef0888b'
|
||||
down_revision = 'cab899dbb213'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# Create a reference to the tenant table
|
||||
tenant_table = table('tenant',
|
||||
column('id', sa.Integer),
|
||||
column('code', sa.String(50)),
|
||||
schema='public' # Assuming the table is in the 'public' schema
|
||||
)
|
||||
|
||||
# Get a connection
|
||||
connection = op.get_bind()
|
||||
session = Session(bind=connection)
|
||||
|
||||
try:
|
||||
# Find all tenants with empty or null code
|
||||
# Note the updated select syntax for SQLAlchemy 2.0
|
||||
query = select(tenant_table.c.id).where(
|
||||
or_(
|
||||
tenant_table.c.code == None,
|
||||
tenant_table.c.code == ''
|
||||
)
|
||||
)
|
||||
|
||||
results = connection.execute(query)
|
||||
|
||||
# Update each tenant with a UUID-based code
|
||||
for row in results:
|
||||
tenant_id = row[0]
|
||||
code = f"TENANT-{str(uuid.uuid4())}"
|
||||
|
||||
# Update the tenant record
|
||||
update_stmt = tenant_table.update().where(
|
||||
tenant_table.c.id == tenant_id
|
||||
).values(
|
||||
code=code
|
||||
)
|
||||
|
||||
connection.execute(update_stmt)
|
||||
|
||||
# Commit changes
|
||||
session.commit()
|
||||
|
||||
# Log how many records were updated
|
||||
count_query = select(sa.func.count()).select_from(tenant_table).where(
|
||||
tenant_table.c.code.like('TENANT-%')
|
||||
)
|
||||
|
||||
updated_count = connection.execute(count_query).scalar()
|
||||
|
||||
print(f"Updated {updated_count} tenant records with UUID-based codes")
|
||||
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
print(f"Error updating tenant codes: {str(e)}")
|
||||
raise e
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
def downgrade():
|
||||
# No downgrade needed for this data migration
|
||||
pass
|
||||
@@ -0,0 +1,86 @@
|
||||
"""Add Partner Models
|
||||
|
||||
Revision ID: 98adf66ce189
|
||||
Revises: 03a1e7633c01
|
||||
Create Date: 2025-03-31 14:43:06.833648
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '98adf66ce189'
|
||||
down_revision = '03a1e7633c01'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('partner',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('code', sa.String(length=50), nullable=False),
|
||||
sa.Column('logo_url', sa.String(length=255), nullable=True),
|
||||
sa.Column('active', sa.Boolean(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('created_by', sa.Integer(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('updated_by', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['created_by'], ['public.user.id'], ),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['public.tenant.id'], ),
|
||||
sa.ForeignKeyConstraint(['updated_by'], ['public.user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('code'),
|
||||
sa.UniqueConstraint('tenant_id'),
|
||||
schema='public'
|
||||
)
|
||||
op.create_table('partner_service',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('partner_id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=50), nullable=False),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('type', sa.String(length=50), nullable=False),
|
||||
sa.Column('type_version', sa.String(length=20), nullable=False),
|
||||
sa.Column('active', sa.Boolean(), nullable=True),
|
||||
sa.Column('configuration', sa.JSON(), nullable=True),
|
||||
sa.Column('system_metadata', sa.JSON(), nullable=True),
|
||||
sa.Column('user_metadata', sa.JSON(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('created_by', sa.Integer(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('updated_by', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['created_by'], ['public.user.id'], ),
|
||||
sa.ForeignKeyConstraint(['partner_id'], ['public.partner.id'], ),
|
||||
sa.ForeignKeyConstraint(['updated_by'], ['public.user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
schema='public'
|
||||
)
|
||||
op.create_table('partner_tenant',
|
||||
sa.Column('partner_service_id', sa.Integer(), nullable=False),
|
||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||||
sa.Column('relationship_type', sa.String(length=20), nullable=False),
|
||||
sa.Column('configuration', sa.JSON(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('created_by', sa.Integer(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('updated_by', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['created_by'], ['public.user.id'], ),
|
||||
sa.ForeignKeyConstraint(['partner_service_id'], ['public.partner_service.id'], ),
|
||||
sa.ForeignKeyConstraint(['tenant_id'], ['public.tenant.id'], ),
|
||||
sa.ForeignKeyConstraint(['updated_by'], ['public.user.id'], ),
|
||||
sa.PrimaryKeyConstraint('partner_service_id', 'tenant_id'),
|
||||
schema='public'
|
||||
)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
|
||||
op.drop_table('partner_tenant', schema='public')
|
||||
op.drop_table('partner_service', schema='public')
|
||||
op.drop_table('partner', schema='public')
|
||||
# ### end Alembic commands ###
|
||||
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.
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.
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.
@@ -0,0 +1,34 @@
|
||||
"""Add code to Tenant
|
||||
|
||||
Revision ID: cab899dbb213
|
||||
Revises: 98adf66ce189
|
||||
Create Date: 2025-04-02 16:08:06.597183
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'cab899dbb213'
|
||||
down_revision = '98adf66ce189'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('tenant', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('code', sa.String(length=50), nullable=True))
|
||||
batch_op.create_unique_constraint(None, ['code'])
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('tenant', schema=None) as batch_op:
|
||||
batch_op.drop_constraint(None, type_='unique')
|
||||
batch_op.drop_column('code')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
BIN
migrations/tenant/.DS_Store
vendored
BIN
migrations/tenant/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
BIN
migrations/tenant/versions/.DS_Store
vendored
BIN
migrations/tenant/versions/.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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -89,4 +89,5 @@ sseclient~=0.0.27
|
||||
termcolor~=2.5.0
|
||||
mistral-common~=1.5.3
|
||||
mistralai~=1.5.0
|
||||
contextvars~=2.4
|
||||
contextvars~=2.4
|
||||
pandas~=2.2.3
|
||||
Reference in New Issue
Block a user