diff --git a/alembic/env.py b/alembic/env.py index 238b121..3051aaa 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -14,6 +14,7 @@ from private_gpt.users.models.role import Role from private_gpt.users.models.user_role import UserRole from private_gpt.users.models.subscription import Subscription from private_gpt.users.models.company import Company +from private_gpt.users.models.documents import Documents # this is the Alembic Config object, which provides # access to the values within the .ini file in use. config = context.config @@ -25,7 +26,7 @@ if config.config_file_name is not None: # add your model's MetaData object here # for 'autogenerate' support -# from myapp import mymodel +# from myapp import mymodel1w # target_metadata = mymodel.Base.metadata target_metadata = Base.metadata @@ -33,7 +34,9 @@ target_metadata = Base.metadata # can be acquired: # my_important_option = config.get_main_option("my_important_option") # ... etc. -config.set_section_option(config.config_ini_section, "sqlalchemy.url", SQLALCHEMY_DATABASE_URI) +config.set_section_option(config.config_ini_section, + "sqlalchemy.url", SQLALCHEMY_DATABASE_URI) + def run_migrations_offline() -> None: """Run migrations in 'offline' mode. @@ -84,4 +87,4 @@ def run_migrations_online() -> None: if context.is_offline_mode(): run_migrations_offline() else: - run_migrations_online() \ No newline at end of file + run_migrations_online() diff --git a/alembic/versions/0ef554491192_updated_documents_primary_key.py b/alembic/versions/0ef554491192_updated_documents_primary_key.py new file mode 100644 index 0000000..d0614e1 --- /dev/null +++ b/alembic/versions/0ef554491192_updated_documents_primary_key.py @@ -0,0 +1,32 @@ +"""Updated documents primary key + +Revision ID: 0ef554491192 +Revises: 9ce6871961b5 +Create Date: 2024-02-08 17:06:32.957163 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '0ef554491192' +down_revision: Union[str, None] = '9ce6871961b5' +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_index(op.f('ix_document_id'), 'document', ['id'], unique=False) + # 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.drop_index(op.f('ix_document_id'), table_name='document') + # ### end Alembic commands ### diff --git a/alembic/versions/488f28da0939_remove_company_from_the_users_table.py b/alembic/versions/488f28da0939_remove_company_from_the_users_table.py new file mode 100644 index 0000000..811d8df --- /dev/null +++ b/alembic/versions/488f28da0939_remove_company_from_the_users_table.py @@ -0,0 +1,36 @@ +"""Remove company from the users table + +Revision ID: 488f28da0939 +Revises: 0ef554491192 +Create Date: 2024-02-09 19:08:12.002449 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '488f28da0939' +down_revision: Union[str, None] = '0ef554491192' +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(None, 'document', ['filename']) + # op.create_unique_constraint('unique_user_role', 'user_roles', ['user_id', 'role_id', 'company_id']) + op.drop_constraint('users_company_id_fkey', 'users', type_='foreignkey') + op.drop_column('users', 'company_id') + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('users', sa.Column('company_id', sa.INTEGER(), autoincrement=False, nullable=True)) + op.create_foreign_key('users_company_id_fkey', 'users', 'companies', ['company_id'], ['id']) + # op.drop_constraint('unique_user_role', 'user_roles', type_='unique') + op.drop_constraint(None, 'document', type_='unique') + # ### end Alembic commands ### diff --git a/alembic/versions/864a15f1ee0a_create_models.py b/alembic/versions/864a15f1ee0a_create_models.py deleted file mode 100644 index e3bf1da..0000000 --- a/alembic/versions/864a15f1ee0a_create_models.py +++ /dev/null @@ -1,88 +0,0 @@ -"""Create models - -Revision ID: 864a15f1ee0a -Revises: -Create Date: 2024-01-29 15:34:21.797387 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '864a15f1ee0a' -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('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.ForeignKeyConstraint(['company_id'], ['companies.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('email'), - sa.UniqueConstraint('fullname'), - sa.UniqueConstraint('fullname', name='unique_username_no_spacing') - ) - 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=True), - 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_table('users') - op.drop_index(op.f('ix_subscriptions_sub_id'), table_name='subscriptions') - op.drop_table('subscriptions') - 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 ### diff --git a/alembic/versions/9ce6871961b5_create_document_model.py b/alembic/versions/9ce6871961b5_create_document_model.py new file mode 100644 index 0000000..ab8ed48 --- /dev/null +++ b/alembic/versions/9ce6871961b5_create_document_model.py @@ -0,0 +1,39 @@ +"""Create document model + +Revision ID: 9ce6871961b5 +Revises: +Create Date: 2024-02-08 16:16:31.072913 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '9ce6871961b5' +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('document', + sa.Column('id', sa.Integer(), autoincrement=True, 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=True), + sa.ForeignKeyConstraint(['uploaded_by'], ['users.id'], ), + sa.PrimaryKeyConstraint('id', 'uploaded_by') + ) + # 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.drop_table('document') + # ### end Alembic commands ### diff --git a/alembic/versions/ce0f3c4dd625_company_column_users_table.py b/alembic/versions/ce0f3c4dd625_company_column_users_table.py new file mode 100644 index 0000000..1dc0cc9 --- /dev/null +++ b/alembic/versions/ce0f3c4dd625_company_column_users_table.py @@ -0,0 +1,34 @@ +"""Company column users table + +Revision ID: ce0f3c4dd625 +Revises: 488f28da0939 +Create Date: 2024-02-09 19:35:09.546805 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'ce0f3c4dd625' +down_revision: Union[str, None] = '488f28da0939' +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('company_id', sa.Integer(), nullable=True)) + op.create_foreign_key(None, 'users', 'companies', ['company_id'], ['id']) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'users', type_='foreignkey') + op.drop_column('users', 'company_id') + # op.drop_constraint('unique_user_role', 'user_roles', type_='unique') + # ### end Alembic commands ### diff --git a/private_gpt/home.py b/private_gpt/home.py index 1b499fc..d871d68 100644 --- a/private_gpt/home.py +++ b/private_gpt/home.py @@ -39,7 +39,7 @@ SOURCES_SEPARATOR = "\n Sources: \n" MODES = ["Query Docs", "Search in Docs", "LLM Chat"] DEFAULT_MODE = MODES[0] -chat_router = APIRouter(prefix="/v1", tags=["Chat"]) +home_router = APIRouter(prefix="/v1", tags=["Chat"]) class ListFilesResponse(BaseModel): uploaded_files: List[str] @@ -165,7 +165,7 @@ def get_home_instance(request: Request) -> Home: return home_instance -@chat_router.post("/chat") +@home_router.post("/chat") async def chat_endpoint( home_instance: Home = Depends(get_home_instance), message: str = Body(...), mode: str = Body(DEFAULT_MODE), @@ -180,7 +180,7 @@ async def chat_endpoint( ) -@chat_router.get("/list_files") +@home_router.get("/list_files") async def list_files( home_instance: Home = Depends(get_home_instance), current_user: models.User = Security( diff --git a/private_gpt/launcher.py b/private_gpt/launcher.py index bd1a8e5..b03a47c 100644 --- a/private_gpt/launcher.py +++ b/private_gpt/launcher.py @@ -14,7 +14,7 @@ from private_gpt.server.ingest.ingest_router import ingest_router from private_gpt.users.api.v1.api import api_router from private_gpt.settings.settings import Settings -from private_gpt.home import chat_router +from private_gpt.home import home_router logger = logging.getLogger(__name__) @@ -33,7 +33,7 @@ def create_app(root_injector: Injector) -> FastAPI: app.include_router(health_router) app.include_router(api_router) - app.include_router(chat_router) + app.include_router(home_router) settings = root_injector.get(Settings) if settings.server.cors.enabled: logger.debug("Setting up CORS middleware") diff --git a/private_gpt/server/ingest/ingest_router.py b/private_gpt/server/ingest/ingest_router.py index 979bdab..dab59ca 100644 --- a/private_gpt/server/ingest/ingest_router.py +++ b/private_gpt/server/ingest/ingest_router.py @@ -1,7 +1,8 @@ import logging from pathlib import Path -from typing import Literal +from typing import Literal, Optional +from sqlalchemy.orm import Session from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile, File, status, Security, Body from fastapi.responses import JSONResponse from pydantic import BaseModel, Field @@ -135,9 +136,7 @@ def delete_file( The `filename` can be obtained from the `GET /ingest/list` endpoint. The document will be effectively deleted from your storage context. """ - filename = delete_input.filename - print(filename) - + filename = delete_input.filename service = request.state.injector.get(IngestService) try: doc_ids = service.get_doc_ids_by_filename(filename) @@ -159,6 +158,7 @@ def delete_file( @ingest_router.post("/ingest/file", response_model=IngestResponse, tags=["Ingestion"]) def ingest_file( request: Request, + db: Session = Depends(deps.get_db), file: UploadFile = File(...), current_user: models.User = Security( deps.get_current_user, @@ -167,10 +167,27 @@ def ingest_file( service = request.state.injector.get(IngestService) try: + file_ingested = crud.documents.get_by_filename(db, file_name=file.filename) + if file_ingested: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="File already exists. Choose a different file.", + ) + if file.filename is None: raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, detail="No file name provided") + status_code=status.HTTP_400_BAD_REQUEST, + detail="No file name provided", + ) + try: + docs_in = schemas.DocumentCreate(filename=file.filename, uploaded_by=current_user.id) + crud.documents.create(db=db, obj_in=docs_in) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Unable to upload file.", + ) upload_path = Path(f"{UPLOAD_DIR}/{file.filename}") with open(upload_path, "wb") as f: @@ -178,6 +195,7 @@ 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) except Exception as e: diff --git a/private_gpt/users/api/v1/routers/auth.py b/private_gpt/users/api/v1/routers/auth.py index 0ae7c58..b1ad79d 100644 --- a/private_gpt/users/api/v1/routers/auth.py +++ b/private_gpt/users/api/v1/routers/auth.py @@ -88,7 +88,7 @@ def login_access_token( user_in = schemas.UserUpdate( email=user.email, fullname=user.fullname, - company_id=user.company_id, + company_id=user.user_role.company_id, last_login=datetime.now() ) user = crud.user.update(db, db_obj=user, obj_in=user_in) @@ -176,21 +176,11 @@ def register( status_code=404, detail="Company not found.", ) - if current_user.user_role.role.name not in {Role.SUPER_ADMIN["name"], Role.ADMIN["name"]}: - raise HTTPException( - status_code=403, - detail="You do not have permission to register users!", - ) user = register_user(db, email, fullname, random_password, company) user_role_name = role_name or Role.GUEST["name"] user_role = create_user_role(db, user, user_role_name, company) else: - if current_user.user_role.role.name != Role.SUPER_ADMIN["name"]: - raise HTTPException( - status_code=403, - detail="You do not have permission to register users without a company.", - ) user = register_user(db, email, fullname, random_password, None) user_role_name = role_name or Role.ADMIN["name"] user_role = create_user_role(db, user, user_role_name, None) diff --git a/private_gpt/users/api/v1/routers/documents.py b/private_gpt/users/api/v1/routers/documents.py new file mode 100644 index 0000000..e69de29 diff --git a/private_gpt/users/api/v1/routers/users.py b/private_gpt/users/api/v1/routers/users.py index 5a11fc8..57873a3 100644 --- a/private_gpt/users/api/v1/routers/users.py +++ b/private_gpt/users/api/v1/routers/users.py @@ -285,3 +285,58 @@ def delete_user( content={"message": "User deleted successfully"}, ) + +@router.post("/update_user") +def admin_update_user( + *, + db: Session = Depends(deps.get_db), + user_update: schemas.UserAdminUpdate, + current_user: models.User = Security( + deps.get_current_user, + scopes=[Role.ADMIN["name"], Role.SUPER_ADMIN["name"]], + ), +) -> Any: + """ + Update the user by the Admin/Super_ADMIN + """ + existing_user = crud.user.get(db, id=user_update.id) + + if existing_user is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"User not found with id: {user_update.id}", + ) + if existing_user.fullname == user_update.fullname: + pass + else: + fullname = crud.user.get_by_name(db, name=user_update.fullname) + if fullname: + raise HTTPException( + status_code=409, + detail="The user with this username already exists!", + ) + + role = crud.role.get_by_name(db, name=user_update.role) + if role.id == 1: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Cannot create SUPER ADMIN!", + ) + + user_role = crud.user_role.get_by_user_id(db, user_id=existing_user.id) + role_in = schemas.UserRoleUpdate( + user_id=existing_user.id, + role_id=role.id, + ) + role = crud.user_role.update(db, db_obj=user_role, obj_in=role_in) + + user_in = schemas.UserUpdate(fullname=user_update.fullname, + email=existing_user.email, company_id=existing_user.user_role.company_id) + print("User in: ", user_in) + user = crud.user.update(db, db_obj=existing_user, obj_in=user_in) + + return JSONResponse( + status_code=status.HTTP_200_OK, + content={"message": "User updated successfully", + } + ) diff --git a/private_gpt/users/core/config.py b/private_gpt/users/core/config.py index f37be46..39eb981 100644 --- a/private_gpt/users/core/config.py +++ b/private_gpt/users/core/config.py @@ -3,6 +3,13 @@ from typing import Any, Dict, Optional from pydantic_settings import BaseSettings +SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{username}:{password}@{host}:{port}/{db_name}".format( + host='localhost', + port='5432', + db_name='QuickGpt', + username='postgres', + password="quick", +) class Settings(BaseSettings): PROJECT_NAME: str = "AUTHENTICATION AND AUTHORIZATION" diff --git a/private_gpt/users/crud/__init__.py b/private_gpt/users/crud/__init__.py index 928dfe1..aa2271b 100644 --- a/private_gpt/users/crud/__init__.py +++ b/private_gpt/users/crud/__init__.py @@ -2,4 +2,5 @@ from .role_crud import role from .user_crud import user from .user_role_crud import user_role from .company_crud import company -from .subscription_crud import subscription \ No newline at end of file +from .subscription_crud import subscription +from .document_crud import documents \ No newline at end of file diff --git a/private_gpt/users/crud/document_crud.py b/private_gpt/users/crud/document_crud.py new file mode 100644 index 0000000..0e25b60 --- /dev/null +++ b/private_gpt/users/crud/document_crud.py @@ -0,0 +1,16 @@ +from sqlalchemy.orm import Session +from private_gpt.users.schemas.documents import DocumentCreate, DocumentUpdate +from private_gpt.users.models.documents import Documents +from private_gpt.users.crud.base import CRUDBase +from typing import Optional + + +class CRUDDocuments(CRUDBase[Documents, DocumentCreate, DocumentUpdate]): + def get_by_id(self, db: Session, *, id: str) -> Optional[Documents]: + return db.query(self.model).filter(Documents.id == id).first() + + def get_by_filename(self, db: Session, *, file_name: str) -> Optional[Documents]: + return db.query(self.model).filter(Documents.filename == file_name).first() + + +documents = CRUDDocuments(Documents) diff --git a/private_gpt/users/crud/user_crud.py b/private_gpt/users/crud/user_crud.py index fb21a24..950d5a4 100644 --- a/private_gpt/users/crud/user_crud.py +++ b/private_gpt/users/crud/user_crud.py @@ -3,12 +3,13 @@ from typing import Any, Dict, List, Optional, Union from private_gpt.users.core.security import get_password_hash, verify_password from private_gpt.users.crud.base import CRUDBase from private_gpt.users.models.user import User -from private_gpt.users.schemas.user import UserCreate, UserUpdate, AdminUpdate +from private_gpt.users.schemas.user import UserCreate, UserUpdate from private_gpt.users.models.user_role import UserRole from private_gpt.users.models.role import Role from sqlalchemy.orm import Session from sqlalchemy.orm import joinedload + class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]): def get_by_email(self, db: Session, *, email: str) -> Optional[User]: return db.query(self.model).filter(User.email == email).first() @@ -40,7 +41,7 @@ class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]): del update_data["password"] update_data["hashed_password"] = hashed_password return super().update(db, db_obj=db_obj, obj_in=update_data) - + def get_multi( self, db: Session, *, skip: int = 0, limit: int = 100, ) -> List[User]: @@ -75,7 +76,6 @@ class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]): .all() ) - def get_multi_by_company_id( self, db: Session, *, company_id: str, skip: int = 0, limit: int = 100 ) -> List[User]: @@ -88,6 +88,8 @@ class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]): .limit(limit) .all() ) - -user = CRUDUser(User) \ No newline at end of file + def get_by_name(self, db: Session, *, name: str) -> Optional[User]: + return db.query(self.model).filter(User.fullname == name).first() + +user = CRUDUser(User) diff --git a/private_gpt/users/db/session.py b/private_gpt/users/db/session.py index ef48bda..259a861 100644 --- a/private_gpt/users/db/session.py +++ b/private_gpt/users/db/session.py @@ -1,6 +1,6 @@ -from private_gpt.users.core.config import settings +from private_gpt.users.core.config import SQLALCHEMY_DATABASE_URI from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker -engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, echo=True, future=True, pool_pre_ping=True) +engine = create_engine(SQLALCHEMY_DATABASE_URI, echo=True, future=True, pool_pre_ping=True) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) diff --git a/private_gpt/users/models/__init__.py b/private_gpt/users/models/__init__.py index a5e9e7c..46bb920 100644 --- a/private_gpt/users/models/__init__.py +++ b/private_gpt/users/models/__init__.py @@ -1,4 +1,6 @@ from .user import User from .company import Company from .user_role import UserRole -from .role import Role \ No newline at end of file +from .role import Role +from .documents import Documents +from .subscription import Subscription \ No newline at end of file diff --git a/private_gpt/users/models/documents.py b/private_gpt/users/models/documents.py new file mode 100644 index 0000000..e0476c3 --- /dev/null +++ b/private_gpt/users/models/documents.py @@ -0,0 +1,22 @@ +from private_gpt.users.db.base_class import Base +from datetime import datetime, timedelta +from sqlalchemy.orm import relationship +from sqlalchemy import Column, Integer, String, Boolean, Float, ForeignKey, DateTime + + +class Documents(Base): + """Models a user table""" + _tablename_ = "document" + id = Column(Integer, primary_key=True, index=True) + filename = Column(String(225), nullable=False, unique=True) + uploaded_by = Column( + Integer, + ForeignKey("users.id"), + primary_key=True, + nullable=False, + ) + uploaded_at = Column( + DateTime, + default=datetime.utcnow, + ) + uploaded_by_user = relationship("User", back_populates="uploaded_documents") \ No newline at end of file diff --git a/private_gpt/users/models/user.py b/private_gpt/users/models/user.py index 985d834..6243ebc 100644 --- a/private_gpt/users/models/user.py +++ b/private_gpt/users/models/user.py @@ -36,7 +36,7 @@ class User(Base): company_id = Column(Integer, ForeignKey("companies.id"), nullable=True) company = relationship("Company", back_populates="users") - + uploaded_documents = relationship("Documents", back_populates="uploaded_by_user") user_role = relationship( "UserRole", back_populates="user", uselist=False, cascade="all, delete-orphan") diff --git a/private_gpt/users/schemas/__init__.py b/private_gpt/users/schemas/__init__.py index deb1a6a..cc14f1d 100644 --- a/private_gpt/users/schemas/__init__.py +++ b/private_gpt/users/schemas/__init__.py @@ -1,6 +1,6 @@ from .role import Role, RoleCreate, RoleInDB, RoleUpdate from .token import TokenSchema, TokenPayload -from .user import User, UserCreate, UserInDB, UserUpdate, UserBaseSchema, Profile, UsernameUpdate, DeleteUser, AdminUpdate +from .user import User, UserCreate, UserInDB, UserUpdate, UserBaseSchema, Profile, UsernameUpdate, DeleteUser, UserAdminUpdate from .user_role import UserRole, UserRoleCreate, UserRoleInDB, UserRoleUpdate from .subscription import Subscription, SubscriptionBase, SubscriptionCreate, SubscriptionUpdate -from .company import Company, CompanyBase, CompanyCreate, CompanyUpdate \ No newline at end of file +from .company import Company, CompanyBase, CompanyCreate, CompanyUpdate diff --git a/private_gpt/users/schemas/documents.py b/private_gpt/users/schemas/documents.py new file mode 100644 index 0000000..4feb17e --- /dev/null +++ b/private_gpt/users/schemas/documents.py @@ -0,0 +1,24 @@ +from pydantic import BaseModel +from datetime import datetime +from typing import Optional + + +class DocumentsBase(BaseModel): + filename: str + + +class DocumentCreate(DocumentsBase): + uploaded_by: int + + +class DocumentUpdate(DocumentsBase): + pass + + +class Document(DocumentsBase): + id: int + uploaded_by: int + uploaded_at: datetime + + class Config: + orm_mode = True diff --git a/private_gpt/users/schemas/user.py b/private_gpt/users/schemas/user.py index e932768..05ad7aa 100644 --- a/private_gpt/users/schemas/user.py +++ b/private_gpt/users/schemas/user.py @@ -6,6 +6,7 @@ from pydantic import BaseModel, Field, EmailStr from private_gpt.users.schemas.user_role import UserRole from private_gpt.users.schemas.company import Company + class UserBaseSchema(BaseModel): email: EmailStr fullname: str @@ -14,20 +15,23 @@ class UserBaseSchema(BaseModel): class Config: arbitrary_types_allowed = True + class UserCreate(UserBaseSchema): password: str = Field(alias="password") + class UsernameUpdate(BaseModel): fullname: str + class UserUpdate(UserBaseSchema): last_login: Optional[datetime] = None class UserLoginSchema(BaseModel): email: EmailStr = Field(alias="email") - password: str - + password: str + class Config: arbitrary_types_allowed = True @@ -44,10 +48,14 @@ class UserSchema(UserBaseSchema): orm_mode = True # Additional properties to return via API + + class User(UserSchema): pass # Additional properties stored in DB + + class UserInDB(UserSchema): hashed_password: str @@ -55,9 +63,12 @@ class UserInDB(UserSchema): class Profile(UserBaseSchema): role: str + class DeleteUser(BaseModel): id: int -class AdminUpdate(BaseModel): + +class UserAdminUpdate(BaseModel): + id: int fullname: str - role: int \ No newline at end of file + role: str diff --git a/private_gpt/users/schemas/user_role.py b/private_gpt/users/schemas/user_role.py index 65894f5..5dd5f4d 100644 --- a/private_gpt/users/schemas/user_role.py +++ b/private_gpt/users/schemas/user_role.py @@ -4,6 +4,8 @@ from private_gpt.users.schemas.role import Role from pydantic import BaseModel # Shared properties + + class UserRoleBase(BaseModel): user_id: Optional[int] role_id: Optional[int] @@ -18,12 +20,16 @@ class UserRoleCreate(UserRoleBase): pass # Properties to receive via API on update + + class UserRoleUpdate(BaseModel): + user_id: int role_id: int class Config: arbitrary_types_allowed = True - + + class UserRoleInDBBase(UserRoleBase): role: Role