- Introduction of Partner Admin role in combination with 'Management Partner' type.

This commit is contained in:
Josako
2025-04-09 09:40:59 +02:00
parent c2c3b01b28
commit f43e79376c
17 changed files with 368 additions and 111 deletions

View File

@@ -144,9 +144,9 @@ class TenantDomain(db.Model):
# Versioning Information # Versioning Information
created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now()) created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
created_by = db.Column(db.Integer, db.ForeignKey(User.id), nullable=False) created_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=False)
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now()) 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(User.id)) updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id'))
def __repr__(self): def __repr__(self):
return f"<TenantDomain {self.id}: {self.domain}>" return f"<TenantDomain {self.id}: {self.domain}>"
@@ -202,13 +202,25 @@ class Partner(db.Model):
tenant = db.relationship('Tenant', backref=db.backref('partner', uselist=False)) tenant = db.relationship('Tenant', backref=db.backref('partner', uselist=False))
def to_dict(self): def to_dict(self):
services_info = []
for service in self.services:
services_info.append({
'id': service.id,
'name': service.name,
'description': service.description,
'type': service.type,
'type_version': service.type_version,
'active': service.active,
'configuration': service.configuration
})
return { return {
'id': self.id, 'id': self.id,
'tenant_id': self.tenant_id, 'tenant_id': self.tenant_id,
'code': self.code, 'code': self.code,
'logo_url': self.logo_url, 'logo_url': self.logo_url,
'active': self.active, 'active': self.active,
'name': self.tenant.name 'name': self.tenant.name,
'services': services_info
} }

View File

View File

@@ -0,0 +1,44 @@
from flask import session
from common.models.user import Partner, Role
# common/services/user_service.py
from common.utils.eveai_exceptions import EveAIRoleAssignmentException
from common.utils.security_utils import current_user_has_role, all_user_roles
class UserService:
@staticmethod
def get_assignable_roles():
"""Retrieves roles that can be assigned to a user depending on the current user logged in,
and the active tenant for the session"""
current_tenant_id = session.get('tenant').get('id', None)
effective_role_names = []
if current_tenant_id:
if current_user_has_role("Super User"):
if current_tenant_id == 1:
effective_role_names.append("Super User")
if session.get('partner'):
effective_role_names.append("Partner Admin")
effective_role_names.append("Tenant Admin")
if current_user_has_role("Tenant Admin"):
effective_role_names.append("Tenant Admin")
if current_user_has_role("Partner Admin"):
effective_role_names.append("Tenant Admin")
if session.get('partner'):
if session.get('partner').get('tenant_id') == current_tenant_id:
effective_role_names.append("Partner Admin")
effective_role_names = list(set(effective_role_names))
effective_roles = [(role.id, role.name) for role in
Role.query.filter(Role.name.in_(effective_role_names)).all()]
return effective_roles
else:
return []
@staticmethod
def validate_role_assignments(role_ids):
"""Validate a set of role assignments, raising exception for first invalid role"""
assignable_roles = UserService.get_assignable_roles()
assignable_role_ids = {role[0] for role in assignable_roles}
role_id_set = set(role_ids)
return role_id_set.issubset(assignable_role_ids)

View File

@@ -147,3 +147,10 @@ class EveAIDoublePartner(EveAIException):
message = f"Tenant with ID '{tenant_id}' is already defined as a Partner." message = f"Tenant with ID '{tenant_id}' is already defined as a Partner."
super().__init__(message, status_code, payload) super().__init__(message, status_code, payload)
class EveAIRoleAssignmentException(EveAIException):
"""Exception raised when a role cannot be assigned due to business rules"""
def __init__(self, message, status_code=403, payload=None):
super().__init__(message, status_code, payload)

View File

@@ -1,7 +1,7 @@
from flask import session, current_app from flask import session, current_app
from sqlalchemy import and_ from sqlalchemy import and_
from common.models.user import Tenant from common.models.user import Tenant, Partner
from common.models.entitlements import License from common.models.entitlements import License
from common.utils.database import Database from common.utils.database import Database
from common.utils.eveai_exceptions import EveAITenantNotFound, EveAITenantInvalid, EveAINoActiveLicense from common.utils.eveai_exceptions import EveAITenantNotFound, EveAITenantInvalid, EveAINoActiveLicense
@@ -13,13 +13,19 @@ def set_tenant_session_data(sender, user, **kwargs):
tenant = Tenant.query.filter_by(id=user.tenant_id).first() tenant = Tenant.query.filter_by(id=user.tenant_id).first()
session['tenant'] = tenant.to_dict() session['tenant'] = tenant.to_dict()
session['default_language'] = tenant.default_language session['default_language'] = tenant.default_language
session['default_llm_model'] = tenant.llm_model partner = Partner.query.filter_by(tenant_id=user.tenant_id).first()
if partner:
session['partner'] = partner.to_dict()
else:
# Remove partner from session if it exists
session.pop('partner', None)
def clear_tenant_session_data(sender, user, **kwargs): def clear_tenant_session_data(sender, user, **kwargs):
session.pop('tenant', None) session.pop('tenant', None)
session.pop('default_language', None) session.pop('default_language', None)
session.pop('default_llm_model', None) session.pop('default_llm_model', None)
session.pop('partner', None)
def is_valid_tenant(tenant_id): def is_valid_tenant(tenant_id):
@@ -40,4 +46,4 @@ def is_valid_tenant(tenant_id):
if not active_license: if not active_license:
raise EveAINoActiveLicense(tenant_id) raise EveAINoActiveLicense(tenant_id)
return True return True

View File

@@ -1,8 +1,10 @@
from flask import current_app, render_template from flask import current_app, render_template
from flask_security import current_user
from flask_mailman import EmailMessage from flask_mailman import EmailMessage
from itsdangerous import URLSafeTimedSerializer from itsdangerous import URLSafeTimedSerializer
import socket import socket
from common.models.user import Role
from common.utils.nginx_utils import prefixed_url_for from common.utils.nginx_utils import prefixed_url_for
@@ -93,3 +95,44 @@ def test_smtp_connection():
except Exception as e: except Exception as e:
current_app.logger.error(f"Failed to connect to SMTP server: {str(e)}") current_app.logger.error(f"Failed to connect to SMTP server: {str(e)}")
return False return False
def get_current_user_roles():
"""Get the roles of the currently authenticated user.
Returns:
List of Role objects or empty list if no user is authenticated
"""
if current_user.is_authenticated:
return current_user.roles
return []
def current_user_has_role(role_name):
"""Check if the current user has the specified role.
Args:
role_name (str): Name of the role to check
Returns:
bool: True if user has the role, False otherwise
"""
if not current_user.is_authenticated:
return False
return any(role.name == role_name for role in current_user.roles)
def current_user_roles():
"""Get the roles of the currently authenticated user.
Returns:
List of Role objects or empty list if no user is authenticated
"""
if current_user.is_authenticated:
return current_user.roles
return []
def all_user_roles():
roles = [(role.id, role.name) for role in Role.query.all()]

View File

@@ -87,16 +87,6 @@ def create_app(config_file=None):
sqlalchemy_logger.setLevel(logging.DEBUG) sqlalchemy_logger.setLevel(logging.DEBUG)
# log_request_middleware(app) # Add this when debugging nginx or another proxy # log_request_middleware(app) # Add this when debugging nginx or another proxy
# Some generic Error Handling Routines
@app.errorhandler(Exception)
def handle_exception(e):
app.logger.error(f"Unhandled Exception: {e}", exc_info=True)
response = {
"message": str(e),
"type": type(e).__name__
}
return jsonify(response), 500
# @app.before_request # @app.before_request
# def before_request(): # def before_request():
# # app.logger.debug(f"Before request - Session ID: {session.sid}") # # app.logger.debug(f"Before request - Session ID: {session.sid}")

View File

@@ -1,5 +1,7 @@
import traceback
import jinja2 import jinja2
from flask import render_template, request, jsonify, redirect, current_app from flask import render_template, request, jsonify, redirect, current_app, flash
from flask_login import current_user from flask_login import current_user
from common.utils.nginx_utils import prefixed_url_for from common.utils.nginx_utils import prefixed_url_for
@@ -41,12 +43,46 @@ def key_error_handler(error):
return render_template('error/generic.html', error_message="An unexpected error occurred"), 500 return render_template('error/generic.html', error_message="An unexpected error occurred"), 500
def attribute_error_handler(error):
"""Handle AttributeError exceptions.
Specifically catches SQLAlchemy relationship errors when string IDs
are used instead of model instances.
"""
error_msg = str(error)
current_app.logger.error(f"AttributeError: {error_msg}")
current_app.logger.error(traceback.format_exc())
# Handle the SQLAlchemy relationship error specifically
if "'str' object has no attribute '_sa_instance_state'" in error_msg:
flash('Database relationship error. Please check your form inputs and try again.', 'error')
return render_template('errors/500.html',
error_type="Relationship Error",
error_details="A string value was provided where a database object was expected."), 500
# Handle other AttributeErrors
flash('An application error occurred. The technical team has been notified.', 'error')
return render_template('errors/500.html',
error_type="Attribute Error",
error_details=error_msg), 500
def general_exception(e):
current_app.logger.error(f"Unhandled Exception: {e}", exc_info=True)
flash('An application error occurred. The technical team has been notified.', 'error')
return render_template('errors/500.html',
error_type=type(e).__name__,
error_details=str(e)), 500
def register_error_handlers(app): def register_error_handlers(app):
app.register_error_handler(404, not_found_error) app.register_error_handler(404, not_found_error)
app.register_error_handler(500, internal_server_error) app.register_error_handler(500, internal_server_error)
app.register_error_handler(401, not_authorised_error) app.register_error_handler(401, not_authorised_error)
app.register_error_handler(403, not_authorised_error) app.register_error_handler(403, not_authorised_error)
app.register_error_handler(KeyError, key_error_handler) app.register_error_handler(KeyError, key_error_handler)
app.register_error_handler(AttributeError, attribute_error_handler)
app.register_error_handler(Exception, general_exception)
@app.errorhandler(jinja2.TemplateNotFound) @app.errorhandler(jinja2.TemplateNotFound)
def template_not_found(error): def template_not_found(error):

View File

@@ -16,6 +16,7 @@
<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="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> <button type="submit" name="action" value="set_session_partner" class="btn btn-primary" onclick="return validateTableSelection('partnersForm')">Set Session Partner</button>
</div> </div>
<button type="submit" name="action" value="create_partner" class="btn btn-success">Register Partner for Tenant</button>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -8,7 +8,6 @@
<!-- Trigger action Form --> <!-- Trigger action Form -->
<form method="POST" action="{{ url_for('administration_bp.handle_trigger_action') }}"> <form method="POST" action="{{ url_for('administration_bp.handle_trigger_action') }}">
<div class="form-group mt-3"> <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> <button type="submit" name="action" value="update_usages" class="btn btn-secondary">Update Usages</button>
</div> </div>
</form> </form>

View File

@@ -9,7 +9,7 @@
{% block content %} {% block content %}
<form method="post"> <form method="post">
{{ form.hidden_tag() }} {{ form.hidden_tag() }}
{% set disabled_fields = ['code'] %} {% set disabled_fields = [] %}
{% set exclude_fields = [] %} {% set exclude_fields = [] %}
{% for field in form %} {% for field in form %}
{{ render_field(field, disabled_fields, exclude_fields) }} {{ render_field(field, disabled_fields, exclude_fields) }}

View File

@@ -41,14 +41,6 @@ def handle_trigger_action():
except Exception as e: except Exception as e:
current_app.logger.error(f"Failed to trigger usage update task: {str(e)}") current_app.logger.error(f"Failed to trigger usage update task: {str(e)}")
flash(f'Failed to trigger usage update: {str(e)}', 'danger') 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')) return redirect(prefixed_url_for('administration_bp.trigger_actions'))
@@ -59,7 +51,8 @@ def edit_partner(partner_id):
partner = Partner.query.get_or_404(partner_id) # This will return a 404 if no partner is found 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) tenant = Tenant.query.get_or_404(partner.tenant_id)
form = EditPartnerForm(obj=partner) form = EditPartnerForm(obj=partner)
form.tenant.data = tenant.name if request.method == 'GET':
form.tenant.data = tenant.name
if form.validate_on_submit(): if form.validate_on_submit():
# Populate the user with form data # Populate the user with form data
@@ -107,6 +100,14 @@ def partners():
@roles_accepted('Super User') @roles_accepted('Super User')
def handle_partner_selection(): def handle_partner_selection():
action = request.form['action'] action = request.form['action']
if action == 'create_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('administration_bp.partners'))
partner_identification = request.form.get('selected_row') partner_identification = request.form.get('selected_row')
partner_id = ast.literal_eval(partner_identification).get('value') partner_id = ast.literal_eval(partner_identification).get('value')
partner = Partner.query.get_or_404(partner_id) partner = Partner.query.get_or_404(partner_id)

View File

@@ -6,6 +6,7 @@ from wtforms.validators import DataRequired, Length, Email, NumberRange, Optiona
import pytz import pytz
from common.models.user import Role from common.models.user import Role
from common.services.user_service import UserService
from config.type_defs.service_types import SERVICE_TYPES from config.type_defs.service_types import SERVICE_TYPES
@@ -54,7 +55,7 @@ class BaseUserForm(FlaskForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(BaseUserForm, self).__init__(*args, **kwargs) super(BaseUserForm, self).__init__(*args, **kwargs)
self.roles.choices = [(role.id, role.name) for role in Role.query.all()] self.roles.choices = UserService.get_assignable_roles()
class CreateUserForm(BaseUserForm): class CreateUserForm(BaseUserForm):

View File

@@ -10,6 +10,7 @@ import ast
from common.models.user import User, Tenant, Role, TenantDomain, TenantProject, Partner from common.models.user import User, Tenant, Role, TenantDomain, TenantProject, Partner
from common.extensions import db, security, minio_client, simple_encryption from common.extensions import db, security, minio_client, simple_encryption
from common.services.user_service import UserService
from common.utils.security_utils import send_confirmation_email, send_reset_email from common.utils.security_utils import send_confirmation_email, send_reset_email
from config.type_defs.service_types import SERVICE_TYPES from config.type_defs.service_types import SERVICE_TYPES
from .user_forms import TenantForm, CreateUserForm, EditUserForm, TenantDomainForm, TenantSelectionForm, \ from .user_forms import TenantForm, CreateUserForm, EditUserForm, TenantDomainForm, TenantSelectionForm, \
@@ -106,7 +107,7 @@ def edit_tenant(tenant_id):
@user_bp.route('/user', methods=['GET', 'POST']) @user_bp.route('/user', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Tenant Admin') @roles_accepted('Super User', 'Tenant Admin', 'Partner Admin')
def user(): def user():
form = CreateUserForm() form = CreateUserForm()
form.tenant_id.data = session.get('tenant').get('id') # It is only possible to create users for the session tenant form.tenant_id.data = session.get('tenant').get('id') # It is only possible to create users for the session tenant
@@ -159,7 +160,7 @@ def user():
@user_bp.route('/user/<int:user_id>', methods=['GET', 'POST']) @user_bp.route('/user/<int:user_id>', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Tenant Admin') @roles_accepted('Super User', 'Tenant Admin', 'Partner Admin')
def edit_user(user_id): def edit_user(user_id):
user = User.query.get_or_404(user_id) # This will return a 404 if no user is found user = User.query.get_or_404(user_id) # This will return a 404 if no user is found
form = EditUserForm(obj=user) form = EditUserForm(obj=user)
@@ -174,16 +175,22 @@ def edit_user(user_id):
# Update roles # Update roles
current_roles = set(role.id for role in user.roles) current_roles = set(role.id for role in user.roles)
selected_roles = set(form.roles.data) selected_roles = set(form.roles.data)
# Add new roles if UserService.validate_role_assignments(selected_roles):
for role_id in selected_roles - current_roles: # Add new roles
role = Role.query.get(role_id) for role_id in selected_roles - current_roles:
if role: role = Role.query.get(role_id)
user.roles.append(role) if role:
# Remove unselected roles user.roles.append(role)
for role_id in current_roles - selected_roles: # Remove unselected roles
role = Role.query.get(role_id) for role_id in current_roles - selected_roles:
if role: role = Role.query.get(role_id)
user.roles.remove(role) if role:
user.roles.remove(role)
else:
flash('Trying to assign unauthorized roles', 'danger')
current_app.logger.error(f"Trying to assign unauthorized roles by user {user_id},"
f"tenant {session['tenant']['id']}")
return redirect(prefixed_url_for('user_bp.edit_user', user_id=user_id))
db.session.commit() db.session.commit()
flash('User updated successfully.', 'success') flash('User updated successfully.', 'success')
@@ -242,14 +249,10 @@ def handle_tenant_selection():
session.pop('catalog_name', None) session.pop('catalog_name', None)
match action: match action:
case 'view_users':
return redirect(prefixed_url_for('user_bp.view_users', tenant_id=tenant_id))
case 'edit_tenant': case 'edit_tenant':
return redirect(prefixed_url_for('user_bp.edit_tenant', tenant_id=tenant_id)) return redirect(prefixed_url_for('user_bp.edit_tenant', tenant_id=tenant_id))
case 'select_tenant': case 'select_tenant':
return redirect(prefixed_url_for('user_bp.tenant_overview')) 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 # Add more conditions for other actions
return redirect(prefixed_url_for('select_tenant')) return redirect(prefixed_url_for('select_tenant'))

View File

@@ -0,0 +1,31 @@
"""Sequence changes
Revision ID: 9ac89fc67661
Revises: 867deef0888b
Create Date: 2025-04-03 13:26:55.480553
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '9ac89fc67661'
down_revision = '867deef0888b'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
tables = ['role', 'tenant', 'user', 'partner', 'tenant_domain', 'tenant_project']
for table in tables:
op.execute(f"ALTER SEQUENCE public.{table}_id_seq RESTART WITH 1000;")
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

View File

@@ -330,11 +330,20 @@ input[type="radio"] {
color: var(--bs-body-color) !important; /* Text color consistent with the theme */ color: var(--bs-body-color) !important; /* Text color consistent with the theme */
} }
.form-control:disabled { /* Style for both disabled and readonly fields - same gray background */
background-color: var(--bs-gray-100) !important; /* Gray background for disabled fields */ .form-control:disabled,
color: var(--bs-gray-600) !important; /* Dimmed text color for disabled fields */ .form-control[readonly] {
background-color: var(--bs-gray-100) !important; /* Gray background */
color: var(--bs-gray-600) !important; /* Dimmed text color */
} }
/* Light orange background for editable fields */
/* TODO
.form-control:not([readonly]):not(:disabled) {
background-color: #ffe4d6 !important;
}
*/
.form-check-input:checked { .form-check-input:checked {
background-color: var(--bs-primary) !important; /* Primary color for checked checkboxes */ background-color: var(--bs-primary) !important; /* Primary color for checked checkboxes */
border-color: var(--bs-primary) !important; /* Primary color for checkbox border */ border-color: var(--bs-primary) !important; /* Primary color for checkbox border */

View File

@@ -1,81 +1,155 @@
from sqlalchemy import text
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from datetime import datetime as dt, timezone as tz from datetime import datetime as dt, timezone as tz
from flask_security import hash_password from flask_security import hash_password
from uuid import uuid4 from uuid import uuid4
from eveai_app import create_app from eveai_app import create_app
from common.extensions import db, security
from common.models.user import User, Tenant, Role, RolesUsers from common.models.user import User, Tenant, Role, RolesUsers
from common.extensions import db, minio_client
from common.utils.database import Database from common.utils.database import Database
from flask_security.utils import hash_password
def initialize_data(): def initialize_data():
app = create_app() """
with app.app_context(): Initialize baseline data for a new EveAI environment.
# Define Initial Tenant
if Tenant.query.first() is None:
print("No tenants found. Creating initial tenant...")
timestamp = dt.now(tz=tz.utc)
tenant = Tenant(name="Jedi",
website="https://askeveai.com",
created_at=timestamp,
updated_at=timestamp)
db.session.add(tenant)
db.session.commit()
else:
print("Tenants already exist. Skipping tenant creation.")
This function creates:
- System roles (IDs 1-999)
- Default tenant (ID 1)
- Admin user (ID 1)
"""
print("Starting data initialization...")
app = create_app()
with app.app_context():
# Step 1: Initialize roles
roles = initialize_roles()
# Step 2: Initialize default tenant
default_tenant = initialize_default_tenant()
# Step 3: Initialize admin user
admin_user = initialize_admin_user(default_tenant)
print("Data initialization completed successfully")
def initialize_roles():
"""Initialize system roles with IDs below 1000"""
print("Initializing system roles...")
# Define system roles - matching the exact IDs and names you specified
roles_data = [
{'id': 1, 'name': 'Super User', 'description': 'System administrator with full access'},
{'id': 2, 'name': 'Tenant Admin', 'description': 'Administrator for a specific tenant'},
{'id': 3, 'name': 'Partner Admin', 'description': 'Partner Administrator, managing different Tenants'},
]
roles = {}
for role_data in roles_data:
role = Role.query.filter_by(name=role_data['name']).first()
if not role:
print(f"Creating role: {role_data['name']} (ID: {role_data['id']})")
role = Role(**role_data)
db.session.add(role)
else:
print(f"Role already exists: {role.name} (ID: {role.id})")
roles[role_data['name']] = role
db.session.commit()
# Verify sequence is set correctly
db.session.execute(text("ALTER SEQUENCE public.role_id_seq RESTART WITH 1000"))
db.session.commit()
return roles
def initialize_default_tenant():
"""Initialize the default system tenant with ID 1"""
print("Initializing default tenant...")
tenant_data = {
'id': 1,
'name': 'Jedi',
'website': 'https://www.askeveai.com',
'timezone': 'UTC',
'default_language': 'en',
'allowed_languages': ['en', 'fr', 'nl', 'de', 'es'],
'llm_model': 'mistral.mistral-large-latest',
'type': 'Active',
'currency': '',
'created_at': dt.now(tz.utc),
'updated_at': dt.now(tz.utc)
}
tenant = db.session.get(Tenant, tenant_data['id'])
if not tenant:
print(f"Creating default tenant: {tenant_data['name']} (ID: {tenant_data['id']})")
tenant = Tenant(**tenant_data)
db.session.add(tenant)
db.session.commit()
# Create tenant schema
print(f"Creating schema for tenant {tenant.id}")
Database(tenant.id).create_tenant_schema() Database(tenant.id).create_tenant_schema()
# Define Roles # Create MinIO bucket
super_user_role = Role.query.filter_by(name="Super User").first() print(f"Creating MinIO bucket for tenant {tenant.id}")
if super_user_role is None: minio_client.create_tenant_bucket(tenant.id)
super_user_role = Role(name="Super User", description="Users allowed to perform all functions") else:
db.session.add(super_user_role) print(f"Default tenant already exists: {tenant.name} (ID: {tenant.id})")
db.session.commit()
tenant_admin_role = Role.query.filter_by(name="Tenant Admin").first()
if tenant_admin_role is None:
tenant_admin_role = Role(name="Tenant Admin", description="Users allowed to manage tenants")
db.session.add(tenant_admin_role)
db.session.commit()
# tenant_tester_role = Role.query.filter_by(name="Tenant Tester").first()
# if tenant_tester_role is None:
# tenant_test_role = Role(name="Tenant Tester", description="Users allowed to test tenants")
# db.session.add(tenant_test_role)
# db.session.commit()
# Check if any users exist return tenant
if User.query.first() is None:
print("No users found. Creating initial user...")
# Ensure tenant exists before creating the user def initialize_admin_user(tenant):
tenant = Tenant.query.filter_by(name="Jedi").first() """Initialize the system admin user with ID 1"""
if tenant: print("Initializing admin user...")
user = User(
user_name="yoda", # Check if admin user already exists
email="yoda@flow-it.net", admin_user = User.query.filter_by(email='yoda@flow-it.net').first()
password=hash_password("Dagobah"),
first_name="Yoda", if not admin_user:
last_name="Skywalker", print("Creating admin user (yoda)")
tenant_id=tenant.id,
created_at=dt.now(tz=tz.utc), # Create a secure password - you can replace this with your preferred default
updated_at=dt.now(tz=tz.utc), password = hash_password('Dagobah')
fs_uniquifier=str(uuid4()),
active=True, # Create the admin user with ID 1
confirmed_at=dt.now(tz=tz.utc) admin_user = User(
) id=1,
db.session.add(user) tenant_id=tenant.id,
db.session.commit() user_name='yoda',
# security.datastore.set_uniquifier() email='yoda@flow-it.net',
print("Initial user created.") password=password,
# Assign SuperUser role to the new user first_name='Yoda',
user_role = RolesUsers(user_id=user.id, role_id=super_user_role.id) last_name='Master',
db.session.add(user_role) active=True,
db.session.commit() fs_uniquifier=str(uuid4()),
print("SuperUser role assigned to the new user.") confirmed_at=dt.now(tz.utc),
else: created_at=dt.now(tz.utc),
print("Failed to find initial tenant for user creation.") updated_at=dt.now(tz.utc)
else: )
print("Users already exist. Skipping user creation.")
db.session.add(admin_user)
db.session.commit()
user_role = RolesUsers(user_id=admin_user.id, role_id=1)
db.session.add(user_role)
db.session.commit()
else:
print(f"Admin user already exists: {admin_user.email} (ID: {admin_user.id})")
return admin_user
if __name__ == "__main__": if __name__ == "__main__":