diff --git a/app/factories/__init__.py b/app/factories/__init__.py new file mode 100644 index 0000000..7242216 --- /dev/null +++ b/app/factories/__init__.py @@ -0,0 +1,33 @@ +import random +import string + + +class Factory: + model = None + + def definition(self) -> dict: + raise NotImplementedError + + def make(self, **overrides) -> dict: + data = self.definition() + data.update(overrides) + return {k: v for k, v in data.items() if v is not None} + + def create(self, db, **overrides): + data = self.make(**overrides) + obj = self.model(**data) + db.add(obj) + db.flush() + return obj + + +def random_string(length: int = 10) -> str: + return "".join(random.choices(string.ascii_lowercase, k=length)) + + +def random_email() -> str: + return f"{random_string(8)}@example.com" + + +def random_phone() -> str: + return f"08{random.randint(100000000, 999999999)}" diff --git a/app/factories/business_type_factory.py b/app/factories/business_type_factory.py new file mode 100644 index 0000000..13b8794 --- /dev/null +++ b/app/factories/business_type_factory.py @@ -0,0 +1,15 @@ +from app.factories import Factory, random_string +from app.models.business_type import BusinessType + + +class BusinessTypeFactory(Factory): + model = BusinessType + + def definition(self) -> dict: + return { + "id": None, + "code": random_string(8), + "name": f"Business {random_string(6).title()}", + "description": None, + "is_active": True, + } diff --git a/app/factories/plan_factory.py b/app/factories/plan_factory.py new file mode 100644 index 0000000..40a43eb --- /dev/null +++ b/app/factories/plan_factory.py @@ -0,0 +1,17 @@ +from app.factories import Factory, random_string +from app.models.plan import Plan + + +class PlanFactory(Factory): + model = Plan + + def definition(self) -> dict: + return { + "id": None, + "code": random_string(6), + "name": f"Plan {random_string(6).title()}", + "description": None, + "price": 0, + "limits": None, + "is_active": True, + } diff --git a/app/factories/subscription_factory.py b/app/factories/subscription_factory.py new file mode 100644 index 0000000..9d3b516 --- /dev/null +++ b/app/factories/subscription_factory.py @@ -0,0 +1,19 @@ +from datetime import datetime, timedelta + +from app.factories import Factory +from app.models.subscription import Subscription + + +class SubscriptionFactory(Factory): + model = Subscription + + def definition(self) -> dict: + return { + "id": None, + "tenant_id": None, + "plan_id": None, + "limits": None, + "status": "TRIAL", + "started_at": None, + "ended_at": None, + } diff --git a/app/factories/tenant_factory.py b/app/factories/tenant_factory.py new file mode 100644 index 0000000..dfe7ec8 --- /dev/null +++ b/app/factories/tenant_factory.py @@ -0,0 +1,19 @@ +from app.factories import Factory, random_phone, random_string +from app.models.tenant import Tenant + + +class TenantFactory(Factory): + model = Tenant + + def definition(self) -> dict: + slug = random_string(10) + return { + "id": None, + "name": f"Tenant {slug.title()}", + "slug": slug, + "business_type_id": None, + "phone": random_phone(), + "address": None, + "email": None, + "status": "ACTIVE", + } diff --git a/app/factories/user_factory.py b/app/factories/user_factory.py new file mode 100644 index 0000000..6e62272 --- /dev/null +++ b/app/factories/user_factory.py @@ -0,0 +1,18 @@ +from app.factories import Factory, random_email, random_string +from app.models.user import User +from app.security import hash_password + + +class UserFactory(Factory): + model = User + + def definition(self) -> dict: + return { + "id": None, + "email": random_email(), + "username": random_string(12), + "password": hash_password("password123"), + "email_verified_at": None, + "tenant_id": None, + "status": "ACTIVE", + } diff --git a/app/factories/user_profile_factory.py b/app/factories/user_profile_factory.py new file mode 100644 index 0000000..3c4fb80 --- /dev/null +++ b/app/factories/user_profile_factory.py @@ -0,0 +1,15 @@ +from app.factories import Factory, random_phone, random_string +from app.models.user_profile import UserProfile + + +class UserProfileFactory(Factory): + model = UserProfile + + def definition(self) -> dict: + return { + "id": None, + "user_id": None, + "full_name": f"{random_string(6).title()} {random_string(8).title()}", + "phone": random_phone(), + "timezone": "Asia/Jakarta", + } diff --git a/app/seeders/__init__.py b/app/seeders/__init__.py new file mode 100644 index 0000000..2e7d164 --- /dev/null +++ b/app/seeders/__init__.py @@ -0,0 +1,3 @@ +class Seeder: + def run(self, db): + raise NotImplementedError diff --git a/app/seeders/business_type_seeder.py b/app/seeders/business_type_seeder.py new file mode 100644 index 0000000..0221992 --- /dev/null +++ b/app/seeders/business_type_seeder.py @@ -0,0 +1,9 @@ +from app.factories.business_type_factory import BusinessTypeFactory + + +def seed_business_types(db, count: int = 5): + result = [] + for i in range(count): + bt = BusinessTypeFactory().create(db) + result.append(bt) + return result diff --git a/app/seeders/database_seeder.py b/app/seeders/database_seeder.py new file mode 100644 index 0000000..db66f9d --- /dev/null +++ b/app/seeders/database_seeder.py @@ -0,0 +1,44 @@ +from sqlalchemy.orm import Session + +from app.database import SessionLocal +from app.seeders.business_type_seeder import seed_business_types +from app.seeders.plan_seeder import seed_plans +from app.seeders.subscription_seeder import seed_subscriptions +from app.seeders.tenant_seeder import seed_tenants +from app.seeders.user_seeder import seed_users + + +def seed_all(db: Session, count: int = 5): + business_types = seed_business_types(db, count) + plans = seed_plans(db, count) + + bt_ids = [bt.id for bt in business_types] + tenants = seed_tenants(db, count, business_type_ids=bt_ids) + + plan_ids = [p.id for p in plans] + tenant_ids = [t.id for t in tenants] + seed_subscriptions(db, count, tenant_ids=tenant_ids, plan_ids=plan_ids) + + users = seed_users(db, count) + + db.commit() + + return { + "business_types": len(business_types), + "plans": len(plans), + "tenants": len(tenants), + "subscriptions": count, + "users": len(users), + "user_profiles": len(users), + } + + +def seed_database(count: int = 5): + db = SessionLocal() + try: + result = seed_all(db, count) + print("Database seeding completed:") + for model, total in result.items(): + print(f" - {model}: {total}") + finally: + db.close() diff --git a/app/seeders/plan_seeder.py b/app/seeders/plan_seeder.py new file mode 100644 index 0000000..4ee97d4 --- /dev/null +++ b/app/seeders/plan_seeder.py @@ -0,0 +1,9 @@ +from app.factories.plan_factory import PlanFactory + + +def seed_plans(db, count: int = 5): + result = [] + for i in range(count): + plan = PlanFactory().create(db) + result.append(plan) + return result diff --git a/app/seeders/subscription_seeder.py b/app/seeders/subscription_seeder.py new file mode 100644 index 0000000..f7c5844 --- /dev/null +++ b/app/seeders/subscription_seeder.py @@ -0,0 +1,14 @@ +from app.factories.subscription_factory import SubscriptionFactory + + +def seed_subscriptions(db, count: int = 5, tenant_ids: list[str] | None = None, plan_ids: list[str] | None = None): + result = [] + for i in range(count): + kwargs = {} + if tenant_ids: + kwargs["tenant_id"] = tenant_ids[i % len(tenant_ids)] + if plan_ids: + kwargs["plan_id"] = plan_ids[i % len(plan_ids)] + sub = SubscriptionFactory().create(db, **kwargs) + result.append(sub) + return result diff --git a/app/seeders/tenant_seeder.py b/app/seeders/tenant_seeder.py new file mode 100644 index 0000000..3351723 --- /dev/null +++ b/app/seeders/tenant_seeder.py @@ -0,0 +1,12 @@ +from app.factories.tenant_factory import TenantFactory + + +def seed_tenants(db, count: int = 5, business_type_ids: list[str] | None = None): + result = [] + for i in range(count): + kwargs = {} + if business_type_ids: + kwargs["business_type_id"] = business_type_ids[i % len(business_type_ids)] + tenant = TenantFactory().create(db, **kwargs) + result.append(tenant) + return result diff --git a/app/seeders/user_seeder.py b/app/seeders/user_seeder.py new file mode 100644 index 0000000..06dfd35 --- /dev/null +++ b/app/seeders/user_seeder.py @@ -0,0 +1,47 @@ +from app.factories.user_factory import UserFactory +from app.models.user import User +from app.models.user_profile import UserProfile +from app.security import hash_password + + +def seed_users(db, count: int = 5, tenant_ids: list[str] | None = None): + result = [] + for i in range(count): + kwargs = {} + if tenant_ids: + kwargs["tenant_id"] = tenant_ids[i % len(tenant_ids)] + user = UserFactory().create(db, **kwargs) + profile = UserProfile( + user_id=user.id, + full_name="User Default", + phone="081234567890", + timezone="Asia/Jakarta", + ) + db.add(profile) + result.append(user) + + admin = db.query(User).filter( + (User.email == "project.pangestuyoga@gmail.com") | (User.username == "pangestu") + ).first() + if not admin: + admin = User( + email="project.pangestuyoga@gmail.com", + username="pangestu", + password=hash_password("Minimal8@"), + status="ACTIVE", + ) + db.add(admin) + db.flush() + + admin_profile = UserProfile( + user_id=admin.id, + full_name="Pangestu Yoga", + phone="081234567890", + timezone="Asia/Jakarta", + ) + db.add(admin_profile) + else: + admin.password = hash_password("Minimal8@") + result.append(admin) + + return result diff --git a/seed.py b/seed.py new file mode 100644 index 0000000..d87cc5e --- /dev/null +++ b/seed.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +import argparse + +from app.seeders.database_seeder import seed_database + + +def main(): + parser = argparse.ArgumentParser(description="Seed the database with sample data") + parser.add_argument( + "--count", "-c", + type=int, + default=5, + help="Number of records to seed for each model (default: 5)", + ) + args = parser.parse_args() + + seed_database(args.count) + + +if __name__ == "__main__": + main()