initial: bootstrap from BukidBountyApp base
This commit is contained in:
131
app/Http/Controllers/Admin/ApiTokenController.php
Normal file
131
app/Http/Controllers/Admin/ApiTokenController.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\PersonalAccessToken;
|
||||
use App\Models\User;
|
||||
use App\Support\TokenAbilities;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
|
||||
class ApiTokenController
|
||||
{
|
||||
public function catalog()
|
||||
{
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'abilities' => TokenAbilities::catalog(),
|
||||
'wildcard' => TokenAbilities::WILDCARD,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$tokens = PersonalAccessToken::query()
|
||||
->where('tokenable_type', User::class)
|
||||
->orderByDesc('id')
|
||||
->get()
|
||||
->map(fn ($t) => $this->present($t));
|
||||
|
||||
return response()->json(['success' => true, 'data' => $tokens]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:120',
|
||||
'description' => 'nullable|string|max:500',
|
||||
'abilities' => 'required|array|min:1',
|
||||
'abilities.*' => 'string',
|
||||
'allowed_ips' => 'nullable|array',
|
||||
'allowed_ips.*' => 'string',
|
||||
'expires_at' => 'nullable|date',
|
||||
'tokenable_user_id' => 'nullable|integer',
|
||||
]);
|
||||
|
||||
foreach ($validated['abilities'] as $ability) {
|
||||
if (! TokenAbilities::exists($ability)) {
|
||||
return ResponseHelper::returnError("Unknown ability: {$ability}", 422);
|
||||
}
|
||||
}
|
||||
|
||||
$owner = Auth::user();
|
||||
$tokenable = $owner;
|
||||
if (! empty($validated['tokenable_user_id'])) {
|
||||
$tokenable = User::query()->find($validated['tokenable_user_id']);
|
||||
if (! $tokenable) {
|
||||
return ResponseHelper::returnError('Target user not found.', 404);
|
||||
}
|
||||
}
|
||||
|
||||
$result = $tokenable->createToken(
|
||||
name: $validated['name'],
|
||||
abilities: $validated['abilities'],
|
||||
allowedIps: $validated['allowed_ips'] ?? null,
|
||||
expiresAt: !empty($validated['expires_at']) ? new \DateTimeImmutable($validated['expires_at']) : null,
|
||||
description: $validated['description'] ?? null,
|
||||
createdBy: $owner->id,
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'token' => $this->present($result['token']),
|
||||
'plain_text_token' => $result['plainTextToken'],
|
||||
],
|
||||
'message' => 'Token created. Copy it now — it will not be shown again.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function revoke(Request $request, int $id)
|
||||
{
|
||||
$token = PersonalAccessToken::query()->find($id);
|
||||
if (! $token) {
|
||||
return ResponseHelper::returnError('Token not found.', 404);
|
||||
}
|
||||
if ($token->revoked_at !== null) {
|
||||
return ResponseHelper::returnError('Token already revoked.', 422);
|
||||
}
|
||||
$token->forceFill([
|
||||
'revoked_at' => now(),
|
||||
'revoked_by' => Auth::id(),
|
||||
])->save();
|
||||
|
||||
return response()->json(['success' => true, 'data' => $this->present($token)]);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $id)
|
||||
{
|
||||
$token = PersonalAccessToken::query()->find($id);
|
||||
if (! $token) {
|
||||
return ResponseHelper::returnError('Token not found.', 404);
|
||||
}
|
||||
$token->delete();
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
private function present(PersonalAccessToken $t): array
|
||||
{
|
||||
return [
|
||||
'id' => $t->id,
|
||||
'name' => $t->name,
|
||||
'description' => $t->description,
|
||||
'tokenable_id' => $t->tokenable_id,
|
||||
'tokenable_type' => $t->tokenable_type,
|
||||
'abilities' => $t->abilities ?? [],
|
||||
'allowed_ips' => $t->allowed_ips ?? [],
|
||||
'expires_at' => $t->expires_at,
|
||||
'last_used_at' => $t->last_used_at,
|
||||
'last_used_ip' => $t->last_used_ip,
|
||||
'revoked_at' => $t->revoked_at,
|
||||
'created_by' => $t->created_by,
|
||||
'created_at' => $t->created_at,
|
||||
'is_active' => $t->isActive(),
|
||||
];
|
||||
}
|
||||
}
|
||||
268
app/Http/Controllers/Admin/LandingPageController.php
Normal file
268
app/Http/Controllers/Admin/LandingPageController.php
Normal file
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\LandingPage;
|
||||
use App\Enums\UserTypes;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use Exception;
|
||||
|
||||
class LandingPageController
|
||||
{
|
||||
/**
|
||||
* Allowed roles for landing page management.
|
||||
*/
|
||||
private static array $allowedTypes = [
|
||||
'ult',
|
||||
'super operator',
|
||||
'coordinator',
|
||||
];
|
||||
|
||||
/**
|
||||
* Check if the current user is authorized.
|
||||
*/
|
||||
private function isAuthorized(): bool
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) return false;
|
||||
|
||||
$type = $user->acct_type;
|
||||
if ($type instanceof UserTypes) {
|
||||
$type = $type->value;
|
||||
}
|
||||
|
||||
return in_array($type, self::$allowedTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all landing pages.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (!$this->isAuthorized()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$pages = LandingPage::orderByDesc('is_active')
|
||||
->orderByDesc('updated_at')
|
||||
->get();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $pages,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single landing page by hashkey.
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
if (!$this->isAuthorized()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$hashkey = $request->input('hashkey');
|
||||
if (!$hashkey) {
|
||||
return response()->json(['success' => false, 'message' => 'Hashkey is required'], 400);
|
||||
}
|
||||
|
||||
$page = LandingPage::where('hashkey', $hashkey)->first();
|
||||
if (!$page) {
|
||||
return response()->json(['success' => false, 'message' => 'Landing page not found'], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $page,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a landing page.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (!$this->isAuthorized()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'html_content' => 'required|string',
|
||||
'description' => 'nullable|string|max:1000',
|
||||
'hashkey' => 'nullable|string', // If provided, it's an update
|
||||
]);
|
||||
|
||||
try {
|
||||
$hashkey = $validated['hashkey'] ?? null;
|
||||
|
||||
if ($hashkey) {
|
||||
// Update existing
|
||||
$page = LandingPage::where('hashkey', $hashkey)->first();
|
||||
if (!$page) {
|
||||
return response()->json(['success' => false, 'message' => 'Landing page not found'], 404);
|
||||
}
|
||||
|
||||
$page->title = $validated['title'];
|
||||
$page->html_content = $validated['html_content'];
|
||||
$page->description = $validated['description'] ?? $page->description;
|
||||
$page->save();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Landing page updated successfully',
|
||||
'data' => $page,
|
||||
]);
|
||||
} else {
|
||||
// Create new
|
||||
$page = LandingPage::create([
|
||||
'title' => $validated['title'],
|
||||
'html_content' => $validated['html_content'],
|
||||
'description' => $validated['description'] ?? null,
|
||||
'is_active' => false,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Landing page created successfully',
|
||||
'data' => $page,
|
||||
]);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to save landing page: ' . $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a landing page as active.
|
||||
*/
|
||||
public function setActive(Request $request)
|
||||
{
|
||||
if (!$this->isAuthorized()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$hashkey = $request->input('hashkey');
|
||||
if (!$hashkey) {
|
||||
return response()->json(['success' => false, 'message' => 'Hashkey is required'], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$page = LandingPage::where('hashkey', $hashkey)->first();
|
||||
if (!$page) {
|
||||
return response()->json(['success' => false, 'message' => 'Landing page not found'], 404);
|
||||
}
|
||||
|
||||
$page->setAsActive();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Landing page activated successfully',
|
||||
'data' => $page,
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to activate landing page: ' . $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate all landing pages (show default homepage for guests).
|
||||
*/
|
||||
public function deactivateAll(Request $request)
|
||||
{
|
||||
if (!$this->isAuthorized()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
try {
|
||||
LandingPage::where('is_active', true)->update(['is_active' => false]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'All landing pages deactivated. Default homepage will be shown.',
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to deactivate: ' . $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a landing page.
|
||||
*/
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
if (!$this->isAuthorized()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$hashkey = $request->input('hashkey');
|
||||
if (!$hashkey) {
|
||||
return response()->json(['success' => false, 'message' => 'Hashkey is required'], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$page = LandingPage::where('hashkey', $hashkey)->first();
|
||||
if (!$page) {
|
||||
return response()->json(['success' => false, 'message' => 'Landing page not found'], 404);
|
||||
}
|
||||
|
||||
$page->delete();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Landing page deleted successfully',
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to delete landing page: ' . $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public endpoint: Get the currently active landing page content.
|
||||
* No authentication required - used to show landing page to guests.
|
||||
*/
|
||||
public function getActiveLandingPage()
|
||||
{
|
||||
try {
|
||||
$page = LandingPage::getActive();
|
||||
} catch (\Throwable $e) {
|
||||
return Response::json(['success' => false, 'data' => null, 'has_landing_page' => false]);
|
||||
}
|
||||
|
||||
if (!$page) {
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'data' => null,
|
||||
'has_landing_page' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'title' => $page->title,
|
||||
'html_content' => $page->html_content,
|
||||
'description' => $page->description,
|
||||
],
|
||||
'has_landing_page' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
174
app/Http/Controllers/Admin/PosAccessKeyController.php
Normal file
174
app/Http/Controllers/Admin/PosAccessKeyController.php
Normal file
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\Market\PosAccessKey;
|
||||
use App\Models\Market\Store;
|
||||
use App\Models\User;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hyperf\Stringable\Str;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Enums\UserActions;
|
||||
|
||||
class PosAccessKeyController
|
||||
{
|
||||
/**
|
||||
* List POS access keys. Auto-expires any past-due keys first.
|
||||
* - Ultimate/super operator/operator: see all keys
|
||||
* - Store owner/manager: see keys for stores they own or manage
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewPosAccessKeys)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
// Auto-deactivate any expired keys
|
||||
PosAccessKey::autoExpire();
|
||||
|
||||
$user = Auth::user();
|
||||
$acctType = $user->acct_type->value ?? $user->acct_type ?? '';
|
||||
|
||||
// Ultimate, Super Operator, and Operator see everything
|
||||
if (in_array($acctType, ['ult', 'super operator', 'operator'])) {
|
||||
$query = PosAccessKey::with(['store:id,name,hashkey,owner_id', 'store.owner:id,name,nickname,hashkey'])
|
||||
->orderBy('id', 'desc');
|
||||
} else {
|
||||
// Non-ultimate users see their own and their descendants' stores' keys
|
||||
$descendants = $user->getAllDescendants();
|
||||
$descendantIds = $descendants->pluck('id')->push($user->id)->toArray();
|
||||
|
||||
$storeIds = Store::whereIn('owner_id', $descendantIds)
|
||||
->orWhereIn('manager_id', $descendantIds)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
$query = PosAccessKey::with(['store:id,name,hashkey,owner_id', 'store.owner:id,name,nickname,hashkey'])
|
||||
->whereIn('store_id', $storeIds)
|
||||
->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
$keys = $query->get();
|
||||
|
||||
return ResponseHelper::returnSuccessResponse($keys, 'pos_access_keys');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::CreatePosAccessKey)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'store_hash' => 'required|string',
|
||||
'expires_at' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$store = Store::where('hashkey', $validated['store_hash'])->first();
|
||||
if (!$store) {
|
||||
return ResponseHelper::returnError('Store not found', 404);
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
$acctType = $user->acct_type->value ?? $user->acct_type ?? '';
|
||||
if (!in_array($acctType, ['ult', 'super operator', 'operator'])) {
|
||||
$descendants = $user->getAllDescendants();
|
||||
$allowedIds = $descendants->pluck('id')->push($user->id)->toArray();
|
||||
if (!in_array($store->owner_id, $allowedIds) && !in_array($store->manager_id, $allowedIds)) {
|
||||
return ResponseHelper::returnError('Unauthorized to create keys for this store', 403);
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'access_key' => 'PK-' . Str::upper(Str::random(16)),
|
||||
'store_id' => $store->id,
|
||||
'name' => $validated['name'],
|
||||
'created_by' => Auth::id(),
|
||||
'status' => 'active',
|
||||
];
|
||||
|
||||
// Set expiry if provided
|
||||
if (!empty($validated['expires_at'])) {
|
||||
$data['expires_at'] = $validated['expires_at'];
|
||||
}
|
||||
|
||||
$key = PosAccessKey::create($data);
|
||||
|
||||
return ResponseHelper::returnSuccessResponse($key, $key->hashkey, 'POS Access Key created');
|
||||
}
|
||||
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::DeletePosAccessKey)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$hashkey = $request->input('target');
|
||||
$key = PosAccessKey::with('store')->where('hashkey', $hashkey)->first();
|
||||
|
||||
if (!$key) {
|
||||
return ResponseHelper::returnError('Key not found', 404);
|
||||
}
|
||||
|
||||
if (!$this->userOwnsKeyStore($key)) {
|
||||
return ResponseHelper::returnError('Unauthorized to delete this key', 403);
|
||||
}
|
||||
|
||||
$key->delete();
|
||||
return ResponseHelper::returnSuccessResponse([], $hashkey, 'Key deleted');
|
||||
}
|
||||
|
||||
public function toggleStatus(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::TogglePosAccessKey)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$hashkey = $request->input('target');
|
||||
$key = PosAccessKey::with('store')->where('hashkey', $hashkey)->first();
|
||||
|
||||
if (!$key) {
|
||||
return ResponseHelper::returnError('Key not found', 404);
|
||||
}
|
||||
|
||||
if (!$this->userOwnsKeyStore($key)) {
|
||||
return ResponseHelper::returnError('Unauthorized to modify this key', 403);
|
||||
}
|
||||
|
||||
$key->status = $key->status === 'active' ? 'inactive' : 'active';
|
||||
$key->save();
|
||||
return ResponseHelper::returnSuccessResponse($key, $hashkey, 'Status updated');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ownership gate shared by destroy/toggleStatus: Big 3 always pass;
|
||||
* everyone else must own or manage (directly or via descendants) the
|
||||
* store the key belongs to.
|
||||
*/
|
||||
private function userOwnsKeyStore(PosAccessKey $key): bool
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$acctType = $user->acct_type->value ?? $user->acct_type ?? '';
|
||||
if (in_array($acctType, ['ult', 'super operator', 'operator'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$store = $key->store;
|
||||
if (!$store) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$allowedIds = $user->getAllDescendants()->pluck('id')->push($user->id)->toArray();
|
||||
return in_array($store->owner_id, $allowedIds) || in_array($store->manager_id, $allowedIds);
|
||||
}
|
||||
}
|
||||
|
||||
284
app/Http/Controllers/Admin/SystemSettingsController.php
Normal file
284
app/Http/Controllers/Admin/SystemSettingsController.php
Normal file
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\FilesMainController;
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\SystemSetting;
|
||||
use App\Enums\UserTypes;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Exception;
|
||||
|
||||
class SystemSettingsController
|
||||
{
|
||||
/**
|
||||
* Get all system settings grouped by group.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (!Auth::user()->isUltimate()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$keys = $request->input('keys', []);
|
||||
|
||||
if (!empty($keys)) {
|
||||
$settings = SystemSetting::whereIn('key', (array)$keys)->get();
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $settings
|
||||
]);
|
||||
}
|
||||
|
||||
$settings = SystemSetting::all()->groupBy('group');
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $settings
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update multiple settings.
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
if (!Auth::user()->isUltimate()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'settings' => 'required|array',
|
||||
]);
|
||||
|
||||
try {
|
||||
foreach ($validated['settings'] as $key => $value) {
|
||||
SystemSetting::setValue($key, $value);
|
||||
}
|
||||
|
||||
SystemSetting::clearCache();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'System settings updated successfully'
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to update settings: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload application logo.
|
||||
*/
|
||||
public function uploadLogo(Request $request)
|
||||
{
|
||||
if (!Auth::user()->isUltimate()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
if (!$request->hasFile('logo')) {
|
||||
return response()->json(['success' => false, 'message' => 'No logo file provided'], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$file = $request->file('logo');
|
||||
$filename = 'app_logo_' . time() . '.' . $file->getExtension();
|
||||
|
||||
// Extract palette from the uploaded file before it is moved by storage.
|
||||
$palette = \App\Support\PaletteExtractor::extract($file->getRealPath());
|
||||
|
||||
// Reusing existing file storage system
|
||||
$result = FilesMainController::uploadFileList(
|
||||
$file,
|
||||
'System Logo',
|
||||
$filename,
|
||||
'Application branding logo',
|
||||
[],
|
||||
'system',
|
||||
[],
|
||||
0,
|
||||
'app_logo',
|
||||
);
|
||||
|
||||
if ($result && isset($result->hashkey)) {
|
||||
SystemSetting::setValue('app_logo', $result->hashkey);
|
||||
|
||||
if ($palette) {
|
||||
SystemSetting::setValue('primary_color', $palette['primary']);
|
||||
SystemSetting::setValue('accent_color', $palette['accent']);
|
||||
SystemSetting::setValue('background_tint', $palette['tint']);
|
||||
}
|
||||
|
||||
SystemSetting::clearCache();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'hashkey' => $result->hashkey,
|
||||
'url' => FilesMainController::generateURLforFileListHash($result->hashkey),
|
||||
'palette' => $palette,
|
||||
'message' => 'Logo uploaded successfully'
|
||||
]);
|
||||
}
|
||||
|
||||
throw new Exception('File upload failed');
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Logo upload failed: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get public settings data.
|
||||
*/
|
||||
public static function getPublicSettingsData()
|
||||
{
|
||||
$publicKeys = ['app_name', 'app_logo', 'app_description', 'app_tagline', 'primary_color', 'accent_color', 'background_tint', 'footer_text', 'disabled_pages', 'top_up_enabled', 'app_mode', 'main_organization', 'accounting_theme', 'default_org_type', 'group_types', 'bible_verse_text', 'bible_verse_reference'];
|
||||
|
||||
$settings = [];
|
||||
foreach ($publicKeys as $key) {
|
||||
$settings[$key] = SystemSetting::getValue($key);
|
||||
}
|
||||
|
||||
// Add module states
|
||||
$settings['module_states'] = \App\Support\ModuleHelper::getModuleStates();
|
||||
|
||||
// Generate URL for logo if it exists
|
||||
if (!empty($settings['app_logo'])) {
|
||||
$settings['app_logo_url'] = FilesMainController::generateURLforFileListHash($settings['app_logo']);
|
||||
} else {
|
||||
$settings['app_logo_url'] = cdn_asset('vendor/assets/icons/192x192.png'); // Fallback to PWA icon on the CDN
|
||||
}
|
||||
|
||||
// Resolve main organization hashkey to a usable data object
|
||||
$settings['main_organization_data'] = null;
|
||||
if (!empty($settings['main_organization'])) {
|
||||
$org = \App\Models\Market\Organization::where('hashkey', $settings['main_organization'])->first();
|
||||
if ($org) {
|
||||
$settings['main_organization_data'] = [
|
||||
'hashkey' => $org->hashkey,
|
||||
'name' => $org->name,
|
||||
'type' => $org->type,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* List organizations available for selection as the main cooperative/organization.
|
||||
*/
|
||||
public function listOrganizations()
|
||||
{
|
||||
if (!Auth::user()->isUltimate()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$orgs = \App\Models\Market\Organization::where('is_active', true)
|
||||
->orderBy('type')
|
||||
->orderBy('name')
|
||||
->get(['hashkey', 'name', 'type']);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $orgs,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all modules with their effective state, env/config default, and
|
||||
* any DB-stored override. Used by the Ultimate Console module manager.
|
||||
*/
|
||||
public function getModules()
|
||||
{
|
||||
if (!Auth::user()->isUltimate()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'system_enabled' => \App\Support\ModuleHelper::isSystemEnabled(),
|
||||
'modules' => \App\Support\ModuleHelper::getAllModules(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the module override map. Accepts an associative array of
|
||||
* { module_key: bool|null }. A null value clears that key's override
|
||||
* so the env/config default takes effect again.
|
||||
*/
|
||||
public function updateModules(Request $request)
|
||||
{
|
||||
if (!Auth::user()->isUltimate()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'overrides' => 'required|array',
|
||||
]);
|
||||
|
||||
$configModules = (array) \Hypervel\Support\Facades\Config::get('modules', []);
|
||||
$current = SystemSetting::getValue(\App\Support\ModuleHelper::OVERRIDE_KEY);
|
||||
if (is_string($current) && $current !== '') {
|
||||
$decoded = json_decode($current, true);
|
||||
$current = is_array($decoded) ? $decoded : [];
|
||||
} elseif (!is_array($current)) {
|
||||
$current = [];
|
||||
}
|
||||
|
||||
foreach ($validated['overrides'] as $key => $value) {
|
||||
// Only allow keys that are actually defined as modules.
|
||||
if (!isset($configModules[$key]) || !is_array($configModules[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value === null || $value === 'null') {
|
||||
unset($current[$key]);
|
||||
} else {
|
||||
$current[$key] = filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
SystemSetting::setValue(
|
||||
\App\Support\ModuleHelper::OVERRIDE_KEY,
|
||||
json_encode((object) $current),
|
||||
'modules',
|
||||
'json'
|
||||
);
|
||||
SystemSetting::clearCache();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Module states updated',
|
||||
'data' => [
|
||||
'modules' => \App\Support\ModuleHelper::getAllModules(),
|
||||
],
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to update modules: ' . $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get public settings for frontend.
|
||||
*/
|
||||
public function getPublicSettings()
|
||||
{
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => self::getPublicSettingsData()
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user