@@ -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