Finished schema creation
Added Navbar functionality Added header
This commit is contained in:
@@ -2,6 +2,7 @@ import os
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
from .extensions import db, migrate, bcrypt, bootstrap, jwt
|
from .extensions import db, migrate, bcrypt, bootstrap, jwt
|
||||||
from .models.user import User, Tenant
|
from .models.user import User, Tenant
|
||||||
|
from .models.document import Document, DocumentLanguage, DocumentVersion
|
||||||
|
|
||||||
|
|
||||||
def create_app(config_file=None):
|
def create_app(config_file=None):
|
||||||
|
|||||||
@@ -1,37 +1,38 @@
|
|||||||
from ..extensions import db
|
from ..extensions import db
|
||||||
|
from .user import User, Tenant
|
||||||
|
|
||||||
|
|
||||||
class Document(db.Model):
|
class Document(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
name = db.Column(db.String(100), nullable=False)
|
name = db.Column(db.String(100), nullable=False)
|
||||||
tenant_id = db.Column(db.Integer, db.ForeignKey('tenant.id'), nullable=False)
|
tenant_id = db.Column(db.Integer, db.ForeignKey(Tenant.id), nullable=False)
|
||||||
valid_from = db.Column(db.DateTime, nullable=True)
|
valid_from = db.Column(db.DateTime, nullable=True)
|
||||||
valid_to = db.Column(db.DateTime, nullable=True)
|
valid_to = db.Column(db.DateTime, nullable=True)
|
||||||
|
|
||||||
# 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(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(User.id))
|
||||||
|
|
||||||
|
|
||||||
class DocumentLanguage(db.Model):
|
class DocumentLanguage(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
document_id = db.Column(db.Integer, db.ForeignKey('document.id'), nullable=False)
|
document_id = db.Column(db.Integer, db.ForeignKey(Document.id), nullable=False)
|
||||||
language = db.Column(db.String(2), nullable=False)
|
language = db.Column(db.String(2), nullable=False)
|
||||||
|
|
||||||
# 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(User.id), nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class DocumentVersion(db.Model):
|
class DocumentVersion(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
doc_lang_id = db.Column(db.Integer, db.ForeignKey('document_language.id'), nullable=False)
|
doc_lang_id = db.Column(db.Integer, db.ForeignKey(DocumentLanguage.id), nullable=False)
|
||||||
url = db.Column(db.String(200), nullable=True)
|
url = db.Column(db.String(200), nullable=True)
|
||||||
embeddings = db.Column(db.PickleType, nullable=True)
|
embeddings = db.Column(db.PickleType, nullable=True)
|
||||||
|
|
||||||
# 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'))
|
created_by = db.Column(db.Integer, db.ForeignKey(User.id))
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,8 @@
|
|||||||
|
|
||||||
<body class="presentation-page bg-gray-200">
|
<body class="presentation-page bg-gray-200">
|
||||||
{% include 'navbar.html' %}
|
{% include 'navbar.html' %}
|
||||||
{% with messages = get_flashed_messages()%}
|
{% include 'header.html' %}
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
{% if messages%}
|
{% if messages%}
|
||||||
{% for message in messages%}
|
{% for message in messages%}
|
||||||
<p>{{message}}</p>
|
<p>{{message}}</p>
|
||||||
|
|||||||
13
eveai_app/templates/header.html
Normal file
13
eveai_app/templates/header.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<header class="header-2">
|
||||||
|
<div class="page-header min-vh-75" style="background-image: url({{url_for('static', filename='/assets/img/EveAI_bg1.jpg')}})" loading="lazy">
|
||||||
|
<span class="mask bg-gradient-primary opacity-4"></span>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-7 text-center mx-auto">
|
||||||
|
<h1 class="text-white pt-3 mt-n5">EveAI Virtual Assistant</h1>
|
||||||
|
<p class="lead text-white mt-3 px-5">Enhance Customer Interaction with AI</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
@@ -1,59 +1,46 @@
|
|||||||
<!-- Navbar Light -->
|
<!-- Navbar Light -->
|
||||||
<nav
|
<div class="navbar navbar-expand-lg navbar-light bg-white z-index-3 py-3">
|
||||||
class="navbar navbar-expand-lg navbar-light bg-white z-index-3 py-3">
|
<div class="container position-sticky z-index-sticky top-0">
|
||||||
<div class="container">
|
<div class="row">
|
||||||
<a class="navbar-brand" href="" rel="tooltip" title="Generated by Kobe & Pieter @ Flow IT" data-placement="bottom" target="_blank">
|
<div class="col-12">
|
||||||
|
<nav class="navbar navbar-expand-lg blur border-radius-xl top-0 z-index-fixed shadow position-absolute my-3 py-2 start-0 end-0 mx-4">
|
||||||
|
<div class="container-fluid px-0">
|
||||||
|
<a class="navbar-brand font-weight-bolder ms-sm-3 d-none d-md-block" href="https://www.flow-it.net" rel="tooltip" title="Idee Generated & Implemented by Flow IT" data-placement="bottom">
|
||||||
EveAI
|
EveAI
|
||||||
</a>
|
</a>
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navigation" aria-controls="navigation" aria-expanded="false" aria-label="Toggle navigation">
|
</div>
|
||||||
<span class="navbar-toggler-icon"></span>
|
<div class="collapse navbar-collapse w-100 pt-3 pb-2 py-lg-0" id="navigation">
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navigation">
|
|
||||||
<ul class="navbar-nav navbar-nav-hover mx-auto">
|
<ul class="navbar-nav navbar-nav-hover mx-auto">
|
||||||
<li class="nav-item px-3">
|
<li class="nav-item dropdown dropdown-hover mx-2">
|
||||||
<a class="nav-link">
|
<a role="button" class="nav-link ps-2 d-flex cursor-pointer align-items-center" id="dropdownMenuUser" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="material-icons opacity-6 me-2 text-md">contacts</i>
|
||||||
User Mgmt
|
User Mgmt
|
||||||
|
<img src="{{url_for('static', filename='/assets/img/down-arrow-dark.svg')}}" alt="down-arrow" class="arrow ms-2 d-lg-block d-none">
|
||||||
|
<img src="{{url_for('static', filename='/assets/img/down-arrow-dark.svg')}}" alt="down-arrow" class="arrow ms-1 d-lg-none d-block ms-auto">
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
<div class="dropdown-menu dropdown-menu-animation ms-n3 dropdown-md p-3 border-radius-xl mt-0 mt-lg-3" aria-labelledby="dropdownMenuPages">
|
||||||
<li>
|
<div class="d-none d-lg-flex">
|
||||||
<a class="dropdown-item" href="/user/tenant">
|
<ul class="list-group w-100">
|
||||||
Tenant Registration
|
<li class="nav-item dropdown dropdown-hover dropdown-subitem list-group-item border-0 p-0">
|
||||||
|
<a class="dropdown-item ps-3 border-radius-md mb-1" href="/user/tenant">
|
||||||
|
<span>Tenant Registration</span>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item ps-3 border-radius-md mb-1" href="/user/user">
|
||||||
|
<span>User Registration</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<a class="dropdown-item" href="/user/user">
|
|
||||||
User Registration
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item px-3">
|
|
||||||
<a class="nav-link">
|
|
||||||
Document Mgmt
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item px-3">
|
|
||||||
<a class="nav-link">
|
|
||||||
Interaction Mgmt
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item px-3">
|
|
||||||
<a class="nav-link ">
|
|
||||||
Login
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<ul class="navbar-nav ms-auto">
|
|
||||||
<button class="btn bg-gradient-primary mb-0">Buy Now</button>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- End Navbar -->
|
<!-- End Navbar -->
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
89
eveai_app/utils/database.py
Normal file
89
eveai_app/utils/database.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
"""Database related functions"""
|
||||||
|
from os import popen
|
||||||
|
from sqlalchemy import text
|
||||||
|
from sqlalchemy.schema import CreateSchema
|
||||||
|
from sqlalchemy.exc import InternalError
|
||||||
|
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||||
|
from flask_migrate import heads
|
||||||
|
|
||||||
|
from ..extensions import db, migrate
|
||||||
|
|
||||||
|
|
||||||
|
class Database:
|
||||||
|
"""used for managing tenant databases related operations"""
|
||||||
|
|
||||||
|
def __init__(self, tenant: str) -> None:
|
||||||
|
self.schema = str(tenant)
|
||||||
|
|
||||||
|
def get_engine(self):
|
||||||
|
"""create new schema engine"""
|
||||||
|
return db.engine.execution_options(
|
||||||
|
schema_translate_map={None: self.schema}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_session(self):
|
||||||
|
"""To get session of tenant/public database schema for quick use
|
||||||
|
|
||||||
|
returns:
|
||||||
|
session: session of tenant/public database schema
|
||||||
|
"""
|
||||||
|
return scoped_session(
|
||||||
|
sessionmaker(bind=self.get_engine(), expire_on_commit=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_schema(self):
|
||||||
|
"""create new database schema, mostly used on tenant creation"""
|
||||||
|
try:
|
||||||
|
db.session.execute(CreateSchema(self.schema))
|
||||||
|
db.session.commit()
|
||||||
|
except InternalError:
|
||||||
|
db.session.rollback()
|
||||||
|
db.session.close()
|
||||||
|
|
||||||
|
def create_tables(self):
|
||||||
|
"""create tables in for schema"""
|
||||||
|
db.metadata.create_all(self.get_engine())
|
||||||
|
|
||||||
|
def switch_schema(self):
|
||||||
|
"""switch between tenant/public database schema"""
|
||||||
|
db.session.execute(text(f'set search_path to "{self.schema}"'))
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
def migrate_tenant_schema(self):
|
||||||
|
"""migrate tenant database schema for new tenant"""
|
||||||
|
# Get the current revision for a database.
|
||||||
|
# NOTE: using popen may have a minor performance impact on the application
|
||||||
|
# you can store it in a different table in public schema and use it from there
|
||||||
|
# may be a faster approach
|
||||||
|
# last_revision = heads(directory="migrations/tenant", verbose=True, resolve_dependencies=False)
|
||||||
|
last_revision = popen(".venv/bin/flask db heads -d migrations/tenant").read()
|
||||||
|
print("LAST REVISION")
|
||||||
|
print(last_revision)
|
||||||
|
last_revision = last_revision.splitlines()[-1].split(" ")[0]
|
||||||
|
|
||||||
|
# creating revision table in tenant schema
|
||||||
|
session = self.get_session()
|
||||||
|
session.execute(
|
||||||
|
text(
|
||||||
|
f'CREATE TABLE "{self.schema}".alembic_version (version_num '
|
||||||
|
"VARCHAR(32) NOT NULL)"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
# Insert last revision to alembic_version table
|
||||||
|
session.execute(
|
||||||
|
text(
|
||||||
|
f'INSERT INTO "{self.schema}".alembic_version (version_num) '
|
||||||
|
"VALUES (:version)"
|
||||||
|
),
|
||||||
|
{"version": last_revision},
|
||||||
|
)
|
||||||
|
session.commit()
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
def create_tenant_schema(self):
|
||||||
|
"""create tenant used for creating new schema and its tables"""
|
||||||
|
self.create_schema()
|
||||||
|
self.create_tables()
|
||||||
|
self.migrate_tenant_schema()
|
||||||
@@ -4,6 +4,7 @@ from flask import request, redirect, url_for, flash, render_template, Blueprint
|
|||||||
from ..models.user import User, Tenant
|
from ..models.user import User, Tenant
|
||||||
from ..extensions import db, bcrypt
|
from ..extensions import db, bcrypt
|
||||||
from .user_forms import TenantForm, UserForm
|
from .user_forms import TenantForm, UserForm
|
||||||
|
from ..utils.database import Database
|
||||||
|
|
||||||
user_bp = Blueprint('user_bp', __name__, url_prefix='/user')
|
user_bp = Blueprint('user_bp', __name__, url_prefix='/user')
|
||||||
|
|
||||||
@@ -31,9 +32,9 @@ def tenant():
|
|||||||
monthly = request.form.get('allowed_monthly_interactions')
|
monthly = request.form.get('allowed_monthly_interactions')
|
||||||
|
|
||||||
if lic_start != '':
|
if lic_start != '':
|
||||||
new_tenant.license_start_date = dt.strptime(lic_start, '%d-%m-%Y')
|
new_tenant.license_start_date = dt.strptime(lic_start, '%Y-%m-%d')
|
||||||
if lic_end != '':
|
if lic_end != '':
|
||||||
new_tenant.license_end_date = dt.strptime(lic_end, '%d-%m-%Y')
|
new_tenant.license_end_date = dt.strptime(lic_end, '%Y-%m-%d')
|
||||||
if monthly != '':
|
if monthly != '':
|
||||||
new_tenant.allowed_monthly_interactions = int(monthly)
|
new_tenant.allowed_monthly_interactions = int(monthly)
|
||||||
|
|
||||||
@@ -43,13 +44,17 @@ def tenant():
|
|||||||
new_tenant.updated_at = timestamp
|
new_tenant.updated_at = timestamp
|
||||||
|
|
||||||
# Add the new tenant to the database and commit the changes
|
# Add the new tenant to the database and commit the changes
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db.session.add(new_tenant)
|
db.session.add(new_tenant)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error = e.args
|
error = e.args
|
||||||
|
|
||||||
|
# Create schema for new tenant
|
||||||
|
if error is None:
|
||||||
|
print(new_tenant.id)
|
||||||
|
Database(new_tenant.id).create_tenant_schema()
|
||||||
|
|
||||||
flash(error) if error else flash('Tenant added successfully.')
|
flash(error) if error else flash('Tenant added successfully.')
|
||||||
|
|
||||||
form = TenantForm()
|
form = TenantForm()
|
||||||
@@ -81,7 +86,8 @@ def user():
|
|||||||
password_hash = bcrypt.generate_password_hash(password).decode('utf-8')
|
password_hash = bcrypt.generate_password_hash(password).decode('utf-8')
|
||||||
|
|
||||||
# Create new user if there is no error
|
# Create new user if there is no error
|
||||||
new_user = User(user_name=username, email=email, password=password_hash, first_name=first_name, last_name=last_name)
|
new_user = User(user_name=username, email=email, password=password_hash, first_name=first_name,
|
||||||
|
last_name=last_name)
|
||||||
|
|
||||||
# Handle optional attributes
|
# Handle optional attributes
|
||||||
new_user.is_active = bool(request.form.get('is_active'))
|
new_user.is_active = bool(request.form.get('is_active'))
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from logging.config import fileConfig
|
|||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
from alembic import context
|
from alembic import context
|
||||||
|
from sqlalchemy import NullPool, engine_from_config, text
|
||||||
|
|
||||||
from eveai_app.models.user import Tenant
|
from eveai_app.models.user import Tenant
|
||||||
|
|
||||||
@@ -92,6 +93,7 @@ def run_migrations_online():
|
|||||||
)
|
)
|
||||||
|
|
||||||
with connectable.connect() as connection:
|
with connectable.connect() as connection:
|
||||||
|
print(tenants)
|
||||||
for tenant in tenants:
|
for tenant in tenants:
|
||||||
logger.info(f"Migrating tenant: {tenant}")
|
logger.info(f"Migrating tenant: {tenant}")
|
||||||
# set search path on the connection, which ensures that
|
# set search path on the connection, which ensures that
|
||||||
@@ -99,7 +101,7 @@ def run_migrations_online():
|
|||||||
# in terms of this schema by default
|
# in terms of this schema by default
|
||||||
connection.execute(text(f'SET search_path TO "{tenant}"'))
|
connection.execute(text(f'SET search_path TO "{tenant}"'))
|
||||||
# in SQLAlchemy v2+ the search path change needs to be committed
|
# in SQLAlchemy v2+ the search path change needs to be committed
|
||||||
# connection.commit()
|
connection.commit()
|
||||||
|
|
||||||
# make use of non-supported SQLAlchemy attribute to ensure
|
# make use of non-supported SQLAlchemy attribute to ensure
|
||||||
# the dialect reflects tables in terms of the current tenant name
|
# the dialect reflects tables in terms of the current tenant name
|
||||||
|
|||||||
Reference in New Issue
Block a user