Fix snooze/due using container UTC instead of client local time
Docker container datetime.now() returns UTC, but all stored datetimes are naive local time from the browser. Both /due and /snooze now accept client_now from the frontend, ensuring snooze computes from the user's actual current time, not the container's clock. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
42e0fff40c
commit
daf2a4d5f1
@ -40,11 +40,12 @@ async def get_reminders(
|
||||
|
||||
@router.get("/due", response_model=List[ReminderResponse])
|
||||
async def get_due_reminders(
|
||||
client_now: Optional[datetime] = Query(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Settings = Depends(get_current_session)
|
||||
):
|
||||
"""Get reminders that are currently due for alerting."""
|
||||
now = datetime.now()
|
||||
now = client_now or datetime.now()
|
||||
query = select(Reminder).where(
|
||||
and_(
|
||||
Reminder.remind_at <= now,
|
||||
@ -82,7 +83,8 @@ async def snooze_reminder(
|
||||
if reminder.is_dismissed or not reminder.is_active:
|
||||
raise HTTPException(status_code=409, detail="Cannot snooze a dismissed or inactive reminder")
|
||||
|
||||
reminder.snoozed_until = datetime.now() + timedelta(minutes=body.minutes)
|
||||
base_time = body.client_now or datetime.now()
|
||||
reminder.snoozed_until = base_time + timedelta(minutes=body.minutes)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(reminder)
|
||||
|
||||
@ -22,6 +22,7 @@ class ReminderUpdate(BaseModel):
|
||||
|
||||
class ReminderSnooze(BaseModel):
|
||||
minutes: Literal[5, 10, 15]
|
||||
client_now: Optional[datetime] = None
|
||||
|
||||
@field_validator('minutes', mode='before')
|
||||
@classmethod
|
||||
|
||||
@ -4,7 +4,7 @@ import { useLocation } from 'react-router-dom';
|
||||
import { toast } from 'sonner';
|
||||
import { Bell } from 'lucide-react';
|
||||
import api from '@/lib/api';
|
||||
import { getRelativeTime } from '@/lib/date-utils';
|
||||
import { getRelativeTime, toLocalDatetime } from '@/lib/date-utils';
|
||||
import SnoozeDropdown from '@/components/reminders/SnoozeDropdown';
|
||||
import type { Reminder } from '@/types';
|
||||
|
||||
@ -36,7 +36,9 @@ export function AlertsProvider({ children }: { children: ReactNode }) {
|
||||
const { data: alerts = [] } = useQuery<Reminder[]>({
|
||||
queryKey: ['reminders', 'due'],
|
||||
queryFn: async () => {
|
||||
const { data } = await api.get<Reminder[]>('/reminders/due');
|
||||
const { data } = await api.get<Reminder[]>('/reminders/due', {
|
||||
params: { client_now: toLocalDatetime() },
|
||||
});
|
||||
return data;
|
||||
},
|
||||
refetchInterval: 30_000,
|
||||
@ -66,7 +68,7 @@ export function AlertsProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
const snoozeMutation = useMutation({
|
||||
mutationFn: ({ id, minutes }: { id: number; minutes: 5 | 10 | 15 }) =>
|
||||
api.patch(`/reminders/${id}/snooze`, { minutes }),
|
||||
api.patch(`/reminders/${id}/snooze`, { minutes, client_now: toLocalDatetime() }),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['reminders'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['dashboard'] });
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/** Format a Date as a naive local datetime string (no Z suffix). */
|
||||
export function toLocalDatetime(d: Date = new Date()): string {
|
||||
const pad = (n: number) => n.toString().padStart(2, '0');
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
||||
}
|
||||
|
||||
export function getRelativeTime(dateStr: string): string {
|
||||
const date = new Date(dateStr);
|
||||
const now = new Date();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user