diff --git a/app/client/modules/backups/components/schedule-summary.tsx b/app/client/modules/backups/components/schedule-summary.tsx index 4422c77..7254368 100644 --- a/app/client/modules/backups/components/schedule-summary.tsx +++ b/app/client/modules/backups/components/schedule-summary.tsx @@ -148,13 +148,13 @@ export const ScheduleSummary = (props: Props) => {

Last backup

- {schedule.lastBackupAt ? new Date(schedule.lastBackupAt).toLocaleString() : "Never"} + {schedule.lastBackupAt ? new Date(schedule.lastBackupAt * 1000).toLocaleString() : "Never"}

Next backup

- {schedule.nextBackupAt ? new Date(schedule.nextBackupAt).toLocaleString() : "Never"} + {schedule.nextBackupAt ? new Date(schedule.nextBackupAt * 1000).toLocaleString() : "Never"}

diff --git a/app/client/modules/backups/routes/backups.tsx b/app/client/modules/backups/routes/backups.tsx index 505d124..16fe07c 100644 --- a/app/client/modules/backups/routes/backups.tsx +++ b/app/client/modules/backups/routes/backups.tsx @@ -97,13 +97,13 @@ export default function Backups({ loaderData }: Route.ComponentProps) {
Last backup - {schedule.lastBackupAt ? new Date(schedule.lastBackupAt).toLocaleDateString() : "Never"} + {schedule.lastBackupAt ? new Date(schedule.lastBackupAt * 1000).toLocaleDateString() : "Never"}
Next backup - {schedule.nextBackupAt ? new Date(schedule.nextBackupAt).toLocaleDateString() : "N/A"} + {schedule.nextBackupAt ? new Date(schedule.nextBackupAt * 1000).toLocaleDateString() : "N/A"}
diff --git a/app/client/modules/repositories/tabs/info.tsx b/app/client/modules/repositories/tabs/info.tsx index e081afc..fc60f21 100644 --- a/app/client/modules/repositories/tabs/info.tsx +++ b/app/client/modules/repositories/tabs/info.tsx @@ -32,12 +32,12 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => {
Created At

{new Date(repository.createdAt * 1000).toLocaleString()}

-
-
Last Checked
-

- {repository.lastChecked ? new Date(repository.lastChecked).toLocaleString() : "Never"} -

-
+
+
Last Checked
+

+ {repository.lastChecked ? new Date(repository.lastChecked * 1000).toLocaleString() : "Never"} +

+
{repository.lastError && ( diff --git a/app/client/modules/volumes/components/healthchecks-card.tsx b/app/client/modules/volumes/components/healthchecks-card.tsx index 49c2e19..b0a261c 100644 --- a/app/client/modules/volumes/components/healthchecks-card.tsx +++ b/app/client/modules/volumes/components/healthchecks-card.tsx @@ -13,7 +13,7 @@ type Props = { }; export const HealthchecksCard = ({ volume }: Props) => { - const timeAgo = formatDistanceToNow(volume.lastHealthCheck, { + const timeAgo = formatDistanceToNow(volume.lastHealthCheck * 1000, { addSuffix: true, }); diff --git a/app/drizzle/0016_fix-timestamps-to-seconds.sql b/app/drizzle/0016_fix-timestamps-to-seconds.sql new file mode 100644 index 0000000..38e25a1 --- /dev/null +++ b/app/drizzle/0016_fix-timestamps-to-seconds.sql @@ -0,0 +1,46 @@ +-- Custom SQL migration file, put your code below! -- +UPDATE `volumes_table` SET `last_health_check` = `last_health_check` / 1000 WHERE `last_health_check` > 4102444800; +--> statement-breakpoint +UPDATE `volumes_table` SET `created_at` = `created_at` / 1000 WHERE `created_at` > 4102444800; +--> statement-breakpoint +UPDATE `volumes_table` SET `updated_at` = `updated_at` / 1000 WHERE `updated_at` > 4102444800; +--> statement-breakpoint + +UPDATE `users_table` SET `created_at` = `created_at` / 1000 WHERE `created_at` > 4102444800; +--> statement-breakpoint +UPDATE `users_table` SET `updated_at` = `updated_at` / 1000 WHERE `updated_at` > 4102444800; +--> statement-breakpoint + +UPDATE `sessions_table` SET `expires_at` = `expires_at` / 1000 WHERE `expires_at` > 4102444800; +--> statement-breakpoint +UPDATE `sessions_table` SET `created_at` = `created_at` / 1000 WHERE `created_at` > 4102444800; +--> statement-breakpoint + +UPDATE `repositories_table` SET `last_checked` = `last_checked` / 1000 WHERE `last_checked` > 4102444800; +--> statement-breakpoint +UPDATE `repositories_table` SET `created_at` = `created_at` / 1000 WHERE `created_at` > 4102444800; +--> statement-breakpoint +UPDATE `repositories_table` SET `updated_at` = `updated_at` / 1000 WHERE `updated_at` > 4102444800; +--> statement-breakpoint + +UPDATE `backup_schedules_table` SET `last_backup_at` = `last_backup_at` / 1000 WHERE `last_backup_at` > 4102444800; +--> statement-breakpoint +UPDATE `backup_schedules_table` SET `next_backup_at` = `next_backup_at` / 1000 WHERE `next_backup_at` > 4102444800; +--> statement-breakpoint +UPDATE `backup_schedules_table` SET `created_at` = `created_at` / 1000 WHERE `created_at` > 4102444800; +--> statement-breakpoint +UPDATE `backup_schedules_table` SET `updated_at` = `updated_at` / 1000 WHERE `updated_at` > 4102444800; +--> statement-breakpoint + +UPDATE `notification_destinations_table` SET `created_at` = `created_at` / 1000 WHERE `created_at` > 4102444800; +--> statement-breakpoint +UPDATE `notification_destinations_table` SET `updated_at` = `updated_at` / 1000 WHERE `updated_at` > 4102444800; +--> statement-breakpoint + +UPDATE `backup_schedule_notifications_table` SET `created_at` = `created_at` / 1000 WHERE `created_at` > 4102444800; +--> statement-breakpoint + +UPDATE `app_metadata` SET `created_at` = `created_at` / 1000 WHERE `created_at` > 4102444800; +--> statement-breakpoint +UPDATE `app_metadata` SET `updated_at` = `updated_at` / 1000 WHERE `updated_at` > 4102444800; + diff --git a/app/drizzle/meta/0016_snapshot.json b/app/drizzle/meta/0016_snapshot.json new file mode 100644 index 0000000..01b65a3 --- /dev/null +++ b/app/drizzle/meta/0016_snapshot.json @@ -0,0 +1,688 @@ +{ + "id": "9cfb3302-0207-4eb1-89ae-d65078ceb0ac", + "prevId": "e52fe10a-3f36-4b21-abef-c15990d28363", + "version": "6", + "dialect": "sqlite", + "tables": { + "app_metadata": { + "name": "app_metadata", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "backup_schedule_notifications_table": { + "name": "backup_schedule_notifications_table", + "columns": { + "schedule_id": { + "name": "schedule_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "destination_id": { + "name": "destination_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "notify_on_start": { + "name": "notify_on_start", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notify_on_success": { + "name": "notify_on_success", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notify_on_failure": { + "name": "notify_on_failure", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": {}, + "foreignKeys": { + "backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk": { + "name": "backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk", + "tableFrom": "backup_schedule_notifications_table", + "columnsFrom": [ + "schedule_id" + ], + "tableTo": "backup_schedules_table", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk": { + "name": "backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk", + "tableFrom": "backup_schedule_notifications_table", + "columnsFrom": [ + "destination_id" + ], + "tableTo": "notification_destinations_table", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "backup_schedule_notifications_table_schedule_id_destination_id_pk": { + "columns": [ + "schedule_id", + "destination_id" + ], + "name": "backup_schedule_notifications_table_schedule_id_destination_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "backup_schedules_table": { + "name": "backup_schedules_table", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "volume_id": { + "name": "volume_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "retention_policy": { + "name": "retention_policy", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "exclude_patterns": { + "name": "exclude_patterns", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'[]'" + }, + "include_patterns": { + "name": "include_patterns", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'[]'" + }, + "last_backup_at": { + "name": "last_backup_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_backup_status": { + "name": "last_backup_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_backup_error": { + "name": "last_backup_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "next_backup_at": { + "name": "next_backup_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": {}, + "foreignKeys": { + "backup_schedules_table_volume_id_volumes_table_id_fk": { + "name": "backup_schedules_table_volume_id_volumes_table_id_fk", + "tableFrom": "backup_schedules_table", + "columnsFrom": [ + "volume_id" + ], + "tableTo": "volumes_table", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "backup_schedules_table_repository_id_repositories_table_id_fk": { + "name": "backup_schedules_table_repository_id_repositories_table_id_fk", + "tableFrom": "backup_schedules_table", + "columnsFrom": [ + "repository_id" + ], + "tableTo": "repositories_table", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_destinations_table": { + "name": "notification_destinations_table", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "notification_destinations_table_name_unique": { + "name": "notification_destinations_table_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "repositories_table": { + "name": "repositories_table", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "short_id": { + "name": "short_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "compression_mode": { + "name": "compression_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'auto'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'unknown'" + }, + "last_checked": { + "name": "last_checked", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "repositories_table_short_id_unique": { + "name": "repositories_table_short_id_unique", + "columns": [ + "short_id" + ], + "isUnique": true + }, + "repositories_table_name_unique": { + "name": "repositories_table_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sessions_table": { + "name": "sessions_table", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_table_user_id_users_table_id_fk": { + "name": "sessions_table_user_id_users_table_id_fk", + "tableFrom": "sessions_table", + "columnsFrom": [ + "user_id" + ], + "tableTo": "users_table", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users_table": { + "name": "users_table", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "has_downloaded_restic_password": { + "name": "has_downloaded_restic_password", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "users_table_username_unique": { + "name": "users_table_username_unique", + "columns": [ + "username" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "volumes_table": { + "name": "volumes_table", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "short_id": { + "name": "short_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unmounted'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_health_check": { + "name": "last_health_check", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "auto_remount": { + "name": "auto_remount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "volumes_table_short_id_unique": { + "name": "volumes_table_short_id_unique", + "columns": [ + "short_id" + ], + "isUnique": true + }, + "volumes_table_name_unique": { + "name": "volumes_table_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/app/drizzle/meta/_journal.json b/app/drizzle/meta/_journal.json index d69d66b..ad979e7 100644 --- a/app/drizzle/meta/_journal.json +++ b/app/drizzle/meta/_journal.json @@ -113,6 +113,13 @@ "when": 1764182465287, "tag": "0015_jazzy_sersi", "breakpoints": true + }, + { + "idx": 16, + "version": "6", + "when": 1764193182689, + "tag": "0016_fix-timestamps-to-seconds", + "breakpoints": true } ] } \ No newline at end of file diff --git a/app/server/modules/auth/auth.controller.ts b/app/server/modules/auth/auth.controller.ts index c78d281..b39079b 100644 --- a/app/server/modules/auth/auth.controller.ts +++ b/app/server/modules/auth/auth.controller.ts @@ -21,6 +21,7 @@ import { } from "./auth.dto"; import { authService } from "./auth.service"; import { toMessage } from "../../utils/errors"; +import { logger } from "~/server/utils/logger"; const COOKIE_NAME = "session_id"; const COOKIE_OPTIONS = { @@ -66,7 +67,7 @@ export const authController = new Hono() setCookie(c, COOKIE_NAME, sessionId, { ...COOKIE_OPTIONS, - expires: new Date(expiresAt), + expires: new Date(expiresAt * 1000), }); return c.json({ diff --git a/app/server/modules/auth/auth.service.ts b/app/server/modules/auth/auth.service.ts index 3b762ec..8a83c9d 100644 --- a/app/server/modules/auth/auth.service.ts +++ b/app/server/modules/auth/auth.service.ts @@ -3,7 +3,7 @@ import { db } from "../../db/db"; import { sessionsTable, usersTable } from "../../db/schema"; import { logger } from "../../utils/logger"; -const SESSION_DURATION = 1000 * 60 * 60 * 24 * 30; // 30 days +const SESSION_DURATION = 60 * 60 * 24 * 30; // 30 days in seconds export class AuthService { /** @@ -30,7 +30,7 @@ export class AuthService { logger.info(`User registered: ${username}`); const sessionId = crypto.randomUUID(); - const expiresAt = new Date(Date.now() + SESSION_DURATION).getTime(); + const expiresAt = Math.floor(Date.now() / 1000) + SESSION_DURATION; await db.insert(sessionsTable).values({ id: sessionId, @@ -66,7 +66,7 @@ export class AuthService { } const sessionId = crypto.randomUUID(); - const expiresAt = new Date(Date.now() + SESSION_DURATION).getTime(); + const expiresAt = Math.floor(Date.now() / 1000) + SESSION_DURATION; await db.insert(sessionsTable).values({ id: sessionId, @@ -112,7 +112,7 @@ export class AuthService { return null; } - if (session.session.expiresAt < Date.now()) { + if (session.session.expiresAt < Math.floor(Date.now() / 1000)) { await db.delete(sessionsTable).where(eq(sessionsTable.id, sessionId)); return null; } @@ -134,7 +134,7 @@ export class AuthService { * Clean up expired sessions */ async cleanupExpiredSessions() { - const result = await db.delete(sessionsTable).where(lt(sessionsTable.expiresAt, Date.now())).returning(); + const result = await db.delete(sessionsTable).where(lt(sessionsTable.expiresAt, Math.floor(Date.now() / 1000))).returning(); if (result.length > 0) { logger.info(`Cleaned up ${result.length} expired sessions`); } diff --git a/app/server/modules/backups/backups.service.ts b/app/server/modules/backups/backups.service.ts index e36bcf1..3525416 100644 --- a/app/server/modules/backups/backups.service.ts +++ b/app/server/modules/backups/backups.service.ts @@ -21,12 +21,12 @@ const calculateNextRun = (cronExpression: string): number => { tz: Intl.DateTimeFormat().resolvedOptions().timeZone, }); - return interval.next().getTime(); + return Math.floor(interval.next().getTime() / 1000); } catch (error) { logger.error(`Failed to parse cron expression "${cronExpression}": ${error}`); const fallback = new Date(); fallback.setMinutes(fallback.getMinutes() + 1); - return fallback.getTime(); + return Math.floor(fallback.getTime() / 1000); } }; @@ -126,7 +126,7 @@ const updateSchedule = async (scheduleId: number, data: UpdateBackupScheduleBody const [updated] = await db .update(backupSchedulesTable) - .set({ ...data, nextBackupAt, updatedAt: Date.now() }) + .set({ ...data, nextBackupAt, updatedAt: Math.floor(Date.now() / 1000) }) .where(eq(backupSchedulesTable.id, scheduleId)) .returning(); @@ -209,7 +209,7 @@ const executeBackup = async (scheduleId: number, manual = false) => { await db .update(backupSchedulesTable) - .set({ lastBackupStatus: "in_progress", updatedAt: Date.now(), lastBackupError: null, nextBackupAt }) + .set({ lastBackupStatus: "in_progress", updatedAt: Math.floor(Date.now() / 1000), lastBackupError: null, nextBackupAt }) .where(eq(backupSchedulesTable.id, scheduleId)); const abortController = new AbortController(); @@ -257,11 +257,11 @@ const executeBackup = async (scheduleId: number, manual = false) => { await db .update(backupSchedulesTable) .set({ - lastBackupAt: Date.now(), + lastBackupAt: Math.floor(Date.now() / 1000), lastBackupStatus: exitCode === 0 ? "success" : "warning", lastBackupError: null, nextBackupAt: nextBackupAt, - updatedAt: Date.now(), + updatedAt: Math.floor(Date.now() / 1000), }) .where(eq(backupSchedulesTable.id, scheduleId)); @@ -292,10 +292,10 @@ const executeBackup = async (scheduleId: number, manual = false) => { await db .update(backupSchedulesTable) .set({ - lastBackupAt: Date.now(), + lastBackupAt: Math.floor(Date.now() / 1000), lastBackupStatus: "error", lastBackupError: toMessage(error), - updatedAt: Date.now(), + updatedAt: Math.floor(Date.now() / 1000), }) .where(eq(backupSchedulesTable.id, scheduleId)); @@ -323,7 +323,7 @@ const executeBackup = async (scheduleId: number, manual = false) => { }; const getSchedulesToExecute = async () => { - const now = Date.now(); + const now = Math.floor(Date.now() / 1000); const schedules = await db.query.backupSchedulesTable.findMany({ where: eq(backupSchedulesTable.enabled, true), }); @@ -362,7 +362,7 @@ const stopBackup = async (scheduleId: number) => { .set({ lastBackupStatus: "error", lastBackupError: "Backup was stopped by user", - updatedAt: Date.now(), + updatedAt: Math.floor(Date.now() / 1000), }) .where(eq(backupSchedulesTable.id, scheduleId)); diff --git a/app/server/modules/repositories/repositories.service.ts b/app/server/modules/repositories/repositories.service.ts index c79f6e5..79ff267 100644 --- a/app/server/modules/repositories/repositories.service.ts +++ b/app/server/modules/repositories/repositories.service.ts @@ -105,7 +105,7 @@ const createRepository = async (name: string, config: RepositoryConfig, compress if (!error) { await db .update(repositoriesTable) - .set({ status: "healthy", lastChecked: Date.now(), lastError: null }) + .set({ status: "healthy", lastChecked: Math.floor(Date.now() / 1000), lastError: null }) .where(eq(repositoriesTable.id, id)); return { repository: created, status: 201 }; @@ -258,7 +258,7 @@ const checkHealth = async (repositoryId: string) => { .update(repositoriesTable) .set({ status, - lastChecked: Date.now(), + lastChecked: Math.floor(Date.now() / 1000), lastError: error, }) .where(eq(repositoriesTable.id, repository.id)); @@ -335,7 +335,7 @@ const doctorRepository = async (name: string) => { .update(repositoriesTable) .set({ status: allSuccessful ? "healthy" : "error", - lastChecked: Date.now(), + lastChecked: Math.floor(Date.now() / 1000), lastError: allSuccessful ? null : steps.find((s) => !s.success)?.error, }) .where(eq(repositoriesTable.id, repository.id)); diff --git a/app/server/modules/volumes/volume.service.ts b/app/server/modules/volumes/volume.service.ts index 7169d6f..94f1324 100644 --- a/app/server/modules/volumes/volume.service.ts +++ b/app/server/modules/volumes/volume.service.ts @@ -57,7 +57,7 @@ const createVolume = async (name: string, backendConfig: BackendConfig) => { await db .update(volumesTable) - .set({ status, lastError: error ?? null, lastHealthCheck: Date.now() }) + .set({ status, lastError: error ?? null, lastHealthCheck: Math.floor(Date.now() / 1000) }) .where(eq(volumesTable.name, slug)); return { volume: created, status: 201 }; @@ -91,7 +91,7 @@ const mountVolume = async (name: string) => { await db .update(volumesTable) - .set({ status, lastError: error ?? null, lastHealthCheck: Date.now() }) + .set({ status, lastError: error ?? null, lastHealthCheck: Math.floor(Date.now() / 1000) }) .where(eq(volumesTable.name, name)); if (status === "mounted") { @@ -196,7 +196,7 @@ const updateVolume = async (name: string, volumeData: UpdateVolumeBody) => { const { error, status } = await backend.mount(); await db .update(volumesTable) - .set({ status, lastError: error ?? null, lastHealthCheck: Date.now() }) + .set({ status, lastError: error ?? null, lastHealthCheck: Math.floor(Date.now() / 1000) }) .where(eq(volumesTable.id, existing.id)); serverEvents.emit("volume:updated", { volumeName: updated.name }); @@ -255,7 +255,7 @@ const checkHealth = async (name: string) => { await db .update(volumesTable) - .set({ lastHealthCheck: Date.now(), status, lastError: error ?? null }) + .set({ lastHealthCheck: Math.floor(Date.now() / 1000), status, lastError: error ?? null }) .where(eq(volumesTable.name, volume.name)); return { status, error };