167 lines
5.2 KiB
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()]);
|
|
}
|
|
}
|
|
}
|