mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-07-07 14:17:51 +02:00
fix: prevent E11000 on concurrent getRepositoryFromGitHub
Replace check-then-insert with atomic findOneAndUpdate upsert keyed on externalId, plus a single E11000 retry fallback. Eliminates the duplicate key race when two requests resolve the same gh_<id> concurrently.
This commit is contained in:
@@ -321,27 +321,63 @@ export async function getRepositoryFromGitHub(opt: {
|
|||||||
repo: opt.repo,
|
repo: opt.repo,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// If the lookup-by-name missed but we already have a record for this
|
let pageSource:
|
||||||
// GitHub repo id (e.g. the repo was renamed on GitHub), reuse it instead
|
| RestEndpointMethodTypes["repos"]["getPages"]["response"]["data"]["source"]
|
||||||
// of creating a duplicate that would violate the externalId unique index.
|
| undefined;
|
||||||
if (!dbModel && isConnected) {
|
if (r.has_pages) {
|
||||||
dbModel = await RepositoryModel.findOne({ externalId: "gh_" + r.id });
|
|
||||||
}
|
|
||||||
const model = dbModel || new RepositoryModel({ externalId: "gh_" + r.id });
|
|
||||||
model.name = r.full_name;
|
|
||||||
model.url = r.html_url;
|
|
||||||
model.size = r.size;
|
|
||||||
model.defaultBranch = r.default_branch;
|
|
||||||
model.hasPage = r.has_pages;
|
|
||||||
if (model.hasPage) {
|
|
||||||
const ghPageRes = await oct.repos.getPages({
|
const ghPageRes = await oct.repos.getPages({
|
||||||
owner: opt.owner,
|
owner: opt.owner,
|
||||||
repo: opt.repo,
|
repo: opt.repo,
|
||||||
});
|
});
|
||||||
model.pageSource = ghPageRes.data.source;
|
pageSource = ghPageRes.data.source;
|
||||||
}
|
}
|
||||||
if (isConnected) {
|
|
||||||
await model.save();
|
if (!isConnected) {
|
||||||
|
const model = dbModel || new RepositoryModel({ externalId: "gh_" + r.id });
|
||||||
|
model.name = r.full_name;
|
||||||
|
model.url = r.html_url;
|
||||||
|
model.size = r.size;
|
||||||
|
model.defaultBranch = r.default_branch;
|
||||||
|
model.hasPage = r.has_pages;
|
||||||
|
if (pageSource) model.pageSource = pageSource;
|
||||||
|
return new GitHubRepository(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atomic upsert keyed on externalId so concurrent requests for the same
|
||||||
|
// GitHub repo can't both insert and trip the unique-index race (E11000).
|
||||||
|
const update: Record<string, unknown> = {
|
||||||
|
$set: {
|
||||||
|
name: r.full_name,
|
||||||
|
url: r.html_url,
|
||||||
|
size: r.size,
|
||||||
|
defaultBranch: r.default_branch,
|
||||||
|
hasPage: r.has_pages,
|
||||||
|
...(pageSource ? { pageSource } : {}),
|
||||||
|
},
|
||||||
|
$setOnInsert: { externalId: "gh_" + r.id },
|
||||||
|
};
|
||||||
|
|
||||||
|
let model: IRepositoryDocument | null;
|
||||||
|
try {
|
||||||
|
model = await RepositoryModel.findOneAndUpdate(
|
||||||
|
{ externalId: "gh_" + r.id },
|
||||||
|
update,
|
||||||
|
{ upsert: true, new: true, setDefaultsOnInsert: true }
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
// Mongo can still raise E11000 on a concurrent upsert insert; retry
|
||||||
|
// once with a plain find — the winning insert is now visible.
|
||||||
|
if ((error as { code?: number }).code === 11000) {
|
||||||
|
model = await RepositoryModel.findOne({ externalId: "gh_" + r.id });
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!model) {
|
||||||
|
throw new AnonymousError("repo_not_found", {
|
||||||
|
httpStatus: 404,
|
||||||
|
object: { owner: opt.owner, repo: opt.repo },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return new GitHubRepository(model);
|
return new GitHubRepository(model);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user