feat(settings): add menu/show_to_user and public get_settings for client apps

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-24 15:32:35 +03:30
co-authored by Cursor
parent 5c542e2733
commit b28a98938e
9 changed files with 321 additions and 44 deletions
@@ -65,6 +65,14 @@ class DeploySettingsConstCommand extends Command
return self::FAILURE;
}
$extraPath = database_path('sql/settings_menu_and_show_to_user.sql');
if (is_file($extraPath)) {
$extraSql = file_get_contents($extraPath);
if ($extraSql !== false && trim($extraSql) !== '') {
$sql .= "\n".$extraSql;
}
}
foreach ($databases as $db) {
$db = trim((string) $db);
if ($db === '') {
@@ -31,22 +31,37 @@ class SettingsController extends Controller
if ($request->boolean('system_only')) {
$local = $query->get();
return response()->json($this->authorizeSettingsPayload(
$this->composeSystemSettings($local)
));
return response()->json($this->composeSystemSettings($local));
}
if ($request->boolean('editable_only')) {
$local = $query->get();
return response()->json($this->authorizeSettingsPayload(
$this->composeEditableSettings($local)
));
return response()->json($this->composeEditableSettings($local));
}
$local = $query->get();
return response()->json($this->authorizeSettingsPayload($this->composeGetSettings($local)));
return response()->json($this->composeGetSettings($local));
}
/**
* اپ کاربر (khanegi_user / theme / ghatreh) — فقط ردیف‌های show_to_user=1، بدون credential.
* پنل ادمین از admin/get_settings استفاده می‌کند.
*/
public function publicIndex()
{
if (! Setting::hasShowToUserColumn()) {
return response()->json([]);
}
$local = Setting::query()
->select(Setting::adminSelectColumns())
->where('show_to_user', 1)
->orderBy('key')
->get();
return response()->json(Setting::forClientApi($this->composeGetSettings($local)));
}
/**
@@ -60,7 +75,7 @@ class SettingsController extends Controller
->orderBy('key')
->get();
return response()->json($this->authorizeSettingsPayload($rows));
return response()->json($rows);
}
public function edit(Request $request)
@@ -174,6 +189,10 @@ class SettingsController extends Controller
$saMap = $overlay->catalogMap();
$byKey = [];
$localByKey = [];
foreach ($localRows as $row) {
$localByKey[(string) $row->key] = $row;
}
if ($saMap !== null) {
foreach ($saMap as $key => $item) {
@@ -188,7 +207,7 @@ class SettingsController extends Controller
}
$value = $item['value'] ?? null;
if ($value !== null && $value !== '') {
$byKey[(string) $key] = $this->overlayRowToSetting($item);
$byKey[(string) $key] = $this->overlayRowToSetting($item, $localByKey[(string) $key] ?? null);
}
}
foreach ($localRows as $row) {
@@ -246,7 +265,7 @@ class SettingsController extends Controller
}
/**
* get_settings اپ/ادمین:
* get_settings ادمین (و پایهٔ publicIndex قبل از فیلتر show_to_user):
* can_edit=1 (+ runtime) از DB لوکال
* can_edit=0 از سوپرادمین؛ اگر کلید در SA نبود یا value خالی بود → کپی لوکال
* اگر سوپرادمین در دسترس نباشد کل جدول لوکال برمی‌گردد.
@@ -259,6 +278,11 @@ class SettingsController extends Controller
return $localRows;
}
$localByKey = [];
foreach ($localRows as $row) {
$localByKey[(string) $row->key] = $row;
}
$byKey = [];
foreach ($localRows as $row) {
@@ -285,7 +309,7 @@ class SettingsController extends Controller
}
$value = $item['value'] ?? null;
if ($value !== null && $value !== '') {
$byKey[$key] = $this->overlayRowToSetting($item);
$byKey[$key] = $this->overlayRowToSetting($item, $localByKey[$key] ?? null);
}
}
@@ -331,7 +355,7 @@ class SettingsController extends Controller
->values();
}
private function overlayRowToSetting(array $item): Setting
private function overlayRowToSetting(array $item, ?Setting $local = null): Setting
{
$setting = new Setting();
$setting->id = isset($item['id']) && is_numeric($item['id']) ? (int) $item['id'] : null;
@@ -342,6 +366,14 @@ class SettingsController extends Controller
$setting->type = $item['type'] ?? '';
$setting->is_active = $item['is_active'] ?? 1;
$setting->can_edit = 0;
if ($local) {
if (Setting::hasMenuColumn()) {
$setting->menu = $local->menu;
}
if (Setting::hasShowToUserColumn()) {
$setting->show_to_user = $local->show_to_user;
}
}
return $setting;
}
@@ -359,20 +391,4 @@ class SettingsController extends Controller
return $row;
}
/**
* اپ کاربر (بدون auth ادمین) هرگز secret/credential نمی‌بیند.
* پنل ادمین با auth:user همه کلیدها را می‌بیند.
*
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Collection|iterable $rows
* @return \Illuminate\Support\Collection
*/
private function authorizeSettingsPayload($rows)
{
if (auth('user')->check()) {
return collect($rows)->values();
}
return Setting::forPublicApi($rows);
}
}
+71 -12
View File
@@ -14,21 +14,35 @@ class Setting extends Model
{
use HasFactory,Selector;
private static ?bool $typeColumnExists = null;
/** @var array<string, bool> */
private static array $columnExists = [];
public static function hasSettingsColumn(string $column): bool
{
if (! isset(static::$columnExists[$column])) {
try {
static::$columnExists[$column] = Schema::hasColumn((new static)->getTable(), $column);
} catch (\Throwable) {
static::$columnExists[$column] = false;
}
}
return static::$columnExists[$column];
}
public static function hasTypeColumn(): bool
{
if (static::$typeColumnExists !== null) {
return static::$typeColumnExists;
}
return static::hasSettingsColumn('type');
}
try {
static::$typeColumnExists = Schema::hasColumn((new static)->getTable(), 'type');
} catch (\Throwable) {
static::$typeColumnExists = false;
}
public static function hasMenuColumn(): bool
{
return static::hasSettingsColumn('menu');
}
return static::$typeColumnExists;
public static function hasShowToUserColumn(): bool
{
return static::hasSettingsColumn('show_to_user');
}
/** @return array<int, string> */
@@ -38,6 +52,12 @@ class Setting extends Model
if (static::hasTypeColumn()) {
$cols[] = 'type';
}
if (static::hasMenuColumn()) {
$cols[] = 'menu';
}
if (static::hasShowToUserColumn()) {
$cols[] = 'show_to_user';
}
return $cols;
}
@@ -90,16 +110,52 @@ class Setting extends Model
return (bool) preg_match('/(^|_)(secret|password|token|api_key|apikey|private)(_|$)/', $key);
}
public static function isShownToUserApp($row): bool
{
$key = (string) (is_array($row) ? ($row['key'] ?? '') : ($row->key ?? ''));
if ($key === '' || static::isSecretOrServerOnlyKey($key)) {
return false;
}
$flag = is_array($row) ? ($row['show_to_user'] ?? 0) : ($row->show_to_user ?? 0);
return $flag === true || $flag === 1 || $flag === '1';
}
/**
* فیلتر تنظیمات برای پاسخ عمومی (اپ کاربر).
* فیلتر تنظیمات برای پاسخ عمومی (اپ کاربر) — ستون show_to_user، بدون secret.
*
* @param \Illuminate\Support\Collection|iterable $settings
* @return \Illuminate\Support\Collection
*/
public static function forPublicApi($settings)
{
return static::forClientApi($settings);
}
/**
* @param \Illuminate\Support\Collection|iterable $settings
* @return \Illuminate\Support\Collection<int, array{key: string, value: mixed, is_active: mixed}>
*/
public static function forClientApi($settings)
{
return collect($settings)
->filter(fn ($row) => ! static::isSecretOrServerOnlyKey((string) (is_array($row) ? ($row['key'] ?? '') : ($row->key ?? ''))))
->filter(fn ($row) => static::isShownToUserApp($row))
->map(function ($row) {
if (is_array($row)) {
return [
'key' => (string) ($row['key'] ?? ''),
'value' => $row['value'] ?? '',
'is_active' => $row['is_active'] ?? 1,
];
}
return [
'key' => (string) ($row->key ?? ''),
'value' => $row->value ?? '',
'is_active' => $row->is_active ?? 1,
];
})
->values();
}
@@ -112,9 +168,12 @@ class Setting extends Model
'value_json',
'type',
'can_edit',
'menu',
'show_to_user',
];
protected $casts = [
'value_json'=>'array',
'is_active'=>'boolean',
'show_to_user'=>'boolean',
];
}