ente/server/pkg/repo/emergency/recovery.go
2025-01-15 15:41:13 +05:30

173 lines
7.0 KiB
Go

package emergency
import (
"context"
"database/sql"
"fmt"
"github.com/sirupsen/logrus"
"github.com/ente-io/museum/ente"
"github.com/ente-io/museum/pkg/utils/time"
"github.com/ente-io/stacktrace"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/lib/pq"
)
type RecoverRow struct {
ID uuid.UUID
UserID int64
EmergencyContactID int64
Status ente.RecoveryStatus
WaitTill int64
NextReminderAt int64
CreatedAt int64
}
func (r RecoverRow) CanRecover() error {
if r.Status != ente.RecoveryStatusReady && r.Status != ente.RecoveryStatusWaiting {
return fmt.Errorf("recovery status is not waiting or ready")
}
if r.WaitTill > time.Microseconds() && r.Status == ente.RecoveryStatusWaiting {
return fmt.Errorf("recovery wait time is not over")
}
return nil
}
func (repo *Repository) InsertIntoRecovery(ctx *gin.Context, contact ente.ContactIdentifier, contactRow ContactRow) (bool, error) {
if contactRow.NoticePeriodInHrs <= 24 {
logrus.Warn("notice period is less than 24 hours")
return false, ente.NewBadRequestWithMessage("notice period should be greater than 24 hours")
}
waitTime := time.MicrosecondsAfterHours(contactRow.NoticePeriodInHrs)
// remind after 7 days.
nextReminder := time.MicrosecondsAfterHours(24 * 7)
if nextReminder >= waitTime {
logrus.Warn("initial reminder is greater than wait time")
// remind in 1 day if notice period is less than 7 days.
nextReminder = time.MicrosecondsAfterHours(24 * 1)
}
result, err := repo.DB.ExecContext(ctx, `INSERT INTO emergency_recovery (id,user_id, emergency_contact_id, status, wait_till, next_reminder_at) VALUES ($1, $2, $3, $4, $5, $6) on conflict DO NOTHING`,
uuid.New(), contact.UserID, contact.EmergencyContactID, ente.RecoveryStatusWaiting, waitTime, nextReminder)
if err != nil {
return false, stacktrace.Propagate(err, "")
}
count, _ := result.RowsAffected()
return count > 0, nil
}
func (repo *Repository) GetActiveRecoverySessions(ctx *gin.Context, userID int64) ([]*RecoverRow, error) {
rows, err := repo.DB.QueryContext(ctx, `SELECT id, user_id, emergency_contact_id, status, wait_till, next_reminder_at, created_at
FROM emergency_recovery WHERE (user_id=$1 OR emergency_contact_id=$1) AND status= ANY($2)`, userID, pq.Array([]ente.RecoveryStatus{ente.RecoveryStatusWaiting, ente.RecoveryStatusReady}))
if err != nil {
return nil, stacktrace.Propagate(err, "")
}
defer rows.Close()
var sessions []*RecoverRow
for rows.Next() {
var row RecoverRow
if err := rows.Scan(&row.ID, &row.UserID, &row.EmergencyContactID, &row.Status, &row.WaitTill, &row.NextReminderAt, &row.CreatedAt); err != nil {
return nil, stacktrace.Propagate(err, "")
}
sessions = append(sessions, &row)
}
return sessions, nil
}
func (repo *Repository) GetActiveSessions(ctx *gin.Context, userID int64, emergencyContactID int64) ([]*RecoverRow, error) {
rows, err := repo.DB.QueryContext(ctx, `SELECT id, user_id, emergency_contact_id, status, wait_till, next_reminder_at, created_at
FROM emergency_recovery WHERE user_id=$1 and emergency_contact_id=$2 AND status= ANY($3)`, userID, emergencyContactID, pq.Array([]ente.RecoveryStatus{ente.RecoveryStatusWaiting, ente.RecoveryStatusReady}))
if err != nil {
return nil, stacktrace.Propagate(err, "")
}
defer rows.Close()
var sessions []*RecoverRow
for rows.Next() {
var row RecoverRow
if err := rows.Scan(&row.ID, &row.UserID, &row.EmergencyContactID, &row.Status, &row.WaitTill, &row.NextReminderAt, &row.CreatedAt); err != nil {
return nil, stacktrace.Propagate(err, "")
}
sessions = append(sessions, &row)
}
return sessions, nil
}
func (r *Repository) GetActiveRecoveryForNotification() (*[]RecoverRow, error) {
rows, err := r.DB.Query(`
SELECT id, user_id, emergency_contact_id, status, wait_till, next_reminder_at, created_at
FROM emergency_recovery WHERE (status = $1) and next_reminder_at < now_utc_micro_seconds()`, ente.RecoveryStatusWaiting)
if err != nil {
return nil, stacktrace.Propagate(err, "")
}
defer rows.Close()
var sessions []RecoverRow
for rows.Next() {
var row RecoverRow
if err := rows.Scan(&row.ID, &row.UserID, &row.EmergencyContactID, &row.Status, &row.WaitTill, &row.NextReminderAt, &row.CreatedAt); err != nil {
return nil, stacktrace.Propagate(err, "")
}
sessions = append(sessions, row)
}
return &sessions, nil
}
func (r *Repository) UpdateNextReminder(ctx context.Context, sessionID uuid.UUID, nextReminder int64) error {
_, err := r.DB.ExecContext(ctx, `UPDATE emergency_recovery SET next_reminder_at=$1 WHERE id=$2`, nextReminder, sessionID)
if err != nil {
return stacktrace.Propagate(err, "")
}
return nil
}
func (repo *Repository) UpdateRecoveryStatusForID(ctx context.Context, sessionID uuid.UUID, status ente.RecoveryStatus) (bool, error) {
validPrevStatus := validPreviousStatus(status)
var result sql.Result
var err error
if status == ente.RecoveryStatusReady {
result, err = repo.DB.ExecContext(ctx, `UPDATE emergency_recovery SET status=$1, wait_till=$2 WHERE id=$3 and status = ANY($4)`, status, time.Microseconds(), sessionID, pq.Array(validPrevStatus))
} else {
result, err = repo.DB.ExecContext(ctx, `UPDATE emergency_recovery SET status=$1 WHERE id=$2 and status = ANY($3)`, status, sessionID, pq.Array(validPrevStatus))
}
if err != nil {
return false, stacktrace.Propagate(err, "")
}
rows, _ := result.RowsAffected()
return rows > 0, nil
}
func (repo *Repository) GetRecoverRowByID(ctx context.Context, sessionID uuid.UUID) (*RecoverRow, error) {
var row RecoverRow
err := repo.DB.QueryRowContext(ctx, `SELECT id, user_id, emergency_contact_id, status, wait_till, next_reminder_at, created_at
FROM emergency_recovery WHERE id=$1`, sessionID).Scan(&row.ID, &row.UserID, &row.EmergencyContactID, &row.Status, &row.WaitTill, &row.NextReminderAt, &row.CreatedAt)
if err != nil {
return nil, stacktrace.Propagate(err, "")
}
return &row, nil
}
func (repo *Repository) UpdateRecoveryStatus(ctx context.Context, userID, emergencyContactID int64, status ente.RecoveryStatus) error {
validPrevStatus := validPreviousStatus(status)
_, err := repo.DB.ExecContext(ctx, `UPDATE emergency_recovery SET status=$1 WHERE user_id =$2 and emergency_contact_id =$3 and status = ANY($4)`, status, userID, emergencyContactID, pq.Array(validPrevStatus))
if err != nil {
return stacktrace.Propagate(err, "")
}
return nil
}
func validPreviousStatus(newStatus ente.RecoveryStatus) []ente.RecoveryStatus {
result := make([]ente.RecoveryStatus, 0)
switch newStatus {
case ente.RecoveryStatusWaiting:
break
case ente.RecoveryStatusReady:
result = append(result, ente.RecoveryStatusWaiting, ente.RecoveryStatusReady)
case ente.RecoveryStatusStopped:
result = append(result, ente.RecoveryStatusWaiting, ente.RecoveryStatusReady)
case ente.RecoveryStatusRejected:
result = append(result, ente.RecoveryStatusWaiting, ente.RecoveryStatusReady)
case ente.RecoveryStatusRecovered:
result = append(result, ente.RecoveryStatusWaiting, ente.RecoveryStatusReady)
}
return result
}