Skip to content

Commit 0e76890

Browse files
committed
feat: add changeOwnership private api
1 parent 74f6689 commit 0e76890

File tree

3 files changed

+161
-0
lines changed

3 files changed

+161
-0
lines changed

.nycrc.unit.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"**/*.spec.js",
1717
"src/index.js",
1818
"src/server.js",
19+
"src/api/changeOwnership.js",
1920
"src/www/bootstrap/*",
2021
"src/www/publish/*"
2122
],

src/api/changeOwnership.js

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Refer https://json-schema.org/understanding-json-schema/index.html
2+
import {
3+
EXTENSIONS_BUCKET,
4+
EXTENSIONS_DETAILS_TABLE,
5+
FIELD_EXTENSION_ID,
6+
REGISTRY_FILE
7+
} from "../constants.js";
8+
import db from "../db.js";
9+
import {HTTP_STATUS_CODES} from "@aicore/libcommonutils";
10+
import {syncRegistryDBToS3JSON} from "../utils/sync.js";
11+
import {S3} from "../s3.js";
12+
import {createIssue} from "../github.js";
13+
14+
const schema = {
15+
schema: {
16+
querystring: {
17+
type: 'object',
18+
required: ["extensionName", 'newOwner', "newRepo"],
19+
properties: {
20+
extensionName: {
21+
type: 'string',
22+
minLength: 1,
23+
maxLength: 256
24+
}, newOwner: {
25+
type: 'string',
26+
minLength: 1,
27+
maxLength: 256
28+
}, newRepo: {
29+
type: 'string',
30+
minLength: 1,
31+
maxLength: 256
32+
}
33+
}
34+
},
35+
response: {
36+
200: { //HTTP_STATUS_CODES.OK
37+
type: 'object',
38+
required: ["oldEntry", 'newEntry'],
39+
properties: {
40+
oldEntry: {type: 'string'},
41+
newEntry: {type: 'string'}
42+
}
43+
}
44+
}
45+
}
46+
};
47+
48+
export function getChangeOwnershipSchema() {
49+
return schema;
50+
}
51+
52+
// internal private API, please change error messages if exposing to public, as internal error details are returned.
53+
async function _getRegistryPkgJSON(extensionName, reply) {
54+
const queryObj = {};
55+
queryObj[FIELD_EXTENSION_ID] = extensionName;
56+
let registryPKGJSON = await db.getFromIndex(EXTENSIONS_DETAILS_TABLE, queryObj);
57+
if(!registryPKGJSON.isSuccess){
58+
// unexpected error
59+
throw new Error("Error getting extensionPKG details from db for: " + extensionName);
60+
}
61+
if(registryPKGJSON.documents.length !== 1){
62+
reply.status(HTTP_STATUS_CODES.BAD_REQUEST);
63+
throw new Error("no such extension id: " + extensionName);
64+
}
65+
return registryPKGJSON.documents[0];
66+
}
67+
68+
69+
async function _updateRegistryJSONinDB(registryPKGJSON) {
70+
const existingRegistryDocumentId = registryPKGJSON.documentId;
71+
let status;
72+
registryPKGJSON.syncPending = 'Y';// coco db doesnt support boolean queries yet
73+
registryPKGJSON.EXTENSION_ID = registryPKGJSON.metadata.name;
74+
// we need to update existing extension release only if no one updated the release while this change
75+
// was being published, so the conditional update with version check.
76+
console.log("updating extension", registryPKGJSON.EXTENSION_ID);
77+
status = await db.update(EXTENSIONS_DETAILS_TABLE, existingRegistryDocumentId,
78+
registryPKGJSON, `$.metadata.version='${registryPKGJSON.metadata.version}'`);
79+
if(!status.isSuccess) {
80+
throw new Error("Another update in progress? Failed to update extension in db for: "
81+
+ registryPKGJSON.EXTENSION_ID);
82+
}
83+
}
84+
85+
async function _validateOwnershipChange(registryPKGJSON) {
86+
let registry = JSON.parse(await S3.getObject(EXTENSIONS_BUCKET, REGISTRY_FILE));
87+
let registryEntry = registry[registryPKGJSON.metadata.name];
88+
if(registryEntry.metadata.repository.url !== registryPKGJSON.metadata.repository.url
89+
|| registryEntry.owner !== registryPKGJSON.owner
90+
|| registryEntry.ownerRepo !== registryPKGJSON.ownerRepo){
91+
throw new Error("registry.json update failed for: " + registryPKGJSON.metadata.name);
92+
}
93+
return registryEntry;
94+
}
95+
96+
async function _notifyOwnersWithGithubIssue(newOwnerRepo, oldOwnerRepo, extensionName) {
97+
if(oldOwnerRepo === newOwnerRepo){
98+
return;
99+
}
100+
try{
101+
let oldOwner = oldOwnerRepo.replace("https://github.com/", "").split("/")[0],
102+
oldRepo = oldOwnerRepo.replace("https://github.com/", "").split("/")[1];
103+
console.log(await createIssue(oldOwner, oldRepo,
104+
`[Phcode.dev Bot] Ownership Transferred. You no longer own the extension \`${extensionName}\`.`,
105+
`Greetings from [phcode.dev](https://phcode.dev). You no longer own the extension \`${extensionName}\`. \n\nOwnership of this extension is now transferred from ${oldOwnerRepo} to ${newOwnerRepo}.`));
106+
} catch (e) {
107+
// ignore error as the old owner repo may not be present if the user deleted old repos
108+
console.error(e);
109+
}
110+
try{
111+
let newOwner = newOwnerRepo.replace("https://github.com/", "").split("/")[0],
112+
newRepo = newOwnerRepo.replace("https://github.com/", "").split("/")[1];
113+
console.log(await createIssue(newOwner, newRepo,
114+
`[Phcode.dev Bot] Ownership Transferred: Gained ownership of \`${extensionName}\``,
115+
`Greetings from [phcode.dev](https://phcode.dev). You have gained ownership of the extension \`${extensionName}\`. \n\nOwnership of this extension is now transferred from ${oldOwnerRepo} to ${newOwnerRepo}.`));
116+
} catch (e) {
117+
console.error(e);
118+
}
119+
}
120+
121+
// this entire file is private api and hence not unit or coverage tested. Please tread carefully if making changes.
122+
// remove this file from covergae exclusion file `.nycrc.unit.json` if adding as public api in the future.
123+
export async function changeOwnership(request, reply) {
124+
const extensionName = request.query.extensionName;
125+
const newOwner = request.query.newOwner;
126+
const newRepo = request.query.newRepo;
127+
const registryPKGJSON = await _getRegistryPkgJSON(extensionName, reply);
128+
console.log("old entry", registryPKGJSON);
129+
const oldEntry = JSON.stringify(registryPKGJSON);
130+
const oldOwnerRepo = registryPKGJSON.ownerRepo;
131+
const newOwnerRepo = `https://github.com/${newOwner}/${newRepo}`;
132+
registryPKGJSON.owner = `github:${newOwner}`;
133+
registryPKGJSON.ownerRepo = newOwnerRepo;
134+
if(registryPKGJSON.metadata.homepage && registryPKGJSON.metadata.homepage.startsWith("https://github.com/")){
135+
registryPKGJSON.metadata.homepage = `https://github.com/${newOwner}/${newRepo}`;
136+
}
137+
registryPKGJSON.metadata.repository= {
138+
type: "git",
139+
url: `https://github.com/${newOwner}/${newRepo}.git`
140+
};
141+
console.log("Updating entry", registryPKGJSON);
142+
await _updateRegistryJSONinDB(registryPKGJSON);
143+
await syncRegistryDBToS3JSON();
144+
const registryEntry = await _validateOwnershipChange(registryPKGJSON);
145+
console.log("updated registry entry", registryEntry);
146+
await _notifyOwnersWithGithubIssue(newOwnerRepo, oldOwnerRepo, extensionName);
147+
const response = {
148+
oldEntry,
149+
newEntry: JSON.stringify(registryEntry)
150+
};
151+
return response;
152+
}

src/server.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { fileURLToPath } from 'url';
3232
import {initGitHubClient, setupGitHubOpsMonitoring} from "./github.js";
3333
import {getGetGithubReleaseStatusSchema, getGithubReleaseStatus} from "./api/getGithubReleaseStatus.js";
3434
import {getCountDownloadSchema, countDownload} from "./api/countDownload.js";
35+
import {getChangeOwnershipSchema, changeOwnership} from "./api/changeOwnership.js";
3536
import {startCollectStarsWorker} from "./utils/sync.js";
3637

3738
const __filename = fileURLToPath(import.meta.url);
@@ -110,6 +111,7 @@ server.get('/helloAuth', getHelloSchema(), function (request, reply) {
110111
return hello(request, reply);
111112
});
112113

114+
// private internal apis
113115
server.get('/backupRegistry', getBackupRegistrySchema(), async function (request, reply) {
114116
return await backupRegistry(request, reply);
115117
});
@@ -118,6 +120,12 @@ server.get('/setupStack', getSetupStackSchema(), async function (request, reply)
118120
return await setupStackForStage(request, reply);
119121
});
120122

123+
addUnAuthenticatedAPI('/changeOwnership'); // todo remove
124+
server.get('/changeOwnership', getChangeOwnershipSchema(), function (request, reply) {
125+
return changeOwnership(request, reply);
126+
});
127+
128+
121129
/**
122130
* It starts the server and listens on the port specified in the configs
123131
*/

0 commit comments

Comments
 (0)