mirror of
https://github.com/kennethnym/aris.git
synced 2026-06-15 12:01:18 +01:00
Compare commits
2 Commits
feat/remin
...
fix/requir
| Author | SHA1 | Date | |
|---|---|---|---|
|
a42a6467d8
|
|||
| efd7537008 |
@@ -30,7 +30,7 @@ describe("GoogleMapsSourceProvider", () => {
|
||||
|
||||
test("throws when service API key is empty", () => {
|
||||
expect(() => new GoogleMapsSourceProvider({ apiKey: "" })).toThrow(
|
||||
"Google Maps MCP API key must be configured",
|
||||
"Google Maps API key must be configured",
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export class GoogleMapsSourceProvider implements FeedSourceProvider {
|
||||
|
||||
constructor(options: GoogleMapsSourceProviderOptions) {
|
||||
if (!nonEmptyString(options.apiKey)) {
|
||||
throw new Error("Google Maps MCP API key must be configured")
|
||||
throw new Error("Google Maps API key must be configured")
|
||||
}
|
||||
|
||||
this.apiKey = options.apiKey
|
||||
|
||||
98
apps/freya-backend/src/lib/env.test.ts
Normal file
98
apps/freya-backend/src/lib/env.test.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
|
||||
import { ensureEnv } from "./env.ts"
|
||||
|
||||
describe("ensureEnv", () => {
|
||||
test("returns trimmed required env values", () => {
|
||||
const env = ensureEnv({
|
||||
BETTER_AUTH_SECRET: " auth-secret ",
|
||||
CREDENTIAL_ENCRYPTION_KEY: " credential-key ",
|
||||
DATABASE_URL: " postgres://example ",
|
||||
EXA_API_KEY: " exa-key ",
|
||||
GOOGLE_MAPS_API_KEY: " google-maps-key ",
|
||||
OPENROUTER_API_KEY: " openrouter-key ",
|
||||
OPENROUTER_MODEL: " model-name ",
|
||||
TFL_API_KEY: " tfl-key ",
|
||||
WEATHERKIT_KEY_ID: " weather-key-id ",
|
||||
WEATHERKIT_PRIVATE_KEY: " weather-private-key ",
|
||||
WEATHERKIT_SERVICE_ID: " weather-service-id ",
|
||||
WEATHERKIT_TEAM_ID: " weather-team-id ",
|
||||
})
|
||||
|
||||
expect(env).toEqual({
|
||||
betterAuthSecret: "auth-secret",
|
||||
credentialEncryptionKey: "credential-key",
|
||||
databaseUrl: "postgres://example",
|
||||
exaApiKey: "exa-key",
|
||||
googleMapsApiKey: "google-maps-key",
|
||||
openrouterApiKey: "openrouter-key",
|
||||
openrouterModel: "model-name",
|
||||
tflApiKey: "tfl-key",
|
||||
weatherkitKeyId: "weather-key-id",
|
||||
weatherkitPrivateKey: "weather-private-key",
|
||||
weatherkitServiceId: "weather-service-id",
|
||||
weatherkitTeamId: "weather-team-id",
|
||||
})
|
||||
})
|
||||
|
||||
test("does not allow the old Google Maps MCP fallback key", () => {
|
||||
expect(() =>
|
||||
ensureEnv({
|
||||
BETTER_AUTH_SECRET: "auth-secret",
|
||||
CREDENTIAL_ENCRYPTION_KEY: "credential-key",
|
||||
DATABASE_URL: "postgres://example",
|
||||
EXA_API_KEY: "exa-key",
|
||||
GOOGLE_MAPS_MCP_API_KEY: "google-maps-mcp-key",
|
||||
OPENROUTER_API_KEY: "openrouter-key",
|
||||
TFL_API_KEY: "tfl-key",
|
||||
WEATHERKIT_KEY_ID: "weather-key-id",
|
||||
WEATHERKIT_PRIVATE_KEY: "weather-private-key",
|
||||
WEATHERKIT_SERVICE_ID: "weather-service-id",
|
||||
WEATHERKIT_TEAM_ID: "weather-team-id",
|
||||
}),
|
||||
).toThrow("Missing required environment variables: GOOGLE_MAPS_API_KEY")
|
||||
})
|
||||
|
||||
test("allows openrouter model to be omitted", () => {
|
||||
const env = ensureEnv({
|
||||
BETTER_AUTH_SECRET: "auth-secret",
|
||||
CREDENTIAL_ENCRYPTION_KEY: "credential-key",
|
||||
DATABASE_URL: "postgres://example",
|
||||
EXA_API_KEY: "exa-key",
|
||||
GOOGLE_MAPS_API_KEY: "google-maps-key",
|
||||
OPENROUTER_API_KEY: "openrouter-key",
|
||||
TFL_API_KEY: "tfl-key",
|
||||
WEATHERKIT_KEY_ID: "weather-key-id",
|
||||
WEATHERKIT_PRIVATE_KEY: "weather-private-key",
|
||||
WEATHERKIT_SERVICE_ID: "weather-service-id",
|
||||
WEATHERKIT_TEAM_ID: "weather-team-id",
|
||||
})
|
||||
|
||||
expect(env.googleMapsApiKey).toBe("google-maps-key")
|
||||
expect(env.openrouterModel).toBeUndefined()
|
||||
})
|
||||
|
||||
test("throws with all missing required env names", () => {
|
||||
expect(() => ensureEnv({})).toThrow(
|
||||
"Missing required environment variables: BETTER_AUTH_SECRET, CREDENTIAL_ENCRYPTION_KEY, DATABASE_URL, EXA_API_KEY, OPENROUTER_API_KEY, TFL_API_KEY, WEATHERKIT_PRIVATE_KEY, WEATHERKIT_KEY_ID, WEATHERKIT_TEAM_ID, WEATHERKIT_SERVICE_ID, GOOGLE_MAPS_API_KEY",
|
||||
)
|
||||
})
|
||||
|
||||
test("treats whitespace-only values as missing", () => {
|
||||
expect(() =>
|
||||
ensureEnv({
|
||||
BETTER_AUTH_SECRET: "auth-secret",
|
||||
CREDENTIAL_ENCRYPTION_KEY: "credential-key",
|
||||
DATABASE_URL: "postgres://example",
|
||||
EXA_API_KEY: " ",
|
||||
GOOGLE_MAPS_API_KEY: "google-maps-key",
|
||||
OPENROUTER_API_KEY: "openrouter-key",
|
||||
TFL_API_KEY: "tfl-key",
|
||||
WEATHERKIT_KEY_ID: "weather-key-id",
|
||||
WEATHERKIT_PRIVATE_KEY: "weather-private-key",
|
||||
WEATHERKIT_SERVICE_ID: "weather-service-id",
|
||||
WEATHERKIT_TEAM_ID: "weather-team-id",
|
||||
}),
|
||||
).toThrow("Missing required environment variables: EXA_API_KEY")
|
||||
})
|
||||
})
|
||||
69
apps/freya-backend/src/lib/env.ts
Normal file
69
apps/freya-backend/src/lib/env.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
export interface ServerEnv {
|
||||
betterAuthSecret: string
|
||||
credentialEncryptionKey: string
|
||||
databaseUrl: string
|
||||
exaApiKey: string
|
||||
googleMapsApiKey: string
|
||||
openrouterApiKey: string
|
||||
openrouterModel: string | undefined
|
||||
tflApiKey: string
|
||||
weatherkitKeyId: string
|
||||
weatherkitPrivateKey: string
|
||||
weatherkitServiceId: string
|
||||
weatherkitTeamId: string
|
||||
}
|
||||
|
||||
export function ensureEnv(env: Record<string, string | undefined>): ServerEnv {
|
||||
const missing: string[] = []
|
||||
|
||||
const betterAuthSecret = readRequiredEnv(env, "BETTER_AUTH_SECRET", missing)
|
||||
const credentialEncryptionKey = readRequiredEnv(env, "CREDENTIAL_ENCRYPTION_KEY", missing)
|
||||
const databaseUrl = readRequiredEnv(env, "DATABASE_URL", missing)
|
||||
const exaApiKey = readRequiredEnv(env, "EXA_API_KEY", missing)
|
||||
const openrouterApiKey = readRequiredEnv(env, "OPENROUTER_API_KEY", missing)
|
||||
const tflApiKey = readRequiredEnv(env, "TFL_API_KEY", missing)
|
||||
const weatherkitPrivateKey = readRequiredEnv(env, "WEATHERKIT_PRIVATE_KEY", missing)
|
||||
const weatherkitKeyId = readRequiredEnv(env, "WEATHERKIT_KEY_ID", missing)
|
||||
const weatherkitTeamId = readRequiredEnv(env, "WEATHERKIT_TEAM_ID", missing)
|
||||
const weatherkitServiceId = readRequiredEnv(env, "WEATHERKIT_SERVICE_ID", missing)
|
||||
const googleMapsApiKey = readRequiredEnv(env, "GOOGLE_MAPS_API_KEY", missing)
|
||||
|
||||
if (missing.length > 0) {
|
||||
throw new Error(`Missing required environment variables: ${missing.join(", ")}`)
|
||||
}
|
||||
|
||||
return {
|
||||
betterAuthSecret,
|
||||
credentialEncryptionKey,
|
||||
databaseUrl,
|
||||
exaApiKey,
|
||||
googleMapsApiKey,
|
||||
openrouterApiKey,
|
||||
openrouterModel: readOptionalEnv(env, "OPENROUTER_MODEL"),
|
||||
tflApiKey,
|
||||
weatherkitKeyId,
|
||||
weatherkitPrivateKey,
|
||||
weatherkitServiceId,
|
||||
weatherkitTeamId,
|
||||
}
|
||||
}
|
||||
|
||||
function readRequiredEnv(
|
||||
env: Record<string, string | undefined>,
|
||||
name: string,
|
||||
missing: string[],
|
||||
): string {
|
||||
const value = readOptionalEnv(env, name)
|
||||
if (!value) {
|
||||
missing.push(name)
|
||||
}
|
||||
return value ?? ""
|
||||
}
|
||||
|
||||
function readOptionalEnv(
|
||||
env: Record<string, string | undefined>,
|
||||
name: string,
|
||||
): string | undefined {
|
||||
const value = env[name]?.trim()
|
||||
return value ? value : undefined
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import { createFeedEnhancer } from "./enhancement/enhance-feed.ts"
|
||||
import { createLlmClient } from "./enhancement/llm-client.ts"
|
||||
import { GoogleMapsSourceProvider } from "./google-maps/provider.ts"
|
||||
import { CredentialEncryptor } from "./lib/crypto.ts"
|
||||
import { ensureEnv } from "./lib/env.ts"
|
||||
import { registerLocationHttpHandlers } from "./location/http.ts"
|
||||
import { LocationSourceProvider } from "./location/provider.ts"
|
||||
import { ReminderSourceProvider } from "./reminders/provider.ts"
|
||||
@@ -23,36 +24,19 @@ import { WeatherSourceProvider } from "./weather/provider.ts"
|
||||
import { WebSearchSourceProvider } from "./web-search/provider.ts"
|
||||
|
||||
function main() {
|
||||
const { db, close: closeDb } = createDatabase(process.env.DATABASE_URL!)
|
||||
const env = ensureEnv(process.env)
|
||||
|
||||
const { db, close: closeDb } = createDatabase(env.databaseUrl)
|
||||
const auth = createAuth(db)
|
||||
|
||||
const openrouterApiKey = process.env.OPENROUTER_API_KEY
|
||||
const feedEnhancer = openrouterApiKey
|
||||
? createFeedEnhancer({
|
||||
client: createLlmClient({
|
||||
apiKey: openrouterApiKey,
|
||||
model: process.env.OPENROUTER_MODEL || undefined,
|
||||
}),
|
||||
})
|
||||
: null
|
||||
if (!feedEnhancer) {
|
||||
console.warn("[enhancement] OPENROUTER_API_KEY not set — feed enhancement disabled")
|
||||
}
|
||||
const feedEnhancer = createFeedEnhancer({
|
||||
client: createLlmClient({
|
||||
apiKey: env.openrouterApiKey,
|
||||
model: env.openrouterModel,
|
||||
}),
|
||||
})
|
||||
|
||||
const credentialEncryptionKey = process.env.CREDENTIAL_ENCRYPTION_KEY
|
||||
const credentialEncryptor = credentialEncryptionKey
|
||||
? new CredentialEncryptor(credentialEncryptionKey)
|
||||
: null
|
||||
if (!credentialEncryptor) {
|
||||
console.warn(
|
||||
"[credentials] CREDENTIAL_ENCRYPTION_KEY not set — per-user credential storage disabled",
|
||||
)
|
||||
}
|
||||
|
||||
const googleMapsApiKey = process.env.GOOGLE_MAPS_API_KEY ?? process.env.GOOGLE_MAPS_MCP_API_KEY
|
||||
if (!googleMapsApiKey) {
|
||||
throw new Error("GOOGLE_MAPS_API_KEY or GOOGLE_MAPS_MCP_API_KEY must be set")
|
||||
}
|
||||
const credentialEncryptor = new CredentialEncryptor(env.credentialEncryptionKey)
|
||||
|
||||
const sessionManager = new UserSessionManager({
|
||||
db,
|
||||
@@ -62,16 +46,16 @@ function main() {
|
||||
new ReminderSourceProvider({ db }),
|
||||
new WeatherSourceProvider({
|
||||
credentials: {
|
||||
privateKey: process.env.WEATHERKIT_PRIVATE_KEY!,
|
||||
keyId: process.env.WEATHERKIT_KEY_ID!,
|
||||
teamId: process.env.WEATHERKIT_TEAM_ID!,
|
||||
serviceId: process.env.WEATHERKIT_SERVICE_ID!,
|
||||
privateKey: env.weatherkitPrivateKey,
|
||||
keyId: env.weatherkitKeyId,
|
||||
teamId: env.weatherkitTeamId,
|
||||
serviceId: env.weatherkitServiceId,
|
||||
},
|
||||
}),
|
||||
new TflSourceProvider({ apiKey: process.env.TFL_API_KEY! }),
|
||||
new WebSearchSourceProvider({ apiKey: process.env.EXA_API_KEY }),
|
||||
new TflSourceProvider({ apiKey: env.tflApiKey }),
|
||||
new WebSearchSourceProvider({ apiKey: env.exaApiKey }),
|
||||
new GoogleMapsSourceProvider({
|
||||
apiKey: googleMapsApiKey,
|
||||
apiKey: env.googleMapsApiKey,
|
||||
}),
|
||||
],
|
||||
feedEnhancer,
|
||||
|
||||
Reference in New Issue
Block a user