Skip to content

Commit e2be837

Browse files
committed
feat: implement database connection check and user feedback for missing DATABASE_URL; update translations for error messages
1 parent 1a7ea3c commit e2be837

7 files changed

Lines changed: 65 additions & 5 deletions

File tree

app/api/auth/check/route.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
import { NextResponse } from "next/server";
22

33
import { verifyPasswordRequest } from "@/utils/passwordServer";
4+
import { sqlExists } from "@/utils/sql";
45

56
export async function GET(request: Request) {
67
const auth = await verifyPasswordRequest(request);
78

89
if (!auth.ok) {
9-
return NextResponse.json({ authenticated: false, passwordLength: auth.passwordLength }, { status: 401 });
10+
return NextResponse.json(
11+
{ authenticated: false, passwordLength: auth.passwordLength, sqlExists },
12+
{ status: 401 }
13+
);
1014
}
1115

1216
return NextResponse.json({
1317
authenticated: true,
1418
enabled: auth.enabled,
1519
passwordLength: auth.passwordLength,
20+
sqlExists,
1621
});
1722
}

app/page.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const Page = () => {
3434
const query = useSearchParams();
3535
const router = useRouter();
3636
const [isAuthorized, setIsAuthorized] = useState(false);
37+
const [sqlExists, setSqlExists] = useState<boolean | null>(null);
3738

3839
const { initData: initGroupData, groupId, setGroupId } = useGroup();
3940
const { initData: initFinanceChangeData } = useFinanceChangeData();
@@ -46,12 +47,18 @@ const Page = () => {
4647
useEffect(() => {
4748
fetch("/api/auth/check")
4849
.then(async (res) => {
50+
const data = await res.json().catch(() => ({}));
51+
setSqlExists(data.sqlExists !== false);
52+
4953
if (res.ok) {
5054
setIsAuthorized(true);
5155
return;
5256
}
5357

54-
const data = await res.json().catch(() => ({}));
58+
if (data.sqlExists === false) {
59+
return;
60+
}
61+
5562
const passwordLength = normalizePasswordLength(data.passwordLength ?? DEFAULT_PASSWORD_LENGTH);
5663

5764
router.replace(`/login?length=${passwordLength}`);
@@ -93,6 +100,34 @@ const Page = () => {
93100
}
94101
}, [groupId, initFinanceChangeData, initFinanceData, isAuthorized]);
95102

103+
if (sqlExists === false) {
104+
return (
105+
<div className="flex min-h-screen items-center justify-center p-6">
106+
<div className="max-w-xl w-full rounded-large border border-default-200 bg-content1 p-6 md:p-8 shadow-sm">
107+
<h1 className="text-xl md:text-2xl font-semibold mb-3">{t("dbMissingTitle")}</h1>
108+
<p className="text-default-600 text-sm md:text-base mb-4">{t("dbMissingDescription")}</p>
109+
<pre className="bg-default-100 text-default-800 text-xs md:text-sm rounded-medium p-3 overflow-x-auto mb-4">
110+
{`DATABASE_URL="postgres://..."`}
111+
</pre>
112+
<p className="text-default-500 text-xs md:text-sm mb-5">{t("dbMissingHint")}</p>
113+
<div className="flex flex-wrap gap-3">
114+
<Button
115+
as={Link}
116+
color="primary"
117+
href="https://github.com/Emiyaaaaa/fibofinance#readme"
118+
target="_blank"
119+
>
120+
{t("dbMissingDocs")}
121+
</Button>
122+
<Button variant="flat" onPress={() => window.location.reload()}>
123+
{t("dbMissingRetry")}
124+
</Button>
125+
</div>
126+
</div>
127+
</div>
128+
);
129+
}
130+
96131
if (!isAuthorized) {
97132
return (
98133
<div className="flex min-h-screen items-center justify-center">

i18n/en.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@
1111
"aiAdviceErrorServer": "The advice service is temporarily unavailable. Please try again in a moment.",
1212
"aiAdviceErrorClient": "We couldn’t complete this request. Please try again or rephrase your question.",
1313
"aiAdviceErrorNetwork": "Network error. Check your connection and try again.",
14-
"changeGroupSuccess": "Successfully switched to - {groupName}"
14+
"changeGroupSuccess": "Successfully switched to - {groupName}",
15+
"dbMissingTitle": "Database is not configured",
16+
"dbMissingDescription": "DATABASE_URL is not set. The app needs a database connection to work. Add it to your deployment environment or local .env.local:",
17+
"dbMissingHint": "After setting it, redeploy or restart the app and refresh this page.",
18+
"dbMissingDocs": "View deployment docs",
19+
"dbMissingRetry": "Refresh"
1520
},
1621
"addGroup": {
1722
"default": "Default",

i18n/zh.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@
1111
"aiAdviceErrorServer": "建议服务暂时不可用,请稍后重试。",
1212
"aiAdviceErrorClient": "请求未能完成,请稍后重试或换个说法。",
1313
"aiAdviceErrorNetwork": "网络异常,请检查连接后重试。",
14-
"changeGroupSuccess": "已成功切换至 - {groupName}"
14+
"changeGroupSuccess": "已成功切换至 - {groupName}",
15+
"dbMissingTitle": "尚未配置数据库",
16+
"dbMissingDescription": "未检测到 DATABASE_URL 环境变量,应用需要连接数据库才能使用。请在部署环境或本地 .env.local 中配置:",
17+
"dbMissingHint": "配置完成后请重新部署或重启应用,并刷新页面。",
18+
"dbMissingDocs": "查看部署文档",
19+
"dbMissingRetry": "刷新页面"
1520
},
1621
"addGroup": {
1722
"default": "默认",

scripts/sync-database.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import { loadEnvConfig } from "@next/env";
33
loadEnvConfig(process.cwd());
44

55
const main = async () => {
6+
if (!process.env.DATABASE_URL) {
7+
console.log("DATABASE_URL is not set, skip database sync");
8+
return;
9+
}
10+
611
const { syncDatabase } = await import("../utils/syncDatabase");
712

813
await syncDatabase();

utils/passwordServer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
PasswordLength,
99
normalizePasswordLength,
1010
} from "./password";
11-
import { sql } from "./sql";
11+
import { sql, sqlExists } from "./sql";
1212

1313
export interface PasswordSettings {
1414
passwordMd5: string;
@@ -81,6 +81,10 @@ const ensureSettingsTable = async () => {
8181
};
8282

8383
export const getPasswordSettings = async (): Promise<PasswordSettings | null> => {
84+
if (!sqlExists) {
85+
return null;
86+
}
87+
8488
await ensureSettingsTable();
8589

8690
const rows = await sql("SELECT value FROM settings WHERE key = $1 LIMIT 1", [PASSWORD_SETTING_KEY]);

utils/sql.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import { neon } from "@neondatabase/serverless";
22

33
export const sql = neon(`${process.env.DATABASE_URL}`);
4+
export const sqlExists = Boolean(process.env.DATABASE_URL);

0 commit comments

Comments
 (0)