Skip to content

Commit

Permalink
Merge pull request #456 from changelia/feature/user-account-access
Browse files Browse the repository at this point in the history
Feature/user account access
  • Loading branch information
Chkhikvadze authored May 30, 2024
2 parents 2eeb0a2 + 7c71151 commit 7d9ca8c
Show file tree
Hide file tree
Showing 26 changed files with 1,326 additions and 8 deletions.
231 changes: 231 additions & 0 deletions apps/server/controllers/user_account_access.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
from fastapi_sqlalchemy import db
from fastapi import APIRouter, Depends, HTTPException, status
import services.auth as auth_service
from typings.user import UserInput
from models.user import UserModel
from models.user_account_access import UserAccountAccessModel
from models.user_account import UserAccountModel
from utils.auth import authenticate
from typings.auth import UserAccount
from typings.user_account_access import (
GetUserAccountAccessOutput,
UserAccountAccessInput,
UserAccountAccessDbInput,
UserAccountAccessOutput,
SharedUserAccountAccessOutput
)
from exceptions import UserAccessNotFoundException
from utils.user_account_access import (
generate_random_string,
convert_user_access_to_list,
shared_user_access_to_list
)

router = APIRouter()


@router.post("", response_model=UserAccountAccessOutput, status_code=201)
def create_user_account_access(
input: UserAccountAccessInput,
auth: UserAccount = Depends(authenticate)
):
"""
Create a new user_account_access with configurations.
Args:
user_account_access (UserAccountAccessInput): Data for creating a new user_account_access with configurations.
Returns:
Message: A message indicating that the user_account_access was successfully created.
"""
try:
body = input.dict()

current_user_account = UserAccountModel.get_user_account_by_user_id(
db=db,
user_id=auth.user.id
)

existing_user = UserModel.get_user_by_email(db=db, email=body['email'])

if not existing_user:
random_pass = generate_random_string(8)
print('random_pass', random_pass)
user_input = UserInput(**{
'name': body['email'],
'email': body['email'],
'account_name': 'Account Name',
'password': random_pass,
'avatar': ""
})

response = auth_service.register(input=user_input)
create_user_account_access_input = UserAccountAccessDbInput(
assigned_user_id=response['user'].id,
assigned_account_id=response['account'].id
)

response = UserAccountAccessModel.create_user_account_access(
db=db,
user_account_access=create_user_account_access_input,
user=auth.user,
account_id=current_user_account.account_id
)
if not response:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="User access not successfully added"
)

return {
"success": True,
"message": "User access successfully added"
}
else:
user_account = UserAccountModel.get_user_account_by_user_id(
db=db,
user_id=existing_user.id
)

if UserAccountAccessModel.get_user_account_access_assigned(
db=db,
assigner_user_id=user_account.user_id,
assigned_account_id=user_account.account_id,
account_id=current_user_account.account_id
):
return {
'success': False,
'message': 'User access already exists'
}

create_user_account_access_input = UserAccountAccessDbInput(
assigned_user_id=user_account.user_id,
assigned_account_id=user_account.account_id
)

response = UserAccountAccessModel.create_user_account_access(
db=db,
user_account_access=create_user_account_access_input,
user=auth.user,
account_id=current_user_account.account_id
)

if response is None:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="User access not successfully added"
)

return {"success": True, "message": "User access successfully added"}
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e)
)


@router.get(
"",
response_model=list[GetUserAccountAccessOutput],
status_code=200
)
def get_user_account_access(auth: UserAccount = Depends(authenticate)):
"""
Get user account access data.
Args:
auth (UserAccount): User account object obtained from authentication.
Returns:
UserAccountAccess: User account access data.
"""
try:
data = UserAccountAccessModel.get_user_account_access(
db=db,
account=auth.account,
user=auth.user
)

result = convert_user_access_to_list(data)

return result
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e)
)


@router.delete(
"/{user_account_access_id}",
response_model=UserAccountAccessOutput,
status_code=201
)
def delete_user_account_access(
user_account_access_id: str,
auth: UserAccount = Depends(authenticate)
):
"""
Delete user account access data.
Args:
auth (UserAccount): User account object obtained from authentication.
Returns:
UserAccountAccess: User account access data.
"""
try:
current_user_account = UserAccountModel.get_user_account_by_user_id(
db=db,
user_id=auth.user.id
)

UserAccountAccessModel.delete_user_account_access_by_id(
db=db,
user_account_access_id=user_account_access_id,
account_id=current_user_account.account_id
)

return {"success": True, "message": "User access successfully deleted"}
except UserAccessNotFoundException:
raise HTTPException(status_code=404, detail="User access not found")

except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e)
)


@router.get(
"/access",
response_model=list[SharedUserAccountAccessOutput],
status_code=200
)
def get_shared_user_account_access(auth: UserAccount = Depends(authenticate)):
"""_summary_
Args:
auth (UserAccount, optional): _description_. Defaults to Depends(authenticate).
Raises:
HTTPException: _description_
Returns:
_type_: _description_
"""
try:
user_access = UserAccountAccessModel.get_shared_user_account_access(
db=db,
account=auth.account,
user=auth.user
)

result = shared_user_access_to_list(user_access)

return result
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e)
)
8 changes: 8 additions & 0 deletions apps/server/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,11 @@ class TranscriberException(AppBaseException):

class SynthesizerException(AppBaseException):
pass


class UserAccessNotFoundException(DatasourceException):
pass


class UserAccountAccessException(AppBaseException):
pass
2 changes: 2 additions & 0 deletions apps/server/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from controllers.tool import router as tool_router
from controllers.voice import router as voice_router
from controllers.workspace import router as workspace_router
from controllers.user_account_access import router as user_account_access_router
from models import * # noqa: F403
from models.db import Base, engine # noqa: F401
from resolvers.account import AccountMutation, AccountQuery
Expand Down Expand Up @@ -133,6 +134,7 @@ def jwt_exception_handler(request: Request, exc: AuthJWTException):
app.include_router(integrations_router, prefix="/integrations", include_in_schema=False)
app.include_router(fine_tuning_router, prefix="/fine-tuning", include_in_schema=False)
app.include_router(voice_router, prefix="/voice", include_in_schema=True)
app.include_router(user_account_access_router, prefix="/user-account-access", include_in_schema=False)


@app.get("/", include_in_schema=False)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Add user_account_access table
Revision ID: 29f8e16f34db
Revises:
Create Date: 2024-05-28 14:27:22.559514
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = '29f8e16f34db'
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('user_account_access',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('assigned_user_id', sa.UUID(), nullable=True),
sa.Column('account_id', sa.UUID(), nullable=True),
sa.Column('is_deleted', sa.Boolean(), nullable=True),
sa.Column('created_by', sa.UUID(), nullable=True),
sa.Column('modified_by', sa.UUID(), nullable=True),
sa.Column('created_on', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_on', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['account_id'], ['account.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['assigned_user_id'], ['user.id'], name='fk_assigned_user_id', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['created_by'], ['user.id'], name='fk_created_by', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['modified_by'], ['user.id'], name='fk_modified_by', ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_user_account_access_assigned_user_id'), 'user_account_access', ['assigned_user_id'], unique=False)
op.create_index(op.f('ix_user_account_access_created_by'), 'user_account_access', ['created_by'], unique=False)
op.create_index(op.f('ix_user_account_access_id'), 'user_account_access', ['id'], unique=False)
op.create_index(op.f('ix_user_account_access_is_deleted'), 'user_account_access', ['is_deleted'], unique=False)
op.create_index(op.f('ix_user_account_access_modified_by'), 'user_account_access', ['modified_by'], unique=False)
op.alter_column('run_log', 'toolkit_id',
existing_type=sa.VARCHAR(length=255),
type_=sa.UUID(),
existing_nullable=True)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('run_log', 'toolkit_id',
existing_type=sa.UUID(),
type_=sa.VARCHAR(length=255),
existing_nullable=True)
op.drop_index(op.f('ix_user_account_access_modified_by'), table_name='user_account_access')
op.drop_index(op.f('ix_user_account_access_is_deleted'), table_name='user_account_access')
op.drop_index(op.f('ix_user_account_access_id'), table_name='user_account_access')
op.drop_index(op.f('ix_user_account_access_created_by'), table_name='user_account_access')
op.drop_index(op.f('ix_user_account_access_assigned_user_id'), table_name='user_account_access')
op.drop_table('user_account_access')
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Add assigned_account_id in user_account_access table
Revision ID: f1d5bc37bceb
Revises: 29f8e16f34db
Create Date: 2024-05-28 15:01:07.211417
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = 'f1d5bc37bceb'
down_revision: Union[str, None] = '29f8e16f34db'
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('user_account_access', sa.Column('assigned_account_id', sa.UUID(), nullable=True))
op.create_index(op.f('ix_user_account_access_assigned_account_id'), 'user_account_access', ['assigned_account_id'], unique=False)
op.create_foreign_key('fk_assigned_account_id', 'user_account_access', 'account', ['assigned_account_id'], ['id'], ondelete='CASCADE')
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('fk_assigned_account_id', 'user_account_access', type_='foreignkey')
op.drop_index(op.f('ix_user_account_access_assigned_account_id'), table_name='user_account_access')
op.drop_column('user_account_access', 'assigned_account_id')
# ### end Alembic commands ###
24 changes: 23 additions & 1 deletion apps/server/models/user_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class UserAccountModel(BaseModel):
def __repr__(self) -> str:
return (
f"User(id={self.id}, "
f"user_id='{self.user_id}', account_id='{self.account_id}', role_id='{self.role_id}')"
f"user_id='{self.user_id}', account_id='{self.account_id}', ')" # TODO add role_id='{self.role_id}
)

@classmethod
Expand Down Expand Up @@ -144,7 +144,29 @@ def get_user_account_by_id(cls, db, user_account_id, account):
.first()
)
return user_accounts

@classmethod
def get_user_account_by_user_id(cls, db, user_id):
"""_summary_
Args:
db (_type_): _description_
user_id (_type_): _description_
account (_type_): _description_
Raises:
UserAccountNotFoundException: _description_
"""

user_account = (
db.session.query(UserAccountModel)
.filter(
UserAccountModel.user_id == user_id,
)
.first()
)
return user_account

@classmethod
def delete_by_id(cls, db, user_account_id):
db_user_account = (
Expand Down
Loading

0 comments on commit 7d9ca8c

Please sign in to comment.