Updated with active subscription

This commit is contained in:
Saurab-Shrestha
2024-02-25 12:47:35 +05:45
parent 808a0fa6fc
commit 8271fedb78
15 changed files with 121 additions and 240 deletions

2
.env
View File

@@ -4,7 +4,7 @@ ENVIRONMENT=dev
DB_HOST=localhost DB_HOST=localhost
DB_USER=postgres DB_USER=postgres
DB_PORT=5432 DB_PORT=5432
DB_PASSWORD=quick DB_PASSWORD=admin
DB_NAME=QuickGpt DB_NAME=QuickGpt
SUPER_ADMIN_EMAIL=superadmin@email.com SUPER_ADMIN_EMAIL=superadmin@email.com

View File

@@ -1,52 +0,0 @@
"""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 ###

View File

@@ -1,18 +1,18 @@
"""Audit log """Create audit model
Revision ID: eb0a7f182c75 Revision ID: 9aa759c05b19
Revises: Revises:
Create Date: 2024-02-24 11:37:43.713632 Create Date: 2024-02-25 10:03:28.092131
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = 'eb0a7f182c75' revision: str = '9aa759c05b19'
down_revision: Union[str, None] = None down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
@@ -22,19 +22,23 @@ def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.create_table('audit', op.create_table('audit',
sa.Column('id', sa.Integer(), nullable=False), sa.Column('id', sa.Integer(), nullable=False),
sa.Column('url', sa.String(), nullable=True), sa.Column('timestamp', sa.DateTime(), nullable=False),
sa.Column('headers', sa.ARRAY(sa.String()), nullable=True), sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('method', sa.String(), nullable=True), sa.Column('model', sa.String(), nullable=False),
sa.Column('response', sa.String(), nullable=True), sa.Column('action', sa.String(), nullable=False),
sa.Column('details', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id') sa.PrimaryKeyConstraint('id')
) )
op.create_index(op.f('ix_audit_id'), 'audit', ['id'], unique=False) 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']) # op.create_unique_constraint('unique_user_role', 'user_roles', ['user_id', 'role_id', 'company_id'])
op.add_column('users', sa.Column('password_created', sa.DateTime(), nullable=True))
# ### end Alembic commands ### # ### end Alembic commands ###
def downgrade() -> None: def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.drop_column('users', 'password_created')
# op.drop_constraint('unique_user_role', 'user_roles', type_='unique') # op.drop_constraint('unique_user_role', 'user_roles', type_='unique')
op.drop_index(op.f('ix_audit_id'), table_name='audit') op.drop_index(op.f('ix_audit_id'), table_name='audit')
op.drop_table('audit') op.drop_table('audit')

View File

@@ -1,50 +0,0 @@
"""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 ###

View File

@@ -1,32 +0,0 @@
"""added password expiry
Revision ID: b7b23e5d5214
Revises: 62c1a29320fc
Create Date: 2024-02-24 19:47:49.069873
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'b7b23e5d5214'
down_revision: Union[str, None] = '62c1a29320fc'
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_unique_constraint('unique_user_role', 'user_roles', ['user_id', 'role_id', 'company_id'])
op.add_column('users', sa.Column('password_created', sa.DateTime(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('users', 'password_created')
# op.drop_constraint('unique_user_role', 'user_roles', type_='unique')
# ### end Alembic commands ###

View File

@@ -103,6 +103,7 @@ def chat_completion(
use_context=body.use_context, use_context=body.use_context,
context_filter=body.context_filter, context_filter=body.context_filter,
) )
return to_openai_response( return to_openai_response(
completion.response, completion.sources if body.include_sources else None completion.response, completion.sources if body.include_sources else None
) )

View File

@@ -106,6 +106,7 @@ def prompt_completion(
db: Session = Depends(deps.get_db), db: Session = Depends(deps.get_db),
current_user: models.User = Security( current_user: models.User = Security(
deps.get_current_user, deps.get_current_user,
deps.get_active_subscription,
), ),
) -> OpenAICompletion | StreamingResponse: ) -> OpenAICompletion | StreamingResponse:
try: try:

View File

@@ -160,10 +160,17 @@ def delete_file(
print("Unable to delete file from the static directory") print("Unable to delete file from the static directory")
document = crud.documents.get_by_filename(db,file_name=filename) document = crud.documents.get_by_filename(db,file_name=filename)
if document: if document:
log_audit(model='Document', action='delete', log_audit(
details={"status": "SUCCESS", "message": f"{filename}' successfully deleted."}, user_id=current_user.id) model='Document',
action='delete',
details={
"detail": f"{filename}' deleted successfully.",
'user': current_user.fullname,
},
user_id=current_user.id
)
crud.documents.remove(db=db, id=document.id) crud.documents.remove(db=db, id=document.id)
return {"status": "SUCCESS", "message": f"{filename}' successfully deleted."} return {"status": "SUCCESS", "message": f"{filename}' deleted successfully."}
except Exception as e: except Exception as e:
print(traceback.print_exc()) print(traceback.print_exc())
logger.error( logger.error(
@@ -200,14 +207,14 @@ def ingest_file(
detail="No file name provided", detail="No file name provided",
) )
try: # try:
docs_in = schemas.DocumentCreate(filename=file.filename, uploaded_by=current_user.id, department_id=current_user.department_id) docs_in = schemas.DocumentCreate(filename=file.filename, uploaded_by=current_user.id, department_id=current_user.department_id)
crud.documents.create(db=db, obj_in=docs_in) crud.documents.create(db=db, obj_in=docs_in)
except Exception as e: # except Exception as e:
raise HTTPException( # raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Unable to upload file.", # detail="Unable to upload file.",
) # )
upload_path = Path(f"{UPLOAD_DIR}/{file.filename}") upload_path = Path(f"{UPLOAD_DIR}/{file.filename}")
with open(upload_path, "wb") as f: with open(upload_path, "wb") as f:
@@ -218,11 +225,13 @@ def ingest_file(
logger.info(f"{file.filename} is uploaded by the {current_user.fullname}.") logger.info(f"{file.filename} is uploaded by the {current_user.fullname}.")
response = IngestResponse( response = IngestResponse(
object="list", model="private-gpt", data=ingested_documents) object="list", model="private-gpt", data=ingested_documents)
log_audit(model='Document', action='create', log_audit(model='Document', action='create',
details={ details={
'filename': file.filename, 'filename': f"{file.filename} uploaded successfully",
'user': current_user.fullname, 'user': current_user.fullname,
}, user_id=current_user.id) }, user_id=current_user.id)
return response return response
except HTTPException: except HTTPException:
print(traceback.print_exc()) print(traceback.print_exc())
@@ -274,8 +283,16 @@ async def common_ingest_logic(
f.write(file.read()) f.write(file.read())
file.seek(0) file.seek(0)
ingested_documents = service.ingest_bin_data(file_name, file) ingested_documents = service.ingest_bin_data(file_name, file)
log_audit(model='Document', action='create',
details={'status': "SUCCESS", 'message': f"{file_name} uploaded successfully."}, user_id=current_user.id) log_audit(
model='Document',
action='create',
details={
'detail': f"{file_name} uploaded successfully",
'user': f"{current_user.fullname}"
},
user_id=current_user.id
)
logger.info( logger.info(
f"{file_name} is uploaded by the {current_user.fullname}.") f"{file_name} is uploaded by the {current_user.fullname}.")

View File

@@ -117,17 +117,16 @@ def get_company_name(company_id: int, db: Session = Depends(get_db)) -> str:
def get_active_subscription( def get_active_subscription(
current_user: models.User = Depends(get_current_user),
db: Session = Depends(get_db), db: Session = Depends(get_db),
): ):
company_id = current_user.user_role.company_id company_id = 1
if company_id: if company_id:
company = crud.company.get(db, company_id) company = crud.company.get(db, company_id)
if company and company.subscriptions: if company and company.subscriptions:
active_subscription = next((sub for sub in company.subscriptions if sub.is_active), None) active_subscription = next((sub for sub in company.subscriptions if sub.is_active), None)
if active_subscription: if active_subscription:
return company print("Has active Subscription")
return active_subscription
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail="Access Forbidden - No Active Subscription", detail="Access Forbidden - No Active Subscription",

View File

@@ -1,3 +1,4 @@
from private_gpt.users.api import deps
from private_gpt.users.api.v1.routers import auth, roles, user_roles, users, subscriptions, companies, departments, documents, audits from private_gpt.users.api.v1.routers import auth, roles, user_roles, users, subscriptions, companies, departments, documents, audits
from fastapi import APIRouter from fastapi import APIRouter

View File

@@ -111,6 +111,7 @@ def login_access_token(
log_audit: models.Audit = Depends(deps.get_audit_logger), log_audit: models.Audit = Depends(deps.get_audit_logger),
db: Session = Depends(deps.get_db), db: Session = Depends(deps.get_db),
form_data: OAuth2PasswordRequestForm = Depends(), form_data: OAuth2PasswordRequestForm = Depends(),
active_subscription: models.Subscription = Depends(deps.get_active_subscription)
) -> Any: ) -> Any:
""" """
OAuth2 compatible token login, get an access token for future requests OAuth2 compatible token login, get an access token for future requests
@@ -208,7 +209,7 @@ def register(
# password: str = Body(...), # password: str = Body(...),
company_id: int = Body(None, title="Company ID", company_id: int = Body(None, title="Company ID",
description="Company ID for the user (if applicable)"), description="Company ID for the user (if applicable)"),
department_id: str = Body(None, title="Department ID", department_id: int = Body(None, title="Department ID",
description="Department name for the user (if applicable)"), description="Department name for the user (if applicable)"),
role_name: str = Body(None, title="Role Name", role_name: str = Body(None, title="Role Name",
description="User role name (if applicable)"), description="User role name (if applicable)"),

View File

@@ -92,7 +92,7 @@ def create_department(
department1 = jsonable_encoder(department) department1 = jsonable_encoder(department)
details = { details = {
'user_id': current_user.id, 'detail': 'Department created successfully',
'department_id': department.id, 'department_id': department.id,
'department_name': department.name 'department_name': department.name
} }
@@ -131,16 +131,6 @@ def read_department(
department = crud.department.get_by_id(db, id=department_id) department = crud.department.get_by_id(db, id=department_id)
if department is None: if department is None:
raise HTTPException(status_code=404, detail="Department not found") 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 return department
except Exception as e: except Exception as e:
print(traceback.format_exc()) print(traceback.format_exc())
@@ -174,8 +164,7 @@ def update_department(
updated_department = jsonable_encoder(updated_department) updated_department = jsonable_encoder(updated_department)
details = { details = {
'status': '200', 'detail': 'Department updated successfully!',
'user_id': current_user.id,
'department_id': department.id, 'department_id': department.id,
'old_department_name': old_name, 'old_department_name': old_name,
'new_department_name': department.name, 'new_department_name': department.name,
@@ -218,18 +207,16 @@ def delete_department(
raise HTTPException(status_code=404, detail="Department not found") raise HTTPException(status_code=404, detail="Department not found")
details = { details = {
'status': 200, 'detail': "Department deleted successfully!",
'user_id': current_user.id,
'department_id': department.id, 'department_id': department.id,
'department_name': department.name 'department_name': department.name
} }
log_audit_department(db, current_user, 'delete', details)
deleted_department = crud.department.remove(db=db, id=department_id) deleted_department = crud.department.remove(db=db, id=department_id)
log_audit_department(db, current_user, 'delete', details)
deleted_department = jsonable_encoder(deleted_department) deleted_department = jsonable_encoder(deleted_department)
return JSONResponse( return JSONResponse(
status_code=status.HTTP_200_OK, status_code=status.HTTP_200_OK,
content={ content={

View File

@@ -127,6 +127,7 @@ def update_username(
Update own username. Update own username.
""" """
user_in = schemas.UserUpdate(fullname=update_in.fullname, email=current_user.email, company_id=current_user.company_id) user_in = schemas.UserUpdate(fullname=update_in.fullname, email=current_user.email, company_id=current_user.company_id)
old_fullname = current_user.fullname
user = crud.user.update(db, db_obj=current_user, obj_in=user_in) user = crud.user.update(db, db_obj=current_user, obj_in=user_in)
user_data = schemas.UserBaseSchema( user_data = schemas.UserBaseSchema(
email=user.email, email=user.email,
@@ -135,11 +136,8 @@ def update_username(
department_id=user.department_id, department_id=user.department_id,
) )
details = { details = {
'user_id': user.id, 'old_fullname': old_fullname,
'email': user.email, 'new_fullname': user.fullname,
'fullname': user.fullname,
'company_id': user.company_id,
'department_id': user.department_id,
} }
log_audit_user(db, current_user, 'update_username', details) log_audit_user(db, current_user, 'update_username', details)
return JSONResponse( return JSONResponse(
@@ -200,11 +198,8 @@ def change_password(
) )
details = { details = {
'detail': 'Password changed successfully!',
'user_id': current_user.id, '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) log_audit_user(db, current_user, 'change_password', details)
@@ -346,11 +341,9 @@ def delete_user(
user = crud.user.get(db, id=user_id) user = crud.user.get(db, id=user_id)
details = { details = {
'admin_id': current_user.id, 'detail': "user deleted successfully!",
'deleted_user_id': user.id,
'email': user.email, 'email': user.email,
'fullname': user.fullname, 'fullname': user.fullname,
'company_id': user.company_id,
'department_id': user.department_id, 'department_id': user.department_id,
} }
@@ -378,13 +371,20 @@ def admin_update_user(
""" """
Update the user by the Admin/Super_ADMIN Update the user by the Admin/Super_ADMIN
""" """
existing_user = crud.user.get(db, id=user_update.id) try:
existing_user = crud.user.get_by_id(db, id=user_update.id)
if existing_user is None: if existing_user is None:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail=f"User not found with id: {user_update.id}", detail=f"User not found with id: {user_update.id}",
) )
old_detail = {
'fullnam': existing_user.fullname,
'role': existing_user.user_role.role.name,
'department': existing_user.department_id
}
if existing_user.fullname == user_update.fullname: if existing_user.fullname == user_update.fullname:
pass pass
else: else:
@@ -408,23 +408,29 @@ def admin_update_user(
role_id=role.id, role_id=role.id,
) )
role = crud.user_role.update(db, db_obj=user_role, obj_in=role_in) role = crud.user_role.update(db, db_obj=user_role, obj_in=role_in)
print(f"THe new user name : {user_update.fullname} Department: {user_update.department_id}")
user_update_in = schemas.UserAdmin(fullname=user_update.fullname, department_id=user_update.department_id)
user_in = schemas.UserAdmin(fullname=user_update.fullname, department_id=user_update.department_id) new_detail = {
details = {
'admin_id': current_user.id,
'user_id': existing_user.id,
'email': existing_user.email, 'email': existing_user.email,
'fullname': existing_user.fullname, 'fullname': existing_user.fullname,
'company_id': existing_user.company_id,
'department_id': existing_user.department_id, 'department_id': existing_user.department_id,
} }
details = {
'old detail': old_detail,
'new detail': new_detail,
}
log_audit_user(db, current_user, 'admin_update', details) log_audit_user(db, current_user, 'admin_update', details)
user = crud.user.get_by_id(db, id=existing_user.id)
crud.user.update(db, db_obj=existing_user, obj_in=user_in) crud.user.update(db, db_obj=user, obj_in=user_update_in)
return JSONResponse( return JSONResponse(
status_code=status.HTTP_200_OK, status_code=status.HTTP_200_OK,
content={"message": "User updated successfully", content={"message": "User updated successfully"}
} )
except Exception as e:
print(traceback.print_exc())
return HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail='Unable to update user'
) )

View File

@@ -8,7 +8,7 @@ SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{username}:{password}@{host}:{p
port='5432', port='5432',
db_name='QuickGpt', db_name='QuickGpt',
username='postgres', username='postgres',
password="quick", password="admin",
) )
class Settings(BaseSettings): class Settings(BaseSettings):

View File

@@ -17,11 +17,9 @@ class CRUDDepartments(CRUDBase[Department, DepartmentCreate, DepartmentUpdate]):
) -> List[Department]: ) -> List[Department]:
return ( return (
db.query(self.model) db.query(self.model)
.filter(Department.department_id == department_id) .filter(Department.id == department_id)
.offset(skip) .offset(skip)
.limit(limit) .limit(limit)
.all() .all()
) )
department = CRUDDepartments(Department) department = CRUDDepartments(Department)