Files
dewemoji/app/app/Http/Controllers/Api/V1/AdminSubscriptionController.php

167 lines
5.2 KiB
PHP

<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\Subscription;
use App\Models\User;
use App\Models\UserApiKey;
use App\Services\Keywords\KeywordQuotaService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
class AdminSubscriptionController extends Controller
{
public function __construct(
private readonly KeywordQuotaService $keywordQuota
) {
}
private function authorizeAdmin(Request $request): ?JsonResponse
{
$token = (string) config('dewemoji.admin.token', '');
$provided = trim((string) $request->header('X-Admin-Token', ''));
if ($token === '' || $provided === '' || !hash_equals($token, $provided)) {
return response()->json(['ok' => false, 'error' => 'unauthorized'], 401);
}
return null;
}
public function index(Request $request): JsonResponse
{
if ($res = $this->authorizeAdmin($request)) {
return $res;
}
$query = Subscription::query()->with('user:id,email,name,tier');
if ($userId = $request->query('user_id')) {
$query->where('user_id', (int) $userId);
}
if ($email = $request->query('email')) {
$query->whereHas('user', fn ($q) => $q->where('email', $email));
}
if ($status = $request->query('status')) {
$query->where('status', (string) $status);
}
$limit = min(max((int) $request->query('limit', 50), 1), 200);
$items = $query->orderByDesc('id')->limit($limit)->get();
return response()->json(['ok' => true, 'items' => $items]);
}
public function grant(Request $request): JsonResponse
{
if ($res = $this->authorizeAdmin($request)) {
return $res;
}
$user = $this->resolveUser($request);
if (!$user) {
return response()->json(['ok' => false, 'error' => 'not_found'], 404);
}
$plan = (string) $request->input('plan', 'personal');
$status = (string) $request->input('status', 'active');
$provider = (string) $request->input('provider', 'admin');
$providerRef = (string) $request->input('provider_ref', '');
$startedAt = $this->parseDate($request->input('started_at')) ?? now();
$expiresAt = $this->parseDate($request->input('expires_at'));
$sub = Subscription::create([
'user_id' => $user->id,
'plan' => $plan,
'status' => $status,
'provider' => $provider,
'provider_ref' => $providerRef !== '' ? $providerRef : null,
'started_at' => $startedAt,
'expires_at' => $expiresAt,
]);
if ($status === 'active') {
$user->update(['tier' => 'personal']);
$this->keywordQuota->enforceForUser((int) $user->id, 'personal');
}
return response()->json(['ok' => true, 'subscription' => $sub]);
}
public function revoke(Request $request): JsonResponse
{
if ($res = $this->authorizeAdmin($request)) {
return $res;
}
$id = (int) $request->input('id', 0);
$now = now();
if ($id > 0) {
$sub = Subscription::find($id);
if (!$sub) {
return response()->json(['ok' => false, 'error' => 'not_found'], 404);
}
$sub->update(['status' => 'revoked', 'expires_at' => $now]);
$this->syncUserTier($sub->user_id);
return response()->json(['ok' => true, 'revoked' => true]);
}
$user = $this->resolveUser($request);
if (!$user) {
return response()->json(['ok' => false, 'error' => 'not_found'], 404);
}
Subscription::where('user_id', $user->id)
->where('status', 'active')
->update(['status' => 'revoked', 'expires_at' => $now]);
$this->syncUserTier($user->id);
return response()->json(['ok' => true, 'revoked' => true]);
}
private function resolveUser(Request $request): ?User
{
if ($userId = $request->input('user_id')) {
return User::find((int) $userId);
}
if ($email = $request->input('email')) {
return User::where('email', (string) $email)->first();
}
return null;
}
private function parseDate(mixed $value): ?Carbon
{
if (!$value) {
return null;
}
try {
return Carbon::parse((string) $value);
} catch (\Throwable) {
return null;
}
}
private function syncUserTier(int $userId): void
{
$active = Subscription::where('user_id', $userId)
->where('status', 'active')
->where(function ($q): void {
$q->whereNull('expires_at')
->orWhere('expires_at', '>', now());
})
->exists();
User::where('id', $userId)->update([
'tier' => $active ? 'personal' : 'free',
]);
$this->keywordQuota->enforceForUser($userId, $active ? 'personal' : 'free');
if (!$active) {
UserApiKey::where('user_id', $userId)->update(['revoked_at' => now()]);
}
}
}