From c1da9cd0bd745fea67aae0250d8ef3ad520db835 Mon Sep 17 00:00:00 2001 From: Babak Alizadeh Date: Fri, 19 Sep 2025 14:34:43 +0330 Subject: [PATCH] progress in permissions --- hesabixAPI/adapters/api/v1/auth.py | 2 +- hesabixAPI/adapters/db/models/__init__.py | 1 + .../adapters/db/models/business_permission.py | 19 +++++++++ hesabixAPI/adapters/db/models/user.py | 4 +- .../adapters/db/repositories/user_repo.py | 21 +++++++++- hesabixAPI/hesabix_api.egg-info/SOURCES.txt | 3 ++ ...117_000006_add_app_permissions_to_users.py | 28 +++++++++++++ ...00007_create_business_permissions_table.py | 42 +++++++++++++++++++ 8 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 hesabixAPI/adapters/db/models/business_permission.py create mode 100644 hesabixAPI/migrations/versions/20250117_000006_add_app_permissions_to_users.py create mode 100644 hesabixAPI/migrations/versions/20250117_000007_create_business_permissions_table.py diff --git a/hesabixAPI/adapters/api/v1/auth.py b/hesabixAPI/adapters/api/v1/auth.py index a2c48a6..be582c2 100644 --- a/hesabixAPI/adapters/api/v1/auth.py +++ b/hesabixAPI/adapters/api/v1/auth.py @@ -51,7 +51,7 @@ def register(request: Request, payload: RegisterRequest, db: Session = Depends(g api_repo.create_session_key(user_id=user_id, key_hash=key_hash, device_id=payload.device_id, user_agent=user_agent, ip=ip, expires_at=None) from adapters.db.models.user import User user_obj = db.get(User, user_id) - user = {"id": user_id, "first_name": payload.first_name, "last_name": payload.last_name, "email": payload.email, "mobile": payload.mobile, "referral_code": getattr(user_obj, "referral_code", None)} + user = {"id": user_id, "first_name": payload.first_name, "last_name": payload.last_name, "email": payload.email, "mobile": payload.mobile, "referral_code": getattr(user_obj, "referral_code", None), "app_permissions": getattr(user_obj, "app_permissions", None)} response_data = {"api_key": api_key, "expires_at": None, "user": user} formatted_data = format_datetime_fields(response_data, request) return success_response(formatted_data, request) diff --git a/hesabixAPI/adapters/db/models/__init__.py b/hesabixAPI/adapters/db/models/__init__.py index ad0fe75..51715b5 100644 --- a/hesabixAPI/adapters/db/models/__init__.py +++ b/hesabixAPI/adapters/db/models/__init__.py @@ -6,5 +6,6 @@ from .api_key import ApiKey # noqa: F401 from .captcha import Captcha # noqa: F401 from .password_reset import PasswordReset # noqa: F401 from .business import Business # noqa: F401 +from .business_permission import BusinessPermission # noqa: F401 diff --git a/hesabixAPI/adapters/db/models/business_permission.py b/hesabixAPI/adapters/db/models/business_permission.py new file mode 100644 index 0000000..fcac273 --- /dev/null +++ b/hesabixAPI/adapters/db/models/business_permission.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from datetime import datetime + +from sqlalchemy import Integer, ForeignKey, JSON, DateTime +from sqlalchemy.orm import Mapped, mapped_column + +from adapters.db.session import Base + + +class BusinessPermission(Base): + __tablename__ = "business_permissions" + + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + business_id: Mapped[int] = mapped_column(Integer, ForeignKey("businesses.id", ondelete="CASCADE"), nullable=False, index=True) + user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True) + business_permissions: Mapped[dict | None] = mapped_column(JSON, nullable=True) + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False) + updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False) diff --git a/hesabixAPI/adapters/db/models/user.py b/hesabixAPI/adapters/db/models/user.py index 9403e64..8c1082f 100644 --- a/hesabixAPI/adapters/db/models/user.py +++ b/hesabixAPI/adapters/db/models/user.py @@ -2,7 +2,7 @@ from __future__ import annotations from datetime import datetime -from sqlalchemy import String, DateTime, Boolean, Integer, ForeignKey +from sqlalchemy import String, DateTime, Boolean, Integer, ForeignKey, JSON from sqlalchemy.orm import Mapped, mapped_column from adapters.db.session import Base @@ -21,6 +21,8 @@ class User(Base): # Marketing/Referral fields referral_code: Mapped[str] = mapped_column(String(32), unique=True, index=True, nullable=False) referred_by_user_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True) + # App permissions + app_permissions: Mapped[dict | None] = mapped_column(JSON, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False) diff --git a/hesabixAPI/adapters/db/repositories/user_repo.py b/hesabixAPI/adapters/db/repositories/user_repo.py index f75faa5..0b8ec1a 100644 --- a/hesabixAPI/adapters/db/repositories/user_repo.py +++ b/hesabixAPI/adapters/db/repositories/user_repo.py @@ -26,8 +26,26 @@ class UserRepository(BaseRepository[User]): stmt = select(User).where(User.referral_code == referral_code) return self.db.execute(stmt).scalars().first() + def is_first_user(self) -> bool: + """بررسی اینکه آیا این اولین کاربر سیستم است یا نه""" + stmt = select(func.count()).select_from(User) + count = self.db.execute(stmt).scalar() or 0 + return count == 0 + def create(self, *, email: str | None, mobile: str | None, password_hash: str, first_name: str | None, last_name: str | None, referral_code: str, referred_by_user_id: int | None = None) -> User: - user = User(email=email, mobile=mobile, password_hash=password_hash, first_name=first_name, last_name=last_name, referral_code=referral_code, referred_by_user_id=referred_by_user_id) + # تعیین دسترسی‌های برنامه بر اساس اینکه آیا کاربر اول است یا نه + app_permissions = {"superadmin": True} if self.is_first_user() else {} + + user = User( + email=email, + mobile=mobile, + password_hash=password_hash, + first_name=first_name, + last_name=last_name, + referral_code=referral_code, + referred_by_user_id=referred_by_user_id, + app_permissions=app_permissions + ) self.db.add(user) self.db.commit() self.db.refresh(user) @@ -85,6 +103,7 @@ class UserRepository(BaseRepository[User]): "is_active": user.is_active, "referral_code": user.referral_code, "referred_by_user_id": user.referred_by_user_id, + "app_permissions": user.app_permissions, "created_at": user.created_at, "updated_at": user.updated_at, } diff --git a/hesabixAPI/hesabix_api.egg-info/SOURCES.txt b/hesabixAPI/hesabix_api.egg-info/SOURCES.txt index e25de59..c2f0bee 100644 --- a/hesabixAPI/hesabix_api.egg-info/SOURCES.txt +++ b/hesabixAPI/hesabix_api.egg-info/SOURCES.txt @@ -12,6 +12,7 @@ adapters/db/session.py adapters/db/models/__init__.py adapters/db/models/api_key.py adapters/db/models/business.py +adapters/db/models/business_permission.py adapters/db/models/captcha.py adapters/db/models/password_reset.py adapters/db/models/user.py @@ -52,6 +53,8 @@ migrations/env.py migrations/versions/20250117_000003_add_business_table.py migrations/versions/20250117_000004_add_business_contact_fields.py migrations/versions/20250117_000005_add_business_geographic_fields.py +migrations/versions/20250117_000006_add_app_permissions_to_users.py +migrations/versions/20250117_000007_create_business_permissions_table.py migrations/versions/20250915_000001_init_auth_tables.py migrations/versions/20250916_000002_add_referral_fields.py tests/__init__.py diff --git a/hesabixAPI/migrations/versions/20250117_000006_add_app_permissions_to_users.py b/hesabixAPI/migrations/versions/20250117_000006_add_app_permissions_to_users.py new file mode 100644 index 0000000..8ea4e9d --- /dev/null +++ b/hesabixAPI/migrations/versions/20250117_000006_add_app_permissions_to_users.py @@ -0,0 +1,28 @@ +"""add app permissions to users + +Revision ID: 20250117_000006 +Revises: 20250117_000005 +Create Date: 2025-01-17 00:00:06.000000 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '20250117_000006' +down_revision = '20250117_000005' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('users', sa.Column('app_permissions', sa.JSON(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('users', 'app_permissions') + # ### end Alembic commands ### diff --git a/hesabixAPI/migrations/versions/20250117_000007_create_business_permissions_table.py b/hesabixAPI/migrations/versions/20250117_000007_create_business_permissions_table.py new file mode 100644 index 0000000..00a06d9 --- /dev/null +++ b/hesabixAPI/migrations/versions/20250117_000007_create_business_permissions_table.py @@ -0,0 +1,42 @@ +"""create business permissions table + +Revision ID: 20250117_000007 +Revises: 20250117_000006 +Create Date: 2025-01-17 00:00:07.000000 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '20250117_000007' +down_revision = '20250117_000006' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('business_permissions', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('business_id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('business_permissions', sa.JSON(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['business_id'], ['businesses.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_business_permissions_business_id'), 'business_permissions', ['business_id'], unique=False) + op.create_index(op.f('ix_business_permissions_user_id'), 'business_permissions', ['user_id'], unique=False) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_business_permissions_user_id'), table_name='business_permissions') + op.drop_index(op.f('ix_business_permissions_business_id'), table_name='business_permissions') + op.drop_table('business_permissions') + # ### end Alembic commands ###