feat: added proper validations for errors with messages

Signed-off-by: Varun Raj <varun@skcript.com>
This commit is contained in:
Varun Raj
2025-01-19 10:31:44 +05:30
parent d5c6f0ac29
commit 9fcda54e4f
8 changed files with 95 additions and 37 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ export default function ProfileInfo() {
</>
)}
</div>
<div className="border text-muted-foreground text-xs py-2 flex justify-between items-center px-2">
<div className="border-t text-muted-foreground text-xs py-2 flex justify-between items-center px-2">
<p>
Made with <span className="text-red-500">&hearts;</span> by{" "}
<Link
+11 -1
View File
@@ -4,6 +4,8 @@ import { Client, Pool } from "pg";
import { ENV } from "./environment";
import * as schema from "@/schema";
import { sql } from 'drizzle-orm';
import { findMissingKeys } from '@/helpers/data.helper';
import { APIError } from '@/lib/api';
const pool = ENV.DATABASE_URL ? new Pool({
connectionString: ENV.DATABASE_URL,
@@ -26,7 +28,15 @@ class DatabaseConnectionError extends Error {
}
export const connectDB = async (db: NodePgDatabase<typeof schema>) => {
try {
await db.execute(sql`SELECT 1`); // Execute a simple query
const missingKeys = findMissingKeys(ENV, ['DB_USERNAME', 'DB_PASSWORD', 'DB_HOST', 'DB_PORT', 'DB_DATABASE_NAME']);
if (!ENV.DATABASE_URL && missingKeys.length > 0) {
throw new APIError({
message: `Some database credentials are missing: ${missingKeys.join(', ')}. Please add them to the .env file`,
status: 500,
});
} else {
return await db.execute(sql`SELECT 1`); // Execute a simple query
}
} catch (error: any) {
throw new DatabaseConnectionError(error.message, "Database connection failed");
}
+19 -1
View File
@@ -1,6 +1,7 @@
import { appConfig } from "@/config/app.config"
import { connectDB, db } from "@/config/db"
import { ENV } from "@/config/environment"
import { APIError } from "@/lib/api"
import { getCookie } from "@/lib/cookie"
import { NextApiRequest } from "next"
@@ -22,7 +23,19 @@ export const getCurrentUserFromAPIKey = () => {
}
})
}
return null
throw new APIError({
message: "Invalid API key. Please check your API key variable `IMMICH_API_KEY` in the .env file",
status: 403,
});
})
.catch((error) => {
if (error instanceof APIError) {
throw error;
}
throw new APIError({
message: "Failed to connect to Immich API: " + ENV.IMMICH_URL,
status: 500,
});
})
}
@@ -37,6 +50,11 @@ export const getCurrentUserFromAccessToken = (token: string) => {
return res.json()
}
return null
}).catch((error) => {
throw new APIError({
message: "Failed to connect to Immich API: " + ENV.IMMICH_URL,
status: 500,
});
})
}
+5
View File
@@ -17,4 +17,9 @@ export const removeNullOrUndefinedProperties = (obj: any) => {
return v !== null && v !== undefined && v !== '' && v !== 'null' && v !== 'undefined' && v !== 'null' && v !== 'undefined'
}));
}
export const findMissingKeys = (obj: any, keys: string[]) => {
return keys.filter((key) => !(key in obj) || obj[key] === '' || obj[key] === null || obj[key] === undefined);
}
+15 -2
View File
@@ -2,6 +2,21 @@ import type { AxiosError, AxiosRequestConfig, Method } from 'axios'
import axios from 'axios'
import qs from 'qs'
export class APIError extends Error {
status: number;
constructor({
message,
status,
}: {
message: string;
status: number;
}) {
super(message);
this.name = 'APIError';
this.message = message;
this.status = status;
}
}
const handleErrors = (error: AxiosError | any) => {
@@ -26,8 +41,6 @@ const handleErrors = (error: AxiosError | any) => {
}
export default class API {
static setBaseURL(url: string) {
if (url) {
axios.defaults.baseURL = url
+29 -27
View File
@@ -106,40 +106,42 @@ export default function ExifDataAnalytics() {
}, []);
return (
<PageLayout className="p-4">
<PageLayout className="!p-0 !mb-0 relative">
<Header leftComponent="Exif Data" />
<div className="grid grid-cols-4 gap-4">
{["Total", "Images", "Videos"].map((type, i) => (
<Card key={i}>
<div className="p-4">
<div className="grid grid-cols-4 gap-4">
{["Total", "Images", "Videos"].map((type, i) => (
<Card key={i}>
<CardHeader>
<CardTitle className="text-center text-lg font-mono">{type}</CardTitle>
</CardHeader>
<CardContent className="text-center text-3xl font-mono">
{loading ? "Loading..." : statistics[type.toLowerCase() as keyof typeof statistics].toLocaleString()}
</CardContent>
</Card>
))}
<Card>
<CardHeader>
<CardTitle className="text-center text-lg font-mono">{type}</CardTitle>
<CardTitle className="text-center text-lg font-mono">Live Photos</CardTitle>
</CardHeader>
<CardContent className="text-center text-3xl font-mono">
{loading ? "Loading..." : statistics[type.toLowerCase() as keyof typeof statistics].toLocaleString()}
{loading ? "Loading..." : livePhotoStatistics.total.toLocaleString()}
</CardContent>
</Card>
))}
<Card>
<CardHeader>
<CardTitle className="text-center text-lg font-mono">Live Photos</CardTitle>
</CardHeader>
<CardContent className="text-center text-3xl font-mono">
{loading ? "Loading..." : livePhotoStatistics.total.toLocaleString()}
</CardContent>
</Card>
</div>
<AssetHeatMap />
<div className="grid grid-cols-3 gap-4">
{exifCharts.map((chart) => (
<EXIFDistribution
key={chart.column}
column={chart.column}
title={chart.title}
description={chart.description}
tooltipValueFormatter={chart.tooltipValueFormatter}
/>
))}
</div>
<AssetHeatMap />
<div className="grid grid-cols-3 gap-4">
{exifCharts.map((chart) => (
<EXIFDistribution
key={chart.column}
column={chart.column}
title={chart.title}
description={chart.description}
tooltipValueFormatter={chart.tooltipValueFormatter}
/>
))}
</div>
</div>
</PageLayout>
);
+6 -2
View File
@@ -13,11 +13,15 @@ export default async function GET(req: NextApiRequest, res: NextApiResponse) {
return res.status(404).json({ message: "Token not found" });
}
if (!ENV.IMMICH_SHARE_LINK_KEY) {
return res.status(401).json({ message: "Please check your link and try again. If you're the admin, check if you've enabled all the configurations in the Immich Power Tools in your environment variables" });
}
const decoded = verify(token as string, ENV.JWT_SECRET);
console.log(decoded);
if (!decoded) {
return res.status(401).json({ message: "Unauthorized" });
return res.status(401).json({ message: "Link is invalid. Please check your link and try again" });
}
+9 -3
View File
@@ -15,11 +15,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(401).json({ message: "Unauthorized" });
}
if (!ENV.IMMICH_SHARE_LINK_KEY) {
return res.status(401).json({ message: "Unauthorized" });
if (!ENV.JWT_SECRET) {
return res.status(401).json({ message: "The JWT_SECRET is missing in the .env file. Please add it to the .env file for generating share links" });
}
console.log(expiresIn);
if (!ENV.IMMICH_SHARE_LINK_KEY) {
return res.status(401).json({ message: "The IMMICH_SHARE_LINK_KEY is missing in the .env file. Please add it to the .env file for generating share links" });
}
if (!ENV.POWER_TOOLS_ENDPOINT_URL) {
return res.status(401).json({ message: "The POWER_TOOLS_ENDPOINT_URL is missing in the .env file. Please add it to the .env file for generating share links" });
}
const token = sign(filters, ENV.JWT_SECRET, expiresIn !== "never" ? {
expiresIn: expiresIn