Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP INDEX users_upstream_id_active_uniq_idx;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- Prevent concurrent user creations (e.g. parallel TMC-server create-user requests during
-- password migration) from inserting duplicate users for the same TMC account. Partial so that
-- soft-deleted users don't block re-creating an account with the same upstream_id.
-- If this migration fails, duplicate active users with the same upstream_id already exist and
-- must be merged manually first:
-- SELECT upstream_id, array_agg(id) FROM users
-- WHERE upstream_id IS NOT NULL AND deleted_at IS NULL
-- GROUP BY upstream_id HAVING count(*) > 1;
CREATE UNIQUE INDEX users_upstream_id_active_uniq_idx ON users (upstream_id)
WHERE upstream_id IS NOT NULL
AND deleted_at IS NULL;
15 changes: 13 additions & 2 deletions services/headless-lms/server/src/domain/authorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,7 @@ pub async fn get_or_create_user_from_tmc_mooc_fi_response(
let user = match models::users::find_by_upstream_id(conn, upstream_id).await? {
Some(existing_user) => existing_user,
None => {
models::users::insert_with_upstream_id_and_moocfi_id(
let inserted = models::users::insert_with_upstream_id_and_moocfi_id(
conn,
&email,
// convert empty names to None
Expand All @@ -959,7 +959,18 @@ pub async fn get_or_create_user_from_tmc_mooc_fi_response(
upstream_id,
id,
)
.await?
.await;
match inserted {
Ok(user) => user,
Err(insert_error) => {
// A concurrent request can create the user between the find and the insert
// (the insert runs in a savepoint, so the connection stays usable). The unique
// index on upstream_id rejects the loser; return the winner's row instead.
models::users::find_by_upstream_id(conn, upstream_id)
.await?
.ok_or(insert_error)?
}
}
}
};
Ok(user)
Expand Down
Loading