Skip to content

Commit 86da28f

Browse files
author
CI Fix
committed
add missing .mjs files
1 parent d2ebe52 commit 86da28f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+4079
-33
lines changed

bin/lib/cli-utils.mjs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import fs from 'fs-extra';
2+
import { red, cyan, bold } from 'colorette';
3+
import { URL } from 'url';
4+
import LDP from '../../lib/ldp.mjs';
5+
import AccountManager from '../../lib/models/account-manager.mjs';
6+
import SolidHost from '../../lib/models/solid-host.mjs';
7+
8+
export function getAccountManager(config, options = {}) {
9+
const ldp = options.ldp || new LDP(config);
10+
const host = options.host || SolidHost.from({ port: config.port, serverUri: config.serverUri });
11+
return AccountManager.from({
12+
host,
13+
store: ldp,
14+
multiuser: config.multiuser
15+
});
16+
}
17+
18+
export function loadConfig(program, options) {
19+
let argv = {
20+
...options,
21+
version: program.version()
22+
};
23+
const configFile = argv.configFile || './config.json';
24+
try {
25+
const file = fs.readFileSync(configFile);
26+
const config = JSON.parse(file);
27+
argv = { ...config, ...argv };
28+
} catch (err) {
29+
if (typeof argv.configFile !== 'undefined') {
30+
if (!fs.existsSync(configFile)) {
31+
console.log(red(bold('ERR')), 'Config file ' + configFile + " doesn't exist.");
32+
process.exit(1);
33+
}
34+
}
35+
if (fs.existsSync(configFile)) {
36+
console.log(red(bold('ERR')), 'config file ' + configFile + " couldn't be parsed: " + err);
37+
process.exit(1);
38+
}
39+
console.log(cyan(bold('TIP')), 'create a config.json: `$ solid init`');
40+
}
41+
return argv;
42+
}
43+
44+
export function loadAccounts({ root, serverUri, hostname }) {
45+
const files = fs.readdirSync(root);
46+
hostname = hostname || new URL(serverUri).hostname;
47+
const isUserDirectory = new RegExp(`.${hostname}$`);
48+
return files.filter(file => isUserDirectory.test(file));
49+
}
50+
51+
export function loadUsernames({ root, serverUri }) {
52+
const hostname = new URL(serverUri).hostname;
53+
return loadAccounts({ root, hostname }).map(userDirectory => userDirectory.substr(0, userDirectory.length - hostname.length - 1));
54+
}

bin/lib/cli.mjs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Command } from 'commander';
2+
import loadInit from './init.mjs';
3+
import loadStart from './start.mjs';
4+
import loadInvalidUsernames from './invalidUsernames.mjs';
5+
import loadMigrateLegacyResources from './migrateLegacyResources.mjs';
6+
import loadUpdateIndex from './updateIndex.mjs';
7+
import { spawnSync } from 'child_process';
8+
import path from 'path';
9+
import { fileURLToPath } from 'url';
10+
11+
const __filename = fileURLToPath(import.meta.url);
12+
const __dirname = path.dirname(__filename);
13+
14+
export default function startCli(server) {
15+
const program = new Command();
16+
program.version(getVersion());
17+
loadInit(program);
18+
loadStart(program, server);
19+
loadInvalidUsernames(program);
20+
loadMigrateLegacyResources(program);
21+
loadUpdateIndex(program);
22+
program.parse(process.argv);
23+
if (program.args.length === 0) program.help();
24+
}
25+
26+
function getVersion() {
27+
try {
28+
const options = { cwd: __dirname, encoding: 'utf8' };
29+
const { stdout } = spawnSync('git', ['describe', '--tags'], options);
30+
const { stdout: gitStatusStdout } = spawnSync('git', ['status'], options);
31+
const version = stdout.trim();
32+
if (version === '' || gitStatusStdout.match('Not currently on any branch')) {
33+
throw new Error('No git version here');
34+
}
35+
return version;
36+
} catch (e) {
37+
const pkgPath = path.join(__dirname, '../../package.json');
38+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
39+
return pkg.version;
40+
}
41+
}

bin/lib/init.mjs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import inquirer from 'inquirer';
2+
import fs from 'fs';
3+
import options from './options.mjs';
4+
import camelize from 'camelize';
5+
6+
let questions = options.map((option) => {
7+
if (!option.type) {
8+
if (option.flag) {
9+
option.type = 'confirm';
10+
} else {
11+
option.type = 'input';
12+
}
13+
}
14+
option.message = option.question || option.help;
15+
return option;
16+
});
17+
18+
export default function (program) {
19+
program
20+
.command('init')
21+
.option('--advanced', 'Ask for all the settings')
22+
.description('create solid server configurations')
23+
.action((opts) => {
24+
let filteredQuestions = questions;
25+
if (!opts.advanced) {
26+
filteredQuestions = filteredQuestions.filter((option) => option.prompt);
27+
}
28+
inquirer.prompt(filteredQuestions)
29+
.then((answers) => {
30+
manipulateEmailSection(answers);
31+
manipulateServerSection(answers);
32+
cleanupAnswers(answers);
33+
const config = JSON.stringify(camelize(answers), null, ' ');
34+
const configPath = process.cwd() + '/config.json';
35+
fs.writeFile(configPath, config, (err) => {
36+
if (err) {
37+
return console.log('failed to write config.json');
38+
}
39+
console.log('config created on', configPath);
40+
});
41+
})
42+
.catch((err) => {
43+
console.log('Error:', err);
44+
});
45+
});
46+
}
47+
48+
function cleanupAnswers(answers) {
49+
Object.keys(answers).forEach((answer) => {
50+
if (answer.startsWith('use')) {
51+
delete answers[answer];
52+
}
53+
});
54+
}
55+
56+
function manipulateEmailSection(answers) {
57+
if (answers.useEmail) {
58+
answers.email = {
59+
host: answers['email-host'],
60+
port: answers['email-port'],
61+
secure: true,
62+
auth: {
63+
user: answers['email-auth-user'],
64+
pass: answers['email-auth-pass']
65+
}
66+
};
67+
delete answers['email-host'];
68+
delete answers['email-port'];
69+
delete answers['email-auth-user'];
70+
delete answers['email-auth-pass'];
71+
}
72+
}
73+
74+
function manipulateServerSection(answers) {
75+
answers.server = {
76+
name: answers['server-info-name'],
77+
description: answers['server-info-description'],
78+
logo: answers['server-info-logo']
79+
};
80+
Object.keys(answers).forEach((answer) => {
81+
if (answer.startsWith('server-info-')) {
82+
delete answers[answer];
83+
}
84+
});
85+
}

bin/lib/invalidUsernames.mjs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import fs from 'fs-extra';
2+
import Handlebars from 'handlebars';
3+
import path from 'path';
4+
import { getAccountManager, loadConfig, loadUsernames } from './cli-utils.js';
5+
import { isValidUsername } from '../../lib/common/user-utils.js';
6+
import blacklistService from '../../lib/services/blacklist-service.js';
7+
import { initConfigDir, initTemplateDirs } from '../../lib/server-config.js';
8+
import { fromServerConfig } from '../../lib/models/oidc-manager.js';
9+
import EmailService from '../../lib/services/email-service.js';
10+
import SolidHost from '../../lib/models/solid-host.js';
11+
12+
export default function (program) {
13+
program
14+
.command('invalidusernames')
15+
.option('--notify', 'Will notify users with usernames that are invalid')
16+
.option('--delete', 'Will delete users with usernames that are invalid')
17+
.description('Manage usernames that are invalid')
18+
.action(async (options) => {
19+
const config = loadConfig(program, options);
20+
if (!config.multiuser) {
21+
return console.error('You are running a single user server, no need to check for invalid usernames');
22+
}
23+
const invalidUsernames = getInvalidUsernames(config);
24+
const host = SolidHost.from({ port: config.port, serverUri: config.serverUri });
25+
const accountManager = getAccountManager(config, { host });
26+
if (options.notify) {
27+
return notifyUsers(invalidUsernames, accountManager, config);
28+
}
29+
if (options.delete) {
30+
return deleteUsers(invalidUsernames, accountManager, config, host);
31+
}
32+
listUsernames(invalidUsernames);
33+
});
34+
}
35+
36+
function backupIndexFile(username, accountManager, invalidUsernameTemplate, dateOfRemoval, supportEmail) {
37+
const userDirectory = accountManager.accountDirFor(username);
38+
const currentIndex = path.join(userDirectory, 'index.html');
39+
const currentIndexExists = fs.existsSync(currentIndex);
40+
const backupIndex = path.join(userDirectory, 'index.backup.html');
41+
const backupIndexExists = fs.existsSync(backupIndex);
42+
if (currentIndexExists && !backupIndexExists) {
43+
fs.renameSync(currentIndex, backupIndex);
44+
createNewIndexAcl(userDirectory);
45+
createNewIndex(username, invalidUsernameTemplate, dateOfRemoval, supportEmail, currentIndex);
46+
console.info(`index.html updated for user ${username}`);
47+
}
48+
}
49+
50+
function createNewIndex(username, invalidUsernameTemplate, dateOfRemoval, supportEmail, currentIndex) {
51+
const newIndexSource = invalidUsernameTemplate({
52+
username,
53+
dateOfRemoval,
54+
supportEmail
55+
});
56+
fs.writeFileSync(currentIndex, newIndexSource, 'utf-8');
57+
}
58+
59+
function createNewIndexAcl(userDirectory) {
60+
const currentIndexAcl = path.join(userDirectory, 'index.html.acl');
61+
const backupIndexAcl = path.join(userDirectory, 'index.backup.html.acl');
62+
const currentIndexSource = fs.readFileSync(currentIndexAcl, 'utf-8');
63+
const backupIndexSource = currentIndexSource.replace(/index.html/g, 'index.backup.html');
64+
fs.writeFileSync(backupIndexAcl, backupIndexSource, 'utf-8');
65+
}
66+
67+
async function deleteUsers(usernames, accountManager, config, host) {
68+
const oidcManager = fromServerConfig({ ...config, host });
69+
const deletingUsers = usernames.map(async username => {
70+
try {
71+
const user = accountManager.userAccountFrom({ username });
72+
await oidcManager.users.deleteUser(user);
73+
} catch (error) {
74+
if (error.message !== 'No email given') {
75+
throw error;
76+
}
77+
}
78+
const userDirectory = accountManager.accountDirFor(username);
79+
await fs.remove(userDirectory);
80+
});
81+
await Promise.all(deletingUsers);
82+
console.info(`Deleted ${deletingUsers.length} users succeeded`);
83+
}
84+
85+
function getInvalidUsernames(config) {
86+
const usernames = loadUsernames(config);
87+
return usernames.filter(username => !isValidUsername(username) || !blacklistService.validate(username));
88+
}
89+
90+
function listUsernames(usernames) {
91+
if (usernames.length === 0) {
92+
return console.info('No invalid usernames was found');
93+
}
94+
console.info(`${usernames.length} invalid usernames were found:${usernames.map(username => `\n- ${username}`)}`);
95+
}
96+
97+
async function notifyUsers(usernames, accountManager, config) {
98+
const twoWeeksFromNow = Date.now() + 14 * 24 * 60 * 60 * 1000;
99+
const dateOfRemoval = (new Date(twoWeeksFromNow)).toLocaleDateString();
100+
const { supportEmail } = config;
101+
updateIndexFiles(usernames, accountManager, dateOfRemoval, supportEmail);
102+
await sendEmails(config, usernames, accountManager, dateOfRemoval, supportEmail);
103+
}
104+
105+
async function sendEmails(config, usernames, accountManager, dateOfRemoval, supportEmail) {
106+
if (config.email && config.email.host) {
107+
const configPath = initConfigDir(config);
108+
const templates = initTemplateDirs(configPath);
109+
const users = await Promise.all(await usernames.map(async username => {
110+
const emailAddress = await accountManager.loadAccountRecoveryEmail({ username });
111+
const accountUri = accountManager.accountUriFor(username);
112+
return { username, emailAddress, accountUri };
113+
}));
114+
const emailService = new EmailService(templates.email, config.email);
115+
const sendingEmails = users
116+
.filter(user => !!user.emailAddress)
117+
.map(user => emailService.sendWithTemplate('invalid-username', {
118+
to: user.emailAddress,
119+
accountUri: user.accountUri,
120+
dateOfRemoval,
121+
supportEmail
122+
}));
123+
const emailsSent = await Promise.all(sendingEmails);
124+
console.info(`${emailsSent.length} emails sent to users with invalid usernames`);
125+
return;
126+
}
127+
console.info('You have not configured an email service.');
128+
console.info('Please set it up to send users email about their accounts');
129+
}
130+
131+
function updateIndexFiles(usernames, accountManager, dateOfRemoval, supportEmail) {
132+
const invalidUsernameFilePath = path.join(process.cwd(), 'default-views', 'account', 'invalid-username.hbs');
133+
const source = fs.readFileSync(invalidUsernameFilePath, 'utf-8');
134+
const invalidUsernameTemplate = Handlebars.compile(source);
135+
usernames.forEach(username => backupIndexFile(username, accountManager, invalidUsernameTemplate, dateOfRemoval, supportEmail));
136+
}

bin/lib/migrateLegacyResources.mjs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import fs from 'fs';
2+
import Path from 'path';
3+
import { promisify } from 'util';
4+
const readdir = promisify(fs.readdir);
5+
const lstat = promisify(fs.lstat);
6+
const rename = promisify(fs.rename);
7+
8+
export default function (program) {
9+
program
10+
.command('migrate-legacy-resources')
11+
.option('-p, --path <path>', 'Path to the data folder, defaults to \'data/\'')
12+
.option('-s, --suffix <path>', 'The suffix to add to extensionless files, defaults to \'$.ttl\'')
13+
.option('-v, --verbose', 'Path to the data folder')
14+
.description('Migrate the data folder from node-solid-server 4 to node-solid-server 5')
15+
.action(async (opts) => {
16+
const verbose = opts.verbose;
17+
const suffix = opts.suffix || '$.ttl';
18+
let paths = opts.path ? [opts.path] : ['data', 'config/templates'];
19+
paths = paths.map(path => path.startsWith(Path.sep) ? path : Path.join(process.cwd(), path));
20+
try {
21+
for (const path of paths) {
22+
if (verbose) {
23+
console.log(`Migrating files in ${path}`);
24+
}
25+
await migrate(path, suffix, verbose);
26+
}
27+
} catch (err) {
28+
console.error(err);
29+
}
30+
});
31+
}
32+
33+
async function migrate(path, suffix, verbose) {
34+
const files = await readdir(path);
35+
for (const file of files) {
36+
const fullFilePath = Path.join(path, file);
37+
const stat = await lstat(fullFilePath);
38+
if (stat.isFile()) {
39+
if (shouldMigrateFile(file)) {
40+
const newFullFilePath = getNewFileName(fullFilePath, suffix);
41+
if (verbose) {
42+
console.log(`${fullFilePath}\n => ${newFullFilePath}`);
43+
}
44+
await rename(fullFilePath, newFullFilePath);
45+
}
46+
} else {
47+
if (shouldMigrateFolder(file)) {
48+
await migrate(fullFilePath, suffix, verbose);
49+
}
50+
}
51+
}
52+
}
53+
54+
function getNewFileName(fullFilePath, suffix) {
55+
return fullFilePath + suffix;
56+
}
57+
58+
function shouldMigrateFile(filename) {
59+
return filename.indexOf('.') < 0;
60+
}
61+
62+
function shouldMigrateFolder(foldername) {
63+
return foldername[0] !== '.';
64+
}

0 commit comments

Comments
 (0)