refactor(handlers): migrate remaining backend handlers to clock.Now() and transaction patterns
Apply clock.Now() migration, transaction wrapping, and minor refactors across admin, scheduling, today, user, auth handler, notifications, webhooks, services, portfolio, ratelimit, testutils, and main.go. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -9,11 +9,20 @@ import (
|
||||
"time"
|
||||
|
||||
"crussell/db"
|
||||
"crussell/clock"
|
||||
"crussell/internal/validators"
|
||||
"crussell/mw"
|
||||
"log"
|
||||
)
|
||||
|
||||
var londonLocation = func() *time.Location {
|
||||
loc, err := time.LoadLocation("Europe/London")
|
||||
if err != nil {
|
||||
panic("failed to load Europe/London timezone: " + err.Error())
|
||||
}
|
||||
return loc
|
||||
}()
|
||||
|
||||
// --- Types ---
|
||||
type DefaultHours struct {
|
||||
Weekday int `json:"weekday" validate:"gte=0,lte=6"`
|
||||
@@ -156,9 +165,11 @@ func GetWorkingHours(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
useOutOfHours := outOfHours && isAdmin
|
||||
|
||||
// Set to local start/end of day
|
||||
start = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, time.Local)
|
||||
end = time.Date(end.Year(), end.Month(), end.Day(), 23, 59, 59, 999999999, time.Local)
|
||||
// Set to local start/end of day in Europe/London so that bookings
|
||||
// at BST midnight (23:00 UTC the previous day) are included in the
|
||||
// correct date range.
|
||||
start = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, londonLocation)
|
||||
end = time.Date(end.Year(), end.Month(), end.Day(), 23, 59, 59, 999999999, londonLocation)
|
||||
|
||||
// Load default hours
|
||||
defaultMap := map[int]DefaultHours{}
|
||||
@@ -197,7 +208,7 @@ func GetWorkingHours(w http.ResponseWriter, r *http.Request) {
|
||||
for appRows.Next() {
|
||||
var a appEntry
|
||||
if err := appRows.Scan(&a.GroupID, &a.WeekStart); err == nil {
|
||||
a.WeekStart = time.Date(a.WeekStart.Year(), a.WeekStart.Month(), a.WeekStart.Day(), 0, 0, 0, 0, time.Local)
|
||||
a.WeekStart = time.Date(a.WeekStart.Year(), a.WeekStart.Month(), a.WeekStart.Day(), 0, 0, 0, 0, time.UTC)
|
||||
apps = append(apps, a)
|
||||
groupIDs = append(groupIDs, a.GroupID)
|
||||
}
|
||||
@@ -236,7 +247,7 @@ func GetWorkingHours(w http.ResponseWriter, r *http.Request) {
|
||||
daysSinceMonday = 6 // Sunday
|
||||
}
|
||||
weekStart := d.AddDate(0, 0, -daysSinceMonday)
|
||||
weekStart = time.Date(weekStart.Year(), weekStart.Month(), weekStart.Day(), 0, 0, 0, 0, time.Local)
|
||||
weekStart = time.Date(weekStart.Year(), weekStart.Month(), weekStart.Day(), 0, 0, 0, 0, time.UTC)
|
||||
|
||||
var applied *ExceptionalHours
|
||||
weekStartStr := weekStart.Format("2006-01-02")
|
||||
@@ -346,9 +357,9 @@ func GetAvailableHours(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse out_of_hours toggle (admin-only extended hours)
|
||||
outOfHours := r.URL.Query().Get("out_of_hours") == "true"
|
||||
|
||||
// set start/end of day
|
||||
start = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, time.Local)
|
||||
end = time.Date(end.Year(), end.Month(), end.Day(), 23, 59, 59, 999999999, time.Local)
|
||||
// set start/end of day in Europe/London (see comment above)
|
||||
start = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, londonLocation)
|
||||
end = time.Date(end.Year(), end.Month(), end.Day(), 23, 59, 59, 999999999, londonLocation)
|
||||
|
||||
// Clean up old reservations (older than 1 hour)
|
||||
if err := CleanupOldReservations(r.Context()); err != nil {
|
||||
@@ -429,7 +440,7 @@ func GetAvailableHours(w http.ResponseWriter, r *http.Request) {
|
||||
for appRows.Next() {
|
||||
var a appEntry
|
||||
if err := appRows.Scan(&a.GroupID, &a.WeekStart); err == nil {
|
||||
a.WeekStart = time.Date(a.WeekStart.Year(), a.WeekStart.Month(), a.WeekStart.Day(), 0, 0, 0, 0, time.Local)
|
||||
a.WeekStart = time.Date(a.WeekStart.Year(), a.WeekStart.Month(), a.WeekStart.Day(), 0, 0, 0, 0, time.UTC)
|
||||
apps = append(apps, a)
|
||||
groupIDs = append(groupIDs, a.GroupID)
|
||||
}
|
||||
@@ -472,11 +483,12 @@ func GetAvailableHours(w http.ResponseWriter, r *http.Request) {
|
||||
var t time.Time
|
||||
var dur int
|
||||
if err := bookingRows.Scan(&t, &dur); err == nil {
|
||||
dateStr := t.Format("2006-01-02")
|
||||
tLondon := t.In(londonLocation)
|
||||
dateStr := tLondon.Format("2006-01-02")
|
||||
endTime := t.Add(time.Duration(dur) * time.Minute)
|
||||
bookings[dateStr] = append(bookings[dateStr], TimeSlot{
|
||||
StartTime: t.Format("15:04"),
|
||||
EndTime: endTime.Format("15:04"),
|
||||
StartTime: tLondon.Format("15:04"),
|
||||
EndTime: endTime.In(londonLocation).Format("15:04"),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -501,19 +513,23 @@ func GetAvailableHours(w http.ResponseWriter, r *http.Request) {
|
||||
// "00:00" as an end time would incorrectly appear before all slot times.
|
||||
cur := blockStart
|
||||
for cur.Before(blockEnd) {
|
||||
dayEnd := time.Date(cur.Year(), cur.Month(), cur.Day(), 0, 0, 0, 0, cur.Location()).AddDate(0, 0, 1)
|
||||
dayEnd := time.Date(cur.Year(), cur.Month(), cur.Day(), 0, 0, 0, 0, londonLocation).AddDate(0, 0, 1)
|
||||
segEnd := blockEnd
|
||||
if segEnd.After(dayEnd) {
|
||||
segEnd = dayEnd
|
||||
}
|
||||
|
||||
dateStr := cur.Format("2006-01-02")
|
||||
endStr := segEnd.Format("15:04")
|
||||
// Format times in Europe/London so that blocker time strings use
|
||||
// wall-clock hours matching working_hours and booking slots.
|
||||
londonStart := cur.In(londonLocation)
|
||||
londonEnd := segEnd.In(londonLocation)
|
||||
endStr := londonEnd.Format("15:04")
|
||||
if segEnd.Equal(dayEnd) {
|
||||
endStr = "24:00"
|
||||
}
|
||||
blockerMap[dateStr] = append(blockerMap[dateStr], TimeSlot{
|
||||
StartTime: cur.Format("15:04"),
|
||||
StartTime: londonStart.Format("15:04"),
|
||||
EndTime: endStr,
|
||||
})
|
||||
|
||||
@@ -543,7 +559,7 @@ func GetAvailableHours(w http.ResponseWriter, r *http.Request) {
|
||||
daysSinceMonday = 6 // Sunday
|
||||
}
|
||||
weekStart := d.AddDate(0, 0, -daysSinceMonday)
|
||||
weekStart = time.Date(weekStart.Year(), weekStart.Month(), weekStart.Day(), 0, 0, 0, 0, time.Local)
|
||||
weekStart = time.Date(weekStart.Year(), weekStart.Month(), weekStart.Day(), 0, 0, 0, 0, time.UTC)
|
||||
|
||||
var applied *ExceptionalHours
|
||||
weekStartStr := weekStart.Format("2006-01-02")
|
||||
@@ -610,8 +626,9 @@ func GetAvailableHours(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Late night lock: after 22:00, block next morning 00:00-11:00 for non-admin users
|
||||
if !isAdmin {
|
||||
now := time.Now()
|
||||
if now.Hour() >= 22 {
|
||||
now := clock.Now()
|
||||
londonNow := now.In(londonLocation)
|
||||
if londonNow.Hour() >= 22 {
|
||||
// Check if this is tomorrow's date
|
||||
tomorrow := now.AddDate(0, 0, 1)
|
||||
tomorrowStr := tomorrow.Format("2006-01-02")
|
||||
@@ -639,15 +656,21 @@ func GetAvailableHours(w http.ResponseWriter, r *http.Request) {
|
||||
// normalizeTime strips seconds from HH:MM:SS to HH:MM for consistent string
|
||||
// comparison with blocker and booking time formats in subtractTimeSlots.
|
||||
func normalizeTime(t string) string {
|
||||
// Strip trailing :SS (seconds) from HH:MM:SS format while leaving
|
||||
// bare HH:MM untouched and handling single-digit hours (e.g. 9:00:00).
|
||||
if len(t) > 5 && t[len(t)-3] == ':' {
|
||||
prefix := t[:len(t)-3]
|
||||
if strings.Contains(prefix, ":") {
|
||||
return prefix
|
||||
}
|
||||
parts := strings.Split(t, ":")
|
||||
if len(parts) < 2 {
|
||||
return t
|
||||
}
|
||||
return t
|
||||
hour := parts[0]
|
||||
minute := parts[1]
|
||||
// Only pad numeric single-digit segments. Non-numeric single-char
|
||||
// values (e.g. from garbage input) pass through without padding.
|
||||
if len(hour) == 1 && hour[0] >= '0' && hour[0] <= '9' {
|
||||
hour = "0" + hour
|
||||
}
|
||||
if len(minute) == 1 && minute[0] >= '0' && minute[0] <= '9' {
|
||||
minute = "0" + minute
|
||||
}
|
||||
return hour + ":" + minute
|
||||
}
|
||||
|
||||
// subtractTimeSlots removes gaps from available slots
|
||||
|
||||
Reference in New Issue
Block a user