1+ """
2+ Authentication CLI commands.
3+
4+ Command-line interface for auth service management tasks.
5+ """
6+
7+ import secrets
8+ import string
9+ from typing import TYPE_CHECKING
10+
11+ if TYPE_CHECKING:
12+ pass
13+
14+ import typer
15+
16+ from app.core.db import db_session
17+ from app.models.user import UserCreate
18+ from app.services.auth.user_service import UserService
19+
20+ app = typer.Typer(help="Authentication management commands")
21+
22+
23+ def generate_password(length: int = 12) -> str:
24+ """Generate a secure random password."""
25+ alphabet = string.ascii_letters + string.digits + "!@#$%^& *"
26+ return "".join(secrets.choice(alphabet) for _ in range(length))
27+
28+
29+ def find_next_available_email(
30+ user_service: UserService, prefix: str = "test", domain: str = "example.com"
31+ ) -> str:
32+ """Find the next available email with auto-increment."""
33+ # Get existing emails with this prefix
34+ existing_emails = user_service.find_existing_emails_with_prefix(prefix, domain)
35+
36+ if not existing_emails:
37+ # No existing emails, start with prefix@domain
38+ return f"{prefix}@{domain}"
39+
40+ # Extract numbers from existing emails
41+ used_numbers = set()
42+ for email in existing_emails:
43+ # Extract the part before @
44+ local_part = email.split("@")[0]
45+
46+ # Check if it matches our pattern (prefix + optional number)
47+ if local_part == prefix:
48+ used_numbers.add(0) # Base email without number
49+ elif local_part.startswith(prefix):
50+ suffix = local_part[len(prefix):]
51+ if suffix.isdigit():
52+ used_numbers.add(int(suffix))
53+
54+ # Find the next available number
55+ counter = 0
56+ while counter in used_numbers:
57+ counter += 1
58+
59+ # Return the email with the next number
60+ if counter == 0:
61+ return f"{prefix}@{domain}"
62+ else:
63+ return f"{prefix}{counter}@{domain}"
64+
65+
66+ @app.command()
67+ def create_test_user(
68+ email: str | None = typer.Option(
69+ None,
70+ help=(
71+ "User email address (auto-increment: test@example.com, "
72+ "test1@example.com, etc.)"
73+ ),
74+ ),
75+ password: str | None = typer.Option(
76+ None, help="User password (generated if not provided)"
77+ ),
78+ full_name: str | None = typer.Option(None, help="User full name"),
79+ prefix: str = typer.Option("test", help="Email prefix for auto-generated emails"),
80+ domain: str = typer.Option(
81+ "example.com", help="Email domain for auto-generated emails"
82+ ),
83+ ) -> None:
84+ """Create a test user for development and testing."""
85+
86+ # Generate password if not provided
87+ if password is None:
88+ password = generate_password()
89+ generated_password = True
90+ else:
91+ generated_password = False
92+
93+ try:
94+ with db_session() as session:
95+ user_service = UserService(session)
96+
97+ # Auto-generate email if not provided
98+ if email is None:
99+ email = find_next_available_email(user_service, prefix, domain)
100+ typer.echo(f"📧 Auto-generated email: {email} (next in sequence)")
101+
102+ # Check if user already exists
103+ existing_user = user_service.get_user_by_email(email)
104+ if existing_user:
105+ typer.echo(f"❌ User with email '{email}' already exists", err=True)
106+ raise typer.Exit(1)
107+
108+ # Create user data
109+ user_data = UserCreate(
110+ email=email,
111+ password=password,
112+ full_name=full_name,
113+ )
114+
115+ # Create the user
116+ user = user_service.create_user(user_data)
117+
118+ # Display success message
119+ typer.echo("✅ Test user created successfully!")
120+ typer.echo("=" * 50)
121+ typer.echo(f"📧 Email: {user.email}")
122+ typer.echo(f"🔑 Password: {password}")
123+ if user.full_name:
124+ typer.echo(f"👤 Name: {user.full_name}")
125+ typer.echo(f"🆔 User ID: {user.id}")
126+ typer.echo("=" * 50)
127+
128+ if generated_password:
129+ typer.echo("💡 Password was auto-generated. Save it for testing!")
130+
131+ typer.echo("🚀 Ready to test auth endpoints at http://localhost:8000/docs")
132+
133+ except Exception as e:
134+ typer.echo(f"❌ Failed to create test user: {str(e)}", err=True)
135+ raise typer.Exit(1)
136+
137+
138+ @app.command()
139+ def create_test_users(
140+ count: int = typer.Option(5, help="Number of test users to create"),
141+ prefix: str = typer.Option("test", help="Email prefix for generated users"),
142+ domain: str = typer.Option("example.com", help="Email domain for generated users"),
143+ password: str | None = typer.Option(
144+ None, help="Shared password (generated if not provided)"
145+ ),
146+ ) -> None:
147+ """Create multiple test users for development and testing."""
148+
149+ # Generate password if not provided
150+ if password is None:
151+ password = generate_password()
152+ generated_password = True
153+ else:
154+ generated_password = False
155+
156+ if count <= 0:
157+ typer.echo("❌ Count must be greater than 0", err=True)
158+ raise typer.Exit(1)
159+
160+ try:
161+ with db_session() as session:
162+ user_service = UserService(session)
163+ created_users = []
164+
165+ typer.echo(f"🚀 Creating {count} test users with prefix '{prefix}'...")
166+ typer.echo("=" * 60)
167+
168+ for i in range(count):
169+ # Find next available email
170+ email = find_next_available_email(user_service, prefix, domain)
171+
172+ # Create user data
173+ user_data = UserCreate(
174+ email=email,
175+ password=password,
176+ full_name=f"Test User {email.split('@')[0].capitalize()}",
177+ )
178+
179+ # Create the user
180+ user = user_service.create_user(user_data)
181+ created_users.append(user)
182+
183+ typer.echo(f"✅ Created: {user.email} (ID: {user.id})")
184+
185+ # Display summary
186+ typer.echo("=" * 60)
187+ typer.echo(f"🎉 Successfully created {len(created_users)} test users!")
188+ typer.echo(f"🔑 Shared password: {password}")
189+
190+ if generated_password:
191+ typer.echo("💡 Password was auto-generated. Save it for testing!")
192+
193+ typer.echo("🚀 Ready to test auth endpoints at http://localhost:8000/docs")
194+
195+ except Exception as e:
196+ typer.echo(f"❌ Failed to create test users: {str(e)}", err=True)
197+ raise typer.Exit(1)
198+
199+
200+ @app.command()
201+ def list_users() -> None:
202+ """List all users in the system."""
203+ try:
204+ with db_session() as session:
205+ user_service = UserService(session)
206+ users = user_service.list_users()
207+
208+ if not users:
209+ typer.echo("No users found.")
210+ return
211+
212+ typer.echo(f"Found {len(users)} user(s):")
213+ typer.echo("=" * 60)
214+
215+ for user in users:
216+ typer.echo(f"🆔 ID: {user.id}")
217+ typer.echo(f"📧 Email: {user.email}")
218+ if user.full_name:
219+ typer.echo(f"👤 Name: {user.full_name}")
220+ typer.echo(f"📅 Created: {user.created_at}")
221+ typer.echo("-" * 40)
222+
223+ except Exception as e:
224+ typer.echo(f"❌ Failed to list users: {str(e)}", err=True)
225+ raise typer.Exit(1)
226+
227+
228+ if __name__ == "__main__":
229+ app()
0 commit comments