diff --git a/README.md b/README.md index 1c87982..680dacf 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,6 @@ services: ports: - "7331:7331" environment: - - NODE_ENV=production - SELF_HOSTED_MODE=true - UPLOAD_PATH=/app/data/uploads - DATABASE_URL=postgresql://postgres:postgres@localhost:5432/taxhacker @@ -146,7 +145,7 @@ Configure TaxHacker to suit your needs with these environment variables: | Variable | Required | Description | Example | |----------|----------|-------------|---------| -| `PORT` | No | Port to run the server on | `7331` | +| `PORT` | No | Port to run the app on | `7331` (default) | | `UPLOAD_PATH` | Yes | Local directory for uploading files | `./data/uploads` | | `DATABASE_URL` | Yes | PostgreSQL connection string | `postgresql://user@localhost:5432/taxhacker` | | `SELF_HOSTED_MODE` | No | Set it to "true" if you're self-hosting the app: it enables auto-login, custom API keys, and more | `false` | diff --git a/app/(app)/unsorted/actions.ts b/app/(app)/unsorted/actions.ts index a49cfac..41de62c 100644 --- a/app/(app)/unsorted/actions.ts +++ b/app/(app)/unsorted/actions.ts @@ -29,6 +29,11 @@ export async function analyzeFileAction( return { success: false, error: "File not found or does not belong to the user" } } + const apiKey = settings.openai_api_key || config.ai.openaiApiKey || "" + if (!apiKey) { + return { success: false, error: "OpenAI API key is not set" } + } + let attachments: AnalyzeAttachment[] = [] try { attachments = await loadAttachmentsForAI(user, file) @@ -46,12 +51,7 @@ export async function analyzeFileAction( const schema = fieldsToJsonSchema(fields) - const results = await analyzeTransaction( - prompt, - schema, - attachments, - settings.openai_api_key || config.ai.openaiApiKey - ) + const results = await analyzeTransaction(prompt, schema, attachments, apiKey) console.log("Analysis results:", results) diff --git a/app/layout.tsx b/app/layout.tsx index 14eff72..cf83a54 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -14,10 +14,32 @@ export const metadata: Metadata = { apple: "/apple-touch-icon.png", }, manifest: "/site.webmanifest", + metadataBase: new URL(config.app.baseURL), + openGraph: { + type: "website", + locale: "en_US", + url: config.app.baseURL, + title: config.app.title, + description: config.app.description, + siteName: config.app.title, + }, + twitter: { + card: "summary_large_image", + title: config.app.title, + description: config.app.description, + }, + robots: { + index: true, + follow: true, + }, } export const viewport: Viewport = { themeColor: "#ffffff", + width: "device-width", + initialScale: 1, + maximumScale: 1, + userScalable: false, } export default async function RootLayout({ children }: { children: React.ReactNode }) { @@ -25,8 +47,9 @@ export default async function RootLayout({ children }: { children: React.ReactNo + - {children} + {children} ) } diff --git a/lib/config.ts b/lib/config.ts index 2ec32b9..2c26be3 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -1,31 +1,50 @@ +import { z } from "zod" + +const envSchema = z.object({ + BASE_URL: z.string().url().default("http://localhost:7331"), + PORT: z.string().default("7331"), + SELF_HOSTED_MODE: z.enum(["true", "false"]).default("false"), + OPENAI_API_KEY: z.string().optional(), + BETTER_AUTH_SECRET: z + .string() + .min(16, "Auth secret must be at least 16 characters") + .default("please-set-your-key-here"), + DISABLE_SIGNUP: z.enum(["true", "false"]).default("false"), + RESEND_API_KEY: z.string().default("please-set-your-resend-api-key-here"), + RESEND_FROM_EMAIL: z.string().default("TaxHacker "), + RESEND_AUDIENCE_ID: z.string().default(""), +}) + +const env = envSchema.parse(process.env) + const config = { app: { title: "TaxHacker", - description: "Your personal AI accountant", + description: "Your personal AI helper for taxes", version: process.env.npm_package_version || "0.0.1", - baseURL: process.env.BASE_URL || "http://localhost:" + process.env.PORT, + baseURL: env.BASE_URL || `http://localhost:${env.PORT || "7331"}`, }, upload: { acceptedMimeTypes: "image/*,.pdf,.doc,.docx,.xls,.xlsx", }, selfHosted: { - isEnabled: process.env.SELF_HOSTED_MODE === "true", + isEnabled: env.SELF_HOSTED_MODE === "true", redirectUrl: "/self-hosted/redirect", welcomeUrl: "/self-hosted", }, ai: { - openaiApiKey: process.env.OPENAI_API_KEY || "", + openaiApiKey: env.OPENAI_API_KEY, }, auth: { - secret: process.env.BETTER_AUTH_SECRET || "please-set-secret", + secret: env.BETTER_AUTH_SECRET, loginUrl: "/enter", - disableSignup: process.env.DISABLE_SIGNUP === "true" || process.env.SELF_HOSTED_MODE === "true", + disableSignup: env.DISABLE_SIGNUP === "true" || env.SELF_HOSTED_MODE === "true", }, email: { - apiKey: process.env.RESEND_API_KEY || "please-set-api-key", - from: process.env.RESEND_FROM_EMAIL || "user@localhost", - audienceId: process.env.RESEND_AUDIENCE_ID || "", + apiKey: env.RESEND_API_KEY, + from: env.RESEND_FROM_EMAIL, + audienceId: env.RESEND_AUDIENCE_ID, }, -} +} as const export default config