Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
41 changes: 25 additions & 16 deletions bin/compactAllPads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import path from 'node:path';
import fs from 'node:fs';
import process from 'node:process';
import axios from 'axios';

export type CompactAllOpts = {
keepRevisions: number | null;
Expand All @@ -33,8 +32,8 @@ export type CompactAllOpts = {

// Minimal interface mirroring the API endpoints the script needs. Tests
// substitute their own implementation that goes through supertest+JWT
// instead of axios+APIKEY, so the loop logic is exercised against a real
// running server without dragging in apikey-file or axios setup.
// instead of fetch+APIKEY, so the loop logic is exercised against a real
// running server without dragging in apikey-file or fetch setup.
export type CompactAllApi = {
listAllPads(): Promise<string[]>;
getRevisionsCount(padId: string): Promise<number>;
Expand Down Expand Up @@ -182,41 +181,51 @@ if (isMain) {
process.on('unhandledRejection', (err) => { throw err; });

const settings = require('ep_etherpad-lite/tests/container/loadSettings').loadSettings();
axios.defaults.baseURL =
`${settings.ssl ? 'https' : 'http'}://${settings.ip}:${settings.port}`;
const baseURL = `${settings.ssl ? 'https' : 'http'}://${settings.ip}:${settings.port}`;

const apiGet = async (p: string): Promise<any> => {
const r = await fetch(baseURL + p);
if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`);
return r.json();
};
const apiPost = async (p: string): Promise<any> => {
const r = await fetch(baseURL + p, {method: 'POST'});
if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`);
return r.json();
};

const opts = parseArgs(process.argv.slice(2));
if (!opts) usage();

const apikey = fs.readFileSync(
path.join(__dirname, '../APIKEY.txt'), {encoding: 'utf-8'}).trim();

// Bind the abstract API to axios + APIKEY auth for the CLI shell.
// Bind the abstract API to fetch + APIKEY auth for the CLI shell.
const cliApi: CompactAllApi = {
async listAllPads() {
const apiInfo = await axios.get('/api/');
const apiVersion: string | undefined = apiInfo.data.currentVersion;
const apiInfo = await apiGet('/api/');
const apiVersion: string | undefined = apiInfo.currentVersion;
if (!apiVersion) throw new Error('No version set in API');
// Stash on this for subsequent calls. Avoids a per-call /api/ ping.
(cliApi as any)._apiVersion = apiVersion;
const r = await axios.get(`/api/${apiVersion}/listAllPads?apikey=${apikey}`);
if (r.data.code !== 0) throw new Error(JSON.stringify(r.data));
return r.data.data.padIDs ?? [];
const r = await apiGet(`/api/${apiVersion}/listAllPads?apikey=${apikey}`);
if (r.code !== 0) throw new Error(JSON.stringify(r));
return r.data.padIDs ?? [];
},
async getRevisionsCount(padId: string) {
const v = (cliApi as any)._apiVersion;
const r = await axios.get(
const r = await apiGet(
`/api/${v}/getRevisionsCount?apikey=${apikey}` +
`&padID=${encodeURIComponent(padId)}`);
if (r.data.code !== 0) throw new Error(JSON.stringify(r.data));
return r.data.data.revisions;
if (r.code !== 0) throw new Error(JSON.stringify(r));
return r.data.revisions;
},
async compactPad(padId: string, keepRevisions: number | null) {
const v = (cliApi as any)._apiVersion;
const params = new URLSearchParams({apikey, padID: padId});
if (keepRevisions != null) params.set('keepRevisions', String(keepRevisions));
const r = await axios.post(`/api/${v}/compactPad?${params.toString()}`);
if (r.data.code !== 0) throw new Error(JSON.stringify(r.data));
const r = await apiPost(`/api/${v}/compactPad?${params.toString()}`);
if (r.code !== 0) throw new Error(JSON.stringify(r));
},
};

Expand Down
37 changes: 23 additions & 14 deletions bin/compactPad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,25 @@
import path from 'node:path';
import fs from 'node:fs';
import process from 'node:process';
import axios from 'axios';

// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.on('unhandledRejection', (err) => { throw err; });

const settings = require('ep_etherpad-lite/tests/container/loadSettings').loadSettings();

axios.defaults.baseURL =
`${settings.ssl ? 'https' : 'http'}://${settings.ip}:${settings.port}`;
const baseURL = `${settings.ssl ? 'https' : 'http'}://${settings.ip}:${settings.port}`;

const apiGet = async (p: string): Promise<any> => {
const r = await fetch(baseURL + p);
if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`);
return r.json();
};
const apiPost = async (p: string): Promise<any> => {
const r = await fetch(baseURL + p, {method: 'POST'});
if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`);
return r.json();
};

const usage = () => {
console.error('Usage:');
Expand Down Expand Up @@ -56,33 +65,33 @@ const filePath = path.join(__dirname, '../APIKEY.txt');
const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'}).trim();

(async () => {
const apiInfo = await axios.get('/api/');
const apiVersion: string | undefined = apiInfo.data.currentVersion;
const apiInfo = await apiGet('/api/');
const apiVersion: string | undefined = apiInfo.currentVersion;
if (!apiVersion) throw new Error('No version set in API');

// Pre-flight: show current revision count so operators can eyeball impact.
const countUri = `/api/${apiVersion}/getRevisionsCount?apikey=${apikey}&padID=${padId}`;
const countRes = await axios.get(countUri);
if (countRes.data.code !== 0) {
console.error(`getRevisionsCount failed: ${JSON.stringify(countRes.data)}`);
const countRes = await apiGet(countUri);
if (countRes.code !== 0) {
console.error(`getRevisionsCount failed: ${JSON.stringify(countRes)}`);
process.exit(1);
}
const before: number = countRes.data.data.revisions;
const before: number = countRes.data.revisions;
const strategy = keepRevisions == null ? 'collapse all' : `keep last ${keepRevisions}`;
console.log(`Pad ${padId}: ${before + 1} revision(s). Strategy: ${strategy}.`);

const params = new URLSearchParams({apikey, padID: padId});
if (keepRevisions != null) params.set('keepRevisions', String(keepRevisions));
const result = await axios.post(`/api/${apiVersion}/compactPad?${params.toString()}`);
if (result.data.code !== 0) {
console.error(`compactPad failed: ${JSON.stringify(result.data)}`);
const result = await apiPost(`/api/${apiVersion}/compactPad?${params.toString()}`);
if (result.code !== 0) {
console.error(`compactPad failed: ${JSON.stringify(result)}`);
process.exit(1);
}

// Post-flight: the pad is now compacted. Re-read the rev count so the
// operator sees concrete savings.
const afterRes = await axios.get(countUri);
const after: number | undefined = afterRes.data?.data?.revisions;
const afterRes = await apiGet(countUri);
const after: number | undefined = afterRes?.data?.revisions;
if (after != null) {
console.log(`Done. Pad ${padId}: ${after + 1} revision(s) remaining ` +
`(was ${before + 1}).`);
Expand Down
42 changes: 25 additions & 17 deletions bin/createUserSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,46 +13,54 @@ import path from "node:path";

import querystring from "node:querystring";

import axios from 'axios'
import process from "node:process";


process.on('unhandledRejection', (err) => { throw err; });
import settings from 'ep_etherpad-lite/node/utils/Settings';
(async () => {
axios.defaults.baseURL = `http://${settings.ip}:${settings.port}`;
const api = axios;
const baseURL = `http://${settings.ip}:${settings.port}`;
const apiGet = async (p: string): Promise<any> => {
const r = await fetch(baseURL + p);
if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`);
return r.json();
};
const apiPost = async (p: string): Promise<any> => {
const r = await fetch(baseURL + p, {method: 'POST'});
if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`);
return r.json();
};

const filePath = path.join(__dirname, '../APIKEY.txt');
const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'});

let res;

res = await api.get('/api/');
const apiVersion = res.data.currentVersion;
res = await apiGet('/api/');
const apiVersion = res.currentVersion;
if (!apiVersion) throw new Error('No version set in API');
console.log('apiVersion', apiVersion);
const uri = (cmd: string, args: querystring.ParsedUrlQueryInput ) => `/api/${apiVersion}/${cmd}?${querystring.stringify(args)}`;

res = await api.post(uri('createGroup', {apikey}));
if (res.data.code === 1) throw new Error(`Error creating group: ${res.data}`);
const groupID = res.data.data.groupID;
res = await apiPost(uri('createGroup', {apikey}));
if (res.code === 1) throw new Error(`Error creating group: ${res}`);
const groupID = res.data.groupID;
console.log('groupID', groupID);

res = await api.post(uri('createGroupPad', {apikey, groupID}));
if (res.data.code === 1) throw new Error(`Error creating group pad: ${res.data}`);
console.log('Test Pad ID ====> ', res.data.data.padID);
res = await apiPost(uri('createGroupPad', {apikey, groupID}));
if (res.code === 1) throw new Error(`Error creating group pad: ${res}`);
console.log('Test Pad ID ====> ', res.data.padID);

res = await api.post(uri('createAuthor', {apikey}));
if (res.data.code === 1) throw new Error(`Error creating author: ${res.data}`);
const authorID = res.data.data.authorID;
res = await apiPost(uri('createAuthor', {apikey}));
if (res.code === 1) throw new Error(`Error creating author: ${res}`);
const authorID = res.data.authorID;
console.log('authorID', authorID);

const validUntil = Math.floor(new Date().getTime() / 1000) + 60000;
console.log('validUntil', validUntil);
res = await api.post(uri('createSession', {apikey, groupID, authorID, validUntil}));
if (res.data.code === 1) throw new Error(`Error creating session: ${JSON.stringify(res.data)}`);
res = await apiPost(uri('createSession', {apikey, groupID, authorID, validUntil}));
if (res.code === 1) throw new Error(`Error creating session: ${JSON.stringify(res)}`);
console.log('Session made: ====> create a cookie named sessionID and set the value to',
res.data.data.sessionID);
res.data.sessionID);
process.exit(0)
})();
34 changes: 21 additions & 13 deletions bin/deleteAllGroupSessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import fs from "node:fs";
import process from "node:process";

process.on('unhandledRejection', (err) => { throw err; });
import axios from 'axios'
// Set a delete counter which will increment on each delete attempt
// TODO: Check delete is successful before incrementing
let deleteCount = 0;
Expand All @@ -23,29 +22,38 @@ const settings = require('ep_etherpad-lite/tests/container/loadSettings').loadSe

(async () => {
const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'});
axios.defaults.baseURL = `http://${settings.ip}:${settings.port}`;

const apiVersionResponse = await axios.get('/api/');
const apiVersion = apiVersionResponse.data.currentVersion; // 1.12.5
const baseURL = `http://${settings.ip}:${settings.port}`;
const apiGet = async (p: string): Promise<any> => {
const r = await fetch(baseURL + p);
if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`);
return r.json();
};
const apiPost = async (p: string): Promise<any> => {
const r = await fetch(baseURL + p, {method: 'POST'});
if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`);
return r.json();
};

const apiVersionResponse = await apiGet('/api/');
const apiVersion = apiVersionResponse.currentVersion; // 1.12.5
console.log('apiVersion', apiVersion);

const groupsResponse = await axios.get(`/api/${apiVersion}/listAllGroups?apikey=${apikey}`);
const groups = groupsResponse.data.data.groupIDs; // ['whateverGroupID']
const groupsResponse = await apiGet(`/api/${apiVersion}/listAllGroups?apikey=${apikey}`);
const groups = groupsResponse.data.groupIDs; // ['whateverGroupID']

for (const groupID of groups) {
const sessionURI = `/api/${apiVersion}/listSessionsOfGroup?apikey=${apikey}&groupID=${groupID}`;
const sessionsResponse = await axios.get(sessionURI);
const sessions = sessionsResponse.data.data;
const sessionsResponse = await apiGet(sessionURI);
const sessions = sessionsResponse.data;

if(sessions == null) continue;

for (const [sessionID, val] of Object.entries(sessions)) {
if(val == null) continue;
const deleteURI = `/api/${apiVersion}/deleteSession?apikey=${apikey}&sessionID=${sessionID}`;
await axios.post(deleteURI).then(c=>{
console.log(c.data)
deleteCount++;
}); // delete
const c = await apiPost(deleteURI);
console.log(c);
deleteCount++;
}
}
console.log(`Deleted ${deleteCount} sessions`);
Expand Down
23 changes: 16 additions & 7 deletions bin/deletePad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,22 @@ import path from "node:path";

import fs from "node:fs";
import process from "node:process";
import axios from "axios";

process.on('unhandledRejection', (err) => { throw err; });

const settings = require('ep_etherpad-lite/tests/container/loadSettings').loadSettings();

axios.defaults.baseURL = `http://${settings.ip}:${settings.port}`;
const baseURL = `http://${settings.ip}:${settings.port}`;
const apiGet = async (p: string): Promise<any> => {
const r = await fetch(baseURL + p);
if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`);
return r.json();
};
const apiPost = async (p: string): Promise<any> => {
const r = await fetch(baseURL + p, {method: 'POST'});
if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`);
return r.json();
};

if (process.argv.length !== 3) throw new Error('Use: node deletePad.js $PADID');

Expand All @@ -29,14 +38,14 @@ const filePath = path.join(__dirname, '../APIKEY.txt');
const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'});

(async () => {
let apiVersion = await axios.get('/api/');
apiVersion = apiVersion.data.currentVersion;
const apiInfo = await apiGet('/api/');
const apiVersion = apiInfo.currentVersion;
if (!apiVersion) throw new Error('No version set in API');

// Now we know the latest API version, let's delete pad
const uri = `/api/${apiVersion}/deletePad?apikey=${apikey}&padID=${padId}`;
const deleteAttempt = await axios.post(uri);
if (deleteAttempt.data.code === 1) throw new Error(`Error deleting pad ${deleteAttempt.data}`);
console.log('Deleted pad', deleteAttempt.data);
const deleteAttempt = await apiPost(uri);
if (deleteAttempt.code === 1) throw new Error(`Error deleting pad ${deleteAttempt}`);
console.log('Deleted pad', deleteAttempt);
process.exit(0)
})();
1 change: 0 additions & 1 deletion bin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"doc": "doc"
},
"dependencies": {
"axios": "^1.16.0",
"ep_etherpad-lite": "workspace:../src",
"log4js": "^6.9.1",
"semver": "^7.7.4",
Expand Down
16 changes: 7 additions & 9 deletions bin/plugins/stalePlugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,20 @@

// Returns a list of stale plugins and their authors email

import axios from 'axios'
import process from "node:process";
const currentTime = new Date();

(async () => {
const res = await axios.get<string>('https://static.etherpad.org/plugins.full.json');
for (const plugin of Object.keys(res.data)) {
// @ts-ignore
const name = res.data[plugin].data.name;
// @ts-ignore
const date = new Date(res.data[plugin].time);
const resp = await fetch('https://static.etherpad.org/plugins.full.json');
if (!resp.ok) throw new Error(`HTTP ${resp.status} ${resp.statusText}`);
const data: any = await resp.json();
for (const plugin of Object.keys(data)) {
const name = data[plugin].data.name;
const date = new Date(data[plugin].time);
const diffTime = Math.abs(currentTime.getTime() - date.getTime());
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
if (diffDays > (365 * 2)) {
// @ts-ignore
console.log(`${name}, ${res.data[plugin].data.maintainers[0].email}`);
console.log(`${name}, ${data[plugin].data.maintainers[0].email}`);
}
}
process.exit(0)
Expand Down
Loading
Loading