webmoney/backend/app/Http/Controllers/Api/UserManagementController.php
marcoitaloesp-ai 3a336eb692
feat: Admin user management + SaaS limits tested v1.51.0
- Add UserManagementController@store for creating users
- Add POST /api/admin/users endpoint
- Support user types: Free, Pro, Admin
- Auto-create 100-year subscription for Pro/Admin users
- Add user creation modal to Users.jsx
- Complete SaaS limit testing:
  - Free user limits: 1 account, 10 categories, 3 budgets, 100 tx
  - Middleware blocks correctly at limits
  - Error messages are user-friendly
  - Usage stats API working correctly
- Update SAAS_STATUS.md with test results
- Bump version to 1.51.0
2025-12-17 15:22:01 +00:00

294 lines
10 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Plan;
use App\Models\Subscription;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
use Carbon\Carbon;
class UserManagementController extends Controller
{
/**
* Create a new user with specified role/plan
* user_type: 'free' | 'pro' | 'admin'
*/
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users,email',
'password' => 'sometimes|string|min:8',
'language' => 'sometimes|string|in:es,pt-BR,en',
'currency' => 'sometimes|string|size:3',
'user_type' => 'sometimes|string|in:free,pro,admin',
]);
// Generate random password if not provided
$password = $validated['password'] ?? bin2hex(random_bytes(8));
$userType = $validated['user_type'] ?? 'free';
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($password),
'language' => $validated['language'] ?? 'es',
'currency' => $validated['currency'] ?? 'EUR',
'email_verified_at' => now(), // Auto-verify admin-created users
'is_admin' => $userType === 'admin',
]);
$subscriptionInfo = null;
// Create Pro subscription if user_type is 'pro' or 'admin'
if (in_array($userType, ['pro', 'admin'])) {
$proPlan = Plan::where('slug', 'pro-annual')->first();
if ($proPlan) {
$subscription = Subscription::create([
'user_id' => $user->id,
'plan_id' => $proPlan->id,
'status' => Subscription::STATUS_ACTIVE,
'current_period_start' => now(),
'current_period_end' => now()->addYears(100), // "Lifetime" subscription
'paypal_subscription_id' => 'ADMIN_GRANTED_' . strtoupper(bin2hex(random_bytes(8))),
'paypal_status' => 'ACTIVE',
'price_paid' => 0,
'currency' => 'EUR',
]);
$subscriptionInfo = [
'plan' => $proPlan->name,
'status' => 'active',
'expires' => 'Nunca (otorgado por admin)',
];
}
}
return response()->json([
'success' => true,
'message' => 'Usuario creado correctamente',
'data' => [
'user' => [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'is_admin' => $user->is_admin,
],
'user_type' => $userType,
'subscription' => $subscriptionInfo,
'temporary_password' => isset($validated['password']) ? null : $password,
],
], 201);
}
/**
* List all users with their subscription info
*/
public function index(Request $request)
{
$query = User::with(['subscription.plan'])
->withCount(['accounts', 'categories', 'budgets', 'transactions']);
// Search
if ($request->has('search') && $request->search) {
$search = $request->search;
$query->where(function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%");
});
}
// Filter by subscription status
if ($request->has('subscription_status')) {
if ($request->subscription_status === 'active') {
$query->whereHas('subscription');
} elseif ($request->subscription_status === 'free') {
$query->whereDoesntHave('subscription');
}
}
// Pagination
$perPage = $request->get('per_page', 20);
$users = $query->orderBy('created_at', 'desc')->paginate($perPage);
// Transform data
$users->getCollection()->transform(function ($user) {
return [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'created_at' => $user->created_at,
'last_login_at' => $user->last_login_at,
'email_verified_at' => $user->email_verified_at,
'subscription' => $user->subscription ? [
'plan_name' => $user->subscription->plan->name ?? 'Unknown',
'plan_slug' => $user->subscription->plan->slug ?? 'unknown',
'status' => $user->subscription->status,
'current_period_end' => $user->subscription->current_period_end,
] : null,
'usage' => [
'accounts' => $user->accounts_count,
'categories' => $user->categories_count,
'budgets' => $user->budgets_count,
'transactions' => $user->transactions_count,
],
];
});
return response()->json([
'success' => true,
'data' => $users->items(),
'pagination' => [
'current_page' => $users->currentPage(),
'last_page' => $users->lastPage(),
'per_page' => $users->perPage(),
'total' => $users->total(),
],
]);
}
/**
* Get single user details
*/
public function show($id)
{
$user = User::with(['subscription.plan', 'subscriptions.plan'])
->withCount(['accounts', 'categories', 'budgets', 'transactions', 'goals'])
->findOrFail($id);
return response()->json([
'success' => true,
'data' => [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'language' => $user->language,
'currency' => $user->currency,
'created_at' => $user->created_at,
'last_login_at' => $user->last_login_at,
'email_verified_at' => $user->email_verified_at,
'subscription' => $user->subscription ? [
'id' => $user->subscription->id,
'plan' => $user->subscription->plan,
'status' => $user->subscription->status,
'paypal_subscription_id' => $user->subscription->paypal_subscription_id,
'current_period_start' => $user->subscription->current_period_start,
'current_period_end' => $user->subscription->current_period_end,
'canceled_at' => $user->subscription->canceled_at,
] : null,
'subscription_history' => $user->subscriptions->map(function ($sub) {
return [
'plan_name' => $sub->plan->name ?? 'Unknown',
'status' => $sub->status,
'created_at' => $sub->created_at,
'canceled_at' => $sub->canceled_at,
];
}),
'usage' => [
'accounts' => $user->accounts_count,
'categories' => $user->categories_count,
'budgets' => $user->budgets_count,
'transactions' => $user->transactions_count,
'goals' => $user->goals_count,
],
],
]);
}
/**
* Update user
*/
public function update(Request $request, $id)
{
$user = User::findOrFail($id);
$validated = $request->validate([
'name' => 'sometimes|string|max:255',
'email' => 'sometimes|email|unique:users,email,' . $id,
'language' => 'sometimes|string|in:es,pt-BR,en',
'currency' => 'sometimes|string|size:3',
]);
$user->update($validated);
return response()->json([
'success' => true,
'message' => 'Usuario actualizado correctamente',
'data' => $user,
]);
}
/**
* Reset user password (generate random)
*/
public function resetPassword($id)
{
$user = User::findOrFail($id);
// Generate random password
$newPassword = bin2hex(random_bytes(8)); // 16 chars
$user->password = Hash::make($newPassword);
$user->save();
return response()->json([
'success' => true,
'message' => 'Contraseña restablecida correctamente',
'data' => [
'temporary_password' => $newPassword,
],
]);
}
/**
* Delete user and all their data
*/
public function destroy($id)
{
$user = User::findOrFail($id);
// Don't allow deleting admin
if ($user->email === 'marco@cnxifly.com') {
return response()->json([
'success' => false,
'message' => 'No se puede eliminar el usuario administrador',
], 403);
}
// Delete user (cascade will handle related data)
$user->delete();
return response()->json([
'success' => true,
'message' => 'Usuario eliminado correctamente',
]);
}
/**
* Get summary statistics
*/
public function summary()
{
$totalUsers = User::count();
$activeSubscribers = Subscription::where('status', 'active')->count();
$freeUsers = $totalUsers - $activeSubscribers;
$newUsersThisMonth = User::whereMonth('created_at', now()->month)
->whereYear('created_at', now()->year)
->count();
return response()->json([
'success' => true,
'data' => [
'total_users' => $totalUsers,
'active_subscribers' => $activeSubscribers,
'free_users' => $freeUsers,
'new_users_this_month' => $newUsersThisMonth,
],
]);
}
}