Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ GEM
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1)
addressable (2.8.9)
addressable (2.9.0)
public_suffix (>= 2.0.2, < 8.0)
administrate (1.0.0)
actionpack (>= 6.0, < 9.0)
Expand All @@ -91,6 +91,7 @@ GEM
base64 (0.3.0)
bcrypt (3.1.22)
bcrypt_pbkdf (1.1.2)
bcrypt_pbkdf (1.1.2-x86_64-darwin)
bigdecimal (3.3.1)
bindex (0.8.1)
blacklight (9.0.0)
Expand Down Expand Up @@ -370,7 +371,7 @@ GEM
racc (~> 1.7)
net-http (0.9.1)
uri (>= 0.11.1)
net-imap (0.6.3)
net-imap (0.6.4)
date
net-protocol
net-pop (0.1.2)
Expand Down Expand Up @@ -659,7 +660,7 @@ GEM
uniform_notifier (1.18.0)
uri (1.1.1)
useragent (0.16.11)
view_component (4.5.0)
view_component (4.10.0)
actionview (>= 7.1.0)
activesupport (>= 7.1.0)
concurrent-ruby (~> 1)
Expand Down Expand Up @@ -695,6 +696,7 @@ PLATFORMS
arm-linux-musl
arm-linux-musleabihf
x86_64-darwin-24
x86_64-darwin-25
x86_64-linux
x86_64-linux-gnu
x86_64-linux-musl
Expand Down Expand Up @@ -771,14 +773,15 @@ CHECKSUMS
activerecord (8.1.3) sha256=8003be7b2466ba0a2a670e603eeb0a61dd66058fccecfc49901e775260ac70ab
activestorage (8.1.3) sha256=0564ce9309143951a67615e1bb4e090ee54b8befed417133cae614479b46384d
activesupport (8.1.3) sha256=21a5e0dfbd4c3ddd9e1317ec6a4d782fa226e7867dc70b0743acda81a1dca20e
addressable (2.8.9) sha256=cc154fcbe689711808a43601dee7b980238ce54368d23e127421753e46895485
addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
administrate (1.0.0) sha256=65b0008214d352cab51e8230086ddcd3af11ac5ee91233db89dbea24bd432471
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
awesome_print (1.9.2) sha256=e99b32b704acff16d768b3468680793ced40bfdc4537eb07e06a4be11133786e
axiom-types (0.1.1) sha256=c1ff113f3de516fa195b2db7e0a9a95fd1b08475a502ff660d04507a09980383
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
bcrypt (3.1.22) sha256=1f0072e88c2d705d94aff7f2c5cb02eb3f1ec4b8368671e19112527489f29032
bcrypt_pbkdf (1.1.2) sha256=c2414c23ce66869b3eb9f643d6a3374d8322dfb5078125c82792304c10b94cf6
bcrypt_pbkdf (1.1.2-x86_64-darwin) sha256=35f5639d0058e6c2cc2f856f9c0b14080543268d3047abe6bc81c513093caa0e
bigdecimal (3.3.1) sha256=eaa01e228be54c4f9f53bf3cc34fe3d5e845c31963e7fcc5bedb05a4e7d52218
bindex (0.8.1) sha256=7b1ecc9dc539ed8bccfc8cb4d2732046227b09d6f37582ff12e50a5047ceb17e
blacklight (9.0.0) sha256=10c796cb81c57ee91b3adf745c50a96c5cf9de17055a2fa66469da73ae1e17f8
Expand Down Expand Up @@ -888,7 +891,7 @@ CHECKSUMS
mysql2 (0.5.7) sha256=ba09ede515a0ae8a7192040a1b778c0fb0f025fa5877e9be895cd325fa5e9d7b
namae (1.2.0) sha256=3541ce4b086fd4981d2376630c03e284402bfe1cdbab4e50e2222a72aeb9d59d
net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996
net-imap (0.6.3) sha256=9bab75f876596d09ee7bf911a291da478e0cd6badc54dfb82874855ccc82f2ad
net-imap (0.6.4) sha256=9a5598c67a3022c284d98430ef1d4948e7dbdb62596f61081ea8ca933270a02b
net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3
net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8
net-scp (4.1.0) sha256=a99b0b92a1e5d360b0de4ffbf2dc0c91531502d3d4f56c28b0139a7c093d1a5d
Expand Down Expand Up @@ -1000,7 +1003,7 @@ CHECKSUMS
uniform_notifier (1.18.0) sha256=4787785556f66f6418486da0f1d78b3239aaff98e2e7938fb05e2062b0ffce9d
uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844
view_component (4.5.0) sha256=0d951360d830752da4d1daa5f6cb643f17c30f295fb779fd4de90a051537d9c1
view_component (4.10.0) sha256=9e86960ee7ea4da3d1d07f65b1a66e4a9fb656be5434f1a649eb6fce6ac9baf9
virtus (2.0.0) sha256=8841dae4eb7fcc097320ba5ea516bf1839e5d056c61ee27138aa4bddd6e3d1c2
warden (1.2.9) sha256=46684f885d35a69dbb883deabf85a222c8e427a957804719e143005df7a1efd0
web-console (4.3.0) sha256=e13b71301cdfc2093f155b5aa3a622db80b4672d1f2f713119cc7ec7ac6a6da4
Expand Down
15 changes: 12 additions & 3 deletions app/controllers/users/registrations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,18 @@ class Users::RegistrationsController < Devise::RegistrationsController
# end

# DELETE /resource
# def destroy
# super
# end
def destroy
blocking_projects = current_user.sole_owned_projects
if blocking_projects.any?
render json: {
error: "Account cannot be deleted while you are the sole owner of one or more projects.",
projects: blocking_projects.map { |p| { id: p.id, title: p.title } }
}, status: :unprocessable_content
return
end

super
end

# GET /resource/cancel
# Forces the session data which is usually expired after sign
Expand Down
9 changes: 8 additions & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,20 @@ class User < ApplicationRecord

has_one :image_file, as: :imageable, dependent: :destroy
accepts_nested_attributes_for :image_file, allow_destroy: true
has_many :project_members
has_many :project_members, dependent: :destroy
has_many :projects, through: :project_members

def role_in(project)
project_members.find_by(project: project)&.role
end

def sole_owned_projects
Project
.joins(:project_members)
.where(project_members: { user: self, role: "owner" })
.select { |p| p.project_members.where(role: "owner").count == 1 }
end

# Check if user is an admin based on admin_at timestamp
def admin?
admin_at.present?
Expand Down
2 changes: 1 addition & 1 deletion lib/tasks/dummy_data_generator.rake
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ namespace :dummy_data_generator do

# Create 3 regular TEI content files
3.times do |i|
core_file = CoreFile.new(title: Faker::Book.title,
core_file = CoreFile.new(title: "#{Faker::Book.title} #{i + 1}",
description: Faker::Book.genre,
depositor_id: collection_users.sample&.id,
collections: [ collection ].compact,
Expand Down
61 changes: 61 additions & 0 deletions spec/requests/users_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,67 @@
end
end

describe "DELETE /users" do
context "when not signed in" do
it "redirects to sign in" do
delete user_registration_path
expect(response).to redirect_to(new_user_session_path)
end
end

context "when signed in" do
before { sign_in user }

context "when the user is not the sole owner of any project" do
it "deletes the account" do
expect { delete user_registration_path, as: :json }.to change(User, :count).by(-1)
end

it "also destroys the user's project memberships" do
project = create(:project)
project.project_members.create!(user: user, role: "contributor")
expect { delete user_registration_path, as: :json }.to change(ProjectMember, :count)
expect(ProjectMember.exists?(user: user)).to be false
end
end

context "when the user is the sole owner of a project" do
before do
project = create(:project, depositor: user)
project.project_members.where(user: user).update_all(role: "owner")
end

it "returns unprocessable content" do
delete user_registration_path, as: :json
expect(response).to have_http_status(:unprocessable_content)
end

it "does not delete the account" do
expect { delete user_registration_path, as: :json }.not_to change(User, :count)
end

it "returns an error message with the blocking project" do
delete user_registration_path, as: :json
json = JSON.parse(response.body)
expect(json["error"]).to include("sole owner")
expect(json["projects"]).not_to be_empty
end
end

context "when the user is a co-owner of a project" do
before do
co_owner = create(:user)
project = create(:project, depositor: user)
project.project_members.create!(user: co_owner, role: "owner")
end

it "deletes the account" do
expect { delete user_registration_path, as: :json }.to change(User, :count).by(-1)
end
end
end
end

describe "PATCH /users/:id" do
let(:valid_params) { { user: { name: "Updated Name", bio: "A new bio", institution: "NEU" } } }

Expand Down