Finished schema creation

Added Navbar functionality
Added header
This commit is contained in:
Josako
2024-04-25 07:51:18 +02:00
parent 6de87e1509
commit 396de4e079
8 changed files with 166 additions and 66 deletions

View File

@@ -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):

View File

@@ -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))

View File

@@ -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>

View 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>

View File

@@ -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">
EveAI <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">
</a> <div class="container-fluid px-0">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navigation" aria-controls="navigation" aria-expanded="false" aria-label="Toggle navigation"> <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">
<span class="navbar-toggler-icon"></span> EveAI
</button> </a>
<div class="collapse navbar-collapse" id="navigation"> </div>
<ul class="navbar-nav navbar-nav-hover mx-auto"> <div class="collapse navbar-collapse w-100 pt-3 pb-2 py-lg-0" id="navigation">
<li class="nav-item px-3"> <ul class="navbar-nav navbar-nav-hover mx-auto">
<a class="nav-link"> <li class="nav-item dropdown dropdown-hover mx-2">
User Mgmt <a role="button" class="nav-link ps-2 d-flex cursor-pointer align-items-center" id="dropdownMenuUser" data-bs-toggle="dropdown" aria-expanded="false">
</a> <i class="material-icons opacity-6 me-2 text-md">contacts</i>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton"> User Mgmt
<li> <img src="{{url_for('static', filename='/assets/img/down-arrow-dark.svg')}}" alt="down-arrow" class="arrow ms-2 d-lg-block d-none">
<a class="dropdown-item" href="/user/tenant"> <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">
Tenant Registration </a>
</a> <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">
<li> <ul class="list-group w-100">
<a class="dropdown-item" href="/user/user"> <li class="nav-item dropdown dropdown-hover dropdown-subitem list-group-item border-0 p-0">
User Registration <a class="dropdown-item ps-3 border-radius-md mb-1" href="/user/tenant">
</a> <span>Tenant Registration</span>
</li> </a>
</ul> <a class="dropdown-item ps-3 border-radius-md mb-1" href="/user/user">
</li> <span>User Registration</span>
</a>
<li class="nav-item px-3"> </li>
<a class="nav-link"> </ul>
Document Mgmt </div>
</a> </div>
</li> </li>
</ul>
<li class="nav-item px-3"> </div>
<a class="nav-link"> </nav>
Interaction Mgmt </div>
</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>
</div> </div>
</div> </div>
</nav> </div>
<!-- End Navbar --> <!-- End Navbar -->
</div>

View 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()

View File

@@ -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'))

View File

@@ -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