mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 01:01:39 -05:00
Rework some parts
This commit is contained in:
parent
17837979e2
commit
8465350c1b
15 changed files with 80 additions and 53 deletions
|
|
@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||
|
||||
- Map V2 points loading is significantly sped up.
|
||||
- Points size on Map V2 was reduced to prevent overlapping.
|
||||
- User deletion now being done in the background to prevent request timeouts for users with large amount of data.
|
||||
|
||||
# [0.37.2] - 2026-01-04
|
||||
|
||||
|
|
|
|||
|
|
@ -29,11 +29,13 @@ class ApiController < ApplicationController
|
|||
def authenticate_active_api_user!
|
||||
if current_api_user.nil?
|
||||
render json: { error: 'User account is not active or has been deleted' }, status: :unauthorized
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
unless current_api_user.active_until&.future?
|
||||
if current_api_user.active_until&.past?
|
||||
render json: { error: 'User subscription is not active' }, status: :unauthorized
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -74,11 +74,11 @@ class ApplicationController < ActionController::Base
|
|||
private
|
||||
|
||||
def sign_out_deleted_users
|
||||
if current_user&.deleted?
|
||||
return unless current_user&.deleted?
|
||||
|
||||
sign_out current_user
|
||||
redirect_to root_path, alert: 'Your account has been deleted.'
|
||||
end
|
||||
end
|
||||
|
||||
def set_self_hosted_status
|
||||
@self_hosted = DawarichSettings.self_hosted?
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class Settings::UsersController < ApplicationController
|
|||
notice: 'User deletion has been initiated. The account will be fully removed shortly.'
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
redirect_to settings_users_url,
|
||||
alert: 'Cannot delete account while being a family owner with other members.',
|
||||
alert: 'Cannot delete account while being owner of a family which has other members.',
|
||||
status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -30,10 +30,8 @@ class Users::RegistrationsController < Devise::RegistrationsController
|
|||
begin
|
||||
resource.mark_as_deleted!
|
||||
|
||||
# Sign out immediately
|
||||
Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
|
||||
|
||||
# Enqueue background job
|
||||
Users::DestroyJob.perform_later(resource.id)
|
||||
|
||||
set_flash_message! :notice, :destroyed
|
||||
|
|
|
|||
|
|
@ -3,15 +3,12 @@
|
|||
class Users::DestroyJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
sidekiq_options retry: false # No retry for destructive operations
|
||||
sidekiq_options retry: false
|
||||
|
||||
def perform(user_id)
|
||||
user = User.deleted_accounts.find_by(id: user_id)
|
||||
|
||||
unless user
|
||||
Rails.logger.warn "User #{user_id} not found or not marked for deletion, skipping"
|
||||
return
|
||||
end
|
||||
return unless user
|
||||
|
||||
Rails.logger.info "Starting hard deletion for user #{user.id} (#{user.email})"
|
||||
|
||||
|
|
@ -21,8 +18,6 @@ class Users::DestroyJob < ApplicationJob
|
|||
rescue ActiveRecord::RecordNotFound
|
||||
Rails.logger.warn "User #{user_id} not found, may have already been deleted"
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "Failed to delete user #{user_id}: #{e.message}"
|
||||
ExceptionReporter.call(e, "User deletion failed for user_id #{user_id}")
|
||||
# Don't raise - leave user in deleted state for manual cleanup
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ class Users::TrialWebhookJob < ApplicationJob
|
|||
def perform(user_id)
|
||||
user = User.find(user_id)
|
||||
|
||||
# Skip webhook for soft-deleted users
|
||||
return if user.deleted?
|
||||
|
||||
payload = {
|
||||
user_id: user.id,
|
||||
email: user.email,
|
||||
|
|
|
|||
31
app/models/concerns/soft_deletable.rb
Normal file
31
app/models/concerns/soft_deletable.rb
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module SoftDeletable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
scope :active_accounts, -> { where(deleted_at: nil) }
|
||||
scope :deleted_accounts, -> { where.not(deleted_at: nil) }
|
||||
end
|
||||
|
||||
def deleted?
|
||||
deleted_at.present?
|
||||
end
|
||||
|
||||
def mark_as_deleted!
|
||||
update!(deleted_at: Time.current)
|
||||
end
|
||||
|
||||
def destroy
|
||||
mark_as_deleted!
|
||||
end
|
||||
|
||||
# Devise authentication overrides
|
||||
def active_for_authentication?
|
||||
super && !deleted?
|
||||
end
|
||||
|
||||
def inactive_message
|
||||
deleted? ? :deleted : super
|
||||
end
|
||||
end
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
class User < ApplicationRecord # rubocop:disable Metrics/ClassLength
|
||||
include UserFamily
|
||||
include Omniauthable
|
||||
include SoftDeletable
|
||||
|
||||
devise :database_authenticatable, :registerable,
|
||||
:recoverable, :rememberable, :validatable, :trackable,
|
||||
|
|
@ -36,33 +37,9 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength
|
|||
attribute :points_count, :integer, default: 0
|
||||
|
||||
scope :active_or_trial, -> { where(status: %i[active trial]) }
|
||||
scope :active_accounts, -> { where(deleted_at: nil) }
|
||||
scope :deleted_accounts, -> { where.not(deleted_at: nil) }
|
||||
|
||||
enum :status, { inactive: 0, active: 1, trial: 2 }
|
||||
|
||||
# Soft-delete methods
|
||||
def deleted?
|
||||
deleted_at.present?
|
||||
end
|
||||
|
||||
def mark_as_deleted!
|
||||
update!(deleted_at: Time.current)
|
||||
end
|
||||
|
||||
def destroy
|
||||
mark_as_deleted!
|
||||
end
|
||||
|
||||
# Devise authentication overrides
|
||||
def active_for_authentication?
|
||||
super && !deleted?
|
||||
end
|
||||
|
||||
def inactive_message
|
||||
deleted? ? :deleted : super
|
||||
end
|
||||
|
||||
def safe_settings
|
||||
Users::SafeSettings.new(settings)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,9 +13,27 @@ class Users::Destroy
|
|||
|
||||
cancel_scheduled_jobs
|
||||
|
||||
# Hard delete with transaction - all associations cascade via dependent: :destroy
|
||||
ActiveRecord::Base.transaction do
|
||||
user.destroy!
|
||||
# Delete associated records first (dependent: :destroy associations)
|
||||
user.points.delete_all
|
||||
user.imports.delete_all
|
||||
user.stats.delete_all
|
||||
user.exports.delete_all
|
||||
user.notifications.delete_all
|
||||
user.areas.delete_all
|
||||
user.visits.delete_all
|
||||
user.places.delete_all
|
||||
user.tags.delete_all
|
||||
user.trips.delete_all
|
||||
user.tracks.delete_all
|
||||
user.raw_data_archives.delete_all
|
||||
user.digests.delete_all
|
||||
user.sent_family_invitations.delete_all if user.respond_to?(:sent_family_invitations)
|
||||
user.family_membership&.delete
|
||||
user.created_family&.delete
|
||||
|
||||
# Hard delete the user (bypasses soft-delete, skips callbacks)
|
||||
user.delete
|
||||
end
|
||||
|
||||
Rails.logger.info "User #{user_id} (#{user_email}) and all associated data deleted"
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ en:
|
|||
updated_not_active: "Your password has been changed successfully."
|
||||
registrations:
|
||||
destroyed: "Your account has been scheduled for deletion. Goodbye!"
|
||||
cannot_delete: "Cannot delete your account while you are a family owner with other members."
|
||||
cannot_delete: "Cannot delete your account while you own a family with other members."
|
||||
signed_up: "Welcome! You have signed up successfully."
|
||||
signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
|
||||
signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ class AddDeletedAtToUsers < ActiveRecord::Migration[8.0]
|
|||
disable_ddl_transaction!
|
||||
|
||||
def change
|
||||
add_column :users, :deleted_at, :datetime
|
||||
add_index :users, :deleted_at, algorithm: :concurrently
|
||||
add_column :users, :deleted_at, :datetime unless column_exists?(:users, :deleted_at)
|
||||
add_index :users, :deleted_at, algorithm: :concurrently unless index_exists?(:users, :deleted_at)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
2
db/schema.rb
generated
2
db/schema.rb
generated
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.0].define(version: 2026_01_08_112905) do
|
||||
ActiveRecord::Schema[8.0].define(version: 2026_01_08_192905) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pg_catalog.plpgsql"
|
||||
enable_extension "postgis"
|
||||
|
|
|
|||
|
|
@ -44,12 +44,12 @@ RSpec.describe Users::TrialWebhookJob, type: :job do
|
|||
end
|
||||
|
||||
context 'when user is deleted' do
|
||||
it 'raises ActiveRecord::RecordNotFound' do
|
||||
it 'skips the webhook for soft-deleted users' do
|
||||
user.destroy
|
||||
|
||||
expect {
|
||||
expect(HTTParty).not_to receive(:post)
|
||||
|
||||
described_class.perform_now(user.id)
|
||||
}.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -118,8 +118,9 @@ RSpec.describe User, 'family methods', type: :model do
|
|||
create(:family_invitation, family: family, invited_by: user)
|
||||
end
|
||||
|
||||
it 'destroys associated invitations when user is destroyed' do
|
||||
expect { user.destroy }.to change(Family::Invitation, :count).by(-1)
|
||||
it 'soft-deletes user but keeps invitations' do
|
||||
expect { user.destroy }.not_to change(Family::Invitation, :count)
|
||||
expect(user.deleted?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -128,8 +129,9 @@ RSpec.describe User, 'family methods', type: :model do
|
|||
create(:family_membership, user: user, family: family)
|
||||
end
|
||||
|
||||
it 'destroys associated membership when user is destroyed' do
|
||||
expect { user.destroy }.to change(Family::Membership, :count).by(-1)
|
||||
it 'soft-deletes user but keeps membership' do
|
||||
expect { user.destroy }.not_to change(Family::Membership, :count)
|
||||
expect(user.deleted?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue