mirror of
https://github.com/imartinez/privateGPT.git
synced 2025-06-28 16:26:56 +00:00
Added audit logs
This commit is contained in:
parent
59d9413898
commit
493963908d
2
.env
2
.env
@ -4,7 +4,7 @@ ENVIRONMENT=dev
|
||||
DB_HOST=localhost
|
||||
DB_USER=postgres
|
||||
DB_PORT=5432
|
||||
DB_PASSWORD=admin
|
||||
DB_PASSWORD=quick
|
||||
DB_NAME=QuickGpt
|
||||
|
||||
SUPER_ADMIN_EMAIL=superadmin@email.com
|
||||
|
@ -16,6 +16,7 @@ from private_gpt.users.models.subscription import Subscription
|
||||
from private_gpt.users.models.company import Company
|
||||
from private_gpt.users.models.document import Document
|
||||
from private_gpt.users.models.department import Department
|
||||
from private_gpt.users.models.audit import Audit
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
@ -1,116 +0,0 @@
|
||||
"""Create models
|
||||
|
||||
Revision ID: 0aeaf9df35a6
|
||||
Revises:
|
||||
Create Date: 2024-02-20 19:16:15.608391
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '0aeaf9df35a6'
|
||||
down_revision: Union[str, None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('companies',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_companies_id'), 'companies', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_companies_name'), 'companies', ['name'], unique=True)
|
||||
op.create_table('roles',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=100), nullable=True),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_roles_id'), 'roles', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_roles_name'), 'roles', ['name'], unique=False)
|
||||
op.create_table('departments',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(), nullable=True),
|
||||
sa.Column('company_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['company_id'], ['companies.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_departments_id'), 'departments', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_departments_name'), 'departments', ['name'], unique=True)
|
||||
op.create_table('subscriptions',
|
||||
sa.Column('sub_id', sa.Integer(), nullable=False),
|
||||
sa.Column('company_id', sa.Integer(), nullable=True),
|
||||
sa.Column('start_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('end_date', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['company_id'], ['companies.id'], ),
|
||||
sa.PrimaryKeyConstraint('sub_id')
|
||||
)
|
||||
op.create_index(op.f('ix_subscriptions_sub_id'), 'subscriptions', ['sub_id'], unique=False)
|
||||
op.create_table('users',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('email', sa.String(length=225), nullable=False),
|
||||
sa.Column('hashed_password', sa.String(), nullable=False),
|
||||
sa.Column('fullname', sa.String(length=225), nullable=False),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=True),
|
||||
sa.Column('last_login', sa.DateTime(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('company_id', sa.Integer(), nullable=True),
|
||||
sa.Column('department_id', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['company_id'], ['companies.id'], ),
|
||||
sa.ForeignKeyConstraint(['department_id'], ['departments.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('email'),
|
||||
sa.UniqueConstraint('fullname'),
|
||||
sa.UniqueConstraint('fullname', name='unique_username_no_spacing')
|
||||
)
|
||||
op.create_table('document',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('filename', sa.String(length=225), nullable=False),
|
||||
sa.Column('uploaded_by', sa.Integer(), nullable=False),
|
||||
sa.Column('uploaded_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('department_id', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['department_id'], ['departments.id'], ),
|
||||
sa.ForeignKeyConstraint(['uploaded_by'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('filename')
|
||||
)
|
||||
op.create_index(op.f('ix_document_id'), 'document', ['id'], unique=False)
|
||||
op.create_table('user_roles',
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('role_id', sa.Integer(), nullable=False),
|
||||
sa.Column('company_id', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['company_id'], ['companies.id'], ),
|
||||
sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('user_id', 'role_id', 'company_id'),
|
||||
sa.UniqueConstraint('user_id', 'role_id', 'company_id', name='unique_user_role')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('user_roles')
|
||||
op.drop_index(op.f('ix_document_id'), table_name='document')
|
||||
op.drop_table('document')
|
||||
op.drop_table('users')
|
||||
op.drop_index(op.f('ix_subscriptions_sub_id'), table_name='subscriptions')
|
||||
op.drop_table('subscriptions')
|
||||
op.drop_index(op.f('ix_departments_name'), table_name='departments')
|
||||
op.drop_index(op.f('ix_departments_id'), table_name='departments')
|
||||
op.drop_table('departments')
|
||||
op.drop_index(op.f('ix_roles_name'), table_name='roles')
|
||||
op.drop_index(op.f('ix_roles_id'), table_name='roles')
|
||||
op.drop_table('roles')
|
||||
op.drop_index(op.f('ix_companies_name'), table_name='companies')
|
||||
op.drop_index(op.f('ix_companies_id'), table_name='companies')
|
||||
op.drop_table('companies')
|
||||
# ### end Alembic commands ###
|
52
alembic/versions/62c1a29320fc_updated_audit_log.py
Normal file
52
alembic/versions/62c1a29320fc_updated_audit_log.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""Updated audit log
|
||||
|
||||
Revision ID: 62c1a29320fc
|
||||
Revises: b526c0676007
|
||||
Create Date: 2024-02-24 17:27:33.407895
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '62c1a29320fc'
|
||||
down_revision: Union[str, None] = 'b526c0676007'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column('audit', 'timestamp',
|
||||
existing_type=postgresql.TIMESTAMP(),
|
||||
nullable=False)
|
||||
op.alter_column('audit', 'model',
|
||||
existing_type=sa.VARCHAR(),
|
||||
nullable=False)
|
||||
op.alter_column('audit', 'action',
|
||||
existing_type=sa.VARCHAR(),
|
||||
nullable=False)
|
||||
op.drop_column('audit', 'url')
|
||||
op.drop_column('audit', 'model_id')
|
||||
# op.create_unique_constraint('unique_user_role', 'user_roles', ['user_id', 'role_id', 'company_id'])
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
# op.drop_constraint('unique_user_role', 'user_roles', type_='unique')
|
||||
op.add_column('audit', sa.Column('model_id', sa.INTEGER(), autoincrement=False, nullable=True))
|
||||
op.add_column('audit', sa.Column('url', sa.VARCHAR(), autoincrement=False, nullable=True))
|
||||
op.alter_column('audit', 'action',
|
||||
existing_type=sa.VARCHAR(),
|
||||
nullable=True)
|
||||
op.alter_column('audit', 'model',
|
||||
existing_type=sa.VARCHAR(),
|
||||
nullable=True)
|
||||
op.alter_column('audit', 'timestamp',
|
||||
existing_type=postgresql.TIMESTAMP(),
|
||||
nullable=True)
|
||||
# ### end Alembic commands ###
|
@ -1,118 +0,0 @@
|
||||
"""Create model
|
||||
|
||||
Revision ID: 8c4bd1aaf45a
|
||||
Revises:
|
||||
Create Date: 2024-02-22 13:19:29.947241
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '8c4bd1aaf45a'
|
||||
down_revision: Union[str, None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('companies',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_companies_id'), 'companies', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_companies_name'), 'companies', ['name'], unique=True)
|
||||
op.create_table('roles',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=100), nullable=True),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_roles_id'), 'roles', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_roles_name'), 'roles', ['name'], unique=False)
|
||||
op.create_table('departments',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(), nullable=True),
|
||||
sa.Column('company_id', sa.Integer(), nullable=True),
|
||||
sa.Column('total_users', sa.Integer(), nullable=True),
|
||||
sa.Column('total_documents', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['company_id'], ['companies.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_departments_id'), 'departments', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_departments_name'), 'departments', ['name'], unique=True)
|
||||
op.create_table('subscriptions',
|
||||
sa.Column('sub_id', sa.Integer(), nullable=False),
|
||||
sa.Column('company_id', sa.Integer(), nullable=True),
|
||||
sa.Column('start_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('end_date', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['company_id'], ['companies.id'], ),
|
||||
sa.PrimaryKeyConstraint('sub_id')
|
||||
)
|
||||
op.create_index(op.f('ix_subscriptions_sub_id'), 'subscriptions', ['sub_id'], unique=False)
|
||||
op.create_table('users',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('email', sa.String(length=225), nullable=False),
|
||||
sa.Column('hashed_password', sa.String(), nullable=False),
|
||||
sa.Column('fullname', sa.String(length=225), nullable=False),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=True),
|
||||
sa.Column('last_login', sa.DateTime(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('company_id', sa.Integer(), nullable=True),
|
||||
sa.Column('department_id', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['company_id'], ['companies.id'], ),
|
||||
sa.ForeignKeyConstraint(['department_id'], ['departments.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('email'),
|
||||
sa.UniqueConstraint('fullname'),
|
||||
sa.UniqueConstraint('fullname', name='unique_username_no_spacing')
|
||||
)
|
||||
op.create_table('document',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('filename', sa.String(length=225), nullable=False),
|
||||
sa.Column('uploaded_by', sa.Integer(), nullable=False),
|
||||
sa.Column('uploaded_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('department_id', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['department_id'], ['departments.id'], ),
|
||||
sa.ForeignKeyConstraint(['uploaded_by'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('filename')
|
||||
)
|
||||
op.create_index(op.f('ix_document_id'), 'document', ['id'], unique=False)
|
||||
op.create_table('user_roles',
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('role_id', sa.Integer(), nullable=False),
|
||||
sa.Column('company_id', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['company_id'], ['companies.id'], ),
|
||||
sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('user_id', 'role_id', 'company_id'),
|
||||
sa.UniqueConstraint('user_id', 'role_id', 'company_id', name='unique_user_role')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('user_roles')
|
||||
op.drop_index(op.f('ix_document_id'), table_name='document')
|
||||
op.drop_table('document')
|
||||
op.drop_table('users')
|
||||
op.drop_index(op.f('ix_subscriptions_sub_id'), table_name='subscriptions')
|
||||
op.drop_table('subscriptions')
|
||||
op.drop_index(op.f('ix_departments_name'), table_name='departments')
|
||||
op.drop_index(op.f('ix_departments_id'), table_name='departments')
|
||||
op.drop_table('departments')
|
||||
op.drop_index(op.f('ix_roles_name'), table_name='roles')
|
||||
op.drop_index(op.f('ix_roles_id'), table_name='roles')
|
||||
op.drop_table('roles')
|
||||
op.drop_index(op.f('ix_companies_name'), table_name='companies')
|
||||
op.drop_index(op.f('ix_companies_id'), table_name='companies')
|
||||
op.drop_table('companies')
|
||||
# ### end Alembic commands ###
|
50
alembic/versions/b526c0676007_audit_log_update.py
Normal file
50
alembic/versions/b526c0676007_audit_log_update.py
Normal file
@ -0,0 +1,50 @@
|
||||
"""Audit log update
|
||||
|
||||
Revision ID: b526c0676007
|
||||
Revises: eb0a7f182c75
|
||||
Create Date: 2024-02-24 12:00:11.632187
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'b526c0676007'
|
||||
down_revision: Union[str, None] = 'eb0a7f182c75'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('audit', sa.Column('timestamp', sa.DateTime(), nullable=True))
|
||||
op.add_column('audit', sa.Column('user_id', sa.Integer(), nullable=True))
|
||||
op.add_column('audit', sa.Column('model', sa.String(), nullable=True))
|
||||
op.add_column('audit', sa.Column('model_id', sa.Integer(), nullable=True))
|
||||
op.add_column('audit', sa.Column('action', sa.String(), nullable=True))
|
||||
op.add_column('audit', sa.Column('details', postgresql.JSONB(astext_type=sa.Text()), nullable=True))
|
||||
op.create_foreign_key(None, 'audit', 'users', ['user_id'], ['id'])
|
||||
op.drop_column('audit', 'headers')
|
||||
op.drop_column('audit', 'response')
|
||||
op.drop_column('audit', 'method')
|
||||
# op.create_unique_constraint('unique_user_role', 'user_roles', ['user_id', 'role_id', 'company_id'])
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
# op.drop_constraint('unique_user_role', 'user_roles', type_='unique')
|
||||
op.add_column('audit', sa.Column('method', sa.VARCHAR(), autoincrement=False, nullable=True))
|
||||
op.add_column('audit', sa.Column('response', sa.VARCHAR(), autoincrement=False, nullable=True))
|
||||
op.add_column('audit', sa.Column('headers', postgresql.ARRAY(sa.VARCHAR()), autoincrement=False, nullable=True))
|
||||
op.drop_constraint(None, 'audit', type_='foreignkey')
|
||||
op.drop_column('audit', 'details')
|
||||
op.drop_column('audit', 'action')
|
||||
op.drop_column('audit', 'model_id')
|
||||
op.drop_column('audit', 'model')
|
||||
op.drop_column('audit', 'user_id')
|
||||
op.drop_column('audit', 'timestamp')
|
||||
# ### end Alembic commands ###
|
@ -1,8 +1,8 @@
|
||||
"""update department model
|
||||
"""Audit log
|
||||
|
||||
Revision ID: 36beb9b73c64
|
||||
Revises: 0aeaf9df35a6
|
||||
Create Date: 2024-02-21 15:12:07.840057
|
||||
Revision ID: eb0a7f182c75
|
||||
Revises:
|
||||
Create Date: 2024-02-24 11:37:43.713632
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
@ -12,16 +12,23 @@ import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '36beb9b73c64'
|
||||
down_revision: Union[str, None] = '0aeaf9df35a6'
|
||||
revision: str = 'eb0a7f182c75'
|
||||
down_revision: Union[str, None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('departments', sa.Column('total_users', sa.Integer(), nullable=True))
|
||||
op.add_column('departments', sa.Column('total_documents', sa.Integer(), nullable=True))
|
||||
op.create_table('audit',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('url', sa.String(), nullable=True),
|
||||
sa.Column('headers', sa.ARRAY(sa.String()), nullable=True),
|
||||
sa.Column('method', sa.String(), nullable=True),
|
||||
sa.Column('response', sa.String(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_audit_id'), 'audit', ['id'], unique=False)
|
||||
# op.create_unique_constraint('unique_user_role', 'user_roles', ['user_id', 'role_id', 'company_id'])
|
||||
# ### end Alembic commands ###
|
||||
|
||||
@ -29,6 +36,6 @@ def upgrade() -> None:
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
# op.drop_constraint('unique_user_role', 'user_roles', type_='unique')
|
||||
op.drop_column('departments', 'total_documents')
|
||||
op.drop_column('departments', 'total_users')
|
||||
op.drop_index(op.f('ix_audit_id'), table_name='audit')
|
||||
op.drop_table('audit')
|
||||
# ### end Alembic commands ###
|
@ -1,5 +1,10 @@
|
||||
# start a fastapi server with uvicorn
|
||||
|
||||
from datetime import datetime
|
||||
from fastapi.middleware import Middleware
|
||||
from private_gpt.users.db.session import SessionLocal
|
||||
from private_gpt.users.models import Audit, User, Department, Document
|
||||
from private_gpt.users.api.deps import get_audit_logger, get_db
|
||||
import uvicorn
|
||||
|
||||
from private_gpt.main import app
|
||||
@ -10,6 +15,7 @@ from private_gpt.constants import UPLOAD_DIR
|
||||
# Set log_config=None to do not use the uvicorn logging configuration, and
|
||||
# use ours instead. For reference, see below:
|
||||
# https://github.com/tiangolo/fastapi/discussions/7457#discussioncomment-5141108
|
||||
|
||||
app.mount("/static", StaticFiles(directory=UPLOAD_DIR), name="static")
|
||||
|
||||
uvicorn.run(app, host="0.0.0.0", port=settings().server.port, log_config=None)
|
||||
|
@ -131,6 +131,7 @@ def delete_ingested(request: Request, doc_id: str) -> None:
|
||||
def delete_file(
|
||||
request: Request,
|
||||
delete_input: DeleteFilename,
|
||||
log_audit: models.Audit = Depends(deps.get_audit_logger),
|
||||
db: Session = Depends(deps.get_db),
|
||||
current_user: models.User = Security(
|
||||
deps.get_current_user,
|
||||
@ -159,9 +160,12 @@ def delete_file(
|
||||
print("Unable to delete file from the static directory")
|
||||
document = crud.documents.get_by_filename(db,file_name=filename)
|
||||
if document:
|
||||
log_audit(model='Document', action='delete',
|
||||
details={"status": "SUCCESS", "message": f"{filename}' successfully deleted."}, user_id=current_user.id)
|
||||
crud.documents.remove(db=db, id=document.id)
|
||||
return {"status": "SUCCESS", "message": f"{filename}' successfully deleted."}
|
||||
except Exception as e:
|
||||
print(traceback.print_exc())
|
||||
logger.error(
|
||||
f"Unexpected error deleting documents with filename '{filename}': {str(e)}")
|
||||
raise HTTPException(
|
||||
@ -171,6 +175,8 @@ def delete_file(
|
||||
@ingest_router.post("/ingest/file", response_model=IngestResponse, tags=["Ingestion"])
|
||||
def ingest_file(
|
||||
request: Request,
|
||||
log_audit: models.Audit = Depends(deps.get_audit_logger),
|
||||
|
||||
db: Session = Depends(deps.get_db),
|
||||
file: UploadFile = File(...),
|
||||
current_user: models.User = Security(
|
||||
@ -210,12 +216,23 @@ def ingest_file(
|
||||
with open(upload_path, "rb") as f:
|
||||
ingested_documents = service.ingest_bin_data(file.filename, f)
|
||||
logger.info(f"{file.filename} is uploaded by the {current_user.fullname}.")
|
||||
|
||||
return IngestResponse(object="list", model="private-gpt", data=ingested_documents)
|
||||
response = IngestResponse(
|
||||
object="list", model="private-gpt", data=ingested_documents)
|
||||
log_audit(model='Document', action='create',
|
||||
details={
|
||||
'status': '200',
|
||||
'filename': file.filename,
|
||||
'user': current_user.fullname,
|
||||
}, user_id=current_user.id)
|
||||
return response
|
||||
except HTTPException:
|
||||
print(traceback.print_exc())
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
print(traceback.print_exc())
|
||||
log_audit(model='Document', action='create',
|
||||
details={"status": 500, "detail": "Internal Server Error: Unable to ingest file.", }, user_id=current_user.id)
|
||||
logger.error(f"There was an error uploading the file(s): {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@ -225,11 +242,13 @@ def ingest_file(
|
||||
|
||||
async def common_ingest_logic(
|
||||
request: Request,
|
||||
|
||||
db: Session,
|
||||
ocr_file,
|
||||
current_user,
|
||||
):
|
||||
service = request.state.injector.get(IngestService)
|
||||
log_audit: models.Audit = Depends(deps.get_audit_logger)
|
||||
try:
|
||||
with open(ocr_file, 'rb') as file:
|
||||
file_name = Path(ocr_file).name
|
||||
@ -257,6 +276,8 @@ async def common_ingest_logic(
|
||||
f.write(file.read())
|
||||
file.seek(0) # Move the file pointer back to the beginning
|
||||
ingested_documents = service.ingest_bin_data(file_name, file)
|
||||
log_audit(model='Document', action='create',
|
||||
details={'status': 200, 'message': "file uploaded successfully."}, user_id=current_user.id)
|
||||
|
||||
logger.info(
|
||||
f"{file_name} is uploaded by the {current_user.fullname}.")
|
||||
@ -264,10 +285,13 @@ async def common_ingest_logic(
|
||||
return ingested_documents
|
||||
|
||||
except HTTPException:
|
||||
print(traceback.print_exc())
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"There was an error uploading the file(s): {str(e)}")
|
||||
print(traceback.print_exc())
|
||||
log_audit(model='Document', action='create',
|
||||
details={"status": 500, "detail": "Internal Server Error: Unable to ingest file.", }, user_id=current_user.id)
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Internal Server Error: Unable to ingest file.",
|
||||
|
@ -1,3 +1,4 @@
|
||||
from fastapi import Request, Depends, HTTPException
|
||||
import logging
|
||||
from private_gpt.users.core.config import settings
|
||||
from private_gpt.users.constants.role import Role
|
||||
@ -5,7 +6,7 @@ from typing import Union, Any, Generator
|
||||
from datetime import datetime
|
||||
from private_gpt.users import crud, models, schemas
|
||||
from private_gpt.users.db.session import SessionLocal
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi import Depends, HTTPException, Request, status
|
||||
from fastapi.security import OAuth2PasswordBearer, SecurityScopes
|
||||
from private_gpt.users.core.security import (
|
||||
ALGORITHM,
|
||||
@ -13,6 +14,7 @@ from private_gpt.users.core.security import (
|
||||
)
|
||||
from fastapi import Depends, HTTPException, Security, status
|
||||
from jose import jwt
|
||||
from private_gpt.users.utils.audit import log_audit_entry
|
||||
from pydantic import ValidationError
|
||||
from private_gpt.users.constants.role import Role
|
||||
from private_gpt.users.schemas.token import TokenPayload
|
||||
@ -130,3 +132,10 @@ def get_active_subscription(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access Forbidden - No Active Subscription",
|
||||
)
|
||||
|
||||
|
||||
def get_audit_logger(request: Request, db: Session = Depends(get_db)):
|
||||
try:
|
||||
return lambda model, action, details, user_id=None: log_audit_entry(db, model, action, details, user_id)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Error in get_audit_logger: {str(e)}")
|
||||
|
@ -1,4 +1,4 @@
|
||||
from private_gpt.users.api.v1.routers import auth, roles, user_roles, users, subscriptions, companies, departments, documents
|
||||
from private_gpt.users.api.v1.routers import auth, roles, user_roles, users, subscriptions, companies, departments, documents, audits
|
||||
from fastapi import APIRouter
|
||||
|
||||
api_router = APIRouter(prefix="/v1")
|
||||
@ -11,4 +11,5 @@ api_router.include_router(companies.router)
|
||||
api_router.include_router(subscriptions.router)
|
||||
api_router.include_router(departments.router)
|
||||
api_router.include_router(documents.router)
|
||||
api_router.include_router(audits.router)
|
||||
|
||||
|
30
private_gpt/users/api/v1/routers/audits.py
Normal file
30
private_gpt/users/api/v1/routers/audits.py
Normal file
@ -0,0 +1,30 @@
|
||||
from typing import Any, List
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Security
|
||||
|
||||
from private_gpt.users.api import deps
|
||||
from private_gpt.users.constants.role import Role
|
||||
from private_gpt.users import crud, models, schemas
|
||||
|
||||
|
||||
router = APIRouter(prefix="/audit", tags=["Companies"])
|
||||
|
||||
|
||||
@router.get("", response_model=List[schemas.Audit])
|
||||
def list_companies(
|
||||
db: Session = Depends(deps.get_db),
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: models.User = Security(
|
||||
deps.get_current_user,
|
||||
scopes=[Role.SUPER_ADMIN["name"]],
|
||||
),
|
||||
) -> List[schemas.Audit]:
|
||||
"""
|
||||
Retrieve a list of companies with pagination support.
|
||||
"""
|
||||
logs = crud.audit.get_multi(db, skip=skip, limit=limit)
|
||||
return logs
|
@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
LDAP_SERVER = settings.LDAP_SERVER
|
||||
# LDAP_ENABLE = settings.LDAP_ENABLE
|
||||
LDAP_ENABLE = True
|
||||
LDAP_ENABLE = False
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["auth"])
|
||||
|
||||
@ -108,6 +108,7 @@ def ad_user_register(
|
||||
|
||||
@router.post("/login", response_model=schemas.TokenSchema)
|
||||
def login_access_token(
|
||||
log_audit: models.Audit = Depends(deps.get_audit_logger),
|
||||
db: Session = Depends(deps.get_db),
|
||||
form_data: OAuth2PasswordRequestForm = Depends(),
|
||||
) -> Any:
|
||||
@ -168,6 +169,8 @@ def login_access_token(
|
||||
"user": token_payload,
|
||||
"token_type": "bearer",
|
||||
}
|
||||
log_audit(model='User', action='update',
|
||||
details=token_payload, user_id=user.id)
|
||||
return JSONResponse(content=response_dict)
|
||||
|
||||
|
||||
@ -197,6 +200,8 @@ def refresh_access_token(
|
||||
@router.post("/register", response_model=schemas.TokenSchema)
|
||||
def register(
|
||||
*,
|
||||
log_audit: models.Audit = Depends(deps.get_audit_logger),
|
||||
|
||||
db: Session = Depends(deps.get_db),
|
||||
email: str = Body(...),
|
||||
fullname: str = Body(...),
|
||||
@ -218,6 +223,8 @@ def register(
|
||||
|
||||
existing_user = crud.user.get_by_email(db, email=email)
|
||||
if existing_user:
|
||||
log_audit(model='User', action='creation',
|
||||
details={"status": '409', 'detail': "The user with this email already exists!", }, user_id=current_user.id)
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="The user with this email already exists!",
|
||||
@ -245,6 +252,8 @@ def register(
|
||||
)
|
||||
user_role_name = role_name or Role.GUEST["name"]
|
||||
user_role = create_user_role(db, user, user_role_name, company)
|
||||
log_audit(model='user_roles', action='creation',
|
||||
details={"status": '201', 'detail': "User role created successfully.", }, user_id=current_user.id)
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
raise HTTPException(
|
||||
@ -258,4 +267,7 @@ def register(
|
||||
"token_type": "bearer",
|
||||
"password": random_password,
|
||||
}
|
||||
log_audit(model='User', action='creation',
|
||||
details={"status": '201', 'detail': "User created successfully.", }, user_id=current_user.id)
|
||||
|
||||
return JSONResponse(content=response_dict, status_code=status.HTTP_201_CREATED)
|
||||
|
@ -1,4 +1,5 @@
|
||||
from typing import Any, List
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from fastapi.responses import JSONResponse
|
||||
@ -9,28 +10,47 @@ from private_gpt.users.api import deps
|
||||
from private_gpt.users.constants.role import Role
|
||||
from private_gpt.users import crud, models, schemas
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/departments", tags=["Departments"])
|
||||
|
||||
|
||||
@router.get("", response_model=List[schemas.Department])
|
||||
def list_deparments(
|
||||
def log_audit_department(
|
||||
db: Session,
|
||||
current_user: models.User,
|
||||
action: str,
|
||||
details: dict
|
||||
):
|
||||
try:
|
||||
audit_entry = models.Audit(
|
||||
user_id=current_user.id,
|
||||
model='Department',
|
||||
action=action,
|
||||
details=details,
|
||||
)
|
||||
db.add(audit_entry)
|
||||
db.commit()
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
|
||||
|
||||
@router.get("", response_model=list[schemas.Department])
|
||||
def list_departments(
|
||||
db: Session = Depends(deps.get_db),
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: models.User = Security(
|
||||
deps.get_current_user,
|
||||
),
|
||||
) -> List[schemas.Department]:
|
||||
) -> list[schemas.Department]:
|
||||
"""
|
||||
Retrieve a list of department with pagination support.
|
||||
Retrieve a list of departments with pagination support.
|
||||
"""
|
||||
deparments = crud.department.get_multi(db, skip=skip, limit=limit)
|
||||
return deparments
|
||||
departments = crud.department.get_multi(db, skip=skip, limit=limit)
|
||||
return departments
|
||||
|
||||
|
||||
@router.post("/create", response_model=schemas.Department)
|
||||
def create_deparment(
|
||||
def create_department(
|
||||
department_in: schemas.DepartmentCreate,
|
||||
db: Session = Depends(deps.get_db),
|
||||
current_user: models.User = Security(
|
||||
@ -41,18 +61,35 @@ def create_deparment(
|
||||
"""
|
||||
Create a new department
|
||||
"""
|
||||
company_id = current_user.company_id
|
||||
department_create_in = schemas.DepartmentAdminCreate(name=department_in.name, company_id=company_id)
|
||||
department = crud.department.create(db=db, obj_in=department_create_in)
|
||||
department = jsonable_encoder(department)
|
||||
try:
|
||||
company_id = current_user.company_id
|
||||
department_create_in = schemas.DepartmentAdminCreate(
|
||||
name=department_in.name, company_id=company_id)
|
||||
department = crud.department.create(db=db, obj_in=department_create_in)
|
||||
department1 = jsonable_encoder(department)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
content={
|
||||
"message": "Department created successfully",
|
||||
"department": department
|
||||
},
|
||||
)
|
||||
details = {
|
||||
'user_id': current_user.id,
|
||||
'department_id': department.id,
|
||||
'department_name': department.name
|
||||
}
|
||||
|
||||
log_audit_department(db, current_user, 'create', details)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
content={
|
||||
"message": "Department created successfully",
|
||||
"department": department1
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
logger.error(f"Error creating department: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Internal Server Error",
|
||||
)
|
||||
|
||||
|
||||
@router.post("/read", response_model=schemas.Department)
|
||||
@ -67,10 +104,28 @@ def read_department(
|
||||
"""
|
||||
Read a Department by ID
|
||||
"""
|
||||
department = crud.department.get_by_id(db, id=department_id)
|
||||
if department is None:
|
||||
raise HTTPException(status_code=404, detail="department not found")
|
||||
return department
|
||||
try:
|
||||
department = crud.department.get_by_id(db, id=department_id)
|
||||
if department is None:
|
||||
raise HTTPException(status_code=404, detail="Department not found")
|
||||
|
||||
details = {
|
||||
'status': 200,
|
||||
'user_id': current_user.id,
|
||||
'department_id': department.id,
|
||||
'department_name': department.name
|
||||
}
|
||||
|
||||
log_audit_department(db, current_user, 'read', details)
|
||||
|
||||
return department
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
logger.error(f"Error reading department: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Internal Server Error",
|
||||
)
|
||||
|
||||
|
||||
@router.post("/update", response_model=schemas.Department)
|
||||
@ -85,20 +140,40 @@ def update_department(
|
||||
"""
|
||||
Update a Department by ID
|
||||
"""
|
||||
department = crud.department.get_by_id(db, id=department_in.id)
|
||||
if department is None:
|
||||
raise HTTPException(status_code=404, detail="department not found")
|
||||
try:
|
||||
department = crud.department.get_by_id(db, id=department_in.id)
|
||||
old_name = department.name
|
||||
if department is None:
|
||||
raise HTTPException(status_code=404, detail="Department not found")
|
||||
|
||||
updated_department = crud.department.update(
|
||||
db=db, db_obj=department, obj_in=department_in)
|
||||
updated_department = jsonable_encoder(updated_department)
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_200_OK,
|
||||
content={
|
||||
"message": f"{department_in} department updated successfully",
|
||||
"department": updated_department
|
||||
},
|
||||
)
|
||||
updated_department = crud.department.update(
|
||||
db=db, db_obj=department, obj_in=department_in)
|
||||
updated_department = jsonable_encoder(updated_department)
|
||||
|
||||
details = {
|
||||
'status': '200',
|
||||
'user_id': current_user.id,
|
||||
'department_id': department.id,
|
||||
'old_department_name': old_name,
|
||||
'new_department_name': department.name,
|
||||
}
|
||||
|
||||
log_audit_department(db, current_user, 'update', details)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_200_OK,
|
||||
content={
|
||||
"message": f"Department updated successfully",
|
||||
"department": updated_department
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
logger.error(f"Error updating department: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Internal Server Error",
|
||||
)
|
||||
|
||||
|
||||
@router.post("/delete", response_model=schemas.Department)
|
||||
@ -113,19 +188,36 @@ def delete_department(
|
||||
"""
|
||||
Delete a Department by ID
|
||||
"""
|
||||
department_id = department_in.id
|
||||
department = crud.department.get(db, id=department_id)
|
||||
if department is None:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
try:
|
||||
department_id = department_in.id
|
||||
department = crud.department.get(db, id=department_id)
|
||||
if department is None:
|
||||
raise HTTPException(status_code=404, detail="Department not found")
|
||||
|
||||
department = crud.department.remove(db=db, id=department_id)
|
||||
if department is None:
|
||||
raise HTTPException(status_code=404, detail="department not found")
|
||||
department = jsonable_encoder(department)
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_200_OK,
|
||||
content={
|
||||
"message": "Department deleted successfully",
|
||||
"deparment": department,
|
||||
},
|
||||
)
|
||||
details = {
|
||||
'status': 200,
|
||||
'user_id': current_user.id,
|
||||
'department_id': department.id,
|
||||
'department_name': department.name
|
||||
}
|
||||
|
||||
log_audit_department(db, current_user, 'delete', details)
|
||||
|
||||
deleted_department = crud.department.remove(db=db, id=department_id)
|
||||
deleted_department = jsonable_encoder(deleted_department)
|
||||
|
||||
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_200_OK,
|
||||
content={
|
||||
"message": "Department deleted successfully",
|
||||
"department": deleted_department,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
logger.error(f"Error deleting department: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Internal Server Error",
|
||||
)
|
||||
|
@ -1,3 +1,4 @@
|
||||
import traceback
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
@ -14,6 +15,26 @@ from private_gpt.users.core.security import verify_password, get_password_hash
|
||||
|
||||
router = APIRouter(prefix="/users", tags=["users"])
|
||||
|
||||
|
||||
def log_audit_user(
|
||||
db: Session,
|
||||
current_user: models.User,
|
||||
action: str,
|
||||
details: dict
|
||||
):
|
||||
try:
|
||||
audit_entry = models.Audit(
|
||||
user_id=current_user.id,
|
||||
model='User',
|
||||
action=action,
|
||||
details=details,
|
||||
)
|
||||
db.add(audit_entry)
|
||||
db.commit()
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
|
||||
|
||||
@router.get("", response_model=List[schemas.User])
|
||||
def read_users(
|
||||
skip: int = 0,
|
||||
@ -78,6 +99,17 @@ def create_user(
|
||||
detail="The user with this email already exists in the system.",
|
||||
)
|
||||
user = crud.user.create(db, obj_in=user_in)
|
||||
|
||||
details = {
|
||||
'admin_id': current_user.id,
|
||||
'user_id': user.id,
|
||||
'email': user.email,
|
||||
'fullname': user.fullname,
|
||||
'company_id': user.company_id,
|
||||
'department_id': user.department_id,
|
||||
}
|
||||
log_audit_user(db, current_user, 'create', details)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
content={"message": "User created successfully", "user": jsonable_encoder(user)},
|
||||
@ -102,6 +134,14 @@ def update_username(
|
||||
company_id=user.company_id,
|
||||
department_id=user.department_id,
|
||||
)
|
||||
details = {
|
||||
'user_id': user.id,
|
||||
'email': user.email,
|
||||
'fullname': user.fullname,
|
||||
'company_id': user.company_id,
|
||||
'department_id': user.department_id,
|
||||
}
|
||||
log_audit_user(db, current_user, 'update_username', details)
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_200_OK,
|
||||
content={"message": "Username updated successfully",
|
||||
@ -158,6 +198,15 @@ def change_password(
|
||||
company_id= current_user.company_id,
|
||||
department_id=current_user.department_id,
|
||||
)
|
||||
|
||||
details = {
|
||||
'user_id': current_user.id,
|
||||
'email': current_user.email,
|
||||
'fullname': current_user.fullname,
|
||||
'company_id': current_user.company_id,
|
||||
'department_id': current_user.department_id,
|
||||
}
|
||||
log_audit_user(db, current_user, 'change_password', details)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_200_OK,
|
||||
@ -201,6 +250,7 @@ def update_user(
|
||||
Update a user.
|
||||
"""
|
||||
user = crud.user.get(db, id=user_id)
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@ -214,6 +264,14 @@ def update_user(
|
||||
company_id=user.company_id,
|
||||
department_id=user.department_id,
|
||||
)
|
||||
details = {
|
||||
'user_id': user.id,
|
||||
'email': user.email,
|
||||
'fullname': user.fullname,
|
||||
'company_id': user.company_id,
|
||||
'department_id': user.department_id,
|
||||
}
|
||||
log_audit_user(db, current_user, 'update user', details)
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_200_OK,
|
||||
content={"message": "User updated successfully", "user": jsonable_encoder(user_data)},
|
||||
@ -286,6 +344,18 @@ def delete_user(
|
||||
"""
|
||||
user_id = delete_user.id
|
||||
user = crud.user.get(db, id=user_id)
|
||||
|
||||
details = {
|
||||
'admin_id': current_user.id,
|
||||
'deleted_user_id': user.id,
|
||||
'email': user.email,
|
||||
'fullname': user.fullname,
|
||||
'company_id': user.company_id,
|
||||
'department_id': user.department_id,
|
||||
}
|
||||
|
||||
log_audit_user(db, current_user, 'delete', details)
|
||||
|
||||
if user is None:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
crud.user.remove(db, id=user_id)
|
||||
@ -340,6 +410,17 @@ def admin_update_user(
|
||||
role = crud.user_role.update(db, db_obj=user_role, obj_in=role_in)
|
||||
|
||||
user_in = schemas.UserAdmin(fullname=user_update.fullname, department_id=user_update.department_id)
|
||||
|
||||
details = {
|
||||
'admin_id': current_user.id,
|
||||
'user_id': existing_user.id,
|
||||
'email': existing_user.email,
|
||||
'fullname': existing_user.fullname,
|
||||
'company_id': existing_user.company_id,
|
||||
'department_id': existing_user.department_id,
|
||||
}
|
||||
log_audit_user(db, current_user, 'admin_update', details)
|
||||
|
||||
crud.user.update(db, db_obj=existing_user, obj_in=user_in)
|
||||
|
||||
return JSONResponse(
|
||||
|
@ -8,7 +8,7 @@ SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{username}:{password}@{host}:{p
|
||||
port='5432',
|
||||
db_name='QuickGpt',
|
||||
username='postgres',
|
||||
password="admin",
|
||||
password="quick",
|
||||
)
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
@ -61,3 +61,4 @@ def verify_refresh_token(token: str) -> Optional[Dict[str, Any]]:
|
||||
return payload
|
||||
except JWTError:
|
||||
return None
|
||||
|
||||
|
@ -5,3 +5,4 @@ from .company_crud import company
|
||||
from .subscription_crud import subscription
|
||||
from .document_crud import documents
|
||||
from .department_crud import department
|
||||
from .audit_crud import audit
|
14
private_gpt/users/crud/audit_crud.py
Normal file
14
private_gpt/users/crud/audit_crud.py
Normal file
@ -0,0 +1,14 @@
|
||||
from typing import Optional
|
||||
|
||||
from private_gpt.users.crud.base import CRUDBase
|
||||
from private_gpt.users.models.audit import Audit
|
||||
from private_gpt.users.schemas.audit import AuditCreate, AuditUpdate
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
class CRUDAudit(CRUDBase[Audit, AuditCreate, AuditUpdate]):
|
||||
def get_by_id(self, db: Session, *, id: str) -> Optional[Audit]:
|
||||
return db.query(self.model).filter(Audit.id == id).first()
|
||||
|
||||
|
||||
audit = CRUDAudit(Audit)
|
@ -4,4 +4,5 @@ from .user_role import UserRole
|
||||
from .role import Role
|
||||
from .document import Document
|
||||
from .subscription import Subscription
|
||||
from .department import Department
|
||||
from .department import Department
|
||||
from .audit import Audit
|
18
private_gpt/users/models/audit.py
Normal file
18
private_gpt/users/models/audit.py
Normal file
@ -0,0 +1,18 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String
|
||||
from private_gpt.users.db.base_class import Base
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
|
||||
|
||||
class Audit(Base):
|
||||
__tablename__ = "audit"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
timestamp = Column(DateTime, nullable=False, default=datetime.utcnow)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
|
||||
model = Column(String, nullable=False)
|
||||
action = Column(String, nullable=False)
|
||||
details = Column(JSONB, nullable=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Audit(id={self.id}, timestamp={self.timestamp}, user_id={self.user_id}, model={self.model}, action={self.action}, details={self.details})>"
|
@ -3,8 +3,8 @@ from sqlalchemy.orm import relationship, Session
|
||||
from sqlalchemy import Column, Integer, String
|
||||
|
||||
from private_gpt.users.db.base_class import Base
|
||||
from private_gpt.users.models.document import Document
|
||||
from private_gpt.users.models.user import User
|
||||
# from private_gpt.users.models.document import Document
|
||||
# from private_gpt.users.models.user import User
|
||||
|
||||
|
||||
class Department(Base):
|
||||
@ -25,21 +25,25 @@ class Department(Base):
|
||||
total_documents = Column(Integer, default=0)
|
||||
|
||||
|
||||
|
||||
def update_total_users(mapper, connection, target):
|
||||
session = Session(bind=connection)
|
||||
target.total_users = session.query(User).filter_by(
|
||||
department_id=target.id).count()
|
||||
# @event.listens_for(Department, 'after_insert')
|
||||
# @event.listens_for(Department, 'after_update')
|
||||
# def update_total_users(mapper, connection, target):
|
||||
# print("--------------------------------------------------------------Calling Event User------------------------------------------------------------------------")
|
||||
# connection.execute(
|
||||
# Department.__table__.update().
|
||||
# where(Department.id == target.id).
|
||||
# values(total_users=Session.object_session(target).query(
|
||||
# User).filter_by(department_id=target.id).count())
|
||||
# )
|
||||
|
||||
|
||||
def update_total_documents(mapper, connection, target):
|
||||
session = Session(bind=connection)
|
||||
target.total_documents = session.query(
|
||||
Document).filter_by(department_id=target.id).count()
|
||||
|
||||
|
||||
# Attach event listeners to Department model
|
||||
event.listen(Department, 'after_insert', update_total_users)
|
||||
event.listen(Department, 'after_update', update_total_users)
|
||||
event.listen(Department, 'after_insert', update_total_documents)
|
||||
event.listen(Department, 'after_update', update_total_documents)
|
||||
# @event.listens_for(Department, 'after_insert')
|
||||
# @event.listens_for(Department, 'after_update')
|
||||
# def update_total_documents(mapper, connection, target):
|
||||
# print("--------------------------------------------------------------Calling Event Department------------------------------------------------------------------------")
|
||||
# connection.execute(
|
||||
# Department.__table__.update().
|
||||
# where(Department.id == target.id).
|
||||
# values(total_documents=Session.object_session(target).query(
|
||||
# Document).filter_by(department_id=target.id).count())
|
||||
# )
|
||||
|
@ -1,7 +1,10 @@
|
||||
from private_gpt.users.db.base_class import Base
|
||||
|
||||
from datetime import datetime
|
||||
from sqlalchemy import event, select, func, update
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime
|
||||
from private_gpt.users.models.department import Department
|
||||
|
||||
|
||||
class Document(Base):
|
||||
@ -25,6 +28,27 @@ class Document(Base):
|
||||
|
||||
department_id = Column(Integer, ForeignKey(
|
||||
"departments.id"), nullable=False)
|
||||
uploaded_by_user = relationship(
|
||||
"User", back_populates="uploaded_documents")
|
||||
department = relationship("Department", back_populates="documents")
|
||||
|
||||
department = relationship("Department", back_populates="documents")
|
||||
|
||||
|
||||
@event.listens_for(Document, 'after_insert')
|
||||
@event.listens_for(Document, 'after_delete')
|
||||
def update_total_documents(mapper, connection, target):
|
||||
department_id = target.department_id
|
||||
print(f"Department ID is: {department_id}")
|
||||
|
||||
# Use SQLAlchemy's ORM constructs for better readability and maintainability:
|
||||
total_documents = connection.execute(
|
||||
select([func.count()]).select_from(Document).where(
|
||||
Document.department_id == department_id)
|
||||
).scalar()
|
||||
|
||||
print(f"Total documents is: {total_documents}")
|
||||
print("Updating total documents")
|
||||
|
||||
# Use the correct update construct for SQLAlchemy:
|
||||
connection.execute(
|
||||
update(Department).values(total_documents=total_documents).where(
|
||||
Department.id == department_id)
|
||||
)
|
||||
|
@ -9,9 +9,10 @@ from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKey
|
||||
)
|
||||
from sqlalchemy import event, func, select, update
|
||||
from sqlalchemy.orm import relationship
|
||||
from private_gpt.users.db.base_class import Base
|
||||
|
||||
from private_gpt.users.models.department import Department
|
||||
class User(Base):
|
||||
"""Models a user table"""
|
||||
__tablename__ = "users"
|
||||
@ -53,3 +54,21 @@ class User(Base):
|
||||
__table_args__ = (
|
||||
UniqueConstraint('fullname', name='unique_username_no_spacing'),
|
||||
)
|
||||
|
||||
|
||||
@event.listens_for(User, 'after_insert')
|
||||
@event.listens_for(User, 'after_delete')
|
||||
def update_total_users(mapper, connection, target):
|
||||
department_id = target.department_id
|
||||
print(f"Department ID is: {department_id}")
|
||||
total_users = connection.execute(
|
||||
select([func.count()]).select_from(User).where(
|
||||
User.department_id == department_id)
|
||||
).scalar()
|
||||
print(f"Total users is: {total_users}")
|
||||
connection.execute(
|
||||
update(Department).values(total_users=total_users).where(
|
||||
Department.id == department_id)
|
||||
)
|
||||
|
||||
|
||||
|
@ -5,4 +5,5 @@ from .user_role import UserRole, UserRoleCreate, UserRoleInDB, UserRoleUpdate
|
||||
from .subscription import Subscription, SubscriptionBase, SubscriptionCreate, SubscriptionUpdate
|
||||
from .company import Company, CompanyBase, CompanyCreate, CompanyUpdate
|
||||
from .documents import Document, DocumentCreate, DocumentsBase, DocumentUpdate, DocumentList
|
||||
from .department import Department, DepartmentCreate, DepartmentUpdate, DepartmentAdminCreate, DepartmentDelete
|
||||
from .department import Department, DepartmentCreate, DepartmentUpdate, DepartmentAdminCreate, DepartmentDelete
|
||||
from .audit import AuditBase, AuditCreate, AuditUpdate, Audit
|
30
private_gpt/users/schemas/audit.py
Normal file
30
private_gpt/users/schemas/audit.py
Normal file
@ -0,0 +1,30 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class AuditBase(BaseModel):
|
||||
id: int
|
||||
model: str
|
||||
user_id: int
|
||||
action: str
|
||||
details: dict
|
||||
timestamp: Optional[datetime]
|
||||
|
||||
|
||||
class AuditCreate(AuditBase):
|
||||
pass
|
||||
|
||||
|
||||
class AuditUpdate(AuditBase):
|
||||
id: int
|
||||
|
||||
|
||||
class AuditInDB(AuditBase):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class Audit(AuditBase):
|
||||
pass
|
@ -7,7 +7,6 @@ class Ldap:
|
||||
self.server = ldap3.Server(server_uri, get_info=ldap3.ALL)
|
||||
print(f"Connected to ldap server: {self.server}")
|
||||
self.conn = ldap3.Connection(self.server, user=ldap_user, password=ldap_pass, auto_bind=True)
|
||||
print(self.conn)
|
||||
|
||||
def who_am_i(self):
|
||||
return self.conn.extend.standard.who_am_i()
|
||||
|
24
private_gpt/users/utils/audit.py
Normal file
24
private_gpt/users/utils/audit.py
Normal file
@ -0,0 +1,24 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy.orm import Session
|
||||
from private_gpt.users.models.audit import Audit
|
||||
|
||||
def log_audit_entry(
|
||||
session: Session,
|
||||
model: str,
|
||||
action: str,
|
||||
details: dict,
|
||||
user_id: int = None,
|
||||
):
|
||||
audit_entry = Audit(
|
||||
timestamp=datetime.utcnow(),
|
||||
user_id=user_id,
|
||||
model=model,
|
||||
action=action,
|
||||
details=details,
|
||||
)
|
||||
|
||||
session.add(audit_entry)
|
||||
session.commit()
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user