Read Afia AI credentials from platform admin settings.
Deploy Ladill Link / deploy (push) Successful in 58s
Deploy Ladill Link / deploy (push) Successful in 58s
Link Afia now resolves provider, model, and API keys from ladilldb.platform_settings like the monolith, with .env fallbacks for local dev. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -17,6 +17,13 @@ DB_DATABASE=ladill_link
|
||||
DB_USERNAME=ladill_link
|
||||
DB_PASSWORD=
|
||||
|
||||
# Read platform admin settings (AI + Paystack keys from ladilldb.platform_settings).
|
||||
PLATFORM_DB_HOST=127.0.0.1
|
||||
PLATFORM_DB_PORT=3306
|
||||
PLATFORM_DB_DATABASE=ladilldb
|
||||
PLATFORM_DB_USERNAME=ladill_link
|
||||
PLATFORM_DB_PASSWORD=
|
||||
|
||||
SESSION_DRIVER=database
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_DOMAIN=.ladill.com
|
||||
|
||||
@@ -34,6 +34,14 @@ php artisan passport:client \
|
||||
|
||||
Set `BILLING_API_KEY_LINK` and `IDENTITY_API_KEY_LINK` on both monolith and this app.
|
||||
|
||||
Grant read access to platform AI settings:
|
||||
|
||||
```bash
|
||||
sudo mysql -e "GRANT SELECT ON ladilldb.platform_settings TO 'ladill_link'@'127.0.0.1'; FLUSH PRIVILEGES;"
|
||||
```
|
||||
|
||||
Afia reads `ai_*` keys from `platform_settings` (same as ladill.com admin). Optional `AFIA_API_KEY` in `.env` overrides for local dev.
|
||||
|
||||
## Legacy URLs
|
||||
|
||||
`https://ladill.com/q/{code}` continues to work for existing QR and storefront codes. New links use `https://ladl.link/{code}`. Unknown slugs on ladl.link fall back to the legacy resolver.
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PlatformSetting extends Model
|
||||
{
|
||||
protected $connection = 'platform';
|
||||
|
||||
protected $fillable = [
|
||||
'group',
|
||||
'key',
|
||||
'label',
|
||||
'description',
|
||||
'type',
|
||||
'value',
|
||||
'is_secret',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'value' => 'array',
|
||||
'is_secret' => 'boolean',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
@@ -10,9 +10,11 @@ use RuntimeException;
|
||||
*/
|
||||
class AfiaService
|
||||
{
|
||||
public function __construct(private PlatformAiConfig $ai) {}
|
||||
|
||||
public function enabled(): bool
|
||||
{
|
||||
return (bool) config('afia.enabled', true) && (string) config('afia.api_key', '') !== '';
|
||||
return $this->ai->enabled();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,9 +27,9 @@ class AfiaService
|
||||
throw new RuntimeException('Afia is not configured.');
|
||||
}
|
||||
|
||||
$provider = (string) config('afia.provider', 'openai');
|
||||
$model = (string) config('afia.model', 'gpt-4o-mini');
|
||||
$apiKey = (string) config('afia.api_key');
|
||||
$provider = $this->ai->provider();
|
||||
$model = $this->ai->model();
|
||||
$apiKey = $this->ai->apiKey();
|
||||
|
||||
$messages = [['role' => 'system', 'content' => $this->systemPrompt($context)]];
|
||||
foreach (array_slice($history, -8) as $turn) {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Afia;
|
||||
|
||||
use App\Models\PlatformSetting;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Resolves AI credentials from ladilldb.platform_settings (admin → Platform settings)
|
||||
* with .env fallbacks for local/dev.
|
||||
*/
|
||||
class PlatformAiConfig
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
private array $cache = [];
|
||||
|
||||
public function enabled(): bool
|
||||
{
|
||||
$platformFlag = $this->setting('ai_enabled');
|
||||
if ($platformFlag !== null && ! filter_var($platformFlag, FILTER_VALIDATE_BOOLEAN)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! (bool) config('afia.enabled', true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->apiKey() !== '';
|
||||
}
|
||||
|
||||
public function provider(): string
|
||||
{
|
||||
$provider = strtolower(trim((string) ($this->setting('ai_provider') ?? config('afia.provider', 'openai'))));
|
||||
|
||||
return in_array($provider, ['openai', 'anthropic'], true) ? $provider : 'openai';
|
||||
}
|
||||
|
||||
public function model(): string
|
||||
{
|
||||
$configured = trim((string) ($this->setting('ai_model') ?? config('afia.model', '')));
|
||||
if ($configured !== '') {
|
||||
return $configured;
|
||||
}
|
||||
|
||||
return $this->provider() === 'anthropic' ? 'claude-3-5-haiku-latest' : 'gpt-4o-mini';
|
||||
}
|
||||
|
||||
public function apiKey(): string
|
||||
{
|
||||
$provider = $this->provider();
|
||||
|
||||
$providerKey = match ($provider) {
|
||||
'anthropic' => trim((string) ($this->setting('ai_anthropic_api_key') ?? '')),
|
||||
default => trim((string) ($this->setting('ai_openai_api_key') ?? '')),
|
||||
};
|
||||
|
||||
if ($providerKey !== '') {
|
||||
return $providerKey;
|
||||
}
|
||||
|
||||
$shared = trim((string) ($this->setting('ai_api_key') ?? ''));
|
||||
if ($shared !== '') {
|
||||
return $shared;
|
||||
}
|
||||
|
||||
return trim((string) config('afia.api_key', ''));
|
||||
}
|
||||
|
||||
private function setting(string $key): mixed
|
||||
{
|
||||
if (array_key_exists($key, $this->cache)) {
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
try {
|
||||
if (! Schema::connection('platform')->hasTable('platform_settings')) {
|
||||
return $this->cache[$key] = null;
|
||||
}
|
||||
|
||||
$setting = PlatformSetting::query()
|
||||
->where('key', $key)
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
|
||||
return $this->cache[$key] = $setting ? ($setting->value['value'] ?? null) : null;
|
||||
} catch (\Throwable) {
|
||||
return $this->cache[$key] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ class PaystackService
|
||||
protected function settingValue(string $key, mixed $fallback = null): mixed
|
||||
{
|
||||
try {
|
||||
if (!Schema::hasTable('platform_settings')) {
|
||||
if (! Schema::connection('platform')->hasTable('platform_settings')) {
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,26 @@ return [
|
||||
]) : [],
|
||||
],
|
||||
|
||||
// Read-only access to platform admin settings (AI keys, Paystack, etc.).
|
||||
'platform' => [
|
||||
'driver' => 'mysql',
|
||||
'host' => env('PLATFORM_DB_HOST', env('DB_HOST', '127.0.0.1')),
|
||||
'port' => env('PLATFORM_DB_PORT', env('DB_PORT', '3306')),
|
||||
'database' => env('PLATFORM_DB_DATABASE', 'ladilldb'),
|
||||
'username' => env('PLATFORM_DB_USERNAME', env('DB_USERNAME', 'root')),
|
||||
'password' => env('PLATFORM_DB_PASSWORD', env('DB_PASSWORD', '')),
|
||||
'unix_socket' => env('PLATFORM_DB_SOCKET', env('DB_SOCKET', '')),
|
||||
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
(PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
'mariadb' => [
|
||||
'driver' => 'mariadb',
|
||||
'url' => env('DB_URL'),
|
||||
|
||||
Reference in New Issue
Block a user