Files
Felitendo e0b2692e83 docs(admin): call it FTStore Admin, not "the console"
The app is FTStore Admin. Its own AGENTS.md already sets the vocabulary — "the
FTStore Admin server" for web/, "the FTStore Admin app" for this one — and then
most of the comments in it said "the console" anyway.

Three user-visible strings went with it: the lock screen, the menu item that
locks it, and the biometric prompt. The storage-notification footer named the
console too. Their catalog keys move with them, in every locale the app ships.

Not "the app": inside these files that already means FTStore on somebody's
phone, so a blind swap produced lines like "Dashboard and app only, never the
app".

AdminConsoleView is AdminHomeView. The Console/ directory keeps its name: the
history of Console/*.swift runs back through manager/FTStore/Admin/ and is
documented as doing so, which is worth more than a path nobody reads as a name.

Also fills a translation gap this turned up — the status-change footer in the
feedback detail was untranslated.
2026-08-10 17:54:46 +02:00

84 lines
3.0 KiB
Swift

//
// AdminConfig.swift
// FTStore Admin
//
// Where this app talks to the FTStore Admin server. The build-time default
// is injected via Info.plist and can be overridden at runtime.
//
// This is the *only* baked-in value in the whole app, and it is not a secret —
// it is the same public domain FTStore already uses as its default source. No
// token, no API key, no privileged path ships here; the server session the
// operator signs in for is the entire security model.
//
import Foundation
enum AdminConfig {
private static let _baseURLOverrideKey = "admin.ftstoreAdminBaseURL"
/// The FTStore Admin address, baked in so the app works out of the box
/// without the operator configuring anything.
static let fallbackBaseURL = "https://ftstore.app"
/// Build-time default base URL: an Info.plist value (`FTStoreAdminBaseURL` ←
/// `$(FTSTORE_ADMIN_BASE_URL)`) if one was injected at build time, else
/// `fallbackBaseURL`.
static var defaultBaseURL: String {
let injected = ((Bundle.main.object(forInfoDictionaryKey: "FTStoreAdminBaseURL") as? String) ?? "")
.trimmingCharacters(in: .whitespacesAndNewlines)
return injected.isEmpty ? fallbackBaseURL : injected
}
/// Effective FTStore Admin base URL: a runtime override if one was set, else
/// the build-time default.
static var baseURL: String {
get {
if
let override = UserDefaults.standard.string(forKey: _baseURLOverrideKey)?
.trimmingCharacters(in: .whitespacesAndNewlines),
!override.isEmpty
{
return override
}
return defaultBaseURL
}
set {
UserDefaults.standard.set(
newValue.trimmingCharacters(in: .whitespacesAndNewlines),
forKey: _baseURLOverrideKey
)
}
}
/// True once we have a usable base URL.
static var isConfigured: Bool {
guard let url = URL(string: baseURL) else { return false }
return url.scheme == "https" || url.scheme == "http"
}
/// Build `<base><path>`, tolerating trailing slashes on the base.
static func endpointURL(path: String) -> URL? {
var base = baseURL
while base.hasSuffix("/") { base.removeLast() }
guard !base.isEmpty else { return nil }
let suffix = path.hasPrefix("/") ? path : "/" + path
return URL(string: base + suffix)
}
/// Marketing version, for the About row and the User-Agent.
static var appVersion: String {
(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "1.0"
}
/// Identifies FTStore Admin to the server. Deliberately *not* one of the
/// `FTStore/…` / `FTStore-Manager/…` agents `web/src/lib/app-ua.ts` gates the
/// feed and the self-update proxy on: FTStore Admin only calls session-
/// authenticated `/api/*` routes, which are protected by the account rather
/// than by a public string. Keeping it distinct means widening that gate is
/// never accidentally required to make FTStore Admin work.
static let userAgent: String = {
let bundleId = Bundle.main.bundleIdentifier ?? "app.ftstore.admin"
return "FTStore-Admin/\(appVersion) (\(bundleId))"
}()
}