feat(settings): set the Google Maps key through PLACES_API_KEY

The server already put PLACES_API_KEY ahead of the key saved in the admin
panel, but nothing said so: the variable was in no template and the admin
field kept showing a stored value that no search used.

GET /me/settings now answers env_keys, the variable behind each key the
environment sets (Google, Unsplash, Amap), and null for that key's value.
The admin field turns read-only and names the variable, Test still probes
the key a search resolves to, and a save leaves those names out so the
stored fallback survives. The variable is documented in .env.example,
docker-compose.yml, the Helm chart (as a secret) and the wiki.

Addresses discussion #1881.
This commit is contained in:
Maurice
2026-09-24 16:17:54 +02:00
parent 79cd3095e0
commit 8906b66bc8
45 changed files with 308 additions and 36 deletions
@@ -1,3 +1,4 @@
import { readEnv } from '../../app-config';
import { DatabaseService } from '../database/database.service';
import { decrypt_api_key, maybe_encrypt_api_key } from '../common/crypto/apiKeyCrypto';
import type { ApiKeySource } from '@trek/shared';
@@ -46,6 +47,26 @@ const USER_ROW_SQL: Record<InstanceApiKeyName, string> = {
amap_api_key: 'SELECT amap_api_key FROM users WHERE id = ?',
};
/**
* The environment variable that overrides each instance key. Whatever it holds
* is the operator key resolveApiKey puts first, so the admin panel names the
* variable rather than showing a field that nothing reads (#1881).
*/
const OPERATOR_KEY_ENV: Record<InstanceApiKeyName, { variable: string; read: () => string | undefined }> = {
maps_api_key: { variable: 'PLACES_API_KEY', read: () => readEnv().maps.placesApiKey },
unsplash_api_key: { variable: 'UNSPLASH_ACCESS_KEY', read: () => readEnv().integrations.unsplashAccessKey },
amap_api_key: { variable: 'AMAP_API_KEY', read: () => readEnv().maps.amapApiKey },
};
/** The instance keys an environment variable sets, each with that variable's name. Never the value. */
export function operatorKeyVariables(): Partial<Record<InstanceApiKeyName, string>> {
const set: Partial<Record<InstanceApiKeyName, string>> = {};
for (const name of INSTANCE_API_KEY_NAMES) {
if (OPERATOR_KEY_ENV[name].read()) set[name] = OPERATOR_KEY_ENV[name].variable;
}
return set;
}
/** The instance-wide value in cleartext, or null when unset/cleared. */
export function readInstanceApiKey(db: DatabaseService, name: InstanceApiKeyName): string | null {
const row = db.get<{ value: string | null }>('SELECT value FROM app_settings WHERE key = ?', name);