Lint and reformat.

This commit is contained in:
Elisiário Couto
2025-09-28 23:01:04 +01:00
committed by Elisiário Couto
parent 22ec0e36b1
commit 222bb2ec64
7 changed files with 242 additions and 214 deletions

View File

@@ -58,9 +58,11 @@ export default function S3BackupConfigDrawer({
setOpen(false); setOpen(false);
toast.success("S3 backup configuration saved successfully"); toast.success("S3 backup configuration saved successfully");
}, },
onError: (error: any) => { onError: (error: Error & { response?: { data?: { detail?: string } } }) => {
console.error("Failed to update S3 backup configuration:", error); console.error("Failed to update S3 backup configuration:", error);
const message = error?.response?.data?.detail || "Failed to save S3 configuration. Please check your settings and try again."; const message =
error?.response?.data?.detail ||
"Failed to save S3 configuration. Please check your settings and try again.";
toast.error(message); toast.error(message);
}, },
}); });
@@ -73,11 +75,15 @@ export default function S3BackupConfigDrawer({
}), }),
onSuccess: () => { onSuccess: () => {
console.log("S3 connection test successful"); console.log("S3 connection test successful");
toast.success("S3 connection test successful! Your configuration is working correctly."); toast.success(
"S3 connection test successful! Your configuration is working correctly.",
);
}, },
onError: (error: any) => { onError: (error: Error & { response?: { data?: { detail?: string } } }) => {
console.error("Failed to test S3 connection:", error); console.error("Failed to test S3 connection:", error);
const message = error?.response?.data?.detail || "S3 connection test failed. Please verify your credentials and settings."; const message =
error?.response?.data?.detail ||
"S3 connection test failed. Please verify your credentials and settings.";
toast.error(message); toast.error(message);
}, },
}); });
@@ -98,9 +104,7 @@ export default function S3BackupConfigDrawer({
return ( return (
<Drawer open={open} onOpenChange={setOpen}> <Drawer open={open} onOpenChange={setOpen}>
<DrawerTrigger asChild> <DrawerTrigger asChild>{trigger || <EditButton />}</DrawerTrigger>
{trigger || <EditButton />}
</DrawerTrigger>
<DrawerContent> <DrawerContent>
<div className="mx-auto w-full max-w-sm"> <div className="mx-auto w-full max-w-sm">
<DrawerHeader> <DrawerHeader>
@@ -148,7 +152,10 @@ export default function S3BackupConfigDrawer({
type="password" type="password"
value={config.secret_access_key} value={config.secret_access_key}
onChange={(e) => onChange={(e) =>
setConfig({ ...config, secret_access_key: e.target.value }) setConfig({
...config,
secret_access_key: e.target.value,
})
} }
placeholder="Your AWS Secret Access Key" placeholder="Your AWS Secret Access Key"
required required
@@ -212,15 +219,21 @@ export default function S3BackupConfigDrawer({
<Label htmlFor="path_style">Use path-style addressing</Label> <Label htmlFor="path_style">Use path-style addressing</Label>
</div> </div>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
Enable for older S3 implementations or certain S3-compatible services Enable for older S3 implementations or certain S3-compatible
services
</p> </p>
</> </>
)} )}
<DrawerFooter className="px-0"> <DrawerFooter className="px-0">
<div className="flex space-x-2"> <div className="flex space-x-2">
<Button type="submit" disabled={updateMutation.isPending || !config.enabled}> <Button
{updateMutation.isPending ? "Saving..." : "Save Configuration"} type="submit"
disabled={updateMutation.isPending || !config.enabled}
>
{updateMutation.isPending
? "Saving..."
: "Save Configuration"}
</Button> </Button>
{config.enabled && isConfigValid && ( {config.enabled && isConfigValid && (
<Button <Button

View File

@@ -203,8 +203,10 @@ export default function Settings() {
} }
}; };
const isLoading = accountsLoading || settingsLoading || servicesLoading || backupLoading; const isLoading =
const hasError = accountsError || settingsError || servicesError || backupError; accountsLoading || settingsLoading || servicesLoading || backupLoading;
const hasError =
accountsError || settingsError || servicesError || backupError;
if (isLoading) { if (isLoading) {
return <AccountsSkeleton />; return <AccountsSkeleton />;
@@ -757,7 +759,8 @@ export default function Settings() {
<span>S3 Backup Configuration</span> <span>S3 Backup Configuration</span>
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>
Configure automatic database backups to Amazon S3 or S3-compatible storage Configure automatic database backups to Amazon S3 or
S3-compatible storage
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
@@ -769,7 +772,8 @@ export default function Settings() {
No S3 backup configured No S3 backup configured
</h3> </h3>
<p className="text-muted-foreground mb-4"> <p className="text-muted-foreground mb-4">
Set up S3 backup to automatically backup your database to the cloud. Set up S3 backup to automatically backup your database to
the cloud.
</p> </p>
<S3BackupConfigDrawer settings={backupSettings} /> <S3BackupConfigDrawer settings={backupSettings} />
</div> </div>
@@ -786,26 +790,33 @@ export default function Settings() {
S3 Backup S3 Backup
</h4> </h4>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div className={`w-2 h-2 rounded-full ${ <div
className={`w-2 h-2 rounded-full ${
backupSettings.s3.enabled backupSettings.s3.enabled
? 'bg-green-500' ? "bg-green-500"
: 'bg-muted-foreground' : "bg-muted-foreground"
}`} /> }`}
/>
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
{backupSettings.s3.enabled ? 'Enabled' : 'Disabled'} {backupSettings.s3.enabled
? "Enabled"
: "Disabled"}
</span> </span>
</div> </div>
</div> </div>
<div className="mt-2 space-y-1"> <div className="mt-2 space-y-1">
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
<span className="font-medium">Bucket:</span> {backupSettings.s3.bucket_name} <span className="font-medium">Bucket:</span>{" "}
{backupSettings.s3.bucket_name}
</p> </p>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
<span className="font-medium">Region:</span> {backupSettings.s3.region} <span className="font-medium">Region:</span>{" "}
{backupSettings.s3.region}
</p> </p>
{backupSettings.s3.endpoint_url && ( {backupSettings.s3.endpoint_url && (
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
<span className="font-medium">Endpoint:</span> {backupSettings.s3.endpoint_url} <span className="font-medium">Endpoint:</span>{" "}
{backupSettings.s3.endpoint_url}
</p> </p>
)} )}
</div> </div>
@@ -817,8 +828,9 @@ export default function Settings() {
<div className="p-4 bg-muted rounded-lg"> <div className="p-4 bg-muted rounded-lg">
<h5 className="font-medium mb-2">Backup Information</h5> <h5 className="font-medium mb-2">Backup Information</h5>
<p className="text-sm text-muted-foreground mb-3"> <p className="text-sm text-muted-foreground mb-3">
Database backups are stored in the "leggen_backups/" folder in your S3 bucket. Database backups are stored in the "leggen_backups/"
Backups include the complete SQLite database file. folder in your S3 bucket. Backups include the complete
SQLite database file.
</p> </p>
<div className="flex space-x-2"> <div className="flex space-x-2">
<Button <Button

View File

@@ -281,9 +281,8 @@ export const apiClient = {
// Backup endpoints // Backup endpoints
getBackupSettings: async (): Promise<BackupSettings> => { getBackupSettings: async (): Promise<BackupSettings> => {
const response = await api.get<ApiResponse<BackupSettings>>( const response =
"/backup/settings", await api.get<ApiResponse<BackupSettings>>("/backup/settings");
);
return response.data.data; return response.data.data;
}, },

View File

@@ -8,170 +8,170 @@
// You should NOT make any changes in this file as it will be overwritten. // You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root' import { Route as rootRouteImport } from "./routes/__root";
import { Route as TransactionsRouteImport } from './routes/transactions' import { Route as TransactionsRouteImport } from "./routes/transactions";
import { Route as SystemRouteImport } from './routes/system' import { Route as SystemRouteImport } from "./routes/system";
import { Route as SettingsRouteImport } from './routes/settings' import { Route as SettingsRouteImport } from "./routes/settings";
import { Route as NotificationsRouteImport } from './routes/notifications' import { Route as NotificationsRouteImport } from "./routes/notifications";
import { Route as BankConnectedRouteImport } from './routes/bank-connected' import { Route as BankConnectedRouteImport } from "./routes/bank-connected";
import { Route as AnalyticsRouteImport } from './routes/analytics' import { Route as AnalyticsRouteImport } from "./routes/analytics";
import { Route as IndexRouteImport } from './routes/index' import { Route as IndexRouteImport } from "./routes/index";
const TransactionsRoute = TransactionsRouteImport.update({ const TransactionsRoute = TransactionsRouteImport.update({
id: '/transactions', id: "/transactions",
path: '/transactions', path: "/transactions",
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any);
const SystemRoute = SystemRouteImport.update({ const SystemRoute = SystemRouteImport.update({
id: '/system', id: "/system",
path: '/system', path: "/system",
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any);
const SettingsRoute = SettingsRouteImport.update({ const SettingsRoute = SettingsRouteImport.update({
id: '/settings', id: "/settings",
path: '/settings', path: "/settings",
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any);
const NotificationsRoute = NotificationsRouteImport.update({ const NotificationsRoute = NotificationsRouteImport.update({
id: '/notifications', id: "/notifications",
path: '/notifications', path: "/notifications",
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any);
const BankConnectedRoute = BankConnectedRouteImport.update({ const BankConnectedRoute = BankConnectedRouteImport.update({
id: '/bank-connected', id: "/bank-connected",
path: '/bank-connected', path: "/bank-connected",
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any);
const AnalyticsRoute = AnalyticsRouteImport.update({ const AnalyticsRoute = AnalyticsRouteImport.update({
id: '/analytics', id: "/analytics",
path: '/analytics', path: "/analytics",
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any);
const IndexRoute = IndexRouteImport.update({ const IndexRoute = IndexRouteImport.update({
id: '/', id: "/",
path: '/', path: "/",
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any);
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute "/": typeof IndexRoute;
'/analytics': typeof AnalyticsRoute "/analytics": typeof AnalyticsRoute;
'/bank-connected': typeof BankConnectedRoute "/bank-connected": typeof BankConnectedRoute;
'/notifications': typeof NotificationsRoute "/notifications": typeof NotificationsRoute;
'/settings': typeof SettingsRoute "/settings": typeof SettingsRoute;
'/system': typeof SystemRoute "/system": typeof SystemRoute;
'/transactions': typeof TransactionsRoute "/transactions": typeof TransactionsRoute;
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute "/": typeof IndexRoute;
'/analytics': typeof AnalyticsRoute "/analytics": typeof AnalyticsRoute;
'/bank-connected': typeof BankConnectedRoute "/bank-connected": typeof BankConnectedRoute;
'/notifications': typeof NotificationsRoute "/notifications": typeof NotificationsRoute;
'/settings': typeof SettingsRoute "/settings": typeof SettingsRoute;
'/system': typeof SystemRoute "/system": typeof SystemRoute;
'/transactions': typeof TransactionsRoute "/transactions": typeof TransactionsRoute;
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRouteImport __root__: typeof rootRouteImport;
'/': typeof IndexRoute "/": typeof IndexRoute;
'/analytics': typeof AnalyticsRoute "/analytics": typeof AnalyticsRoute;
'/bank-connected': typeof BankConnectedRoute "/bank-connected": typeof BankConnectedRoute;
'/notifications': typeof NotificationsRoute "/notifications": typeof NotificationsRoute;
'/settings': typeof SettingsRoute "/settings": typeof SettingsRoute;
'/system': typeof SystemRoute "/system": typeof SystemRoute;
'/transactions': typeof TransactionsRoute "/transactions": typeof TransactionsRoute;
} }
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath fileRoutesByFullPath: FileRoutesByFullPath;
fullPaths: fullPaths:
| '/' | "/"
| '/analytics' | "/analytics"
| '/bank-connected' | "/bank-connected"
| '/notifications' | "/notifications"
| '/settings' | "/settings"
| '/system' | "/system"
| '/transactions' | "/transactions";
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo;
to: to:
| '/' | "/"
| '/analytics' | "/analytics"
| '/bank-connected' | "/bank-connected"
| '/notifications' | "/notifications"
| '/settings' | "/settings"
| '/system' | "/system"
| '/transactions' | "/transactions";
id: id:
| '__root__' | "__root__"
| '/' | "/"
| '/analytics' | "/analytics"
| '/bank-connected' | "/bank-connected"
| '/notifications' | "/notifications"
| '/settings' | "/settings"
| '/system' | "/system"
| '/transactions' | "/transactions";
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById;
} }
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute IndexRoute: typeof IndexRoute;
AnalyticsRoute: typeof AnalyticsRoute AnalyticsRoute: typeof AnalyticsRoute;
BankConnectedRoute: typeof BankConnectedRoute BankConnectedRoute: typeof BankConnectedRoute;
NotificationsRoute: typeof NotificationsRoute NotificationsRoute: typeof NotificationsRoute;
SettingsRoute: typeof SettingsRoute SettingsRoute: typeof SettingsRoute;
SystemRoute: typeof SystemRoute SystemRoute: typeof SystemRoute;
TransactionsRoute: typeof TransactionsRoute TransactionsRoute: typeof TransactionsRoute;
} }
declare module '@tanstack/react-router' { declare module "@tanstack/react-router" {
interface FileRoutesByPath { interface FileRoutesByPath {
'/transactions': { "/transactions": {
id: '/transactions' id: "/transactions";
path: '/transactions' path: "/transactions";
fullPath: '/transactions' fullPath: "/transactions";
preLoaderRoute: typeof TransactionsRouteImport preLoaderRoute: typeof TransactionsRouteImport;
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport;
} };
'/system': { "/system": {
id: '/system' id: "/system";
path: '/system' path: "/system";
fullPath: '/system' fullPath: "/system";
preLoaderRoute: typeof SystemRouteImport preLoaderRoute: typeof SystemRouteImport;
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport;
} };
'/settings': { "/settings": {
id: '/settings' id: "/settings";
path: '/settings' path: "/settings";
fullPath: '/settings' fullPath: "/settings";
preLoaderRoute: typeof SettingsRouteImport preLoaderRoute: typeof SettingsRouteImport;
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport;
} };
'/notifications': { "/notifications": {
id: '/notifications' id: "/notifications";
path: '/notifications' path: "/notifications";
fullPath: '/notifications' fullPath: "/notifications";
preLoaderRoute: typeof NotificationsRouteImport preLoaderRoute: typeof NotificationsRouteImport;
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport;
} };
'/bank-connected': { "/bank-connected": {
id: '/bank-connected' id: "/bank-connected";
path: '/bank-connected' path: "/bank-connected";
fullPath: '/bank-connected' fullPath: "/bank-connected";
preLoaderRoute: typeof BankConnectedRouteImport preLoaderRoute: typeof BankConnectedRouteImport;
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport;
} };
'/analytics': { "/analytics": {
id: '/analytics' id: "/analytics";
path: '/analytics' path: "/analytics";
fullPath: '/analytics' fullPath: "/analytics";
preLoaderRoute: typeof AnalyticsRouteImport preLoaderRoute: typeof AnalyticsRouteImport;
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport;
} };
'/': { "/": {
id: '/' id: "/";
path: '/' path: "/";
fullPath: '/' fullPath: "/";
preLoaderRoute: typeof IndexRouteImport preLoaderRoute: typeof IndexRouteImport;
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport;
} };
} }
} }
@@ -183,7 +183,7 @@ const rootRouteChildren: RootRouteChildren = {
SettingsRoute: SettingsRoute, SettingsRoute: SettingsRoute,
SystemRoute: SystemRoute, SystemRoute: SystemRoute,
TransactionsRoute: TransactionsRoute, TransactionsRoute: TransactionsRoute,
} };
export const routeTree = rootRouteImport export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren) ._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>() ._addFileTypes<FileRouteTypes>();

View File

@@ -12,7 +12,9 @@ class S3Config(BaseModel):
secret_access_key: str = Field(..., description="AWS S3 secret access key") secret_access_key: str = Field(..., description="AWS S3 secret access key")
bucket_name: str = Field(..., description="S3 bucket name") bucket_name: str = Field(..., description="S3 bucket name")
region: str = Field(default="us-east-1", description="AWS S3 region") region: str = Field(default="us-east-1", description="AWS S3 region")
endpoint_url: Optional[str] = Field(default=None, description="Custom S3 endpoint URL") endpoint_url: Optional[str] = Field(
default=None, description="Custom S3 endpoint URL"
)
path_style: bool = Field(default=False, description="Use path-style addressing") path_style: bool = Field(default=False, description="Use path-style addressing")
enabled: bool = Field(default=True, description="Enable S3 backups") enabled: bool = Field(default=True, description="Enable S3 backups")
@@ -42,4 +44,6 @@ class BackupOperation(BaseModel):
"""Backup operation request model.""" """Backup operation request model."""
operation: str = Field(..., description="Operation type (backup, restore)") operation: str = Field(..., description="Operation type (backup, restore)")
backup_key: Optional[str] = Field(default=None, description="Backup key for restore operations") backup_key: Optional[str] = Field(
default=None, description="Backup key for restore operations"
)

View File

@@ -1,6 +1,5 @@
"""API routes for backup management.""" """API routes for backup management."""
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
from loguru import logger from loguru import logger
@@ -79,7 +78,7 @@ async def update_backup_settings(settings: BackupSettings) -> APIResponse:
if not connection_success: if not connection_success:
raise HTTPException( raise HTTPException(
status_code=400, status_code=400,
detail="S3 connection test failed. Please check your configuration." detail="S3 connection test failed. Please check your configuration.",
) )
# Update backup config # Update backup config
@@ -198,9 +197,7 @@ async def backup_operation(operation_request: BackupOperation) -> APIResponse:
backup_config = config.backup_config.get("s3", {}) backup_config = config.backup_config.get("s3", {})
if not backup_config.get("bucket_name"): if not backup_config.get("bucket_name"):
raise HTTPException( raise HTTPException(status_code=400, detail="S3 backup is not configured")
status_code=400, detail="S3 backup is not configured"
)
# Convert config to model with validation # Convert config to model with validation
try: try:
@@ -232,7 +229,8 @@ async def backup_operation(operation_request: BackupOperation) -> APIResponse:
elif operation_request.operation == "restore": elif operation_request.operation == "restore":
if not operation_request.backup_key: if not operation_request.backup_key:
raise HTTPException( raise HTTPException(
status_code=400, detail="backup_key is required for restore operation" status_code=400,
detail="backup_key is required for restore operation",
) )
# Restore database # Restore database

View File

@@ -37,7 +37,9 @@ class S3BackupConfig(BaseModel):
secret_access_key: str = Field(..., description="AWS S3 secret access key") secret_access_key: str = Field(..., description="AWS S3 secret access key")
bucket_name: str = Field(..., description="S3 bucket name") bucket_name: str = Field(..., description="S3 bucket name")
region: str = Field(default="us-east-1", description="AWS S3 region") region: str = Field(default="us-east-1", description="AWS S3 region")
endpoint_url: Optional[str] = Field(default=None, description="Custom S3 endpoint URL") endpoint_url: Optional[str] = Field(
default=None, description="Custom S3 endpoint URL"
)
path_style: bool = Field(default=False, description="Use path-style addressing") path_style: bool = Field(default=False, description="Use path-style addressing")
enabled: bool = Field(default=True, description="Enable S3 backups") enabled: bool = Field(default=True, description="Enable S3 backups")