feat: add delete account button in settings, implement all remaining achievements
All checks were successful
CI / build (push) Successful in 37s
All checks were successful
CI / build (push) Successful in 37s
- Add delete all data button with red danger style and confirmation prompt - Add DeleteUserData repo method that wipes user from all tables in a transaction - Add helper methods deleteAccountPrompt and deleteAccountConfirm - Implement consistency_master, perfect_week, remote_veteran, onsite_master achievements - Add GetDailyXPInRange and SumWorkSecondsByWorkType repo methods - Update Dockerfile to alpine3.23, docker-compose to use env vars for proxy/paths
This commit is contained in:
@@ -44,6 +44,8 @@ type Repository interface {
|
||||
UpsertDailyXP(userID int64, date string, xpEarned, workSeconds int64) error
|
||||
SetDailyWorkSeconds(userID int64, date string, workSeconds int64) error
|
||||
SumDailyWorkSeconds(userID int64) (int64, error)
|
||||
GetDailyXPInRange(userID int64, start, end string) ([]domain.DailyXP, error)
|
||||
SumWorkSecondsByWorkType(userID int64) (remoteSeconds, onsiteSeconds int64, _ error)
|
||||
|
||||
// Achievements
|
||||
GetAchievementByCode(code string) (*domain.Achievement, error)
|
||||
@@ -55,5 +57,6 @@ type Repository interface {
|
||||
GetLeagueRankings(orderBy string) ([]domain.LeagueEntry, error)
|
||||
|
||||
// Lifecycle
|
||||
DeleteUserData(userID int64) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
@@ -35,6 +35,38 @@ func NewStore(path string) (*Store, error) {
|
||||
return &Store{db: db}, nil
|
||||
}
|
||||
|
||||
func (s *Store) DeleteUserData(userID int64) error {
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if _, err := tx.Exec(`DELETE FROM events WHERE day_id IN (SELECT id FROM days WHERE user_id=?)`, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(`DELETE FROM days WHERE user_id=?`, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(`DELETE FROM user_achievements WHERE user_id=?`, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(`DELETE FROM daily_xp WHERE user_id=?`, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(`DELETE FROM user_rpg WHERE user_id=?`, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(`DELETE FROM user_settings WHERE user_id=?`, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(`DELETE FROM users WHERE id=?`, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (s *Store) Close() error {
|
||||
return s.db.Close()
|
||||
}
|
||||
@@ -233,6 +265,57 @@ func (s *Store) SumDailyWorkSeconds(userID int64) (int64, error) {
|
||||
return total, err
|
||||
}
|
||||
|
||||
func (s *Store) GetDailyXPInRange(userID int64, start, end string) ([]domain.DailyXP, error) {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT id, user_id, date, xp_earned, work_seconds, created_at
|
||||
FROM daily_xp WHERE user_id=? AND date >= ? AND date <= ?
|
||||
ORDER BY date`, userID, start, end,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var result []domain.DailyXP
|
||||
for rows.Next() {
|
||||
var x domain.DailyXP
|
||||
if err := rows.Scan(&x.ID, &x.UserID, &x.Date, &x.XPEarned, &x.WorkSeconds, &x.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, x)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) SumWorkSecondsByWorkType(userID int64) (remoteSeconds, onsiteSeconds int64, _ error) {
|
||||
// Each day has a current_work_type_id. Daily XP work_seconds are per day.
|
||||
// Join with days to get the day's primary work type, then sum by type.
|
||||
rows, err := s.db.Query(`
|
||||
SELECT COALESCE(d.current_work_type_id, 1), COALESCE(SUM(dx.work_seconds), 0)
|
||||
FROM daily_xp dx
|
||||
JOIN days d ON d.user_id = dx.user_id AND d.date = dx.date
|
||||
WHERE dx.user_id = ? AND dx.work_seconds > 0
|
||||
GROUP BY d.current_work_type_id
|
||||
`, userID)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var wtID int64
|
||||
var secs int64
|
||||
if err := rows.Scan(&wtID, &secs); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
switch wtID {
|
||||
case 2:
|
||||
remoteSeconds += secs
|
||||
default:
|
||||
onsiteSeconds += secs
|
||||
}
|
||||
}
|
||||
return remoteSeconds, onsiteSeconds, rows.Err()
|
||||
}
|
||||
|
||||
// Achievements
|
||||
|
||||
func (s *Store) GetAchievementByCode(code string) (*domain.Achievement, error) {
|
||||
|
||||
Reference in New Issue
Block a user