Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit cef4344

Browse files
committed
Archived projects still using copilot handle
#442
1 parent eb360cf commit cef4344

File tree

5 files changed

+133
-28
lines changed

5 files changed

+133
-28
lines changed

src/common/db-helper.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,35 @@ async function queryOneOrganisation(model, organisation) {
470470
});
471471
}
472472

473+
/**
474+
* Query one active repository
475+
* @param {String} url the repository url
476+
* @returns {Promise<Object>}
477+
*/
478+
async function queryOneRepository(url) {
479+
return await new Promise((resolve, reject) => {
480+
models.Repository.query({
481+
url,
482+
})
483+
.all()
484+
.exec((err, repos) => {
485+
if (err) {
486+
return reject(err);
487+
}
488+
if (!repos || repos.length === 0) resolve(null);
489+
if (repos.length > 1) {
490+
let error = `Repository's url is unique in this version.
491+
This Error must be caused by old data in the Repository table.
492+
The old version can only guarrentee that the active Repository's url is unique.
493+
Please migrate the old Repository table.`;
494+
logger.debug(`queryOneRepository. Error. ${error}`);
495+
reject(error);
496+
}
497+
return resolve(repos[0]);
498+
});
499+
});
500+
}
501+
473502
/**
474503
* Query one active repository
475504
* @param {Object} model the dynamoose model
@@ -597,6 +626,7 @@ module.exports = {
597626
queryOneActiveProject,
598627
queryOneActiveProjectWithFilter,
599628
queryOneActiveRepository,
629+
queryOneRepository,
600630
queryOneOrganisation,
601631
queryOneIssue,
602632
queryOneUserByType,

src/models/Issue.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const schema = new Schema({
3838
repoUrl: {
3939
type: String
4040
},
41+
repositoryIdStr: {type: String, required: false},
4142
labels: {
4243
type: Array,
4344
required: false,
@@ -49,6 +50,7 @@ const schema = new Schema({
4950
},
5051
// From topcoder api
5152
challengeId: {type: Number, required: false},
53+
challengeUUID: {type: String, required: false},
5254
projectId: {type: String},
5355
status: {type: String},
5456
assignedAt: {type: Date, required: false},

src/services/CopilotPaymentService.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ async function _ensureEditPermissionAndGetInfo(paymentId, topcoderUser) {
8686
if (dbPayment.closed === true) {
8787
throw new Error('Closed payment can not be updated');
8888
}
89+
if (dbProject.archived) {
90+
throw new errors.ForbiddenError('You can\'t edit this payment in an archived project');
91+
}
8992
return dbPayment;
9093
}
9194

@@ -203,6 +206,9 @@ async function create(topcoderUser, payment) {
203206
if (dbProject.copilot !== topcoderUser.handle && dbProject.owner !== topcoderUser.handle) {
204207
throw new errors.ForbiddenError('You do not have permission to edit this payment');
205208
}
209+
if (dbProject.archived) {
210+
throw new errors.ForbiddenError('You can\'t edit this payment in an archived project');
211+
}
206212
payment.username = dbProject.copilot;
207213
payment.closed = false;
208214
payment.id = helper.generateIdentifier();

src/services/IssueService.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ async function _ensureEditPermissionAndGetInfo(projectId, currentUser) {
117117
) {
118118
throw new errors.ForbiddenError('You don\'t have access on this project');
119119
}
120+
if (dbProject.archived) {
121+
throw new errors.ForbiddenError('You can\'t access on this archived project');
122+
}
120123
return dbProject;
121124
}
122125

src/services/ProjectService.js

Lines changed: 92 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ async function _validateProjectData(project, repoUrl) {
8282
}
8383
if (existsInDatabase) {
8484
throw new errors.ValidationError(`This repo already has a Topcoder-X project associated with it.
85-
Copilot: ${existsInDatabase.copilot}, Owner: ${existsInDatabase.owner}`)
85+
Repo: ${repoUrl}, Copilot: ${existsInDatabase.copilot}, Owner: ${existsInDatabase.owner}`)
8686
}
8787
const provider = await helper.getProviderType(repoUrl);
8888
const userRole = project.copilot ? project.copilot : project.owner;
@@ -112,9 +112,80 @@ async function _ensureEditPermissionAndGetInfo(projectId, currentUser) {
112112
) {
113113
throw new errors.ForbiddenError('You don\'t have access on this project');
114114
}
115+
if (dbProject.archived) {
116+
throw new errors.ForbiddenError('You can\'t access on this archived project');
117+
}
115118
return dbProject;
116119
}
117120

121+
/**
122+
* create Repository as well as adding git label, hook, wiki
123+
* or
124+
* migrate Repository as well as related Issue and CopilotPayment
125+
* @param {String} repoUrl the repository url
126+
* @param {Object} project the new project
127+
* @param {String} currentUser the topcoder current user
128+
* @returns {void}
129+
* @private
130+
*/
131+
async function _createOrMigrateRepository(repoUrl, project, currentUser) {
132+
let oldRepo = await dbHelper.queryOneRepository(repoUrl);
133+
if (oldRepo) {
134+
if (oldRepo.projectId === project.id) {
135+
throw new Error(`This error should never occur: the projectId of the repository to be migrate
136+
will never equal to the new project id`);
137+
}
138+
if (oldRepo.archived === false) {
139+
throw new Error(`Duplicate active repository should be blocked by _validateProjectData,
140+
or a time-sequence cornercase encountered here`);
141+
}
142+
try {
143+
let oldIssues = await models.Issue.query({repoUrl: oldRepo.url});
144+
let oldCopilotPaymentPromise = oldIssues.filter(issue => issue.challengeUUID)
145+
.map(issue => models.CopilotPayment.query({challengeUUID: issue.challengeUUID})
146+
.then(payments => {
147+
if (!payments || payments.length === 0) {
148+
/* eslint-disable-next-line no-console */
149+
console.log(`No CopilotPayment correspond to Issue with challengeUUID ${issue.challengeUUID}.
150+
The corresponding CopilotPayment may have been removed.
151+
Or, there is bug in old version.`);
152+
return null;
153+
}
154+
if (payments.length > 1) {
155+
throw new Error(`Duplicate CopilotPayment correspond to one Issue with challengeUUID ${issue.challengeUUID}.
156+
There must be bug in old version`);
157+
}
158+
return payments[0];
159+
}));
160+
let oldCopilotPayment = await Promise.all(oldCopilotPaymentPromise).filter(payment => payment);
161+
162+
await models.Repository.update({id: oldRepo.id}, {projectId: project.id, archived: false});
163+
await oldIssues.forEach(issue => models.Issue.update({id: issue.id}, {projectId: project.id}));
164+
await oldCopilotPayment.forEach(
165+
payment => models.CopilotPayment.update({id: payment.id}, {project: project.id})
166+
);
167+
}
168+
catch (err) {
169+
throw new Error(`Update ProjectId for Repository, Issue, CopilotPayment failed. Repo ${repoUrl}. Internal Error: ${err.message}`);
170+
}
171+
} else {
172+
try {
173+
await dbHelper.create(models.Repository, {
174+
id: helper.generateIdentifier(),
175+
projectId: project.id,
176+
url: repoUrl,
177+
archived: project.archived
178+
})
179+
await createLabel({projectId: project.id}, currentUser, repoUrl);
180+
await createHook({projectId: project.id}, currentUser, repoUrl);
181+
await addWikiRules({projectId: project.id}, currentUser, repoUrl);
182+
}
183+
catch (err) {
184+
throw new Error(`Project created. Adding the webhook, issue labels, and wiki rules failed. Repo ${repoUrl}. Internal Error: ${err.message}`);
185+
}
186+
}
187+
}
188+
118189
/**
119190
* creates project
120191
* @param {Object} project the project detail
@@ -142,24 +213,16 @@ async function create(project, currentUser) {
142213
project.copilot = project.copilot ? project.copilot.toLowerCase() : null;
143214
project.id = helper.generateIdentifier();
144215

145-
const createdProject = await dbHelper.create(models.Project, project);
146-
216+
// TODO: The following db operation should/could be moved into one transaction
147217
for (const repoUrl of repoUrls) { // eslint-disable-line no-restricted-syntax
148-
await dbHelper.create(models.Repository, {
149-
id: helper.generateIdentifier(),
150-
projectId: project.id,
151-
url: repoUrl,
152-
archived: project.archived
153-
})
154218
try {
155-
await createLabel({projectId: project.id}, currentUser, repoUrl);
156-
await createHook({projectId: project.id}, currentUser, repoUrl);
157-
await addWikiRules({projectId: project.id}, currentUser, repoUrl);
219+
await _createOrMigrateRepository(repoUrl, project, currentUser);
158220
}
159221
catch (err) {
160-
throw new Error(`Project created. Adding the webhook, issue labels, and wiki rules failed. Repo ${repoUrl}`);
222+
throw new Error(`Create or migrate repository failed. Repo ${repoUrl}. Internal Error: ${err.message}`);
161223
}
162224
}
225+
const createdProject = await dbHelper.create(models.Project, project);
163226

164227
return createdProject;
165228
}
@@ -178,6 +241,7 @@ async function update(project, currentUser) {
178241
for (const repoUrl of repoUrls) { // eslint-disable-line no-restricted-syntax
179242
await _validateProjectData(project, repoUrl);
180243
}
244+
// TODO: remove the useless code-block
181245
if (dbProject.archived === 'false' && project.archived === true) {
182246
// project archived detected.
183247
const result = {
@@ -201,22 +265,22 @@ async function update(project, currentUser) {
201265
dbProject[item[0]] = item[1];
202266
return item;
203267
});
204-
const oldRepositories = await dbHelper.queryRepositoriesByProjectId(dbProject.id);
205-
const weebhookIds = {};
206-
for (const repo of oldRepositories) { // eslint-disable-line
207-
if (repo.registeredWebhookId) {
208-
weebhookIds[repo.url] = repo.registeredWebhookId;
209-
}
210-
await dbHelper.removeById(models.Repository, repo.id);
211-
}
268+
269+
// TODO: move the following logic into one dynamoose transaction
270+
const repoUrl2Repo = await dbHelper.queryRepositoriesByProjectId(dbProject.id)
271+
.map(repo => { return {[repo.url]: repo}; });
272+
212273
for (const repoUrl of repoUrls) { // eslint-disable-line no-restricted-syntax
213-
await dbHelper.create(models.Repository, {
214-
id: helper.generateIdentifier(),
215-
projectId: dbProject.id,
216-
url: repoUrl,
217-
archived: project.archived,
218-
registeredWebhookId: weebhookIds[repoUrl]
219-
})
274+
if (repoUrl in repoUrl2Repo) {
275+
await models.Repository.update({id: repoUrl2Repo[repoUrl].id}, {archived: project.archived});
276+
} else {
277+
try {
278+
await _createOrMigrateRepository(repoUrl, project, currentUser);
279+
}
280+
catch (err) {
281+
throw new Error(`Create or migrate repository failed. Repo ${repoUrl}. Internal Error: ${err.message}`);
282+
}
283+
}
220284
}
221285
dbProject.updatedAt = new Date();
222286
return await dbHelper.update(models.Project, dbProject.id, dbProject);

0 commit comments

Comments
 (0)