mirror of
https://github.com/HeyPuter/puter.git
synced 2026-02-06 13:59:04 -06:00
fix: eslint autofixable errors (#2002)
This commit is contained in:
@@ -4,23 +4,23 @@ import events from './events.json.js';
|
||||
const mdlib = {};
|
||||
mdlib.h = (out, n, str) => {
|
||||
out(`${'#'.repeat(n)} ${str}\n\n`);
|
||||
}
|
||||
};
|
||||
|
||||
const N_START = 3;
|
||||
|
||||
const out = str => process.stdout.write(str);
|
||||
for ( const event of events ) {
|
||||
mdlib.h(out, N_START, `\`${event.id}\``);
|
||||
out(dedent(event.description) + '\n\n');
|
||||
|
||||
out(`${dedent(event.description) }\n\n`);
|
||||
|
||||
for ( const k in event.properties ) {
|
||||
const prop = event.properties[k];
|
||||
mdlib.h(out, N_START + 1, `Property \`${k}\``);
|
||||
out(prop.summary + '\n');
|
||||
out(`${prop.summary }\n`);
|
||||
out(`- **Type**: ${prop.type}\n`);
|
||||
out(`- **Mutability**: ${prop.mutability}\n`);
|
||||
if ( prop.notes ) {
|
||||
out(`- **Notes**:\n`);
|
||||
out('- **Notes**:\n');
|
||||
for ( const note of prop.notes ) {
|
||||
out(` - ${note}\n`);
|
||||
}
|
||||
@@ -29,7 +29,7 @@ for ( const event of events ) {
|
||||
}
|
||||
|
||||
if ( event.example ) {
|
||||
mdlib.h(out, N_START + 1, `Example`);
|
||||
mdlib.h(out, N_START + 1, 'Example');
|
||||
out(`\`\`\`${event.example.language}\n${dedent(event.example.code)}\n\`\`\`\n`);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export default [
|
||||
summary: 'the email being validated',
|
||||
notes: [
|
||||
'The email may have already been cleaned.',
|
||||
]
|
||||
],
|
||||
},
|
||||
allow: {
|
||||
type: 'boolean',
|
||||
@@ -20,7 +20,7 @@ export default [
|
||||
summary: 'whether the email is allowed',
|
||||
notes: [
|
||||
'If set to false, the email will be considered invalid.',
|
||||
]
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -44,8 +44,8 @@ export default [
|
||||
measurements: data.measurements
|
||||
});
|
||||
});
|
||||
`
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'core.fs.create.directory',
|
||||
@@ -61,8 +61,8 @@ export default [
|
||||
context: {
|
||||
type: 'Context',
|
||||
mutability: 'no-effect',
|
||||
summary: 'current context'
|
||||
summary: 'current context',
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -8,8 +8,8 @@ export default [
|
||||
`,
|
||||
example_values: [
|
||||
'example.com',
|
||||
'subdomain.example.com'
|
||||
]
|
||||
'subdomain.example.com',
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'protocol',
|
||||
@@ -18,8 +18,8 @@ export default [
|
||||
`,
|
||||
example_values: [
|
||||
'http',
|
||||
'https'
|
||||
]
|
||||
'https',
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'static_hosting_domain',
|
||||
@@ -30,7 +30,7 @@ export default [
|
||||
you could set this to something like
|
||||
\`site.192.168.555.12.nip.io\`, replacing
|
||||
\`192.168.555.12\` with a valid IP address belonging to the server.
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
key: 'allow_all_host_values',
|
||||
@@ -44,7 +44,7 @@ export default [
|
||||
description: `
|
||||
If true, Puter will allow requests with host headers that end in nip.io.
|
||||
This is useful for development, LAN, and VPN configurations.
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
key: 'http_port',
|
||||
@@ -57,21 +57,21 @@ export default [
|
||||
description: `
|
||||
If true, any /username/Public directory will be available to all
|
||||
users, including anonymous users.
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
key: 'disable_temp_users',
|
||||
description: `
|
||||
If true, new users will see the login/signup page instead of being
|
||||
automatically logged in as a temporary user.
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
key: 'disable_user_signup',
|
||||
description: `
|
||||
If true, the signup page will be disabled and the backend will not
|
||||
accept new user registrations.
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
key: 'disable_fallback_mechanisms',
|
||||
@@ -79,6 +79,6 @@ export default [
|
||||
A general setting to prevent any fallback behavior that might
|
||||
"hide" errors. It is recommended to set this to true when
|
||||
debugging, testing, or developing new features.
|
||||
`
|
||||
}
|
||||
]
|
||||
`,
|
||||
},
|
||||
];
|
||||
@@ -4,17 +4,17 @@ import configVals from './config-vals.json.js';
|
||||
const mdlib = {};
|
||||
mdlib.h = (out, n, str) => {
|
||||
out(`${'#'.repeat(n)} ${str}\n\n`);
|
||||
}
|
||||
};
|
||||
|
||||
const N_START = 3;
|
||||
|
||||
const out = str => process.stdout.write(str);
|
||||
for ( const configVal of configVals ) {
|
||||
mdlib.h(out, N_START, `\`${configVal.key}\``);
|
||||
out(dedent(configVal.description) + '\n\n');
|
||||
|
||||
out(`${dedent(configVal.description) }\n\n`);
|
||||
|
||||
if ( configVal.example_values ) {
|
||||
mdlib.h(out, N_START + 1, `Examples`);
|
||||
mdlib.h(out, N_START + 1, 'Examples');
|
||||
for ( const example of configVal.example_values ) {
|
||||
out(`- \`"${configVal.key}": ${JSON.stringify(example)}\`\n`);
|
||||
}
|
||||
|
||||
@@ -10,15 +10,14 @@ import spaceUnaryOpsWithException from './eslint/space-unary-ops-with-exception.
|
||||
|
||||
const rules = {
|
||||
'no-unused-vars': ['error', {
|
||||
'vars': 'all',
|
||||
'args': 'after-used',
|
||||
'caughtErrors': 'all',
|
||||
'ignoreRestSiblings': false,
|
||||
'ignoreUsingDeclarations': false,
|
||||
'reportUsedIgnorePattern': false,
|
||||
'argsIgnorePattern': '^_',
|
||||
'caughtErrorsIgnorePattern': '^_',
|
||||
'destructuredArrayIgnorePattern': '^_',
|
||||
vars: 'all',
|
||||
args: 'after-used',
|
||||
caughtErrors: 'none',
|
||||
ignoreRestSiblings: false,
|
||||
ignoreUsingDeclarations: false,
|
||||
reportUsedIgnorePattern: false,
|
||||
argsIgnorePattern: '^_',
|
||||
destructuredArrayIgnorePattern: '^_',
|
||||
|
||||
}],
|
||||
curly: ['error', 'multi-line'],
|
||||
@@ -60,7 +59,7 @@ export default defineConfig([
|
||||
// TypeScript support block
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
ignores: ['tests/**/*.ts', 'extensions'],
|
||||
ignores: ['tests/**/*.ts', 'extensions/**/*.ts'],
|
||||
languageOptions: {
|
||||
parser: tseslintParser,
|
||||
parserOptions: {
|
||||
@@ -75,7 +74,7 @@ export default defineConfig([
|
||||
rules: {
|
||||
// Recommended rules for TypeScript
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', caughtErrors: 'none' }],
|
||||
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
|
||||
},
|
||||
@@ -88,7 +87,7 @@ export default defineConfig([
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
project: 'extensions/tsconfig.json',
|
||||
project: './extensions/tsconfig.json',
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
@@ -97,16 +96,18 @@ export default defineConfig([
|
||||
rules: {
|
||||
// Recommended rules for TypeScript
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', caughtErrors: 'none' }],
|
||||
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
|
||||
},
|
||||
},
|
||||
} },
|
||||
// TypeScript support for tests
|
||||
{
|
||||
files: ['tests/**/*.ts'],
|
||||
ignores: ['tests/playwright/tests/**/*.ts'],
|
||||
|
||||
languageOptions: {
|
||||
parser: tseslintParser,
|
||||
globals: { ...globals.jest, ...globals.node },
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
@@ -119,11 +120,10 @@ export default defineConfig([
|
||||
rules: {
|
||||
// Recommended rules for TypeScript
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', caughtErrors: 'none' }],
|
||||
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
|
||||
},
|
||||
},
|
||||
} },
|
||||
{
|
||||
plugins: {
|
||||
js,
|
||||
@@ -136,7 +136,14 @@ export default defineConfig([
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/backend/**/*.{js,mjs,cjs,ts}'],
|
||||
files: [
|
||||
'src/backend/**/*.{js,mjs,cjs,ts}',
|
||||
'src/backend-core-0/**/*.{js,mjs,cjs,ts}',
|
||||
'src/putility/**/*.{js,mjs,cjs,ts}',
|
||||
],
|
||||
ignores: [
|
||||
'**/*.test.js',
|
||||
],
|
||||
languageOptions: { globals: globals.node },
|
||||
rules,
|
||||
extends: ['js/recommended'],
|
||||
@@ -145,6 +152,17 @@ export default defineConfig([
|
||||
'@stylistic': stylistic,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'**/*.test.js',
|
||||
],
|
||||
languageOptions: { globals: { ...globals.jest, ...globals.node } },
|
||||
rules,
|
||||
plugins: {
|
||||
js,
|
||||
'@stylistic': stylistic,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['extensions/**/*.{js,mjs,cjs,ts}'],
|
||||
languageOptions: {
|
||||
@@ -163,28 +181,29 @@ export default defineConfig([
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.{js,mjs,cjs,ts}'],
|
||||
files: ['**/*.{js,mjs,cjs,ts}', 'src/gui/src/**/*.js'],
|
||||
ignores: [
|
||||
'src/backend/**/*.{js,mjs,cjs,ts}',
|
||||
'extensions/**/*.{js,mjs,cjs,ts}',
|
||||
'src/backend-core-0/**/*.{js,mjs,cjs,ts}',
|
||||
'submodules/**',
|
||||
'tests/**',
|
||||
'tools/**',
|
||||
'**/*.min.js',
|
||||
'**/*.min.cjs',
|
||||
'**/*.min.mjs',
|
||||
'**/socket.io.js',
|
||||
'**/dist/*.js',
|
||||
'src/phoenix/test/**',
|
||||
'src/gui/src/lib/**',
|
||||
'src/gui/dist/**',
|
||||
],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.jquery,
|
||||
i18n: 'readonly',
|
||||
},
|
||||
},
|
||||
rules,
|
||||
},
|
||||
{
|
||||
files: ['**/*.{js,mjs,cjs,ts}'],
|
||||
ignores: ['src/backend/**/*.{js,mjs,cjs,ts}'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.jquery,
|
||||
i18n: 'readonly',
|
||||
puter: 'readonly',
|
||||
},
|
||||
},
|
||||
rules,
|
||||
@@ -192,7 +211,6 @@ export default defineConfig([
|
||||
plugins: {
|
||||
js,
|
||||
'@stylistic': stylistic,
|
||||
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -29,7 +29,7 @@ export default {
|
||||
return {
|
||||
IfStatement (ifNode) {
|
||||
const testRaw = ifNode.test;
|
||||
if ( !testRaw ) return;
|
||||
if ( ! testRaw ) return;
|
||||
|
||||
const test = unwrapParens(testRaw);
|
||||
if ( !test || test.type !== 'UnaryExpression' || test.operator !== '!' ) {
|
||||
|
||||
@@ -15,10 +15,10 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
create(context) {
|
||||
create (context) {
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
function checkControlStructureSpacing(node) {
|
||||
function checkControlStructureSpacing (node) {
|
||||
// For control structures, we need to find the parentheses around the condition/test
|
||||
let conditionNode;
|
||||
|
||||
@@ -33,7 +33,7 @@ export default {
|
||||
conditionNode = node.param;
|
||||
}
|
||||
|
||||
if ( !conditionNode ) return;
|
||||
if ( ! conditionNode ) return;
|
||||
|
||||
// Find the opening paren - it should be right before the condition starts
|
||||
const openParen = sourceCode.getTokenBefore(conditionNode, token => token.value === '(');
|
||||
@@ -62,7 +62,7 @@ export default {
|
||||
node,
|
||||
loc: openParen.loc,
|
||||
messageId: 'missingSpaceAfterOpen',
|
||||
fix(fixer) {
|
||||
fix (fixer) {
|
||||
return fixer.insertTextAfter(openParen, ' ');
|
||||
},
|
||||
});
|
||||
@@ -73,25 +73,25 @@ export default {
|
||||
node,
|
||||
loc: closeParen.loc,
|
||||
messageId: 'missingSpaceBeforeClose',
|
||||
fix(fixer) {
|
||||
fix (fixer) {
|
||||
return fixer.insertTextBefore(closeParen, ' ');
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function checkForLoopSpacing(node) {
|
||||
function checkForLoopSpacing (node) {
|
||||
// For loops are special - we need to find the opening paren after the 'for' keyword
|
||||
// and the closing paren before the body
|
||||
const forKeyword = sourceCode.getFirstToken(node);
|
||||
if ( !forKeyword || forKeyword.value !== 'for' ) return;
|
||||
|
||||
const openParen = sourceCode.getTokenAfter(forKeyword, token => token.value === '(');
|
||||
if ( !openParen ) return;
|
||||
if ( ! openParen ) return;
|
||||
|
||||
// The closing paren should be right before the body
|
||||
const closeParen = sourceCode.getTokenBefore(node.body, token => token.value === ')');
|
||||
if ( !closeParen ) return;
|
||||
if ( ! closeParen ) return;
|
||||
|
||||
const afterOpen = sourceCode.getTokenAfter(openParen);
|
||||
const beforeClose = sourceCode.getTokenBefore(closeParen);
|
||||
@@ -101,7 +101,7 @@ export default {
|
||||
node,
|
||||
loc: openParen.loc,
|
||||
messageId: 'missingSpaceAfterOpen',
|
||||
fix(fixer) {
|
||||
fix (fixer) {
|
||||
return fixer.insertTextAfter(openParen, ' ');
|
||||
},
|
||||
});
|
||||
@@ -112,14 +112,14 @@ export default {
|
||||
node,
|
||||
loc: closeParen.loc,
|
||||
messageId: 'missingSpaceBeforeClose',
|
||||
fix(fixer) {
|
||||
fix (fixer) {
|
||||
return fixer.insertTextBefore(closeParen, ' ');
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function checkFunctionCallSpacing(node) {
|
||||
function checkFunctionCallSpacing (node) {
|
||||
// Find the opening parenthesis for this function call
|
||||
const openParen = sourceCode.getFirstToken(node, token => token.value === '(');
|
||||
const closeParen = sourceCode.getLastToken(node, token => token.value === ')');
|
||||
@@ -137,7 +137,7 @@ export default {
|
||||
node,
|
||||
loc: openParen.loc,
|
||||
messageId: 'unexpectedSpaceAfterOpen',
|
||||
fix(fixer) {
|
||||
fix (fixer) {
|
||||
return fixer.removeRange([openParen.range[1], afterOpen.range[0]]);
|
||||
},
|
||||
});
|
||||
@@ -151,7 +151,7 @@ export default {
|
||||
node,
|
||||
loc: closeParen.loc,
|
||||
messageId: 'unexpectedSpaceBeforeClose',
|
||||
fix(fixer) {
|
||||
fix (fixer) {
|
||||
return fixer.removeRange([beforeClose.range[1], closeParen.range[0]]);
|
||||
},
|
||||
});
|
||||
@@ -161,40 +161,40 @@ export default {
|
||||
|
||||
return {
|
||||
// Control structures that should have spacing
|
||||
IfStatement(node) {
|
||||
IfStatement (node) {
|
||||
checkControlStructureSpacing(node);
|
||||
},
|
||||
WhileStatement(node) {
|
||||
WhileStatement (node) {
|
||||
checkControlStructureSpacing(node);
|
||||
},
|
||||
DoWhileStatement(node) {
|
||||
DoWhileStatement (node) {
|
||||
checkControlStructureSpacing(node);
|
||||
},
|
||||
SwitchStatement(node) {
|
||||
SwitchStatement (node) {
|
||||
checkControlStructureSpacing(node);
|
||||
},
|
||||
CatchClause(node) {
|
||||
CatchClause (node) {
|
||||
if ( node.param ) {
|
||||
checkControlStructureSpacing(node);
|
||||
}
|
||||
},
|
||||
|
||||
// For loops need special handling
|
||||
ForStatement(node) {
|
||||
ForStatement (node) {
|
||||
checkForLoopSpacing(node);
|
||||
},
|
||||
ForInStatement(node) {
|
||||
ForInStatement (node) {
|
||||
checkForLoopSpacing(node);
|
||||
},
|
||||
ForOfStatement(node) {
|
||||
ForOfStatement (node) {
|
||||
checkForLoopSpacing(node);
|
||||
},
|
||||
|
||||
// Function calls that should NOT have spacing
|
||||
CallExpression(node) {
|
||||
CallExpression (node) {
|
||||
checkFunctionCallSpacing(node);
|
||||
},
|
||||
NewExpression(node) {
|
||||
NewExpression (node) {
|
||||
if ( node.arguments.length > 0 || sourceCode.getLastToken(node).value === ')' ) {
|
||||
checkFunctionCallSpacing(node);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//@puter priority -1
|
||||
console.log('exporting something...');
|
||||
extension.exports = {
|
||||
testval: 5
|
||||
testval: 5,
|
||||
};
|
||||
|
||||
extension.on('init', () => {
|
||||
|
||||
@@ -5,7 +5,7 @@ extension.get('/metering/usage', { subdomain: 'api' }, async (req, res) => {
|
||||
const meteringService = meteringServiceWrapper.meteringService;
|
||||
|
||||
const actor = req.actor;
|
||||
if ( !actor ) {
|
||||
if ( ! actor ) {
|
||||
throw Error('actor not found in context');
|
||||
}
|
||||
const actorUsagePromise = meteringService.getActorCurrentMonthUsageDetails(actor);
|
||||
@@ -20,11 +20,11 @@ extension.get('/metering/usage/:appId', { subdomain: 'api' }, async (req, res) =
|
||||
const meteringService = meteringServiceWrapper.meteringService;
|
||||
|
||||
const actor = req.actor;
|
||||
if ( !actor ) {
|
||||
if ( ! actor ) {
|
||||
throw Error('actor not found in context');
|
||||
}
|
||||
const appId = req.params.appId;
|
||||
if ( !appId ) {
|
||||
if ( ! appId ) {
|
||||
res.status(400).json({ error: 'appId parameter is required' });
|
||||
return;
|
||||
}
|
||||
@@ -37,13 +37,13 @@ extension.get('/metering/usage/:appId', { subdomain: 'api' }, async (req, res) =
|
||||
extension.get('/metering/globalUsage', { subdomain: 'api' }, async (req, res) => {
|
||||
const meteringService = meteringServiceWrapper.meteringService;
|
||||
const actor = req.actor;
|
||||
if ( !actor ) {
|
||||
if ( ! actor ) {
|
||||
throw Error('actor not found in context');
|
||||
}
|
||||
|
||||
// check if actor is allowed to view global usage
|
||||
const allowedUsers = extension.config.allowedGlobalUsageUsers || [];
|
||||
if ( !allowedUsers.includes(actor.type?.user.uuid) ) {
|
||||
if ( ! allowedUsers.includes(actor.type?.user.uuid) ) {
|
||||
res.status(403).json({ error: 'You are not authorized to view global usage' });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@@ -23,4 +23,4 @@ extension.get('/example-onefile-get', (req, res) => {
|
||||
|
||||
extension.on('install', ({ services }) => {
|
||||
// console.log('install was called');
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@@ -23,4 +23,4 @@ extension.get('/example-mod-get', (req, res) => {
|
||||
|
||||
extension.on('install', ({ services }) => {
|
||||
// console.log('install was called');
|
||||
})
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ class CustomPuterService extends use.Service {
|
||||
async _init () {
|
||||
const svc_commands = this.services.get('commands');
|
||||
this._register_commands(svc_commands);
|
||||
|
||||
|
||||
const svc_puterHomepage = this.services.get('puter-homepage');
|
||||
svc_puterHomepage.register_script('/custom-gui/main.js');
|
||||
}
|
||||
@@ -32,29 +32,28 @@ class CustomPuterService extends use.Service {
|
||||
const path_ = require('path');
|
||||
|
||||
app.use('/custom-gui',
|
||||
express.static(path.join(__dirname, 'gui')));
|
||||
express.static(path.join(__dirname, 'gui')));
|
||||
}
|
||||
async ['__on_boot.consolidation'] () {
|
||||
const then = Date.now();
|
||||
this.tod_widget = () => {
|
||||
const s = 5 - Math.floor(
|
||||
(Date.now() - then) / 1000);
|
||||
const s = 5 - Math.floor((Date.now() - then) / 1000);
|
||||
const lines = [
|
||||
"\x1B[36;1mKDMOD ENABLED\x1B[0m" +
|
||||
` (👁️ ${s}s)`
|
||||
'\x1B[36;1mKDMOD ENABLED\x1B[0m' +
|
||||
` (👁️ ${s}s)`,
|
||||
];
|
||||
// It would be super cool to be able to use this here
|
||||
// surrounding_box('33;1', lines);
|
||||
return lines;
|
||||
}
|
||||
};
|
||||
|
||||
const svc_devConsole = this.services.get('dev-console', { optional: true });
|
||||
if ( ! svc_devConsole ) return;
|
||||
svc_devConsole.add_widget(this.tod_widget);
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
svc_devConsole.remove_widget(this.tod_widget);
|
||||
}, 5000)
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
_register_commands (commands) {
|
||||
@@ -69,8 +68,8 @@ class CustomPuterService extends use.Service {
|
||||
const lines = this.tod_widget();
|
||||
for ( const line of lines ) log.log(line);
|
||||
this.tod_widget = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class ShareTestService extends use.Service {
|
||||
uuidv4: require('uuid').v4,
|
||||
};
|
||||
|
||||
async _init() {
|
||||
async _init () {
|
||||
const svc_commands = this.services.get('commands');
|
||||
this._register_commands(svc_commands);
|
||||
|
||||
@@ -51,7 +51,7 @@ class ShareTestService extends use.Service {
|
||||
this.db = svc_db.get(svc_db.DB_WRITE, 'share-test');
|
||||
}
|
||||
|
||||
_register_commands(commands) {
|
||||
_register_commands (commands) {
|
||||
commands.registerCommands('share-test', [
|
||||
{
|
||||
id: 'start',
|
||||
@@ -74,7 +74,7 @@ class ShareTestService extends use.Service {
|
||||
]);
|
||||
}
|
||||
|
||||
async runit() {
|
||||
async runit () {
|
||||
await this.teardown_();
|
||||
await this.setup_();
|
||||
|
||||
@@ -94,13 +94,13 @@ class ShareTestService extends use.Service {
|
||||
return results;
|
||||
}
|
||||
|
||||
async setup_() {
|
||||
async setup_ () {
|
||||
await this.create_test_user_('testuser_eric');
|
||||
await this.create_test_user_('testuser_stan');
|
||||
await this.create_test_user_('testuser_kyle');
|
||||
await this.create_test_user_('testuser_kenny');
|
||||
}
|
||||
async run_scenario_(scenario) {
|
||||
async run_scenario_ (scenario) {
|
||||
let error;
|
||||
// Run sequence
|
||||
for ( const step of scenario.sequence ) {
|
||||
@@ -119,14 +119,14 @@ class ShareTestService extends use.Service {
|
||||
}
|
||||
return error;
|
||||
}
|
||||
async teardown_() {
|
||||
async teardown_ () {
|
||||
await this.delete_test_user_('testuser_eric');
|
||||
await this.delete_test_user_('testuser_stan');
|
||||
await this.delete_test_user_('testuser_kyle');
|
||||
await this.delete_test_user_('testuser_kenny');
|
||||
}
|
||||
|
||||
async create_test_user_(username) {
|
||||
async create_test_user_ (username) {
|
||||
await this.db.write(`
|
||||
INSERT INTO user (uuid, username, email, free_storage, password)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
@@ -145,14 +145,14 @@ class ShareTestService extends use.Service {
|
||||
return user;
|
||||
}
|
||||
|
||||
async delete_test_user_(username) {
|
||||
async delete_test_user_ (username) {
|
||||
const user = await get_user({ username });
|
||||
if ( ! user ) return;
|
||||
await deleteUser(user.id);
|
||||
}
|
||||
|
||||
// API for scenarios
|
||||
async ['__scenario:create-example-file'](
|
||||
async ['__scenario:create-example-file'] (
|
||||
{ actor, user },
|
||||
{ name, contents },
|
||||
) {
|
||||
@@ -178,7 +178,7 @@ class ShareTestService extends use.Service {
|
||||
file,
|
||||
});
|
||||
}
|
||||
async ['__scenario:assert-no-access'](
|
||||
async ['__scenario:assert-no-access'] (
|
||||
{ actor, user },
|
||||
{ path },
|
||||
) {
|
||||
@@ -190,21 +190,21 @@ class ShareTestService extends use.Service {
|
||||
fsNode: node,
|
||||
actor,
|
||||
});
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
expected_e = e;
|
||||
}
|
||||
if ( ! expected_e ) {
|
||||
return { message: 'expected error, got none' };
|
||||
}
|
||||
}
|
||||
async ['__scenario:grant'](
|
||||
async ['__scenario:grant'] (
|
||||
{ actor, user },
|
||||
{ to, permission },
|
||||
) {
|
||||
const svc_permission = this.services.get('permission');
|
||||
await svc_permission.grant_user_user_permission(actor, to, permission, {}, {});
|
||||
}
|
||||
async ['__scenario:assert-access'](
|
||||
async ['__scenario:assert-access'] (
|
||||
{ actor, user },
|
||||
{ path, level },
|
||||
) {
|
||||
|
||||
@@ -26,17 +26,17 @@ module.exports = [
|
||||
with: {
|
||||
name: 'example.txt',
|
||||
contents: 'secret file',
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Eric tries to access it',
|
||||
call: 'assert-no-access',
|
||||
as: 'testuser_eric',
|
||||
with: {
|
||||
path: '/testuser_kyle/Desktop/example.txt'
|
||||
}
|
||||
path: '/testuser_kyle/Desktop/example.txt',
|
||||
},
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
sequence: [
|
||||
@@ -47,7 +47,7 @@ module.exports = [
|
||||
with: {
|
||||
name: 'example.txt',
|
||||
contents: 'secret file',
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Stan grants permission to Eric',
|
||||
@@ -55,8 +55,8 @@ module.exports = [
|
||||
as: 'testuser_stan',
|
||||
with: {
|
||||
to: 'testuser_eric',
|
||||
permission: 'fs:/testuser_stan/Desktop/example.txt:read'
|
||||
}
|
||||
permission: 'fs:/testuser_stan/Desktop/example.txt:read',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Eric tries to access it',
|
||||
@@ -64,10 +64,10 @@ module.exports = [
|
||||
as: 'testuser_eric',
|
||||
with: {
|
||||
path: '/testuser_stan/Desktop/example.txt',
|
||||
level: 'read'
|
||||
}
|
||||
level: 'read',
|
||||
},
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
sequence: [
|
||||
@@ -77,8 +77,8 @@ module.exports = [
|
||||
as: 'testuser_stan',
|
||||
with: {
|
||||
to: 'testuser_eric',
|
||||
permission: 'fs:/testuser_kyle/Desktop/example.txt:read'
|
||||
}
|
||||
permission: 'fs:/testuser_kyle/Desktop/example.txt:read',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Eric tries to access it',
|
||||
@@ -86,8 +86,8 @@ module.exports = [
|
||||
as: 'testuser_eric',
|
||||
with: {
|
||||
path: '/testuser_kyle/Desktop/example.txt',
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -22,15 +22,15 @@ const request_examples = [
|
||||
fetch: async (args) => {
|
||||
return await fetch(`${window.api_origin}/drivers/call`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${puter.authToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${puter.authToken}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
interface: 'puter-apps',
|
||||
method: 'read',
|
||||
args,
|
||||
}),
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
});
|
||||
},
|
||||
out: async (resp) => {
|
||||
@@ -48,15 +48,15 @@ const request_examples = [
|
||||
fetch: async () => {
|
||||
return await fetch(`${window.api_origin}/drivers/call`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${puter.authToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${puter.authToken}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
interface: 'puter-apps',
|
||||
method: 'select',
|
||||
args: { predicate: [] },
|
||||
}),
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
});
|
||||
},
|
||||
out: async (resp) => {
|
||||
@@ -73,15 +73,15 @@ const request_examples = [
|
||||
name: 'grant permission from a user to a user',
|
||||
fetch: async (user, perm) => {
|
||||
return await fetch(`${window.api_origin}/auth/grant-user-user`, {
|
||||
"headers": {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${puter.authToken}`,
|
||||
},
|
||||
"body": JSON.stringify({
|
||||
target_username: user,
|
||||
permission: perm,
|
||||
}),
|
||||
"method": "POST",
|
||||
'headers': {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${puter.authToken}`,
|
||||
},
|
||||
'body': JSON.stringify({
|
||||
target_username: user,
|
||||
permission: perm,
|
||||
}),
|
||||
'method': 'POST',
|
||||
});
|
||||
},
|
||||
out: async (resp) => {
|
||||
@@ -98,7 +98,7 @@ const request_examples = [
|
||||
fetch: async (path, str) => {
|
||||
const endpoint = `${window.api_origin}/write`;
|
||||
const token = puter.authToken;
|
||||
|
||||
|
||||
const blob = new Blob([str], { type: 'text/plain' });
|
||||
const formData = new FormData();
|
||||
formData.append('create_missing_ancestors', true);
|
||||
@@ -106,15 +106,15 @@ const request_examples = [
|
||||
formData.append('size', 8);
|
||||
formData.append('overwrite', true);
|
||||
formData.append('file', blob, 'something.txt');
|
||||
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
body: formData
|
||||
body: formData,
|
||||
});
|
||||
return await response.json();
|
||||
},
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
globalThis.reqex = request_examples;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
extension.on('install', ({ services }) => {
|
||||
const { CustomPuterService } = require('./CustomPuterService.js');
|
||||
services.registerService('__custom-puter', CustomPuterService);
|
||||
|
||||
|
||||
const { ShareTestService } = require('./ShareTestService.js');
|
||||
services.registerService('__share-test', ShareTestService);
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.35.0",
|
||||
"@playwright/test": "^1.56.1",
|
||||
"@stylistic/eslint-plugin": "^5.3.1",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.46.1",
|
||||
@@ -35,7 +36,8 @@
|
||||
"vite-plugin-static-copy": "^3.1.3",
|
||||
"vitest": "^3.2.4",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-cli": "^5.1.1"
|
||||
"webpack-cli": "^5.1.1",
|
||||
"yaml": "^2.8.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "npx mocha src/phoenix/test && npx vitest run src/backend && node src/backend/tools/test",
|
||||
@@ -89,4 +91,4 @@
|
||||
"engines": {
|
||||
"node": ">=20.19.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
export const is_valid_uuid = ( uuid ) => {
|
||||
let s = "" + uuid;
|
||||
let s = `${ uuid}`;
|
||||
s = s.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
|
||||
return !! s;
|
||||
}
|
||||
return !!s;
|
||||
};
|
||||
|
||||
export const is_valid_uuid4 = ( uuid ) => {
|
||||
return is_valid_uuid(uuid);
|
||||
}
|
||||
};
|
||||
|
||||
export const is_specifically_uuidv4 = ( uuid ) => {
|
||||
let s = "" + uuid;
|
||||
let s = `${ uuid}`;
|
||||
|
||||
s = s.match(/^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i);
|
||||
if (!s) {
|
||||
return false;
|
||||
if ( ! s ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
export const is_valid_url = ( url ) => {
|
||||
let s = "" + url;
|
||||
let s = `${ url}`;
|
||||
|
||||
try {
|
||||
new URL(s);
|
||||
@@ -27,7 +27,7 @@ export const is_valid_url = ( url ) => {
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const path_excludes = () => /[\x00-\x1F]/g;
|
||||
|
||||
@@ -40,8 +40,8 @@ const safety_excludes = [
|
||||
/[\u2066-\u2069]/, // RTL and LTR isolate
|
||||
/[\u2028-\u2029]/, // line and paragraph separator
|
||||
/[\uFF01-\uFF5E]/, // fullwidth ASCII
|
||||
/[\u2060]/, // word joiner
|
||||
/[\uFEFF]/, // zero width no-break space
|
||||
/[\u2060]/, // word joiner
|
||||
/[\uFEFF]/, // zero width no-break space
|
||||
/[\uFFFE-\uFFFF]/, // non-characters
|
||||
];
|
||||
|
||||
@@ -56,8 +56,10 @@ export const is_valid_path = (path, {
|
||||
if ( exclude.test(path) ) return false;
|
||||
}
|
||||
|
||||
if ( ! allow_path_fragment ) if ( path[0] !== '/' && path[0] !== '.' ) {
|
||||
return false;
|
||||
if ( ! allow_path_fragment ) {
|
||||
if ( path[0] !== '/' && path[0] !== '.' ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( no_relative_components ) {
|
||||
@@ -70,4 +72,4 @@ export const is_valid_path = (path, {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -51,4 +51,3 @@ const cjsConfig = {
|
||||
};
|
||||
|
||||
export default [esmConfig, cjsConfig];
|
||||
|
||||
|
||||
@@ -16,33 +16,33 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const CoreModule = require("./src/CoreModule.js");
|
||||
const { Kernel } = require("./src/Kernel.js");
|
||||
const DatabaseModule = require("./src/DatabaseModule.js");
|
||||
const LocalDiskStorageModule = require("./src/LocalDiskStorageModule.js");
|
||||
const MemoryStorageModule = require("./src/MemoryStorageModule.js");
|
||||
const SelfHostedModule = require("./src/modules/selfhosted/SelfHostedModule.js");
|
||||
const { testlaunch } = require("./src/index.js");
|
||||
const BaseService = require("./src/services/BaseService.js");
|
||||
const { Context } = require("./src/util/context.js");
|
||||
const { TestDriversModule } = require("./src/modules/test-drivers/TestDriversModule.js");
|
||||
const { PuterAIModule } = require("./src/modules/puterai/PuterAIModule.js");
|
||||
const { BroadcastModule } = require("./src/modules/broadcast/BroadcastModule.js");
|
||||
const { WebModule } = require("./src/modules/web/WebModule.js");
|
||||
const { Core2Module } = require("./src/modules/core/Core2Module.js");
|
||||
const { TemplateModule } = require("./src/modules/template/TemplateModule.js");
|
||||
const { PuterFSModule } = require("./src/modules/puterfs/PuterFSModule.js");
|
||||
const { PerfMonModule } = require("./src/modules/perfmon/PerfMonModule.js");
|
||||
const { AppsModule } = require("./src/modules/apps/AppsModule.js");
|
||||
const { DevelopmentModule } = require("./src/modules/development/DevelopmentModule.js");
|
||||
const { HostOSModule } = require("./src/modules/hostos/HostOSModule.js");
|
||||
const { InternetModule } = require("./src/modules/internet/InternetModule.js");
|
||||
const { CaptchaModule } = require("./src/modules/captcha/CaptchaModule.js");
|
||||
const { EntityStoreModule } = require("./src/modules/entitystore/EntityStoreModule.js");
|
||||
const { KVStoreModule } = require("./src/modules/kvstore/KVStoreModule.js");
|
||||
const { DomainModule } = require("./src/modules/domain/DomainModule.js");
|
||||
const { DNSModule } = require("./src/modules/dns/DNSModule.js");
|
||||
const { TestConfigModule } = require("./src/modules/test-config/TestConfigModule.js");
|
||||
const CoreModule = require('./src/CoreModule.js');
|
||||
const { Kernel } = require('./src/Kernel.js');
|
||||
const DatabaseModule = require('./src/DatabaseModule.js');
|
||||
const LocalDiskStorageModule = require('./src/LocalDiskStorageModule.js');
|
||||
const MemoryStorageModule = require('./src/MemoryStorageModule.js');
|
||||
const SelfHostedModule = require('./src/modules/selfhosted/SelfHostedModule.js');
|
||||
const { testlaunch } = require('./src/index.js');
|
||||
const BaseService = require('./src/services/BaseService.js');
|
||||
const { Context } = require('./src/util/context.js');
|
||||
const { TestDriversModule } = require('./src/modules/test-drivers/TestDriversModule.js');
|
||||
const { PuterAIModule } = require('./src/modules/puterai/PuterAIModule.js');
|
||||
const { BroadcastModule } = require('./src/modules/broadcast/BroadcastModule.js');
|
||||
const { WebModule } = require('./src/modules/web/WebModule.js');
|
||||
const { Core2Module } = require('./src/modules/core/Core2Module.js');
|
||||
const { TemplateModule } = require('./src/modules/template/TemplateModule.js');
|
||||
const { PuterFSModule } = require('./src/modules/puterfs/PuterFSModule.js');
|
||||
const { PerfMonModule } = require('./src/modules/perfmon/PerfMonModule.js');
|
||||
const { AppsModule } = require('./src/modules/apps/AppsModule.js');
|
||||
const { DevelopmentModule } = require('./src/modules/development/DevelopmentModule.js');
|
||||
const { HostOSModule } = require('./src/modules/hostos/HostOSModule.js');
|
||||
const { InternetModule } = require('./src/modules/internet/InternetModule.js');
|
||||
const { CaptchaModule } = require('./src/modules/captcha/CaptchaModule.js');
|
||||
const { EntityStoreModule } = require('./src/modules/entitystore/EntityStoreModule.js');
|
||||
const { KVStoreModule } = require('./src/modules/kvstore/KVStoreModule.js');
|
||||
const { DomainModule } = require('./src/modules/domain/DomainModule.js');
|
||||
const { DNSModule } = require('./src/modules/dns/DNSModule.js');
|
||||
const { TestConfigModule } = require('./src/modules/test-config/TestConfigModule.js');
|
||||
|
||||
module.exports = {
|
||||
helloworld: () => {
|
||||
@@ -56,7 +56,7 @@ module.exports = {
|
||||
Context,
|
||||
|
||||
Kernel,
|
||||
|
||||
|
||||
EssentialModules: [
|
||||
Core2Module,
|
||||
PuterFSModule,
|
||||
@@ -69,7 +69,7 @@ module.exports = {
|
||||
EntityStoreModule,
|
||||
KVStoreModule,
|
||||
],
|
||||
|
||||
|
||||
// Pre-built modules
|
||||
CoreModule,
|
||||
WebModule,
|
||||
@@ -86,7 +86,7 @@ module.exports = {
|
||||
KVStoreModule,
|
||||
DNSModule,
|
||||
DomainModule,
|
||||
|
||||
|
||||
// Development modules
|
||||
PerfMonModule,
|
||||
DevelopmentModule,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { AdvancedBase } = require("@heyputer/putility");
|
||||
const { AdvancedBase } = require('@heyputer/putility');
|
||||
|
||||
class DatabaseModule extends AdvancedBase {
|
||||
async install (context) {
|
||||
@@ -28,8 +28,8 @@ class DatabaseModule extends AdvancedBase {
|
||||
strategy_key: 'engine',
|
||||
strategies: {
|
||||
sqlite: [SqliteDatabaseAccessService],
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class Extension extends AdvancedBase {
|
||||
}),
|
||||
];
|
||||
|
||||
randomBrightColor() {
|
||||
randomBrightColor () {
|
||||
// Bright colors in ANSI (foreground codes 90–97)
|
||||
const brightColors = [
|
||||
// 91, // Bright Red
|
||||
@@ -54,7 +54,7 @@ class Extension extends AdvancedBase {
|
||||
return brightColors[Math.floor(Math.random() * brightColors.length)];
|
||||
}
|
||||
|
||||
constructor(...a) {
|
||||
constructor (...a) {
|
||||
super(...a);
|
||||
this.service = null;
|
||||
this.log = null;
|
||||
@@ -97,18 +97,18 @@ class Extension extends AdvancedBase {
|
||||
};
|
||||
}
|
||||
|
||||
example() {
|
||||
example () {
|
||||
console.log('Example method called by an extension.');
|
||||
}
|
||||
|
||||
// === [START] RuntimeModule aliases ===
|
||||
set exports(value) {
|
||||
set exports (value) {
|
||||
this.runtime.exports = value;
|
||||
}
|
||||
get exports() {
|
||||
get exports () {
|
||||
return this.runtime.exports;
|
||||
}
|
||||
import(name) {
|
||||
import (name) {
|
||||
return this.runtime.import(name);
|
||||
}
|
||||
// === [END] RuntimeModule aliases ===
|
||||
@@ -116,7 +116,7 @@ class Extension extends AdvancedBase {
|
||||
/**
|
||||
* This will get a database instance from the default service.
|
||||
*/
|
||||
get db() {
|
||||
get db () {
|
||||
const db = this.service.values.get('db');
|
||||
if ( ! db ) {
|
||||
throw new Error('extension tried to access database before it was ' +
|
||||
@@ -125,7 +125,7 @@ class Extension extends AdvancedBase {
|
||||
return db;
|
||||
}
|
||||
|
||||
get services() {
|
||||
get services () {
|
||||
const services = this.service.values.get('services');
|
||||
if ( ! services ) {
|
||||
throw new Error('extension tried to access "services" before it was ' +
|
||||
@@ -134,7 +134,7 @@ class Extension extends AdvancedBase {
|
||||
return services;
|
||||
}
|
||||
|
||||
get log_context() {
|
||||
get log_context () {
|
||||
const log_context = this.service.values.get('log_context');
|
||||
if ( ! log_context ) {
|
||||
throw new Error('extension tried to access "log_context" before it was ' +
|
||||
@@ -142,7 +142,7 @@ class Extension extends AdvancedBase {
|
||||
}
|
||||
return log_context;
|
||||
}
|
||||
|
||||
|
||||
get errors () {
|
||||
return memoized_errors ?? (() => {
|
||||
return this.services.get('error-service').create(this.log_context);
|
||||
@@ -155,7 +155,7 @@ class Extension extends AdvancedBase {
|
||||
* @param {string} [key] Key of data being registered
|
||||
* @param {any} data The data to be registered
|
||||
*/
|
||||
register(typeKey, keyOrData, data) {
|
||||
register (typeKey, keyOrData, data) {
|
||||
if ( ! this.registry_[typeKey] ) {
|
||||
this.registry_[typeKey] = {
|
||||
named: {},
|
||||
@@ -191,7 +191,7 @@ class Extension extends AdvancedBase {
|
||||
* @param {string} [key] Key of data being registered
|
||||
* @param {any} data The data to be registered
|
||||
*/
|
||||
reg(...a) {
|
||||
reg (...a) {
|
||||
this.register(...a);
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ class Extension extends AdvancedBase {
|
||||
* @param {*} handler - function to handle the endpoint
|
||||
* @param {*} options - options like noauth (bool) and mw (array)
|
||||
*/
|
||||
get(path, handler, options) {
|
||||
get (path, handler, options) {
|
||||
// this extension will have a default service
|
||||
this.ensure_service_();
|
||||
|
||||
@@ -223,7 +223,7 @@ class Extension extends AdvancedBase {
|
||||
* @param {*} handler - function to handle the endpoint
|
||||
* @param {*} options - options like noauth (bool) and mw (array)
|
||||
*/
|
||||
post(path, handler, options) {
|
||||
post (path, handler, options) {
|
||||
// this extension will have a default service
|
||||
this.ensure_service_();
|
||||
|
||||
@@ -245,7 +245,7 @@ class Extension extends AdvancedBase {
|
||||
* @param {*} handler - function to handle the endpoint
|
||||
* @param {*} options - options like noauth (bool) and mw (array)
|
||||
*/
|
||||
put(path, handler, options) {
|
||||
put (path, handler, options) {
|
||||
// this extension will have a default service
|
||||
this.ensure_service_();
|
||||
|
||||
@@ -267,7 +267,7 @@ class Extension extends AdvancedBase {
|
||||
* @param {*} options - options like noauth (bool) and mw (array)
|
||||
*/
|
||||
|
||||
delete(path, handler, options) {
|
||||
delete (path, handler, options) {
|
||||
// this extension will have a default service
|
||||
this.ensure_service_();
|
||||
|
||||
@@ -283,7 +283,7 @@ class Extension extends AdvancedBase {
|
||||
});
|
||||
}
|
||||
|
||||
use(...args) {
|
||||
use (...args) {
|
||||
this.ensure_service_();
|
||||
this.service.expressThings_.push({
|
||||
type: 'router',
|
||||
@@ -291,12 +291,12 @@ class Extension extends AdvancedBase {
|
||||
});
|
||||
}
|
||||
|
||||
get preinit() {
|
||||
return (function(callback) {
|
||||
get preinit () {
|
||||
return (function (callback) {
|
||||
this.on('preinit', callback);
|
||||
}).bind(this);
|
||||
}
|
||||
set preinit(callback) {
|
||||
set preinit (callback) {
|
||||
if ( this.only_one_preinit_fn === null ) {
|
||||
this.on('preinit', (...a) => {
|
||||
this.only_one_preinit_fn(...a);
|
||||
@@ -309,12 +309,12 @@ class Extension extends AdvancedBase {
|
||||
this.only_one_preinit_fn = callback;
|
||||
}
|
||||
|
||||
get init() {
|
||||
return (function(callback) {
|
||||
get init () {
|
||||
return (function (callback) {
|
||||
this.on('init', callback);
|
||||
}).bind(this);
|
||||
}
|
||||
set init(callback) {
|
||||
set init (callback) {
|
||||
if ( this.only_one_init_fn === null ) {
|
||||
this.on('init', (...a) => {
|
||||
this.only_one_init_fn(...a);
|
||||
@@ -327,7 +327,7 @@ class Extension extends AdvancedBase {
|
||||
this.only_one_init_fn = callback;
|
||||
}
|
||||
|
||||
get console() {
|
||||
get console () {
|
||||
const extensionConsole = Object.create(console);
|
||||
const logfn = level => (...a) => {
|
||||
let svc_log;
|
||||
@@ -368,7 +368,7 @@ class Extension extends AdvancedBase {
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
ensure_service_() {
|
||||
ensure_service_ () {
|
||||
if ( this.service ) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const { AdvancedBase } = require("@heyputer/putility");
|
||||
const { AdvancedBase } = require('@heyputer/putility');
|
||||
const uuid = require('uuid');
|
||||
const { ExtensionService } = require("./ExtensionService");
|
||||
const { ExtensionService } = require('./ExtensionService');
|
||||
|
||||
class ExtensionModule extends AdvancedBase {
|
||||
async install (context) {
|
||||
const services = context.get('services');
|
||||
|
||||
|
||||
this.extension.name = this.extension.name ?? context.name;
|
||||
this.extension.emit('install', { context, services });
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ const { Actor } = require('./services/auth/Actor');
|
||||
* future) to the default service.
|
||||
*/
|
||||
class ExtensionServiceState extends AdvancedBase {
|
||||
constructor(...a) {
|
||||
constructor (...a) {
|
||||
super(...a);
|
||||
|
||||
this.extension = a[0].extension;
|
||||
@@ -41,7 +41,7 @@ class ExtensionServiceState extends AdvancedBase {
|
||||
// Values shared between the `extension` global and its service
|
||||
this.values = new Context();
|
||||
}
|
||||
register_route_handler_(path, handler, options = {}) {
|
||||
register_route_handler_ (path, handler, options = {}) {
|
||||
// handler and options may be flipped
|
||||
if ( typeof handler === 'object' ) {
|
||||
[handler, options] = [options, handler];
|
||||
@@ -77,10 +77,10 @@ class ExtensionServiceState extends AdvancedBase {
|
||||
* provide a default service for extensions.
|
||||
*/
|
||||
class ExtensionService extends BaseService {
|
||||
_construct() {
|
||||
_construct () {
|
||||
this.expressThings_ = [];
|
||||
}
|
||||
async _init(args) {
|
||||
async _init (args) {
|
||||
this.state = args.state;
|
||||
|
||||
this.state.values.set('services', this.services);
|
||||
@@ -153,7 +153,7 @@ class ExtensionService extends BaseService {
|
||||
this.state.extension.emit('preinit');
|
||||
}
|
||||
|
||||
async ['__on_boot.consolidation'](...a) {
|
||||
async ['__on_boot.consolidation'] (...a) {
|
||||
const svc_su = this.services.get('su');
|
||||
await svc_su.sudo(async () => {
|
||||
await this.state.extension.emit('init', {}, {
|
||||
@@ -161,7 +161,7 @@ class ExtensionService extends BaseService {
|
||||
});
|
||||
});
|
||||
}
|
||||
async ['__on_boot.activation'](...a) {
|
||||
async ['__on_boot.activation'] (...a) {
|
||||
const svc_su = this.services.get('su');
|
||||
await svc_su.sudo(async () => {
|
||||
await this.state.extension.emit('activate', {}, {
|
||||
@@ -169,7 +169,7 @@ class ExtensionService extends BaseService {
|
||||
});
|
||||
});
|
||||
}
|
||||
async ['__on_boot.ready'](...a) {
|
||||
async ['__on_boot.ready'] (...a) {
|
||||
const svc_su = this.services.get('su');
|
||||
await svc_su.sudo(async () => {
|
||||
await this.state.extension.emit('ready', {}, {
|
||||
@@ -178,7 +178,7 @@ class ExtensionService extends BaseService {
|
||||
});
|
||||
}
|
||||
|
||||
['__on_install.routes'](_, { app }) {
|
||||
['__on_install.routes'] (_, { app }) {
|
||||
if ( ! this.state ) debugger;
|
||||
for ( const thing of this.state.expressThings_ ) {
|
||||
if ( thing.type === 'endpoint' ) {
|
||||
|
||||
@@ -16,25 +16,25 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { AdvancedBase, libs } = require("@heyputer/putility");
|
||||
const { AdvancedBase, libs } = require('@heyputer/putility');
|
||||
const { Context } = require('./util/context');
|
||||
const BaseService = require("./services/BaseService");
|
||||
const BaseService = require('./services/BaseService');
|
||||
const useapi = require('useapi');
|
||||
const yargs = require('yargs/yargs')
|
||||
const yargs = require('yargs/yargs');
|
||||
const { hideBin } = require('yargs/helpers');
|
||||
const { Extension } = require("./Extension");
|
||||
const { ExtensionModule } = require("./ExtensionModule");
|
||||
const { spawn } = require("node:child_process");
|
||||
const { Extension } = require('./Extension');
|
||||
const { ExtensionModule } = require('./ExtensionModule');
|
||||
const { spawn } = require('node:child_process');
|
||||
|
||||
const fs = require('fs');
|
||||
const path_ = require('path');
|
||||
const { prependToJSFiles } = require("./kernel/modutil");
|
||||
const { prependToJSFiles } = require('./kernel/modutil');
|
||||
|
||||
const uuid = require('uuid');
|
||||
const readline = require("node:readline/promises");
|
||||
const { RuntimeModuleRegistry } = require("./extension/RuntimeModuleRegistry");
|
||||
const { RuntimeModule } = require("./extension/RuntimeModule");
|
||||
const deep_proto_merge = require("./config/deep_proto_merge");
|
||||
const readline = require('node:readline/promises');
|
||||
const { RuntimeModuleRegistry } = require('./extension/RuntimeModuleRegistry');
|
||||
const { RuntimeModule } = require('./extension/RuntimeModule');
|
||||
const deep_proto_merge = require('./config/deep_proto_merge');
|
||||
|
||||
const { quot } = libs.string;
|
||||
|
||||
@@ -54,7 +54,7 @@ class Kernel extends AdvancedBase {
|
||||
this.extensionExports = {};
|
||||
this.extensionInfo = {};
|
||||
this.registry = {};
|
||||
|
||||
|
||||
this.runtimeModuleRegistry = new RuntimeModuleRegistry();
|
||||
}
|
||||
|
||||
@@ -90,17 +90,18 @@ class Kernel extends AdvancedBase {
|
||||
}
|
||||
|
||||
boot () {
|
||||
const args = yargs(hideBin(process.argv)).argv
|
||||
const args = yargs(hideBin(process.argv)).argv;
|
||||
|
||||
this._runtime_init({ args });
|
||||
|
||||
const config = require('./config');
|
||||
|
||||
globalThis.ll = o => o;
|
||||
globalThis.xtra_log = () => {};
|
||||
globalThis.xtra_log = () => {
|
||||
};
|
||||
if ( config.env === 'dev' ) {
|
||||
globalThis.ll = o => {
|
||||
console.log('debug: ' + require('node:util').inspect(o));
|
||||
console.log(`debug: ${ require('node:util').inspect(o)}`);
|
||||
return o;
|
||||
};
|
||||
globalThis.xtra_log = (...args) => {
|
||||
@@ -108,8 +109,8 @@ class Kernel extends AdvancedBase {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const log_path = path.join('/tmp/xtra_log.txt');
|
||||
fs.appendFileSync(log_path, args.join(' ') + '\n');
|
||||
}
|
||||
fs.appendFileSync(log_path, `${args.join(' ') }\n`);
|
||||
};
|
||||
}
|
||||
|
||||
const { consoleLogManager } = require('./util/consolelog');
|
||||
@@ -140,7 +141,6 @@ class Kernel extends AdvancedBase {
|
||||
await this._boot_services();
|
||||
});
|
||||
|
||||
|
||||
Error.stackTraceLimit = 200;
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ class Kernel extends AdvancedBase {
|
||||
});
|
||||
await module_.install(mod_context);
|
||||
}
|
||||
|
||||
|
||||
for ( const k in services.instances_ ) {
|
||||
const service_exports = new RuntimeModule({ name: `service:${k}` });
|
||||
this.runtimeModuleRegistry.register(service_exports);
|
||||
@@ -188,10 +188,8 @@ class Kernel extends AdvancedBase {
|
||||
throw e;
|
||||
}
|
||||
|
||||
await svc_systemValidation.mark_invalid(
|
||||
'failed to initialize services',
|
||||
e,
|
||||
);
|
||||
await svc_systemValidation.mark_invalid('failed to initialize services',
|
||||
e);
|
||||
}
|
||||
|
||||
for ( const module of this.modules ) {
|
||||
@@ -228,24 +226,24 @@ class Kernel extends AdvancedBase {
|
||||
}
|
||||
|
||||
async install_extern_mods_ () {
|
||||
|
||||
|
||||
// In runtime directory, we'll create a `mod_packages` directory.`
|
||||
if ( fs.existsSync('mod_packages') ) {
|
||||
fs.rmSync('mod_packages', { recursive: true, force: true });
|
||||
}
|
||||
fs.mkdirSync('mod_packages');
|
||||
|
||||
|
||||
// Initialize some globals that external mods depend on
|
||||
globalThis.__puter_extension_globals__ = {
|
||||
extensionObjectRegistry: {},
|
||||
useapi: this.useapi,
|
||||
global_config: require('./config'),
|
||||
};
|
||||
|
||||
|
||||
// Install the mods...
|
||||
|
||||
|
||||
const mod_install_root_context = Context.get();
|
||||
|
||||
|
||||
const mod_directory_promises = [];
|
||||
const mod_installation_promises = [];
|
||||
|
||||
@@ -253,9 +251,7 @@ class Kernel extends AdvancedBase {
|
||||
for ( const mods_dirpath of mod_paths ) {
|
||||
const p = (async () => {
|
||||
if ( ! fs.existsSync(mods_dirpath) ) {
|
||||
this.services.logger.error(
|
||||
`mod directory not found: ${quot(mods_dirpath)}; skipping...`
|
||||
);
|
||||
this.services.logger.error(`mod directory not found: ${quot(mods_dirpath)}; skipping...`);
|
||||
// intentional delay so error is seen
|
||||
this.services.logger.info('boot will continue in 4 seconds');
|
||||
await new Promise(rslv => setTimeout(rslv, 4000));
|
||||
@@ -266,7 +262,7 @@ class Kernel extends AdvancedBase {
|
||||
const ignoreList = new Set([
|
||||
'.git',
|
||||
]);
|
||||
|
||||
|
||||
for ( const mod_dirname of mod_dirnames ) {
|
||||
if ( ignoreList.has(mod_dirname) ) continue;
|
||||
mod_installation_promises.push(this.install_extern_mod_({
|
||||
@@ -279,31 +275,31 @@ class Kernel extends AdvancedBase {
|
||||
if ( process.env.SYNC_MOD_INSTALL ) await p;
|
||||
mod_directory_promises.push(p);
|
||||
}
|
||||
|
||||
|
||||
await Promise.all(mod_directory_promises);
|
||||
|
||||
|
||||
const mods_to_run = (await Promise.all(mod_installation_promises))
|
||||
.filter(v => v !== undefined);
|
||||
mods_to_run.sort((a, b) => a.priority - b.priority);
|
||||
let i = 0;
|
||||
while (i < mods_to_run.length) {
|
||||
while ( i < mods_to_run.length ) {
|
||||
const currentPriority = mods_to_run[i].priority;
|
||||
const samePriorityMods = [];
|
||||
|
||||
|
||||
// Collect all mods with the same priority
|
||||
while (i < mods_to_run.length && mods_to_run[i].priority === currentPriority) {
|
||||
while ( i < mods_to_run.length && mods_to_run[i].priority === currentPriority ) {
|
||||
samePriorityMods.push(mods_to_run[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
|
||||
// Run all mods with the same priority concurrently
|
||||
await Promise.all(samePriorityMods.map(mod_entry => {
|
||||
return this._run_extern_mod(mod_entry);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
async install_extern_mod_({
|
||||
|
||||
async install_extern_mod_ ({
|
||||
mod_install_root_context,
|
||||
mod_dirname,
|
||||
mod_path,
|
||||
@@ -315,19 +311,19 @@ class Kernel extends AdvancedBase {
|
||||
}
|
||||
|
||||
// Mod must be a directory or javascript file
|
||||
if ( ! stat.isDirectory() && !(mod_path.endsWith('.js')) ) {
|
||||
if ( !stat.isDirectory() && !(mod_path.endsWith('.js')) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let mod_name = path_.parse(mod_path).name;
|
||||
const mod_package_dir = `mod_packages/${mod_name}`;
|
||||
fs.mkdirSync(mod_package_dir);
|
||||
|
||||
|
||||
const mod_entry = {
|
||||
priority: 0,
|
||||
jsons: {},
|
||||
};
|
||||
|
||||
|
||||
if ( ! stat.isDirectory() ) {
|
||||
const rl = readline.createInterface({
|
||||
input: fs.createReadStream(mod_path),
|
||||
@@ -340,7 +336,7 @@ class Kernel extends AdvancedBase {
|
||||
mod_entry.priority = Number(tokens[2]);
|
||||
}
|
||||
if ( tokens[1] === 'name' ) {
|
||||
mod_name = '' + tokens[2];
|
||||
mod_name = `${ tokens[2]}`;
|
||||
}
|
||||
}
|
||||
mod_entry.jsons.package = await this.create_mod_package_json(mod_package_dir, {
|
||||
@@ -354,7 +350,7 @@ class Kernel extends AdvancedBase {
|
||||
this.bootLogger.warn(`Empty mod directory ${quot(mod_path)}; skipping...`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const promises = [];
|
||||
|
||||
// Create package.json if it doesn't exist
|
||||
@@ -369,7 +365,7 @@ class Kernel extends AdvancedBase {
|
||||
mod_entry.jsons.package = JSON.parse(str);
|
||||
}
|
||||
})());
|
||||
|
||||
|
||||
const puter_json_path = path_.join(mod_path, 'puter.json');
|
||||
if ( fs.existsSync(puter_json_path) ) {
|
||||
promises.push((async () => {
|
||||
@@ -391,45 +387,45 @@ class Kernel extends AdvancedBase {
|
||||
mod_entry.jsons.config = obj;
|
||||
})());
|
||||
}
|
||||
|
||||
|
||||
// Copy mod contents to `/mod_packages`
|
||||
promises.push(fs.promises.cp(mod_path, mod_package_dir, {
|
||||
recursive: true,
|
||||
}));
|
||||
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
|
||||
mod_entry.priority = mod_entry.jsons.puter?.priority ?? mod_entry.priority;
|
||||
|
||||
|
||||
const extension_id = uuid.v4();
|
||||
|
||||
await prependToJSFiles(mod_package_dir, [
|
||||
`const { use, def } = globalThis.__puter_extension_globals__.useapi;`,
|
||||
`const { use: puter } = globalThis.__puter_extension_globals__.useapi;`,
|
||||
`const extension = globalThis.__puter_extension_globals__` +
|
||||
`.extensionObjectRegistry[${JSON.stringify(extension_id)}];`,
|
||||
`const console = extension.console;`,
|
||||
`const runtime = extension.runtime;`,
|
||||
`const config = extension.config;`,
|
||||
`const registry = extension.registry;`,
|
||||
`const register = registry.register;`,
|
||||
`const global_config = globalThis.__puter_extension_globals__.global_config`,
|
||||
].join('\n') + '\n');
|
||||
|
||||
await prependToJSFiles(mod_package_dir, `${[
|
||||
'const { use, def } = globalThis.__puter_extension_globals__.useapi;',
|
||||
'const { use: puter } = globalThis.__puter_extension_globals__.useapi;',
|
||||
'const extension = globalThis.__puter_extension_globals__' +
|
||||
`.extensionObjectRegistry[${JSON.stringify(extension_id)}];`,
|
||||
'const console = extension.console;',
|
||||
'const runtime = extension.runtime;',
|
||||
'const config = extension.config;',
|
||||
'const registry = extension.registry;',
|
||||
'const register = registry.register;',
|
||||
'const global_config = globalThis.__puter_extension_globals__.global_config',
|
||||
].join('\n') }\n`);
|
||||
|
||||
mod_entry.require_dir = path_.join(process.cwd(), mod_package_dir);
|
||||
|
||||
|
||||
await this.run_npm_install(mod_entry.require_dir);
|
||||
|
||||
|
||||
const mod = new ExtensionModule();
|
||||
mod.extension = new Extension();
|
||||
|
||||
|
||||
const runtimeModule = new RuntimeModule({ name: mod_name });
|
||||
this.runtimeModuleRegistry.register(runtimeModule);
|
||||
mod.extension.runtime = runtimeModule;
|
||||
|
||||
mod_entry.module = mod;
|
||||
|
||||
|
||||
globalThis.__puter_extension_globals__.extensionObjectRegistry[extension_id]
|
||||
= mod.extension;
|
||||
|
||||
@@ -439,23 +435,23 @@ class Kernel extends AdvancedBase {
|
||||
external: true,
|
||||
mod_path,
|
||||
});
|
||||
|
||||
|
||||
mod_entry.context = mod_context;
|
||||
|
||||
return mod_entry;
|
||||
};
|
||||
|
||||
async _run_extern_mod(mod_entry) {
|
||||
|
||||
async _run_extern_mod (mod_entry) {
|
||||
let exportObject = null;
|
||||
|
||||
|
||||
const {
|
||||
module: mod,
|
||||
require_dir,
|
||||
context,
|
||||
} = mod_entry;
|
||||
|
||||
|
||||
const packageJSON = mod_entry.jsons.package;
|
||||
|
||||
|
||||
Object.defineProperty(mod.extension, 'config', {
|
||||
get: () => {
|
||||
const builtin_config = mod_entry.jsons.config ?? {};
|
||||
@@ -463,9 +459,9 @@ class Kernel extends AdvancedBase {
|
||||
return deep_proto_merge(user_config, builtin_config);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
mod.extension.name = packageJSON.name;
|
||||
|
||||
|
||||
const maybe_promise = (typ => typ.trim().toLowerCase())(packageJSON.type ?? '') === 'module'
|
||||
? await import(path_.join(require_dir, packageJSON.main ?? 'index.js'))
|
||||
: require(require_dir);
|
||||
@@ -473,7 +469,7 @@ class Kernel extends AdvancedBase {
|
||||
if ( maybe_promise && maybe_promise instanceof Promise ) {
|
||||
exportObject = await maybe_promise;
|
||||
} else exportObject = maybe_promise;
|
||||
|
||||
|
||||
const extension_name = exportObject?.name ?? packageJSON.name;
|
||||
this.extensionExports[extension_name] = exportObject;
|
||||
this.extensionInfo[extension_name] = {
|
||||
@@ -483,7 +479,7 @@ class Kernel extends AdvancedBase {
|
||||
};
|
||||
mod.extension.registry = this.registry;
|
||||
mod.extension.name = extension_name;
|
||||
|
||||
|
||||
if ( exportObject.construct ) {
|
||||
mod.extension.on('construct', exportObject.construct);
|
||||
}
|
||||
@@ -503,7 +499,7 @@ class Kernel extends AdvancedBase {
|
||||
const modapi = {};
|
||||
|
||||
let mod_path = options.mod_path;
|
||||
if ( ! mod_path && options.module.dirname ) {
|
||||
if ( !mod_path && options.module.dirname ) {
|
||||
mod_path = options.module.dirname();
|
||||
}
|
||||
|
||||
@@ -531,13 +527,13 @@ class Kernel extends AdvancedBase {
|
||||
// getter-like behavior to useapi.
|
||||
this.useapi.def(`${prefix}.${name}`, lib);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
const mod_context = parent.sub({ modapi }, `mod:${options.name}`);
|
||||
return mod_context;
|
||||
|
||||
}
|
||||
|
||||
|
||||
async create_mod_package_json (mod_path, { name, entry }) {
|
||||
// Expect main.js or index.js to exist
|
||||
const options = ['main.js', 'index.js'];
|
||||
@@ -556,7 +552,7 @@ class Kernel extends AdvancedBase {
|
||||
if ( ! entry ) {
|
||||
this.bootLogger.error(`Expected main.js or index.js in ${quot(mod_path)}`);
|
||||
if ( ! process.env.SKIP_INVALID_MODS ) {
|
||||
this.bootLogger.error(`Set SKIP_INVALID_MODS=1 (environment variable) to run anyway.`);
|
||||
this.bootLogger.error('Set SKIP_INVALID_MODS=1 (environment variable) to run anyway.');
|
||||
process.exit(1);
|
||||
} else {
|
||||
return;
|
||||
@@ -569,16 +565,16 @@ class Kernel extends AdvancedBase {
|
||||
main: entry ?? 'main.js',
|
||||
};
|
||||
const data_json = JSON.stringify(data);
|
||||
|
||||
this.bootLogger.debug('WRITING TO: ' + path_.join(mod_path, 'package.json'));
|
||||
|
||||
|
||||
this.bootLogger.debug(`WRITING TO: ${ path_.join(mod_path, 'package.json')}`);
|
||||
|
||||
await fs.promises.writeFile(path_.join(mod_path, 'package.json'), data_json);
|
||||
return data;
|
||||
}
|
||||
|
||||
async run_npm_install (path) {
|
||||
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
||||
const proc = spawn(npmCmd, ["install"], { cwd: path, stdio: "pipe" });
|
||||
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
const proc = spawn(npmCmd, ['install'], { cwd: path, stdio: 'pipe' });
|
||||
|
||||
let buffer = '';
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { AdvancedBase } = require("@heyputer/putility");
|
||||
const { AdvancedBase } = require('@heyputer/putility');
|
||||
|
||||
class LocalDiskStorageModule extends AdvancedBase {
|
||||
async install (context) {
|
||||
const services = context.get('services');
|
||||
const LocalDiskStorageService = require("./services/LocalDiskStorageService");
|
||||
const LocalDiskStorageService = require('./services/LocalDiskStorageService');
|
||||
services.registerService('local-disk-storage', LocalDiskStorageService);
|
||||
|
||||
const HostDiskUsageService = require('./services/HostDiskUsageService');
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
class MemoryStorageModule {
|
||||
async install (context) {
|
||||
const services = context.get('services');
|
||||
const MemoryStorageService = require("./services/MemoryStorageService");
|
||||
const MemoryStorageService = require('./services/MemoryStorageService');
|
||||
services.registerService('memory-storage', MemoryStorageService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { AdvancedBase } = require("@heyputer/putility");
|
||||
const { AdvancedBase } = require('@heyputer/putility');
|
||||
|
||||
class ThirdPartyDriversModule extends AdvancedBase {
|
||||
// constructor () {
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
|
||||
// These annotated classes provide a solution to wrap plain objects.
|
||||
|
||||
|
||||
class AnnotatedObject {
|
||||
constructor (o) {
|
||||
for ( const k in o ) this[k] = o[k];
|
||||
}
|
||||
}
|
||||
|
||||
class object_returned_by_get_app extends AnnotatedObject {};
|
||||
class object_returned_by_get_app extends AnnotatedObject {
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
object_returned_by_get_app,
|
||||
|
||||
@@ -23,7 +23,7 @@ const _path = require('path');
|
||||
* PathOrUIDValidator validates that either `path` or `uid` is present
|
||||
* in the request and requires a valid value for the parameter that was
|
||||
* used. Additionally, resolves the path if a path was provided.
|
||||
*
|
||||
*
|
||||
* @class PathOrUIDValidator
|
||||
* @static
|
||||
* @throws {APIError} if `path` and `uid` are both missing
|
||||
@@ -37,20 +37,30 @@ module.exports = class PathOrUIDValidator {
|
||||
const params = req.method === 'GET'
|
||||
? req.query : req.body ;
|
||||
|
||||
if(!params.path && !params.uid)
|
||||
if ( !params.path && !params.uid )
|
||||
{
|
||||
throw new APIError(400, '`path` or `uid` must be provided.');
|
||||
}
|
||||
// `path` must be a string
|
||||
else if (params.path && !params.uid && typeof params.path !== 'string')
|
||||
else if ( params.path && !params.uid && typeof params.path !== 'string' )
|
||||
{
|
||||
throw new APIError(400, '`path` must be a string.');
|
||||
}
|
||||
// `path` cannot be empty
|
||||
else if(params.path && !params.uid && params.path.trim() === '')
|
||||
else if ( params.path && !params.uid && params.path.trim() === '' )
|
||||
{
|
||||
throw new APIError(400, '`path` cannot be empty');
|
||||
}
|
||||
// `uid` must be a valid uuid
|
||||
else if(params.uid && !params.path && !require('uuid').validate(params.uid))
|
||||
else if ( params.uid && !params.path && !require('uuid').validate(params.uid) )
|
||||
{
|
||||
throw new APIError(400, '`uid` must be a valid uuid');
|
||||
}
|
||||
|
||||
// resolve path if provided
|
||||
if(params.path)
|
||||
if ( params.path )
|
||||
{
|
||||
params.path = _path.resolve('/', params.path);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -16,63 +16,63 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const APIError = require("./APIError");
|
||||
const APIError = require('./APIError');
|
||||
|
||||
/**
|
||||
* api_error_handler() is an express error handler for API errors.
|
||||
* It adheres to the express error handler signature and should be
|
||||
* used as the last middleware in an express app.
|
||||
*
|
||||
*
|
||||
* Since Express 5 is not yet released, this function is used by
|
||||
* eggspress() to handle errors instead of as a middleware.
|
||||
*
|
||||
*
|
||||
* @todo remove this function and use express error handling
|
||||
* when Express 5 is released
|
||||
*
|
||||
* @param {*} err
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
* @param {*} next
|
||||
* @returns
|
||||
*
|
||||
* @param {*} err
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
* @param {*} next
|
||||
* @returns
|
||||
*/
|
||||
module.exports = function (err, req, res, next) {
|
||||
if (res.headersSent) {
|
||||
console.error('error after headers were sent:', err);
|
||||
return next(err)
|
||||
}
|
||||
if ( res.headersSent ) {
|
||||
console.error('error after headers were sent:', err);
|
||||
return next(err);
|
||||
}
|
||||
|
||||
// API errors might have a response to help the
|
||||
// developer resolve the issue.
|
||||
if ( err instanceof APIError ) {
|
||||
return err.write(res);
|
||||
}
|
||||
// API errors might have a response to help the
|
||||
// developer resolve the issue.
|
||||
if ( err instanceof APIError ) {
|
||||
return err.write(res);
|
||||
}
|
||||
|
||||
if (
|
||||
typeof err === 'object' &&
|
||||
! (err instanceof Error) &&
|
||||
err.hasOwnProperty('message')
|
||||
) {
|
||||
const apiError = APIError.create(400, err);
|
||||
return apiError.write(res);
|
||||
}
|
||||
if (
|
||||
typeof err === 'object' &&
|
||||
!(err instanceof Error) &&
|
||||
err.hasOwnProperty('message')
|
||||
) {
|
||||
const apiError = APIError.create(400, err);
|
||||
return apiError.write(res);
|
||||
}
|
||||
|
||||
console.error('internal server error:', err);
|
||||
console.error('internal server error:', err);
|
||||
|
||||
const services = globalThis.services;
|
||||
if ( services && services.has('alarm') ) {
|
||||
const alarm = services.get('alarm');
|
||||
alarm.create('api_error_handler', err.message, {
|
||||
error: err,
|
||||
url: req.url,
|
||||
method: req.method,
|
||||
body: req.body,
|
||||
headers: req.headers,
|
||||
});
|
||||
}
|
||||
const services = globalThis.services;
|
||||
if ( services && services.has('alarm') ) {
|
||||
const alarm = services.get('alarm');
|
||||
alarm.create('api_error_handler', err.message, {
|
||||
error: err,
|
||||
url: req.url,
|
||||
method: req.method,
|
||||
body: req.body,
|
||||
headers: req.headers,
|
||||
});
|
||||
}
|
||||
|
||||
req.__error_handled = true;
|
||||
|
||||
// Other errors should provide as little information
|
||||
// to the client as possible for security reasons.
|
||||
return res.send(500, 'Internal Server Error');
|
||||
req.__error_handled = true;
|
||||
|
||||
// Other errors should provide as little information
|
||||
// to the client as possible for security reasons.
|
||||
return res.send(500, 'Internal Server Error');
|
||||
};
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { is_valid_path } = require("../../filesystem/validation");
|
||||
const { is_valid_uuid4 } = require("../../helpers");
|
||||
const { Context } = require("../../util/context");
|
||||
const { PathBuilder } = require("../../util/pathutil");
|
||||
const APIError = require("../APIError");
|
||||
const { is_valid_path } = require('../../filesystem/validation');
|
||||
const { is_valid_uuid4 } = require('../../helpers');
|
||||
const { Context } = require('../../util/context');
|
||||
const { PathBuilder } = require('../../util/pathutil');
|
||||
const APIError = require('../APIError');
|
||||
const _path = require('path');
|
||||
|
||||
module.exports = class FSNodeParam {
|
||||
@@ -49,12 +49,12 @@ module.exports = class FSNodeParam {
|
||||
});
|
||||
}
|
||||
|
||||
if ( ! ['/','.','~'].includes(uidOrPath[0]) ) {
|
||||
if ( ! ['/', '.', '~'].includes(uidOrPath[0]) ) {
|
||||
if ( is_valid_uuid4(uidOrPath) ) {
|
||||
return await fs.node({ uid: uidOrPath });
|
||||
}
|
||||
|
||||
log.debug('tried uuid', { uidOrPath })
|
||||
log.debug('tried uuid', { uidOrPath });
|
||||
throw APIError.create('field_invalid', null, {
|
||||
key: this.srckey,
|
||||
expected: 'unix-style path or uuid4',
|
||||
@@ -67,7 +67,7 @@ module.exports = class FSNodeParam {
|
||||
}
|
||||
|
||||
if ( ! is_valid_path(uidOrPath) ) {
|
||||
log.debug('tried path', { uidOrPath })
|
||||
log.debug('tried path', { uidOrPath });
|
||||
throw APIError.create('field_invalid', null, {
|
||||
key: this.srckey,
|
||||
expected: 'unix-style path or uuid4',
|
||||
@@ -77,4 +77,4 @@ module.exports = class FSNodeParam {
|
||||
const resolved_path = PathBuilder.resolve(uidOrPath, { puterfs: true });
|
||||
return await fs.node({ path: resolved_path });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -56,10 +56,10 @@ module.exports = class FlagParam {
|
||||
return value;
|
||||
}
|
||||
|
||||
log.debug('tried boolean', { value })
|
||||
log.debug('tried boolean', { value });
|
||||
throw APIError.create('field_invalid', null, {
|
||||
key: this.srckey,
|
||||
expected: 'boolean',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -44,7 +44,7 @@ module.exports = class StringParam {
|
||||
}
|
||||
|
||||
if ( typeof value !== 'string' ) {
|
||||
log.debug('tried string', { value })
|
||||
log.debug('tried string', { value });
|
||||
throw APIError.create('field_invalid', null, {
|
||||
key: this.srckey,
|
||||
expected: 'string',
|
||||
@@ -53,4 +53,4 @@ module.exports = class StringParam {
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,4 +20,4 @@ module.exports = class UserParam {
|
||||
consolidate ({ req }) {
|
||||
return req.user;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -17,4 +17,4 @@ require('dotenv').config();
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
"use strict"
|
||||
'use strict';
|
||||
|
||||
@@ -18,26 +18,20 @@
|
||||
*/
|
||||
class BootLogger {
|
||||
info (...args) {
|
||||
console.log(
|
||||
'\x1B[36;1m[BOOT/INFO]\x1B[0m',
|
||||
...args,
|
||||
);
|
||||
console.log('\x1B[36;1m[BOOT/INFO]\x1B[0m',
|
||||
...args);
|
||||
}
|
||||
debug (...args) {
|
||||
if ( ! process.env.DEBUG ) return;
|
||||
console.log('\x1B[37m[BOOT/DEBUG]', ...args, '\x1B[0m');
|
||||
}
|
||||
error (...args) {
|
||||
console.log(
|
||||
'\x1B[31;1m[BOOT/ERROR]\x1B[0m',
|
||||
...args,
|
||||
);
|
||||
console.log('\x1B[31;1m[BOOT/ERROR]\x1B[0m',
|
||||
...args);
|
||||
}
|
||||
warn (...args) {
|
||||
console.log(
|
||||
'\x1B[33;1m[BOOT/WARN]\x1B[0m',
|
||||
...args,
|
||||
);
|
||||
console.log('\x1B[33;1m[BOOT/WARN]\x1B[0m',
|
||||
...args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { AdvancedBase } = require("@heyputer/putility");
|
||||
const { AdvancedBase } = require('@heyputer/putility');
|
||||
const { quot } = require('@heyputer/putility').libs.string;
|
||||
const { TechnicalError } = require("../errors/TechnicalError");
|
||||
const { print_error_help } = require("../errors/error_help_details");
|
||||
const default_config = require("./default_config");
|
||||
const config = require("../config");
|
||||
const { ConfigLoader } = require("../config/ConfigLoader");
|
||||
const { TechnicalError } = require('../errors/TechnicalError');
|
||||
const { print_error_help } = require('../errors/error_help_details');
|
||||
const default_config = require('./default_config');
|
||||
const config = require('../config');
|
||||
const { ConfigLoader } = require('../config/ConfigLoader');
|
||||
|
||||
// highlights a string
|
||||
const hl = s => `\x1b[33;1m${s}\x1b[0m`;
|
||||
@@ -48,7 +48,7 @@ const path_checks = ({ logger }) => ({ fs, path_ }) => ({
|
||||
if ( path == undefined ) return false;
|
||||
|
||||
const exists = fs.existsSync(path);
|
||||
if ( !exists ) {
|
||||
if ( ! exists ) {
|
||||
throw new Error(`Path does not exist: ${path}`);
|
||||
}
|
||||
|
||||
@@ -93,8 +93,8 @@ const path_checks = ({ logger }) => ({ fs, path_ }) => ({
|
||||
throw new Error(`No valid config file found in path: ${path}`);
|
||||
},
|
||||
env_not_set: name => () => {
|
||||
return ! process.env[name];
|
||||
}
|
||||
return !process.env[name];
|
||||
},
|
||||
});
|
||||
|
||||
// Configuration paths in order of precedence.
|
||||
@@ -102,7 +102,9 @@ const path_checks = ({ logger }) => ({ fs, path_ }) => ({
|
||||
const config_paths = ({ path_checks }) => ({ path_ }) => [
|
||||
{
|
||||
label: '$CONFIG_PATH',
|
||||
get path () { return process.env.CONFIG_PATH },
|
||||
get path () {
|
||||
return process.env.CONFIG_PATH;
|
||||
},
|
||||
checks: [
|
||||
path_checks.require_if_not_undefined,
|
||||
],
|
||||
@@ -135,7 +137,9 @@ const valid_config_names = [
|
||||
const runtime_paths = ({ path_checks }) => ({ path_ }) => [
|
||||
{
|
||||
label: '$RUNTIME_PATH',
|
||||
get path () { return process.env.RUNTIME_PATH },
|
||||
get path () {
|
||||
return process.env.RUNTIME_PATH;
|
||||
},
|
||||
checks: [
|
||||
path_checks.require_if_not_undefined,
|
||||
],
|
||||
@@ -165,7 +169,9 @@ const runtime_paths = ({ path_checks }) => ({ path_ }) => [
|
||||
const mod_paths = ({ path_checks, entry_path }) => ({ path_ }) => [
|
||||
{
|
||||
label: '$MOD_PATH',
|
||||
get path () { return process.env.MOD_PATH },
|
||||
get path () {
|
||||
return process.env.MOD_PATH;
|
||||
},
|
||||
checks: [
|
||||
path_checks.require_if_not_undefined,
|
||||
],
|
||||
@@ -179,8 +185,7 @@ const mod_paths = ({ path_checks, entry_path }) => ({ path_ }) => [
|
||||
},
|
||||
{
|
||||
get path () {
|
||||
return path_.join(path_.dirname(
|
||||
entry_path || require.main.filename), '../mods');
|
||||
return path_.join(path_.dirname(entry_path || require.main.filename), '../mods');
|
||||
},
|
||||
checks: [ path_checks.skip_if_not_exists ],
|
||||
},
|
||||
@@ -192,7 +197,7 @@ class RuntimeEnvironment extends AdvancedBase {
|
||||
path_: require('node:path'),
|
||||
crypto: require('node:crypto'),
|
||||
format: require('string-template'),
|
||||
}
|
||||
};
|
||||
|
||||
constructor ({ logger, entry_path, boot_parameters }) {
|
||||
super();
|
||||
@@ -220,35 +225,28 @@ class RuntimeEnvironment extends AdvancedBase {
|
||||
// with some helpful values. A partial-population of this object later
|
||||
// in this function will be used when evaluating configured paths.
|
||||
const environment = {};
|
||||
environment.source = this.modules.path_.dirname(
|
||||
this.entry_path || require.main.filename);
|
||||
environment.source = this.modules.path_.dirname(this.entry_path || require.main.filename);
|
||||
environment.repo = this.modules.path_.dirname(environment.source);
|
||||
|
||||
const config_path_entry = this.get_first_suitable_path_(
|
||||
{ pathFor: 'configuration' },
|
||||
this.config_paths,
|
||||
[
|
||||
this.path_checks.require_read_permission,
|
||||
// this.path_checks.contains_config_file,
|
||||
]
|
||||
);
|
||||
const config_path_entry = this.get_first_suitable_path_({ pathFor: 'configuration' },
|
||||
this.config_paths,
|
||||
[
|
||||
this.path_checks.require_read_permission,
|
||||
// this.path_checks.contains_config_file,
|
||||
]);
|
||||
|
||||
// Note: there used to be a 'mods_path_entry' here too
|
||||
// but it was never used
|
||||
const pwd_path_entry = this.get_first_suitable_path_(
|
||||
{ pathFor: 'working directory' },
|
||||
this.runtime_paths,
|
||||
[ this.path_checks.require_write_permission ]
|
||||
);
|
||||
const pwd_path_entry = this.get_first_suitable_path_({ pathFor: 'working directory' },
|
||||
this.runtime_paths,
|
||||
[ this.path_checks.require_write_permission ]);
|
||||
|
||||
process.chdir(pwd_path_entry.path);
|
||||
|
||||
// Check for a valid config file in the config path
|
||||
let using_config;
|
||||
for ( const name of valid_config_names ) {
|
||||
const exists = this.modules.fs.existsSync(
|
||||
this.modules.path_.join(config_path_entry.path, name)
|
||||
);
|
||||
const exists = this.modules.fs.existsSync(this.modules.path_.join(config_path_entry.path, name));
|
||||
if ( exists ) {
|
||||
using_config = name;
|
||||
break;
|
||||
@@ -266,21 +264,15 @@ class RuntimeEnvironment extends AdvancedBase {
|
||||
generated_values.private_uid_secret = crypto.randomBytes(24).toString('hex');
|
||||
generated_values.private_uid_namespace = crypto.randomUUID();
|
||||
if ( using_config ) {
|
||||
this.logger.debug(
|
||||
`Overwriting ${quot(using_config)} because ` +
|
||||
`${hl('--overwrite-config')} is set`
|
||||
);
|
||||
this.logger.debug(`Overwriting ${quot(using_config)} because ` +
|
||||
`${hl('--overwrite-config')} is set`);
|
||||
// make backup
|
||||
fs.copyFileSync(
|
||||
path_.join(config_path_entry.path, using_config),
|
||||
path_.join(config_path_entry.path, using_config + '.bak'),
|
||||
);
|
||||
fs.copyFileSync(path_.join(config_path_entry.path, using_config),
|
||||
path_.join(config_path_entry.path, `${using_config }.bak`));
|
||||
// preserve generated values
|
||||
{
|
||||
const config_raw = fs.readFileSync(
|
||||
path_.join(config_path_entry.path, using_config),
|
||||
'utf8',
|
||||
);
|
||||
const config_raw = fs.readFileSync(path_.join(config_path_entry.path, using_config),
|
||||
'utf8');
|
||||
const config_values = JSON.parse(config_raw);
|
||||
for ( const k in generated_values ) {
|
||||
if ( ! config_values[k] ) continue;
|
||||
@@ -292,33 +284,25 @@ class RuntimeEnvironment extends AdvancedBase {
|
||||
...default_config,
|
||||
...generated_values,
|
||||
};
|
||||
generated_config[""] = null; // for trailing comma
|
||||
fs.writeFileSync(
|
||||
path_.join(config_path_entry.path, 'config.json'),
|
||||
JSON.stringify(generated_config, null, 4) + '\n',
|
||||
);
|
||||
generated_config[''] = null; // for trailing comma
|
||||
fs.writeFileSync(path_.join(config_path_entry.path, 'config.json'),
|
||||
`${JSON.stringify(generated_config, null, 4) }\n`);
|
||||
using_config = 'config.json';
|
||||
}
|
||||
|
||||
let config_to_load = 'config.json';
|
||||
if ( process.env.PUTER_CONFIG_PROFILE ) {
|
||||
this.logger.debug(
|
||||
hl('PROFILE') + ' ' +
|
||||
quot(process.env.PUTER_CONFIG_PROFILE) + ' ' +
|
||||
`because $PUTER_CONFIG_PROFILE is set`
|
||||
);
|
||||
config_to_load = `${process.env.PUTER_CONFIG_PROFILE}.json`
|
||||
const exists = fs.existsSync(
|
||||
path_.join(config_path_entry.path, config_to_load)
|
||||
);
|
||||
this.logger.debug(`${hl('PROFILE') } ${
|
||||
quot(process.env.PUTER_CONFIG_PROFILE) } ` +
|
||||
'because $PUTER_CONFIG_PROFILE is set');
|
||||
config_to_load = `${process.env.PUTER_CONFIG_PROFILE}.json`;
|
||||
const exists = fs.existsSync(path_.join(config_path_entry.path, config_to_load));
|
||||
if ( ! exists ) {
|
||||
fs.writeFileSync(
|
||||
path_.join(config_path_entry.path, config_to_load),
|
||||
JSON.stringify({
|
||||
config_name: process.env.PUTER_CONFIG_PROFILE,
|
||||
$imports: ['config.json'],
|
||||
}, null, 4) + '\n',
|
||||
);
|
||||
fs.writeFileSync(path_.join(config_path_entry.path, config_to_load),
|
||||
`${JSON.stringify({
|
||||
config_name: process.env.PUTER_CONFIG_PROFILE,
|
||||
$imports: ['config.json'],
|
||||
}, null, 4) }\n`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +314,7 @@ class RuntimeEnvironment extends AdvancedBase {
|
||||
if ( ! config.config_name ) {
|
||||
throw new Error('config_name is required');
|
||||
}
|
||||
this.logger.debug(hl(`config name`) + ` ${quot(config.config_name)}`);
|
||||
this.logger.debug(`${hl('config name') } ${quot(config.config_name)}`);
|
||||
|
||||
const mod_paths = [];
|
||||
environment.mod_paths = mod_paths;
|
||||
@@ -346,9 +330,7 @@ class RuntimeEnvironment extends AdvancedBase {
|
||||
// If configured, add a user-specified mod path
|
||||
if ( config.mod_directories ) {
|
||||
for ( const dir of config.mod_directories ) {
|
||||
const mods_directory = this.modules.format(
|
||||
dir, environment,
|
||||
);
|
||||
const mods_directory = this.modules.format(dir, environment);
|
||||
mod_paths.push(mods_directory);
|
||||
}
|
||||
}
|
||||
@@ -359,30 +341,22 @@ class RuntimeEnvironment extends AdvancedBase {
|
||||
get_first_suitable_path_ (meta, paths, last_checks) {
|
||||
for ( const entry of paths ) {
|
||||
const checks = [...(entry.checks ?? []), ...last_checks];
|
||||
this.logger.debug(
|
||||
`Checking path ${quot(entry.label ?? entry.path)} for ${meta.pathFor}...`
|
||||
);
|
||||
|
||||
this.logger.debug(`Checking path ${quot(entry.label ?? entry.path)} for ${meta.pathFor}...`);
|
||||
|
||||
let checks_pass = true;
|
||||
for ( const check of checks ) {
|
||||
this.logger.debug(
|
||||
`-> doing ${quot(check.name)} on path ${quot(entry.path)}...`
|
||||
);
|
||||
this.logger.debug(`-> doing ${quot(check.name)} on path ${quot(entry.path)}...`);
|
||||
const result = check(entry);
|
||||
if ( result === false ) {
|
||||
this.logger.debug(
|
||||
`-> ${quot(check.name)} doesn't like this path`
|
||||
);
|
||||
this.logger.debug(`-> ${quot(check.name)} doesn't like this path`);
|
||||
checks_pass = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ( ! checks_pass ) continue;
|
||||
|
||||
this.logger.info(
|
||||
`${hl(meta.pathFor)} ${quot(entry.path)}`
|
||||
)
|
||||
this.logger.info(`${hl(meta.pathFor)} ${quot(entry.path)}`);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ module.exports = {
|
||||
path: 'puter-database.sqlite',
|
||||
},
|
||||
thumbnails: {
|
||||
engine: 'purejs'
|
||||
engine: 'purejs',
|
||||
},
|
||||
'file-cache': {
|
||||
disk_limit: 16384,
|
||||
@@ -40,6 +40,6 @@ module.exports = {
|
||||
precache_size: 16384,
|
||||
path: './file-cache',
|
||||
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@ class CodeUtil {
|
||||
async _run () {
|
||||
return await method.call(this.self, this.values);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperty(cls, 'name', { value: cls_name });
|
||||
|
||||
@@ -44,7 +44,7 @@ class CodeUtil {
|
||||
const op = new cls();
|
||||
op.self = this;
|
||||
return await op.run(...a);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
* Supports conditional steps, deferred steps, and can be used as a runnable implementation for classes.
|
||||
* @class @extends Function
|
||||
*/
|
||||
class Sequence {
|
||||
class Sequence {
|
||||
/**
|
||||
* SequenceState represents the state of a Sequence execution.
|
||||
* Provides access to the sequence scope, step control, and utility methods for step functions.
|
||||
@@ -116,7 +116,7 @@ class Sequence {
|
||||
* @param {Sequence|function} sequence - The Sequence instance or its callable function.
|
||||
* @param {Object} [thisArg] - The instance to bind as `this` for step functions.
|
||||
*/
|
||||
constructor(sequence, thisArg) {
|
||||
constructor (sequence, thisArg) {
|
||||
if ( typeof sequence === 'function' ) {
|
||||
sequence = sequence.sequence;
|
||||
}
|
||||
@@ -138,7 +138,7 @@ class Sequence {
|
||||
* Get the current steps array for this sequence execution.
|
||||
* @returns {Array<function|Object>} The steps to execute.
|
||||
*/
|
||||
get steps() {
|
||||
get steps () {
|
||||
return this.steps_ ?? this.sequence_?.steps_;
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ class Sequence {
|
||||
* @param {Object} [values] - Initial values for the sequence scope.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async run(values) {
|
||||
async run (values) {
|
||||
// Initialize scope
|
||||
values = values || this.thisArg?.values || {};
|
||||
Object.setPrototypeOf(this.scope_, values);
|
||||
@@ -162,7 +162,7 @@ class Sequence {
|
||||
};
|
||||
}
|
||||
|
||||
if ( step.condition && ! await step.condition(this) ) {
|
||||
if ( step.condition && !await step.condition(this) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ class Sequence {
|
||||
* The first time defer is called, clones the steps and sets up for deferred insertion.
|
||||
* @param {function(Sequence.SequenceState): Promise<any>} fn - The function to defer.
|
||||
*/
|
||||
static defer_0 = function(fn) {
|
||||
static defer_0 = function (fn) {
|
||||
this.steps_ = [...this.sequence_.steps_];
|
||||
this.defer = this.constructor.defer_1;
|
||||
this.defer_ptr_ = this.steps_.length;
|
||||
@@ -213,7 +213,7 @@ class Sequence {
|
||||
* Subsequent calls to defer insert the function before the deferred pointer.
|
||||
* @param {function(Sequence.SequenceState): Promise<any>} fn - The function to defer.
|
||||
*/
|
||||
static defer_1 = function(fn) {
|
||||
static defer_1 = function (fn) {
|
||||
// Deferred functions don't affect the return value
|
||||
const real_fn = fn;
|
||||
fn = async () => {
|
||||
@@ -230,7 +230,7 @@ class Sequence {
|
||||
* @param {string} k - The key to retrieve.
|
||||
* @returns {any} The value associated with the key.
|
||||
*/
|
||||
get(k) {
|
||||
get (k) {
|
||||
// TODO: record read1
|
||||
return this.scope_[k];
|
||||
}
|
||||
@@ -240,7 +240,7 @@ class Sequence {
|
||||
* @param {string} k - The key to set.
|
||||
* @param {any} v - The value to assign.
|
||||
*/
|
||||
set(k, v) {
|
||||
set (k, v) {
|
||||
// TODO: record mutation
|
||||
this.scope_[k] = v;
|
||||
}
|
||||
@@ -250,7 +250,7 @@ class Sequence {
|
||||
* @param {Object} [opt_itemsToSet] - Optional object of key-value pairs to set.
|
||||
* @returns {Object} Proxy to the current scope for value access.
|
||||
*/
|
||||
values(opt_itemsToSet) {
|
||||
values (opt_itemsToSet) {
|
||||
if ( opt_itemsToSet ) {
|
||||
for ( const k in opt_itemsToSet ) {
|
||||
this.set(k, opt_itemsToSet[k]);
|
||||
@@ -273,7 +273,7 @@ class Sequence {
|
||||
* @param {string} [k] - The property name to retrieve. If omitted, returns the instance.
|
||||
* @returns {any} The value from the instance or the instance itself.
|
||||
*/
|
||||
iget(k) {
|
||||
iget (k) {
|
||||
if ( k === undefined ) return this.thisArg;
|
||||
return this.thisArg?.[k];
|
||||
}
|
||||
@@ -285,7 +285,7 @@ class Sequence {
|
||||
* @param {...any} args - Arguments to pass to the method.
|
||||
* @returns {any} The result of the method call.
|
||||
*/
|
||||
icall(k, ...args) {
|
||||
icall (k, ...args) {
|
||||
return this.thisArg?.[k]?.call(this.thisArg, ...args);
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ class Sequence {
|
||||
* @param {...any} args - Arguments to pass after the sequence state.
|
||||
* @returns {any} The result of the method call.
|
||||
*/
|
||||
idcall(k, ...args) {
|
||||
idcall (k, ...args) {
|
||||
return this.thisArg?.[k]?.call(this.thisArg, this, ...args);
|
||||
}
|
||||
|
||||
@@ -305,7 +305,7 @@ class Sequence {
|
||||
* Get the logger from the instance, if available.
|
||||
* @returns {Object|undefined} The logger object.
|
||||
*/
|
||||
get log() {
|
||||
get log () {
|
||||
return this.iget('log');
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ class Sequence {
|
||||
* @param {any} [return_value] - Value to return from the sequence.
|
||||
* @returns {any} The provided return value.
|
||||
*/
|
||||
stop(return_value) {
|
||||
stop (return_value) {
|
||||
this.stopped_ = true;
|
||||
return return_value;
|
||||
}
|
||||
@@ -333,7 +333,7 @@ class Sequence {
|
||||
* - Options object may include `name`, `record_history`, `before_each`, `after_each`.
|
||||
* @returns {SequenceCallable} A callable function that runs the sequence.
|
||||
*/
|
||||
constructor(...args) {
|
||||
constructor (...args) {
|
||||
const sequence = this;
|
||||
|
||||
const steps = [];
|
||||
@@ -356,7 +356,7 @@ class Sequence {
|
||||
* @param {Object|Sequence.SequenceState} [opt_values] - Initial values or a SequenceState.
|
||||
* @returns {Promise<any>} The return value of the last step.
|
||||
*/
|
||||
const fn = async function(opt_values) {
|
||||
const fn = async function (opt_values) {
|
||||
if ( opt_values && opt_values instanceof Sequence.SequenceState ) {
|
||||
opt_values = opt_values.scope_;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
"use strict"
|
||||
'use strict';
|
||||
const deep_proto_merge = require('./config/deep_proto_merge');
|
||||
// const reserved_words = require('./config/reserved_words');
|
||||
|
||||
@@ -53,9 +53,9 @@ config.kv_max_value_size = 400 * 1024;
|
||||
|
||||
// Captcha configuration
|
||||
config.captcha = {
|
||||
enabled: false, // Enable captcha by default
|
||||
enabled: false, // Enable captcha by default
|
||||
expirationTime: 10 * 60 * 1000, // 10 minutes default expiration time
|
||||
difficulty: 'medium' // Default difficulty level
|
||||
difficulty: 'medium', // Default difficulty level
|
||||
};
|
||||
|
||||
config.monitor = {
|
||||
@@ -64,7 +64,7 @@ config.monitor = {
|
||||
};
|
||||
|
||||
config.max_subdomains_per_user = 2000;
|
||||
config.storage_capacity = 1*1024*1024*1024;
|
||||
config.storage_capacity = 1 * 1024 * 1024 * 1024;
|
||||
config.static_hosting_domain = 'site.puter.localhost';
|
||||
|
||||
// Storage limiting is set to false by default
|
||||
@@ -74,11 +74,11 @@ config.available_device_storage = null;
|
||||
|
||||
config.thumb_width = 80;
|
||||
config.thumb_height = 80;
|
||||
config.app_max_icon_size = 5*1024*1024;
|
||||
config.app_max_icon_size = 5 * 1024 * 1024;
|
||||
|
||||
config.defaultjs_asset_path = '../../';
|
||||
|
||||
config.short_description = `Puter is a privacy-first personal cloud that houses all your files, apps, and games in one private and secure place, accessible from anywhere at any time.`;
|
||||
config.short_description = 'Puter is a privacy-first personal cloud that houses all your files, apps, and games in one private and secure place, accessible from anywhere at any time.';
|
||||
config.title = 'Puter';
|
||||
config.company = 'Puter Technologies Inc.';
|
||||
|
||||
@@ -103,18 +103,18 @@ config.reserved_words = [];
|
||||
}
|
||||
|
||||
// set default S3 settings for this server, if any
|
||||
if (config.server_id) {
|
||||
// see if this server has a specific bucket
|
||||
if ( config.server_id ) {
|
||||
// see if this server has a specific bucket
|
||||
for ( const server of config.servers ) {
|
||||
if ( server.id !== config.server_id ) continue;
|
||||
if ( ! server.s3_bucket ) continue;
|
||||
|
||||
config.s3_bucket = server.s3_bucket;
|
||||
config.s3_region = server.region;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.contact_email = 'hey@' + config.domain;
|
||||
config.contact_email = `hey@${ config.domain}`;
|
||||
|
||||
// TODO: default value will be changed to false in a future release;
|
||||
// details to follow in a future announcement.
|
||||
@@ -154,14 +154,14 @@ module.exports = config;
|
||||
|
||||
// NEW_CONFIG_LOADING
|
||||
const maybe_port = config =>
|
||||
config.pub_port !== 80 && config.pub_port !== 443 ? ':' + config.pub_port : '';
|
||||
config.pub_port !== 80 && config.pub_port !== 443 ? `:${ config.pub_port}` : '';
|
||||
|
||||
const computed_defaults = {
|
||||
pub_port: config => config.http_port,
|
||||
origin: config => config.protocol + '://' + config.domain + maybe_port(config),
|
||||
origin: config => `${config.protocol }://${ config.domain }${maybe_port(config)}`,
|
||||
api_base_url: config => config.experimental_no_subdomain
|
||||
? config.origin
|
||||
: config.protocol + '://api.' + config.domain + maybe_port(config),
|
||||
: `${config.protocol }://api.${ config.domain }${maybe_port(config)}`,
|
||||
social_card: config => `${config.origin}/assets/img/screenshot.png`,
|
||||
};
|
||||
|
||||
@@ -186,7 +186,7 @@ const config_pointer = {};
|
||||
};
|
||||
replacement_config = deep_proto_merge(replacement_config, Object.getPrototypeOf(config_pointer), {
|
||||
preserve_flag: true,
|
||||
})
|
||||
});
|
||||
Object.setPrototypeOf(config_pointer, replacement_config);
|
||||
};
|
||||
|
||||
@@ -198,37 +198,37 @@ const config_pointer = {};
|
||||
// We have some values with computed defaults
|
||||
{
|
||||
const get_implied = (target, prop) => {
|
||||
if (prop in computed_defaults) {
|
||||
if ( prop in computed_defaults ) {
|
||||
return computed_defaults[prop](target);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
config_to_export = new Proxy(config_to_export, {
|
||||
get: (target, prop, receiver) => {
|
||||
if (prop in target) {
|
||||
if ( prop in target ) {
|
||||
return target[prop];
|
||||
} else {
|
||||
return get_implied(config_to_export, prop);
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// We'd like to store values changed at runtime separately
|
||||
// for easier runtime debugging
|
||||
{
|
||||
const config_runtime_values = {
|
||||
$: 'runtime-values'
|
||||
$: 'runtime-values',
|
||||
};
|
||||
let initialPrototype = config_to_export;
|
||||
Object.setPrototypeOf(config_runtime_values, config_to_export);
|
||||
config_to_export = config_runtime_values
|
||||
|
||||
config_to_export = config_runtime_values;
|
||||
|
||||
config_to_export.__set_config_object__ = (object, options = {}) => {
|
||||
// options for this method
|
||||
const replacePrototype = options.replacePrototype ?? true;
|
||||
const useInitialPrototype = options.useInitialPrototype ?? true;
|
||||
|
||||
|
||||
// maybe replace prototype
|
||||
if ( replacePrototype ) {
|
||||
const newProto = useInitialPrototype
|
||||
@@ -236,7 +236,7 @@ const config_pointer = {};
|
||||
: Object.getPrototypeOf(config_runtime_values);
|
||||
Object.setPrototypeOf(object, newProto);
|
||||
}
|
||||
|
||||
|
||||
// use this object as the prototype
|
||||
Object.setPrototypeOf(config_runtime_values, object);
|
||||
};
|
||||
@@ -247,14 +247,14 @@ const config_pointer = {};
|
||||
set: (target, prop, value, receiver) => {
|
||||
const logger = Context.get('logger', { allow_fallback: true });
|
||||
// If no logger, just give up
|
||||
if ( logger ) logger.debug(
|
||||
'\x1B[36;1mCONFIGURATION MUTATED AT RUNTIME\x1B[0m',
|
||||
{ prop, value },
|
||||
);
|
||||
if ( logger ) {
|
||||
logger.debug('\x1B[36;1mCONFIGURATION MUTATED AT RUNTIME\x1B[0m',
|
||||
{ prop, value });
|
||||
}
|
||||
target[prop] = value;
|
||||
return true;
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// We configure the behavior in context.js from here to avoid a cyclic
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { AdvancedBase } = require("@heyputer/putility");
|
||||
const { AdvancedBase } = require('@heyputer/putility');
|
||||
const { quot } = require('@heyputer/putility').libs.string;
|
||||
|
||||
class ConfigLoader extends AdvancedBase {
|
||||
static MODULES = {
|
||||
path_: require("path"),
|
||||
fs: require("fs"),
|
||||
}
|
||||
path_: require('path'),
|
||||
fs: require('fs'),
|
||||
};
|
||||
|
||||
constructor (logger, path, config) {
|
||||
super();
|
||||
@@ -47,10 +47,8 @@ class ConfigLoader extends AdvancedBase {
|
||||
delete config_values.$requires;
|
||||
this.apply_requires(this.path, config_list, { by: name });
|
||||
}
|
||||
this.logger.debug(
|
||||
`Applying config: ${path_.relative(this.path, config_path)}` +
|
||||
(meta.by ? ` (required by ${meta.by})` : '')
|
||||
);
|
||||
this.logger.debug(`Applying config: ${path_.relative(this.path, config_path)}${
|
||||
meta.by ? ` (required by ${meta.by})` : ''}`);
|
||||
this.config.load_config(config_values);
|
||||
|
||||
}
|
||||
|
||||
@@ -39,15 +39,13 @@ const deep_proto_merge = (replacement, delegate, options) => {
|
||||
for ( const key in replacement ) {
|
||||
if ( ! is_object(replacement[key]) ) continue;
|
||||
|
||||
if ( options?.preserve_flag && ! replacement[key].$preserve ) {
|
||||
if ( options?.preserve_flag && !replacement[key].$preserve ) {
|
||||
continue;
|
||||
}
|
||||
if ( ! is_object(delegate[key]) ) {
|
||||
continue;
|
||||
}
|
||||
replacement[key] = deep_proto_merge(
|
||||
replacement[key], delegate[key], options,
|
||||
);
|
||||
replacement[key] = deep_proto_merge(replacement[key], delegate[key], options);
|
||||
}
|
||||
|
||||
// use a Proxy object to ensure all keys are present
|
||||
@@ -62,10 +60,10 @@ const deep_proto_merge = (replacement, delegate, options) => {
|
||||
// Combine and deduplicate properties using a Set, then convert back to an array
|
||||
const s = new Set([
|
||||
...protoProps,
|
||||
...ownProps
|
||||
...ownProps,
|
||||
]);
|
||||
|
||||
if (options?.preserve_flag) {
|
||||
if ( options?.preserve_flag ) {
|
||||
// remove $preserve if it exists
|
||||
s.delete('$preserve');
|
||||
}
|
||||
@@ -76,16 +74,16 @@ const deep_proto_merge = (replacement, delegate, options) => {
|
||||
// Real descriptor
|
||||
let descriptor = Object.getOwnPropertyDescriptor(target, prop);
|
||||
|
||||
if (descriptor) return descriptor;
|
||||
if ( descriptor ) return descriptor;
|
||||
|
||||
// Immediate prototype descriptor
|
||||
const proto = Object.getPrototypeOf(target);
|
||||
descriptor = Object.getOwnPropertyDescriptor(proto, prop);
|
||||
|
||||
if (descriptor) return descriptor;
|
||||
if ( descriptor ) return descriptor;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@@ -92,7 +92,7 @@ const policy_perm = selector => ({
|
||||
$: 'json-address',
|
||||
path: '/admin/.policy/drivers.json',
|
||||
selector,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const hardcoded_user_group_permissions = {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const BaseService = require("../services/BaseService");
|
||||
const BaseService = require('../services/BaseService');
|
||||
|
||||
class Library extends BaseService {
|
||||
//
|
||||
|
||||
@@ -16,21 +16,21 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { Context } = require("../util/context");
|
||||
const { Context } = require('../util/context');
|
||||
|
||||
module.exports = function SimpleEntity ({ name, methods, fetchers }) {
|
||||
const create = function (values) {
|
||||
const entity = { values };
|
||||
Object.assign(entity, methods);
|
||||
for ( const fetcher_name in fetchers ) {
|
||||
entity['fetch_' + fetcher_name] = async function () {
|
||||
entity[`fetch_${ fetcher_name}`] = async function () {
|
||||
if ( this.values.hasOwnProperty(fetcher_name) ) {
|
||||
return this.values[fetcher_name];
|
||||
}
|
||||
const value = await fetchers[fetcher_name].call(this);
|
||||
this.values[fetcher_name] = value;
|
||||
return value;
|
||||
}
|
||||
};
|
||||
}
|
||||
entity.context = values.context ?? Context.get();
|
||||
entity.services = entity.context.get('services');
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const SimpleEntity = require("../definitions/SimpleEntity");
|
||||
const SimpleEntity = require('../definitions/SimpleEntity');
|
||||
|
||||
module.exports = SimpleEntity({
|
||||
name: 'group',
|
||||
@@ -25,7 +25,7 @@ module.exports = SimpleEntity({
|
||||
const svc_group = this.services.get('group');
|
||||
const members = await svc_group.list_members({ uid: this.values.uid });
|
||||
return members;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async get_client_value (options = {}) {
|
||||
@@ -38,6 +38,6 @@ module.exports = SimpleEntity({
|
||||
...(options.members ? { members: this.values.members } : {}),
|
||||
};
|
||||
return group;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ class TechnicalError extends Error {
|
||||
|
||||
const ERR_HINT_NOSTACK = e => {
|
||||
e.toString = () => e.message;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
TechnicalError,
|
||||
|
||||
@@ -34,12 +34,12 @@ const reused = {
|
||||
subject: 'RuntimeEnvironment.js',
|
||||
location: 'src/boot/ in repository',
|
||||
use: 'code that performs the checks',
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const programmer_errors = [
|
||||
'Assignment to constant variable.'
|
||||
'Assignment to constant variable.',
|
||||
];
|
||||
|
||||
const error_help_details = [
|
||||
@@ -51,7 +51,7 @@ const error_help_details = [
|
||||
more.references = [
|
||||
...reused.runtime_env_references,
|
||||
];
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
match: ({ message }) => (
|
||||
@@ -72,7 +72,7 @@ const error_help_details = [
|
||||
more.references = [
|
||||
...reused.runtime_env_references,
|
||||
];
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
match: ({ message }) => (
|
||||
@@ -84,21 +84,19 @@ const error_help_details = [
|
||||
title: 'Create a valid config file',
|
||||
},
|
||||
];
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
match: ({ message }) => (
|
||||
message === `config_name is required`
|
||||
message === 'config_name is required'
|
||||
),
|
||||
apply (more) {
|
||||
more.solutions = [
|
||||
'ensure config_name is present in your config file',
|
||||
'Seek help on ' + osclink(
|
||||
'https://discord.gg/PQcx7Teh8u',
|
||||
'our Discord server'
|
||||
),
|
||||
`Seek help on ${ osclink('https://discord.gg/PQcx7Teh8u',
|
||||
'our Discord server')}`,
|
||||
];
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
match: ({ message }) => (
|
||||
@@ -110,10 +108,10 @@ const error_help_details = [
|
||||
subject: 'MDN Reference for this error',
|
||||
location: 'on the internet',
|
||||
use: 'describes why this error occurs',
|
||||
url: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_const_assignment'
|
||||
url: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_const_assignment',
|
||||
},
|
||||
];
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
match: ({ message }) => (
|
||||
@@ -125,18 +123,16 @@ const error_help_details = [
|
||||
];
|
||||
more.solutions = [
|
||||
{
|
||||
title: `Check for an issue on ` +
|
||||
osclink('https://github.com/HeyPuter/puter/issues')
|
||||
title: `Check for an issue on ${
|
||||
osclink('https://github.com/HeyPuter/puter/issues')}`,
|
||||
},
|
||||
{
|
||||
title: `If there's no issue, please ` +
|
||||
osclink(
|
||||
'https://github.com/HeyPuter/puter/issues/new',
|
||||
'create one'
|
||||
) + '.'
|
||||
}
|
||||
title: `If there's no issue, please ${
|
||||
osclink('https://github.com/HeyPuter/puter/issues/new',
|
||||
'create one') }.`,
|
||||
},
|
||||
];
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
match: ({ message }) => (
|
||||
@@ -146,8 +142,8 @@ const error_help_details = [
|
||||
more.notes = [
|
||||
'There might be a trailing-comma in your config',
|
||||
];
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -178,13 +174,13 @@ const print_error_help = (err, out = process.stdout) => {
|
||||
const wrap_list_title = s =>
|
||||
`\x1B[36;1m${s}:\x1B[0m`;
|
||||
|
||||
write(wrap_msg(err.message) + '\n');
|
||||
write(`${wrap_msg(err.message) }\n`);
|
||||
|
||||
write = (s) => out.write('\x1B[31;1m┃\x1B[0m ' + s);
|
||||
write = (s) => out.write(`\x1B[31;1m┃\x1B[0m ${ s}`);
|
||||
|
||||
const vis = (stok, etok, str) => {
|
||||
return `\x1B[36;1m${stok}\x1B[0m${str}\x1B[36;1m${etok}\x1B[0m`;
|
||||
}
|
||||
};
|
||||
|
||||
let lf_sep = false;
|
||||
|
||||
@@ -204,9 +200,9 @@ const print_error_help = (err, out = process.stdout) => {
|
||||
if ( lf_sep ) write('\n');
|
||||
lf_sep = true;
|
||||
any_help = true;
|
||||
write('The suggestions below may help resolve this issue.\n')
|
||||
write('The suggestions below may help resolve this issue.\n');
|
||||
write('\n');
|
||||
write(wrap_list_title('Possible Solutions') + '\n');
|
||||
write(`${wrap_list_title('Possible Solutions') }\n`);
|
||||
for ( const sol of err.more.solutions ) {
|
||||
write(` - ${sol.title}\n`);
|
||||
}
|
||||
@@ -216,9 +212,9 @@ const print_error_help = (err, out = process.stdout) => {
|
||||
if ( lf_sep ) write('\n');
|
||||
lf_sep = true;
|
||||
any_help = true;
|
||||
write('The references below may be related to this issue.\n')
|
||||
write('The references below may be related to this issue.\n');
|
||||
write('\n');
|
||||
write(wrap_list_title('References') + '\n');
|
||||
write(`${wrap_list_title('References') }\n`);
|
||||
for ( const ref of err.more.references ) {
|
||||
write(` - ${vis('[', ']', ref.subject)} ` +
|
||||
`${vis('(', ')', ref.location)};\n`);
|
||||
@@ -234,9 +230,9 @@ const print_error_help = (err, out = process.stdout) => {
|
||||
write('Help can be added in src/errors/error_help_details.\n');
|
||||
}
|
||||
|
||||
out.write(`\x1B[31;1m┗━━ [ END HELP ]\x1B[0m\n`)
|
||||
out.write('\x1B[31;1m┗━━ [ END HELP ]\x1B[0m\n');
|
||||
out.write('\n');
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
error_help_details,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { AdvancedBase } = require("@heyputer/putility");
|
||||
const { AdvancedBase } = require('@heyputer/putility');
|
||||
|
||||
class RuntimeModule extends AdvancedBase {
|
||||
constructor (options = {}) {
|
||||
@@ -6,7 +6,7 @@ class RuntimeModule extends AdvancedBase {
|
||||
this.exports_ = undefined;
|
||||
this.exports_is_set_ = false;
|
||||
this.remappings = options.remappings ?? {};
|
||||
|
||||
|
||||
this.name = options.name ?? undefined;
|
||||
}
|
||||
set exports (value) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
const { AdvancedBase } = require("@heyputer/putility");
|
||||
const { RuntimeModule } = require("./RuntimeModule");
|
||||
const { AdvancedBase } = require('@heyputer/putility');
|
||||
const { RuntimeModule } = require('./RuntimeModule');
|
||||
|
||||
class RuntimeModuleRegistry extends AdvancedBase {
|
||||
constructor () {
|
||||
super();
|
||||
this.modules_ = {};
|
||||
}
|
||||
|
||||
|
||||
register (extensionModule, options = {}) {
|
||||
if ( ! (extensionModule instanceof RuntimeModule) ) {
|
||||
throw new Error(`expected a RuntimeModule, but got: ${
|
||||
@@ -19,7 +19,7 @@ class RuntimeModuleRegistry extends AdvancedBase {
|
||||
this.modules_[uniqueName] = extensionModule;
|
||||
extensionModule.runtimeModuleRegistry = this;
|
||||
}
|
||||
|
||||
|
||||
exportsOf (name) {
|
||||
if ( ! this.modules_[name] ) {
|
||||
throw new Error(`could not find runtime module: ${name}`);
|
||||
@@ -29,5 +29,5 @@ class RuntimeModuleRegistry extends AdvancedBase {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
RuntimeModuleRegistry
|
||||
RuntimeModuleRegistry,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { Context } = require("../util/context");
|
||||
const { NodeUIDSelector, NodePathSelector, NodeInternalIDSelector } = require("./node/selectors");
|
||||
const { Context } = require('../util/context');
|
||||
const { NodeUIDSelector, NodePathSelector, NodeInternalIDSelector } = require('./node/selectors');
|
||||
|
||||
const LOG_PREFIX = '\x1B[31;1m[[\x1B[33;1mEC\x1B[32;1mMAP\x1B[31;1m]]\x1B[0m';
|
||||
|
||||
@@ -8,28 +8,28 @@ const LOG_PREFIX = '\x1B[31;1m[[\x1B[33;1mEC\x1B[32;1mMAP\x1B[31;1m]]\x1B[0m';
|
||||
* whenever it is present in the execution context (AsyncLocalStorage).
|
||||
* It is assumed that this object is transient and invalidation of stale
|
||||
* entries is not necessary.
|
||||
*
|
||||
*
|
||||
* The name ECMAP simple means Execution Context Map, because the map
|
||||
* exists in memory at a particular frame of the execution context.
|
||||
*/
|
||||
class ECMAP {
|
||||
static SYMBOL = Symbol('ECMAP');
|
||||
|
||||
|
||||
constructor () {
|
||||
this.identifier = require('uuid').v4();
|
||||
|
||||
|
||||
// entry caches
|
||||
this.uuid_to_fsNodeContext = {};
|
||||
this.path_to_fsNodeContext = {};
|
||||
this.id_to_fsNodeContext = {};
|
||||
|
||||
|
||||
// identifier association caches
|
||||
this.path_to_uuid = {};
|
||||
this.uuid_to_path = {};
|
||||
|
||||
|
||||
this.unlinked = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* unlink() clears all references from this ECMAP to ensure that it will be
|
||||
* GC'd. This is called by ECMAP.arun() after the callback has resolved.
|
||||
@@ -42,16 +42,16 @@ class ECMAP {
|
||||
this.path_to_uuid = null;
|
||||
this.uuid_to_path = null;
|
||||
}
|
||||
|
||||
|
||||
get logPrefix () {
|
||||
return `${LOG_PREFIX} \x1B[36[1m${this.identifier}\x1B[0m`;
|
||||
}
|
||||
|
||||
|
||||
log (...a) {
|
||||
if ( ! process.env.LOG_ECMAP ) return;
|
||||
console.log(this.logPrefix, ...a);
|
||||
}
|
||||
|
||||
|
||||
get_fsNodeContext_from_selector (selector) {
|
||||
if ( this.unlinked ) return null;
|
||||
|
||||
@@ -61,21 +61,21 @@ class ECMAP {
|
||||
if ( selector instanceof NodeUIDSelector ) {
|
||||
value = this.uuid_to_fsNodeContext[selector.value];
|
||||
if ( value ) return value;
|
||||
|
||||
|
||||
let maybe_path = this.uuid_to_path[value];
|
||||
if ( ! maybe_path ) return;
|
||||
value = this.path_to_fsNodeContext[maybe_path];
|
||||
if ( value ) return value;
|
||||
}
|
||||
else
|
||||
if ( selector instanceof NodePathSelector ) {
|
||||
value = this.path_to_fsNodeContext[selector.value];
|
||||
if ( value ) return value;
|
||||
|
||||
let maybe_uid = this.path_to_uuid[value];
|
||||
value = this.uuid_to_fsNodeContext[maybe_uid];
|
||||
if ( value ) return value;
|
||||
}
|
||||
if ( selector instanceof NodePathSelector ) {
|
||||
value = this.path_to_fsNodeContext[selector.value];
|
||||
if ( value ) return value;
|
||||
|
||||
let maybe_uid = this.path_to_uuid[value];
|
||||
value = this.uuid_to_fsNodeContext[maybe_uid];
|
||||
if ( value ) return value;
|
||||
}
|
||||
})();
|
||||
if ( retvalue ) {
|
||||
this.log('\x1B[32;1m <<<<< ECMAP HIT >>>>> \x1B[0m');
|
||||
@@ -84,7 +84,7 @@ class ECMAP {
|
||||
}
|
||||
return retvalue;
|
||||
}
|
||||
|
||||
|
||||
store_fsNodeContext_to_selector (selector, node) {
|
||||
if ( this.unlinked ) return null;
|
||||
|
||||
@@ -96,16 +96,16 @@ class ECMAP {
|
||||
this.path_to_fsNodeContext[selector.value] = node;
|
||||
}
|
||||
if ( selector instanceof NodeInternalIDSelector ) {
|
||||
this.id_to_fsNodeContext[selector.service+':'+selector.id] = node;
|
||||
this.id_to_fsNodeContext[`${selector.service}:${selector.id}`] = node;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
store_fsNodeContext (node) {
|
||||
if ( this.unlinked ) return;
|
||||
|
||||
this.store_fsNodeContext_to_selector(node.selector, node);
|
||||
}
|
||||
|
||||
|
||||
static async arun (cb) {
|
||||
let context = Context.get();
|
||||
if ( ! context.get(this.SYMBOL) ) {
|
||||
|
||||
@@ -16,19 +16,19 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { get_user, id2path, id2uuid, is_empty, suggest_app_for_fsentry, get_app } = require("../helpers");
|
||||
const { get_user, id2path, id2uuid, is_empty, suggest_app_for_fsentry, get_app } = require('../helpers');
|
||||
|
||||
const putility = require('@heyputer/putility');
|
||||
const config = require("../config");
|
||||
const config = require('../config');
|
||||
const _path = require('path');
|
||||
const { NodeInternalIDSelector, NodeChildSelector, NodeUIDSelector, RootNodeSelector, NodePathSelector } = require("./node/selectors");
|
||||
const { Context } = require("../util/context");
|
||||
const { NodeRawEntrySelector } = require("./node/selectors");
|
||||
const { DB_READ } = require("../services/database/consts");
|
||||
const { UserActorType, AppUnderUserActorType, Actor } = require("../services/auth/Actor");
|
||||
const { PermissionUtil } = require("../services/auth/permissionUtils.mjs");
|
||||
const { ECMAP } = require("./ECMAP");
|
||||
const { MANAGE_PERM_PREFIX } = require("../services/auth/permissionConts.mjs");
|
||||
const { NodeInternalIDSelector, NodeChildSelector, NodeUIDSelector, RootNodeSelector, NodePathSelector } = require('./node/selectors');
|
||||
const { Context } = require('../util/context');
|
||||
const { NodeRawEntrySelector } = require('./node/selectors');
|
||||
const { DB_READ } = require('../services/database/consts');
|
||||
const { UserActorType, AppUnderUserActorType, Actor } = require('../services/auth/Actor');
|
||||
const { PermissionUtil } = require('../services/auth/permissionUtils.mjs');
|
||||
const { ECMAP } = require('./ECMAP');
|
||||
const { MANAGE_PERM_PREFIX } = require('../services/auth/permissionConts.mjs');
|
||||
|
||||
/**
|
||||
* Container for information collected about a node
|
||||
@@ -73,7 +73,7 @@ module.exports = class FSNodeContext {
|
||||
* @param {*} opt_identifier.id please pass mysql_id instead
|
||||
* @param {*} opt_identifier.mysql_id a MySQL ID of the filesystem entry
|
||||
*/
|
||||
constructor({
|
||||
constructor ({
|
||||
services,
|
||||
selector,
|
||||
provider,
|
||||
@@ -147,7 +147,7 @@ module.exports = class FSNodeContext {
|
||||
}
|
||||
}
|
||||
|
||||
set selector(new_selector) {
|
||||
set selector (new_selector) {
|
||||
// Only add the selector if we don't already have it
|
||||
for ( const selector of this.selectors_ ) {
|
||||
if ( selector instanceof new_selector.constructor ) return;
|
||||
@@ -162,11 +162,11 @@ module.exports = class FSNodeContext {
|
||||
this.selector_ = new_selector;
|
||||
}
|
||||
|
||||
get selector() {
|
||||
get selector () {
|
||||
return this.get_optimal_selector();
|
||||
}
|
||||
|
||||
get_selector_of_type(cls) {
|
||||
get_selector_of_type (cls) {
|
||||
// Reverse iterate over selectors
|
||||
for ( let i = this.selectors_.length - 1; i >= 0; i-- ) {
|
||||
const selector = this.selectors_[i];
|
||||
@@ -182,7 +182,7 @@ module.exports = class FSNodeContext {
|
||||
return null;
|
||||
}
|
||||
|
||||
get_optimal_selector() {
|
||||
get_optimal_selector () {
|
||||
for ( const cls of FSNodeContext.SELECTOR_PRIORITY_ORDER ) {
|
||||
const selector = this.get_selector_of_type(cls);
|
||||
if ( selector ) return selector;
|
||||
@@ -191,21 +191,21 @@ module.exports = class FSNodeContext {
|
||||
return this.selector_;
|
||||
}
|
||||
|
||||
get isRoot() {
|
||||
get isRoot () {
|
||||
return this.path === '/';
|
||||
}
|
||||
|
||||
async isUserDirectory() {
|
||||
async isUserDirectory () {
|
||||
if ( this.isRoot ) return false;
|
||||
if ( this.found === undefined ) {
|
||||
await this.fetchEntry();
|
||||
}
|
||||
if ( this.isRoot ) return false;
|
||||
if ( this.found === false ) return undefined;
|
||||
return ! this.entry.parent_uid;
|
||||
return !this.entry.parent_uid;
|
||||
}
|
||||
|
||||
async isAppDataDirectory() {
|
||||
async isAppDataDirectory () {
|
||||
if ( this.isRoot ) return false;
|
||||
if ( this.found === undefined ) {
|
||||
await this.fetchEntry();
|
||||
@@ -217,7 +217,7 @@ module.exports = class FSNodeContext {
|
||||
return components[1] === 'AppData';
|
||||
}
|
||||
|
||||
async isPublic() {
|
||||
async isPublic () {
|
||||
if ( this.isRoot ) return false;
|
||||
const components = await this.getPathComponents();
|
||||
if ( await this.isUserDirectory() ) return false;
|
||||
@@ -225,7 +225,7 @@ module.exports = class FSNodeContext {
|
||||
return false;
|
||||
}
|
||||
|
||||
async getPathComponents() {
|
||||
async getPathComponents () {
|
||||
if ( this.isRoot ) return [];
|
||||
|
||||
// We can get path components for non-existing nodes if they
|
||||
@@ -245,30 +245,30 @@ module.exports = class FSNodeContext {
|
||||
return path.split('/');
|
||||
}
|
||||
|
||||
async getUserPart() {
|
||||
async getUserPart () {
|
||||
if ( this.isRoot ) return;
|
||||
const components = await this.getPathComponents();
|
||||
return components[0];
|
||||
}
|
||||
|
||||
async getPathSize() {
|
||||
async getPathSize () {
|
||||
if ( this.isRoot ) return;
|
||||
const components = await this.getPathComponents();
|
||||
return components.length;
|
||||
}
|
||||
|
||||
async exists({ fetch_options } = {}) {
|
||||
async exists ({ fetch_options } = {}) {
|
||||
await this.fetchEntry(fetch_options);
|
||||
if ( ! this.found ) {
|
||||
this.log.debug('here\'s why it doesn\'t exist: ' +
|
||||
this.selector.describe() + ' -> ' +
|
||||
this.uid + ' ' +
|
||||
JSON.stringify(this.entry, null, ' '));
|
||||
this.log.debug(`here's why it doesn't exist: ${
|
||||
this.selector.describe() } -> ${
|
||||
this.uid } ${
|
||||
JSON.stringify(this.entry, null, ' ')}`);
|
||||
}
|
||||
return this.found;
|
||||
}
|
||||
|
||||
async fetchPath() {
|
||||
async fetchPath () {
|
||||
if ( this.path ) return;
|
||||
|
||||
this.path = await this.services.get('information')
|
||||
@@ -299,10 +299,10 @@ module.exports = class FSNodeContext {
|
||||
|
||||
if (
|
||||
this.found === true &&
|
||||
! fetch_entry_options.force &&
|
||||
!fetch_entry_options.force &&
|
||||
(
|
||||
// thumbnail already fetched, or not asked for
|
||||
! fetch_entry_options.thumbnail || this.entry?.thumbnail ||
|
||||
!fetch_entry_options.thumbnail || this.entry?.thumbnail ||
|
||||
this.found_thumbnail !== undefined
|
||||
)
|
||||
) {
|
||||
@@ -319,7 +319,7 @@ module.exports = class FSNodeContext {
|
||||
},
|
||||
};
|
||||
|
||||
this.log.debug('fetching entry: ' + this.selector.describe());
|
||||
this.log.debug(`fetching entry: ${ this.selector.describe()}`);
|
||||
|
||||
const entry = await this.provider.stat({
|
||||
selector: this.selector,
|
||||
@@ -334,19 +334,19 @@ module.exports = class FSNodeContext {
|
||||
} else {
|
||||
this.found = true;
|
||||
|
||||
if ( ! this.uid && entry.uuid ) {
|
||||
if ( !this.uid && entry.uuid ) {
|
||||
this.uid = entry.uuid;
|
||||
}
|
||||
|
||||
if ( ! this.mysql_id && entry.id ) {
|
||||
if ( !this.mysql_id && entry.id ) {
|
||||
this.mysql_id = entry.id;
|
||||
}
|
||||
|
||||
if ( ! this.path && entry.path ) {
|
||||
if ( !this.path && entry.path ) {
|
||||
this.path = entry.path;
|
||||
}
|
||||
|
||||
if ( ! this.name && entry.name ) {
|
||||
if ( !this.name && entry.name ) {
|
||||
this.name = entry.name;
|
||||
}
|
||||
|
||||
@@ -365,7 +365,7 @@ module.exports = class FSNodeContext {
|
||||
*
|
||||
* This just calls ResourceService under the hood.
|
||||
*/
|
||||
async awaitStableEntry() {
|
||||
async awaitStableEntry () {
|
||||
const resourceService = Context.get('services').get('resourceService');
|
||||
await resourceService.waitForResource(this.selector);
|
||||
}
|
||||
@@ -378,19 +378,19 @@ module.exports = class FSNodeContext {
|
||||
*
|
||||
* @param fs:decouple-subdomains
|
||||
*/
|
||||
async fetchSubdomains(user, _force) {
|
||||
async fetchSubdomains (user, _force) {
|
||||
if ( ! this.entry.is_dir ) return;
|
||||
|
||||
const db = this.services.get('database').get(DB_READ, 'filesystem');
|
||||
|
||||
this.entry.subdomains = [];
|
||||
let subdomains = await db.read(`SELECT * FROM subdomains WHERE root_dir_id = ? AND user_id = ?`,
|
||||
let subdomains = await db.read('SELECT * FROM subdomains WHERE root_dir_id = ? AND user_id = ?',
|
||||
[this.entry.id, user.id]);
|
||||
if ( subdomains.length > 0 ){
|
||||
if ( subdomains.length > 0 ) {
|
||||
subdomains.forEach((sd) => {
|
||||
this.entry.subdomains.push({
|
||||
subdomain: sd.subdomain,
|
||||
address: config.protocol + '://' + sd.subdomain + "." + 'puter.site',
|
||||
address: `${config.protocol }://${ sd.subdomain }.` + 'puter.site',
|
||||
uuid: sd.uuid,
|
||||
});
|
||||
});
|
||||
@@ -403,7 +403,7 @@ module.exports = class FSNodeContext {
|
||||
* `owner` property of the fsentry.
|
||||
* @param {bool} force fetch owner if it was already fetched
|
||||
*/
|
||||
async fetchOwner(_force) {
|
||||
async fetchOwner (_force) {
|
||||
if ( this.isRoot ) return;
|
||||
const owner = await get_user({ id: this.entry.user_id });
|
||||
this.entry.owner = {
|
||||
@@ -418,8 +418,8 @@ module.exports = class FSNodeContext {
|
||||
* of the fsentry.
|
||||
* @param {bool} force fetch shares if they were already fetched
|
||||
*/
|
||||
async fetchShares(force) {
|
||||
if ( this.entry.shares && ! force ) return;
|
||||
async fetchShares (force) {
|
||||
if ( this.entry.shares && !force ) return;
|
||||
|
||||
const actor = Context.get('actor');
|
||||
if ( ! actor ) {
|
||||
@@ -503,12 +503,12 @@ module.exports = class FSNodeContext {
|
||||
*
|
||||
* @todo fs:decouple-versions
|
||||
*/
|
||||
async fetchVersions(force) {
|
||||
if ( this.entry.versions && ! force ) return;
|
||||
async fetchVersions (force) {
|
||||
if ( this.entry.versions && !force ) return;
|
||||
|
||||
const db = this.services.get('database').get(DB_READ, 'filesystem');
|
||||
|
||||
let versions = await db.read(`SELECT * FROM fsentry_versions WHERE fsentry_id = ?`,
|
||||
let versions = await db.read('SELECT * FROM fsentry_versions WHERE fsentry_id = ?',
|
||||
[this.entry.id]);
|
||||
const versions_tidy = [];
|
||||
for ( const version of versions ) {
|
||||
@@ -530,7 +530,7 @@ module.exports = class FSNodeContext {
|
||||
* Fetches the size of a file or directory if it was not
|
||||
* already fetched.
|
||||
*/
|
||||
async fetchSize() {
|
||||
async fetchSize () {
|
||||
// we already have the size for files
|
||||
if ( ! this.entry.is_dir ) {
|
||||
await this.fetchEntry();
|
||||
@@ -542,8 +542,8 @@ module.exports = class FSNodeContext {
|
||||
return this.entry.size;
|
||||
}
|
||||
|
||||
async fetchSuggestedApps(user, force) {
|
||||
if ( this.entry.suggested_apps && ! force ) return;
|
||||
async fetchSuggestedApps (user, force) {
|
||||
if ( this.entry.suggested_apps && !force ) return;
|
||||
|
||||
await this.fetchEntry();
|
||||
if ( ! this.entry ) return;
|
||||
@@ -552,15 +552,15 @@ module.exports = class FSNodeContext {
|
||||
await suggest_app_for_fsentry(this.entry, { user });
|
||||
}
|
||||
|
||||
async fetchIsEmpty() {
|
||||
if ( ! this.uid && ! this.path ) return;
|
||||
async fetchIsEmpty () {
|
||||
if ( !this.uid && !this.path ) return;
|
||||
this.entry.is_empty = await is_empty({
|
||||
uid: this.uid,
|
||||
path: this.path,
|
||||
});
|
||||
}
|
||||
|
||||
async fetchAll(_fsEntryFetcher, user, _force) {
|
||||
async fetchAll (_fsEntryFetcher, user, _force) {
|
||||
await this.fetchEntry({ thumbnail: true });
|
||||
await this.fetchSubdomains(user);
|
||||
await this.fetchOwner();
|
||||
@@ -571,7 +571,7 @@ module.exports = class FSNodeContext {
|
||||
await this.fetchIsEmpty();
|
||||
}
|
||||
|
||||
async get(key) {
|
||||
async get (key) {
|
||||
/*
|
||||
This isn't supposed to stay like this!
|
||||
|
||||
@@ -585,15 +585,15 @@ module.exports = class FSNodeContext {
|
||||
*/
|
||||
|
||||
if ( this.found === false ) {
|
||||
throw new Error(`Tried to get ${key} of non-existent fsentry: ` +
|
||||
this.selector.describe(true));
|
||||
throw new Error(`Tried to get ${key} of non-existent fsentry: ${
|
||||
this.selector.describe(true)}`);
|
||||
}
|
||||
|
||||
if ( key === 'entry' ) {
|
||||
await this.fetchEntry();
|
||||
if ( this.found === false ) {
|
||||
throw new Error(`Tried to get entry of non-existent fsentry: ` +
|
||||
this.selector.describe(true));
|
||||
throw new Error(`Tried to get entry of non-existent fsentry: ${
|
||||
this.selector.describe(true)}`);
|
||||
}
|
||||
return this.entry;
|
||||
}
|
||||
@@ -601,14 +601,14 @@ module.exports = class FSNodeContext {
|
||||
if ( key === 'path' ) {
|
||||
if ( ! this.path ) await this.fetchEntry();
|
||||
if ( this.found === false ) {
|
||||
throw new Error(`Tried to get path of non-existent fsentry: ` +
|
||||
this.selector.describe(true));
|
||||
throw new Error(`Tried to get path of non-existent fsentry: ${
|
||||
this.selector.describe(true)}`);
|
||||
}
|
||||
if ( ! this.path ) {
|
||||
await this.fetchPath();
|
||||
}
|
||||
if ( ! this.path ) {
|
||||
throw new Error(`failed to get path`);
|
||||
throw new Error('failed to get path');
|
||||
}
|
||||
return this.path;
|
||||
}
|
||||
@@ -642,8 +642,8 @@ module.exports = class FSNodeContext {
|
||||
if ( key === k ) {
|
||||
await this.fetchEntry();
|
||||
if ( this.found === false ) {
|
||||
throw new Error(`Tried to get ${key} of non-existent fsentry: ` +
|
||||
this.selector.describe(true));
|
||||
throw new Error(`Tried to get ${key} of non-existent fsentry: ${
|
||||
this.selector.describe(true)}`);
|
||||
}
|
||||
return this.entry[k];
|
||||
}
|
||||
@@ -700,7 +700,7 @@ module.exports = class FSNodeContext {
|
||||
throw new Error(`unrecognize key for FSNodeContext.get: ${key}`);
|
||||
}
|
||||
|
||||
async getParent() {
|
||||
async getParent () {
|
||||
if ( this.isRoot ) {
|
||||
throw new Error('tried to get parent of root');
|
||||
}
|
||||
@@ -729,7 +729,7 @@ module.exports = class FSNodeContext {
|
||||
return this.fs.node(new NodeUIDSelector(parent_uid));
|
||||
}
|
||||
|
||||
async getChild(name) {
|
||||
async getChild (name) {
|
||||
// If we have a path, we can get an FSNodeContext for the child
|
||||
// without fetching anything.
|
||||
if ( this.path ) {
|
||||
@@ -741,12 +741,12 @@ module.exports = class FSNodeContext {
|
||||
|
||||
return await this.fs.node(new NodeChildSelector(this.selector, name));
|
||||
}
|
||||
|
||||
async hasChild(name) {
|
||||
|
||||
async hasChild (name) {
|
||||
return await this.provider.directory_has_name({ parent: this, name });
|
||||
}
|
||||
|
||||
async getTarget() {
|
||||
async getTarget () {
|
||||
await this.fetchEntry();
|
||||
const type = await this.get('type');
|
||||
|
||||
@@ -763,16 +763,16 @@ module.exports = class FSNodeContext {
|
||||
return this;
|
||||
}
|
||||
|
||||
async is_above(child_fsNode) {
|
||||
async is_above (child_fsNode) {
|
||||
if ( this.isRoot ) return true;
|
||||
|
||||
const path_this = await this.get('path');
|
||||
const path_child = await child_fsNode.get('path');
|
||||
|
||||
return path_child.startsWith(path_this + '/');
|
||||
return path_child.startsWith(`${path_this }/`);
|
||||
}
|
||||
|
||||
async is(fsNode) {
|
||||
async is (fsNode) {
|
||||
if ( this.mysql_id && fsNode.mysql_id ) {
|
||||
return this.mysql_id === fsNode.mysql_id;
|
||||
}
|
||||
@@ -786,19 +786,19 @@ module.exports = class FSNodeContext {
|
||||
return this.uid === fsNode.uid;
|
||||
}
|
||||
|
||||
async getSafeEntry(fetch_options = {}) {
|
||||
async getSafeEntry (fetch_options = {}) {
|
||||
const svc_event = this.services.get('event');
|
||||
|
||||
|
||||
if ( this.found === false ) {
|
||||
throw new Error(`Tried to get entry of non-existent fsentry: ` +
|
||||
this.selector.describe(true));
|
||||
throw new Error(`Tried to get entry of non-existent fsentry: ${
|
||||
this.selector.describe(true)}`);
|
||||
}
|
||||
await this.fetchEntry(fetch_options);
|
||||
|
||||
const res = this.entry;
|
||||
const fsentry = {};
|
||||
if (res.thumbnail) {
|
||||
await svc_event.emit("thumbnail.read", this.entry);
|
||||
if ( res.thumbnail ) {
|
||||
await svc_event.emit('thumbnail.read', this.entry);
|
||||
}
|
||||
|
||||
// This property will not be serialized, but it can be checked
|
||||
@@ -818,7 +818,7 @@ module.exports = class FSNodeContext {
|
||||
} catch ( _e ) {
|
||||
// fail silently
|
||||
}
|
||||
if ( ! actor?.type?.user || actor.type.user.id !== res.user_id ) {
|
||||
if ( !actor?.type?.user || actor.type.user.id !== res.user_id ) {
|
||||
if ( ! fsentry.owner ) await this.fetchOwner();
|
||||
fsentry.owner = {
|
||||
username: res.owner?.username,
|
||||
@@ -830,10 +830,10 @@ module.exports = class FSNodeContext {
|
||||
|
||||
const info = this.services.get('information');
|
||||
|
||||
if ( ! this.uid && ! this.entry.uuid ) {
|
||||
this.log.noticeme('whats even happening!?!? ' +
|
||||
this.selector.describe() + ' ' +
|
||||
JSON.stringify(this.entry, null, ' '));
|
||||
if ( !this.uid && !this.entry.uuid ) {
|
||||
this.log.noticeme(`whats even happening!?!? ${
|
||||
this.selector.describe() } ${
|
||||
JSON.stringify(this.entry, null, ' ')}`);
|
||||
}
|
||||
|
||||
// If fsentry was found by a path but the entry doesn't
|
||||
@@ -884,9 +884,9 @@ module.exports = class FSNodeContext {
|
||||
}
|
||||
|
||||
// Add file_request_url
|
||||
if ( res.file_request_token && res.file_request_token !== '' ){
|
||||
fsentry.file_request_url = config.origin +
|
||||
'/upload?token=' + res.file_request_token;
|
||||
if ( res.file_request_token && res.file_request_token !== '' ) {
|
||||
fsentry.file_request_url = `${config.origin
|
||||
}/upload?token=${ res.file_request_token}`;
|
||||
}
|
||||
|
||||
if ( fsentry.associated_app_id ) {
|
||||
@@ -900,7 +900,7 @@ module.exports = class FSNodeContext {
|
||||
fsentry.appdata_app = components[2];
|
||||
}
|
||||
|
||||
fsentry.is_dir = !! fsentry.is_dir;
|
||||
fsentry.is_dir = !!fsentry.is_dir;
|
||||
|
||||
// Ensure `size` is numeric
|
||||
if ( fsentry.size ) {
|
||||
@@ -910,7 +910,7 @@ module.exports = class FSNodeContext {
|
||||
return fsentry;
|
||||
}
|
||||
|
||||
static sanitize_pending_entry_info(res) {
|
||||
static sanitize_pending_entry_info (res) {
|
||||
const fsentry = {};
|
||||
|
||||
// This property will not be serialized, but it can be checked
|
||||
|
||||
@@ -31,7 +31,7 @@ class BatchExecutor extends AdvancedBase {
|
||||
constructor (x, { actor, log, errors }) {
|
||||
super();
|
||||
this.x = x;
|
||||
this.actor = actor
|
||||
this.actor = actor;
|
||||
this.pathResolver = new PathResolver({ actor });
|
||||
this.expectations = x.get('services').get('expectations');
|
||||
this.log = log;
|
||||
@@ -46,7 +46,7 @@ class BatchExecutor extends AdvancedBase {
|
||||
this.concurrent_ops = 0;
|
||||
this.max_concurrent_ops = 20;
|
||||
this.ops_promise = null;
|
||||
|
||||
|
||||
this.log_batchCommands = (config.logging ?? []).includes('batch-commands');
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ class BatchExecutor extends AdvancedBase {
|
||||
const workUnit = WorkUnit.create();
|
||||
expectations.expect_eventually({
|
||||
workUnit,
|
||||
checkpoint: 'operation responded'
|
||||
checkpoint: 'operation responded',
|
||||
});
|
||||
|
||||
// TEMP: event service will handle this
|
||||
@@ -87,7 +87,7 @@ class BatchExecutor extends AdvancedBase {
|
||||
|
||||
// run the operation
|
||||
let p = this.x.arun(async () => {
|
||||
const x= Context.get();
|
||||
const x = Context.get();
|
||||
if ( ! x ) throw new Error('no context');
|
||||
|
||||
try {
|
||||
@@ -97,12 +97,12 @@ class BatchExecutor extends AdvancedBase {
|
||||
});
|
||||
}
|
||||
|
||||
if ( file ) workUnit.checkpoint(
|
||||
'about to run << ' +
|
||||
(file.originalname ?? file.name) +
|
||||
' >> ' +
|
||||
JSON.stringify(op)
|
||||
);
|
||||
if ( file ) {
|
||||
workUnit.checkpoint(`about to run << ${
|
||||
file.originalname ?? file.name
|
||||
} >> ${
|
||||
JSON.stringify(op)}`);
|
||||
}
|
||||
const command_ins = await command_cls.run({
|
||||
getFile: () => file,
|
||||
pathResolver: this.pathResolver,
|
||||
|
||||
@@ -16,27 +16,26 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { AdvancedBase } = require("@heyputer/putility");
|
||||
const { AsyncProviderFeature } = require("../../traits/AsyncProviderFeature");
|
||||
const { HLMkdir, QuickMkdir } = require("../hl_operations/hl_mkdir");
|
||||
const { Context } = require("../../util/context");
|
||||
const { HLWrite } = require("../hl_operations/hl_write");
|
||||
const { get_app } = require("../../helpers");
|
||||
const { OperationFrame } = require("../../services/OperationTraceService");
|
||||
const { HLMkShortcut } = require("../hl_operations/hl_mkshortcut");
|
||||
const { HLMkLink } = require("../hl_operations/hl_mklink");
|
||||
const { HLRemove } = require("../hl_operations/hl_remove");
|
||||
|
||||
const { AdvancedBase } = require('@heyputer/putility');
|
||||
const { AsyncProviderFeature } = require('../../traits/AsyncProviderFeature');
|
||||
const { HLMkdir, QuickMkdir } = require('../hl_operations/hl_mkdir');
|
||||
const { Context } = require('../../util/context');
|
||||
const { HLWrite } = require('../hl_operations/hl_write');
|
||||
const { get_app } = require('../../helpers');
|
||||
const { OperationFrame } = require('../../services/OperationTraceService');
|
||||
const { HLMkShortcut } = require('../hl_operations/hl_mkshortcut');
|
||||
const { HLMkLink } = require('../hl_operations/hl_mklink');
|
||||
const { HLRemove } = require('../hl_operations/hl_remove');
|
||||
|
||||
class BatchCommand extends AdvancedBase {
|
||||
static FEATURES = [
|
||||
new AsyncProviderFeature(),
|
||||
]
|
||||
];
|
||||
static async run (executor, parameters) {
|
||||
const instance = new this();
|
||||
let x = Context.get();
|
||||
const operationTraceSvc = x.get('services').get('operationTrace');
|
||||
const frame = await operationTraceSvc.add_frame('batch:' + this.name);
|
||||
const frame = await operationTraceSvc.add_frame(`batch:${ this.name}`);
|
||||
if ( parameters.hasOwnProperty('item_upload_id') ) {
|
||||
frame.attr('gui_metadata', {
|
||||
...(frame.get_attr('gui_metadata') || {}),
|
||||
@@ -73,11 +72,9 @@ class MkdirCommand extends BatchCommand {
|
||||
path: parameters.path,
|
||||
});
|
||||
if ( parameters.as ) {
|
||||
executor.pathResolver.putSelector(
|
||||
parameters.as,
|
||||
q_mkdir.created.selector,
|
||||
{ conflict_free: true }
|
||||
);
|
||||
executor.pathResolver.putSelector(parameters.as,
|
||||
q_mkdir.created.selector,
|
||||
{ conflict_free: true });
|
||||
}
|
||||
this.setFactory('result', async () => {
|
||||
await q_mkdir.created.awaitStableEntry();
|
||||
@@ -101,15 +98,13 @@ class MkdirCommand extends BatchCommand {
|
||||
actor: executor.actor,
|
||||
});
|
||||
if ( parameters.as ) {
|
||||
executor.pathResolver.putSelector(
|
||||
parameters.as,
|
||||
hl_mkdir.created.selector,
|
||||
hl_mkdir.used_existing
|
||||
? undefined
|
||||
: { conflict_free: true }
|
||||
);
|
||||
executor.pathResolver.putSelector(parameters.as,
|
||||
hl_mkdir.created.selector,
|
||||
hl_mkdir.used_existing
|
||||
? undefined
|
||||
: { conflict_free: true });
|
||||
}
|
||||
this.provideValue('result', response)
|
||||
this.provideValue('result', response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +120,7 @@ class WriteCommand extends BatchCommand {
|
||||
|
||||
let app;
|
||||
if ( parameters.app_uid ) {
|
||||
app = await get_app({uid: parameters.app_uid})
|
||||
app = await get_app({ uid: parameters.app_uid });
|
||||
}
|
||||
|
||||
const hl_write = new HLWrite();
|
||||
@@ -158,7 +153,6 @@ class WriteCommand extends BatchCommand {
|
||||
|
||||
this.provideValue('result', response);
|
||||
|
||||
|
||||
// const opctx = await fs.write(fs, {
|
||||
// // --- per file ---
|
||||
// name: parameters.name,
|
||||
@@ -197,7 +191,7 @@ class ShortcutCommand extends BatchCommand {
|
||||
|
||||
let app;
|
||||
if ( parameters.app_uid ) {
|
||||
app = await get_app({uid: parameters.app_uid})
|
||||
app = await get_app({ uid: parameters.app_uid });
|
||||
}
|
||||
|
||||
await destinationOrParent.fetchEntry({ thumbnail: true });
|
||||
@@ -232,7 +226,7 @@ class SymlinkCommand extends BatchCommand {
|
||||
|
||||
let app;
|
||||
if ( parameters.app_uid ) {
|
||||
app = await get_app({uid: parameters.app_uid})
|
||||
app = await get_app({ uid: parameters.app_uid });
|
||||
}
|
||||
|
||||
await destinationOrParent.fetchEntry({ thumbnail: true });
|
||||
@@ -281,5 +275,5 @@ module.exports = {
|
||||
shortcut: ShortcutCommand,
|
||||
symlink: SymlinkCommand,
|
||||
delete: DeleteCommand,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"use strict";
|
||||
'use strict';
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.8.0
|
||||
// protoc v3.21.12
|
||||
// source: fsentry.proto
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
exports.FSEntry = exports.protobufPackage = void 0;
|
||||
/* eslint-disable */
|
||||
const wire_1 = require("@bufbuild/protobuf/wire");
|
||||
|
||||
@@ -18,8 +18,9 @@
|
||||
*/
|
||||
const { BaseOperation } = require('../../services/OperationTraceService');
|
||||
|
||||
class HLFilesystemOperation extends BaseOperation {}
|
||||
class HLFilesystemOperation extends BaseOperation {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
HLFilesystemOperation
|
||||
HLFilesystemOperation,
|
||||
};
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const APIError = require("../../api/APIError");
|
||||
const { chkperm, validate_fsentry_name, get_user, is_ancestor_of } = require("../../helpers");
|
||||
const { TYPE_DIRECTORY } = require("../FSNodeContext");
|
||||
const { NodePathSelector, RootNodeSelector } = require("../node/selectors");
|
||||
const { HLFilesystemOperation } = require("./definitions");
|
||||
const { MkTree } = require("./hl_mkdir");
|
||||
const { HLRemove } = require("./hl_remove");
|
||||
const { LLCopy } = require("../ll_operations/ll_copy");
|
||||
const APIError = require('../../api/APIError');
|
||||
const { chkperm, validate_fsentry_name, get_user, is_ancestor_of } = require('../../helpers');
|
||||
const { TYPE_DIRECTORY } = require('../FSNodeContext');
|
||||
const { NodePathSelector, RootNodeSelector } = require('../node/selectors');
|
||||
const { HLFilesystemOperation } = require('./definitions');
|
||||
const { MkTree } = require('./hl_mkdir');
|
||||
const { HLRemove } = require('./hl_remove');
|
||||
const { LLCopy } = require('../ll_operations/ll_copy');
|
||||
|
||||
class HLCopy extends HLFilesystemOperation {
|
||||
static DESCRIPTION = `
|
||||
@@ -34,11 +34,11 @@ class HLCopy extends HLFilesystemOperation {
|
||||
- create missing parent directories
|
||||
- overwrite existing files or directories
|
||||
- deduplicate files/directories with the same name
|
||||
`
|
||||
`;
|
||||
|
||||
static MODULES = {
|
||||
_path: require('path'),
|
||||
}
|
||||
};
|
||||
|
||||
static PARAMETERS = {
|
||||
source: {},
|
||||
@@ -51,7 +51,7 @@ class HLCopy extends HLFilesystemOperation {
|
||||
create_missing_parents: {},
|
||||
|
||||
user: {},
|
||||
}
|
||||
};
|
||||
|
||||
async _run () {
|
||||
const { _path } = this.modules;
|
||||
@@ -84,7 +84,7 @@ class HLCopy extends HLFilesystemOperation {
|
||||
// If parent exists and is a file, and a new name wasn't
|
||||
// specified, the intention must be to overwrite the file.
|
||||
if (
|
||||
! values.new_name &&
|
||||
!values.new_name &&
|
||||
await parent.exists() &&
|
||||
await parent.get('type') !== TYPE_DIRECTORY
|
||||
) {
|
||||
@@ -130,20 +130,20 @@ class HLCopy extends HLFilesystemOperation {
|
||||
|
||||
// NEXT: implement _verify_room with profiling
|
||||
const tracer = svc.get('traceService').tracer;
|
||||
await tracer.startActiveSpan(`fs:cp:verify-size-constraints`, async span => {
|
||||
await tracer.startActiveSpan('fs:cp:verify-size-constraints', async span => {
|
||||
const source_file = source.entry;
|
||||
const dest_fsentry = parent.entry;
|
||||
|
||||
let source_user = await get_user({id: source_file.user_id});
|
||||
let source_user = await get_user({ id: source_file.user_id });
|
||||
let dest_user = source_user.id !== dest_fsentry.user_id
|
||||
? await get_user({id: dest_fsentry.user_id})
|
||||
? await get_user({ id: dest_fsentry.user_id })
|
||||
: source_user ;
|
||||
const sizeService = svc.get('sizeService');
|
||||
let deset_usage = await sizeService.get_usage(dest_user.id);
|
||||
|
||||
const size = await source.fetchSize();
|
||||
const capacity = await sizeService.get_storage_capacity(dest_user.id);
|
||||
if(capacity - deset_usage - size < 0){
|
||||
if ( capacity - deset_usage - size < 0 ) {
|
||||
throw APIError.create('storage_limit_reached');
|
||||
}
|
||||
span.end();
|
||||
@@ -166,16 +166,16 @@ class HLCopy extends HLFilesystemOperation {
|
||||
let overwritten;
|
||||
if ( await dest.exists() ) {
|
||||
// condition: no overwrite behaviour specified
|
||||
if ( ! values.overwrite && ! values.dedupe_name ) {
|
||||
if ( !values.overwrite && !values.dedupe_name ) {
|
||||
throw APIError.create('item_with_same_name_exists', null, {
|
||||
entry_name: dest.entry.name
|
||||
entry_name: dest.entry.name,
|
||||
});
|
||||
}
|
||||
|
||||
if ( values.dedupe_name ) {
|
||||
const target_ext = _path.extname(target_name);
|
||||
const target_noext = _path.basename(target_name, target_ext);
|
||||
for ( let i=1 ;; i++ ) {
|
||||
for ( let i = 1 ;; i++ ) {
|
||||
const try_new_name = `${target_noext} (${i})${target_ext}`;
|
||||
const exists = await parent.hasChild(try_new_name);
|
||||
if ( ! exists ) {
|
||||
@@ -209,13 +209,13 @@ class HLCopy extends HLFilesystemOperation {
|
||||
parent,
|
||||
user: values.user,
|
||||
target_name,
|
||||
})
|
||||
});
|
||||
|
||||
await this.copied.awaitStableEntry();
|
||||
const response = await this.copied.getSafeEntry({ thumbnail: true });
|
||||
return {
|
||||
copied : response,
|
||||
overwritten
|
||||
copied: response,
|
||||
overwritten,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { HLFilesystemOperation } = require("./definitions");
|
||||
const { HLFilesystemOperation } = require('./definitions');
|
||||
const { chkperm } = require('../../helpers');
|
||||
const { LLRead } = require('../ll_operations/ll_read');
|
||||
const APIError = require('../../api/APIError');
|
||||
@@ -29,7 +29,7 @@ const APIError = require('../../api/APIError');
|
||||
class HLDataRead extends HLFilesystemOperation {
|
||||
static MODULES = {
|
||||
'stream': require('stream'),
|
||||
}
|
||||
};
|
||||
|
||||
async _run () {
|
||||
const { context } = this;
|
||||
@@ -53,7 +53,8 @@ class HLDataRead extends HLFilesystemOperation {
|
||||
|
||||
const ll_read = new LLRead();
|
||||
let stream = await ll_read.run({
|
||||
fsNode, user,
|
||||
fsNode,
|
||||
user,
|
||||
version_id,
|
||||
});
|
||||
|
||||
@@ -66,8 +67,8 @@ class HLDataRead extends HLFilesystemOperation {
|
||||
_stream_bytes_to_lines (stream) {
|
||||
const readline = require('readline');
|
||||
const rl = readline.createInterface({
|
||||
input: stream,
|
||||
terminal: false
|
||||
input: stream,
|
||||
terminal: false,
|
||||
});
|
||||
|
||||
const { PassThrough } = this.modules.stream;
|
||||
@@ -88,7 +89,7 @@ class HLDataRead extends HLFilesystemOperation {
|
||||
const { PassThrough } = this.modules.stream;
|
||||
const output_stream = new PassThrough();
|
||||
(async () => {
|
||||
for await (const line of stream) {
|
||||
for await ( const line of stream ) {
|
||||
output_stream.write(JSON.parse(line));
|
||||
}
|
||||
output_stream.end();
|
||||
@@ -98,5 +99,5 @@ class HLDataRead extends HLFilesystemOperation {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
HLDataRead
|
||||
HLDataRead,
|
||||
};
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
*/
|
||||
const { chkperm } = require('../../helpers');
|
||||
|
||||
const { RootNodeSelector, NodeChildSelector, NodePathSelector } = require("../node/selectors");
|
||||
const { RootNodeSelector, NodeChildSelector, NodePathSelector } = require('../node/selectors');
|
||||
const APIError = require('../../api/APIError');
|
||||
|
||||
const FSNodeParam = require('../../api/filesystem/FSNodeParam');
|
||||
const StringParam = require('../../api/filesystem/StringParam');
|
||||
const FlagParam = require("../../api/filesystem/FlagParam");
|
||||
const FlagParam = require('../../api/filesystem/FlagParam');
|
||||
const UserParam = require('../../api/filesystem/UserParam');
|
||||
const FSNodeContext = require('../FSNodeContext');
|
||||
const { OtelFeature } = require('../../traits/OtelFeature');
|
||||
@@ -50,16 +50,16 @@ class MkTree extends HLFilesystemOperation {
|
||||
├── q
|
||||
└── r
|
||||
└── s
|
||||
`
|
||||
`;
|
||||
|
||||
static PARAMETERS = {
|
||||
parent: new FSNodeParam('parent', { optional: true }),
|
||||
}
|
||||
};
|
||||
|
||||
static PROPERTIES = {
|
||||
leaves: () => [],
|
||||
directories_created: () => [],
|
||||
}
|
||||
};
|
||||
|
||||
async _run () {
|
||||
const { values, context } = this;
|
||||
@@ -94,31 +94,33 @@ class MkTree extends HLFilesystemOperation {
|
||||
// This is just a loop that goes through each part of the path
|
||||
// until it finds the first directory that doesn't exist yet.
|
||||
let i = 0;
|
||||
if ( parent_exists ) for ( ; i < dirs.length ; i++ ) {
|
||||
const dir = dirs[i];
|
||||
const currentParent = current;
|
||||
current = new NodeChildSelector(current, dir);
|
||||
if ( parent_exists ) {
|
||||
for ( ; i < dirs.length ; i++ ) {
|
||||
const dir = dirs[i];
|
||||
const currentParent = current;
|
||||
current = new NodeChildSelector(current, dir);
|
||||
|
||||
const maybe_dir = await fs.node(current);
|
||||
const maybe_dir = await fs.node(current);
|
||||
|
||||
if ( maybe_dir.isRoot ) continue;
|
||||
if ( await maybe_dir.isUserDirectory() ) continue;
|
||||
if ( maybe_dir.isRoot ) continue;
|
||||
if ( await maybe_dir.isUserDirectory() ) continue;
|
||||
|
||||
if ( await maybe_dir.exists() ) {
|
||||
if ( await maybe_dir.exists() ) {
|
||||
|
||||
if ( await maybe_dir.get('type') !== FSNodeContext.TYPE_DIRECTORY ) {
|
||||
throw APIError.create('dest_is_not_a_directory');
|
||||
if ( await maybe_dir.get('type') !== FSNodeContext.TYPE_DIRECTORY ) {
|
||||
throw APIError.create('dest_is_not_a_directory');
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
continue;
|
||||
current = currentParent;
|
||||
parent_exists = false;
|
||||
break;
|
||||
}
|
||||
|
||||
current = currentParent;
|
||||
parent_exists = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( parent_did_exist && ! parent_exists ) {
|
||||
if ( parent_did_exist && !parent_exists ) {
|
||||
const node = await fs.node(current);
|
||||
const has_perm = await chkperm(await node.get('entry'), actor.type.user.id, 'write');
|
||||
if ( ! has_perm ) throw APIError.create('permission_denied');
|
||||
@@ -145,7 +147,7 @@ class MkTree extends HLFilesystemOperation {
|
||||
parent: await fs.node(currentParent),
|
||||
name: current.name,
|
||||
actor,
|
||||
})
|
||||
});
|
||||
|
||||
current = node.selector;
|
||||
|
||||
@@ -191,9 +193,7 @@ class QuickMkdir extends HLFilesystemOperation {
|
||||
currentSpan.setAttribute('parent', parent.selector.describe());
|
||||
}
|
||||
|
||||
|
||||
|
||||
for ( let i=0 ; i < dirs.length ; i++ ) {
|
||||
for ( let i = 0 ; i < dirs.length ; i++ ) {
|
||||
const dir = dirs[i];
|
||||
const currentParent = current;
|
||||
current = new NodeChildSelector(current, dir);
|
||||
@@ -203,7 +203,7 @@ class QuickMkdir extends HLFilesystemOperation {
|
||||
parent: await fs.node(currentParent),
|
||||
name: current.name,
|
||||
actor,
|
||||
})
|
||||
});
|
||||
|
||||
current = node.selector;
|
||||
|
||||
@@ -224,7 +224,7 @@ class HLMkdir extends HLFilesystemOperation {
|
||||
- overwrite existing files
|
||||
- dedupe names
|
||||
- create shortcuts
|
||||
`
|
||||
`;
|
||||
|
||||
static PARAMETERS = {
|
||||
parent: new FSNodeParam('parent', { optional: true }),
|
||||
@@ -238,18 +238,18 @@ class HLMkdir extends HLFilesystemOperation {
|
||||
|
||||
static MODULES = {
|
||||
_path: require('path'),
|
||||
}
|
||||
};
|
||||
|
||||
static PROPERTIES = {
|
||||
parent_directories_created: () => [],
|
||||
}
|
||||
};
|
||||
|
||||
static FEATURES = [
|
||||
new OtelFeature([
|
||||
'_get_existing_parent',
|
||||
'_create_parents',
|
||||
]),
|
||||
]
|
||||
];
|
||||
|
||||
async _run () {
|
||||
const { context, values } = this;
|
||||
@@ -290,7 +290,7 @@ class HLMkdir extends HLFilesystemOperation {
|
||||
if ( top_parent.isRoot ) {
|
||||
// root directory is read-only
|
||||
throw APIError.create('forbidden', null, {
|
||||
message: 'Cannot create directories in the root directory.'
|
||||
message: 'Cannot create directories in the root directory.',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -306,9 +306,7 @@ class HLMkdir extends HLFilesystemOperation {
|
||||
const has_perm = await chkperm(await parent_node.get('entry'), user_id, 'write');
|
||||
if ( ! has_perm ) throw APIError.create('permission_denied');
|
||||
|
||||
const existing = await fs.node(
|
||||
new NodeChildSelector(parent_node.selector, target_basename)
|
||||
);
|
||||
const existing = await fs.node(new NodeChildSelector(parent_node.selector, target_basename));
|
||||
|
||||
await existing.fetchEntry();
|
||||
|
||||
@@ -328,7 +326,7 @@ class HLMkdir extends HLFilesystemOperation {
|
||||
else if ( dedupe_name ) {
|
||||
const fs = context.get('services').get('filesystem');
|
||||
const parent_selector = parent_node.selector;
|
||||
for ( let i=1 ;; i++ ) {
|
||||
for ( let i = 1 ;; i++ ) {
|
||||
let try_new_name = `${target_basename} (${i})`;
|
||||
const selector = new NodeChildSelector(parent_selector, try_new_name);
|
||||
const exists = await parent_node.provider.quick_check({
|
||||
@@ -411,7 +409,7 @@ class HLMkdir extends HLFilesystemOperation {
|
||||
let remaining_path = _path.dirname(values.path).split('/').filter(Boolean);
|
||||
{
|
||||
const parts = remaining_path.slice();
|
||||
for (;;) {
|
||||
for ( ;; ) {
|
||||
if ( remaining_path.length === 0 ) {
|
||||
return deepest_existing;
|
||||
}
|
||||
@@ -447,7 +445,7 @@ class HLMkdir extends HLFilesystemOperation {
|
||||
: target_dirname.split('/').filter(Boolean);
|
||||
|
||||
let current = parent_node.selector;
|
||||
for ( let i=0 ; i < dirs.length ; i++ ) {
|
||||
for ( let i = 0 ; i < dirs.length ; i++ ) {
|
||||
current = new NodeChildSelector(current, dirs[i]);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,22 +16,22 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const FSNodeParam = require("../../api/filesystem/FSNodeParam");
|
||||
const StringParam = require("../../api/filesystem/StringParam");
|
||||
const { HLFilesystemOperation } = require("./definitions");
|
||||
const APIError = require("../../api/APIError");
|
||||
const { TYPE_DIRECTORY } = require("../FSNodeContext");
|
||||
const FSNodeParam = require('../../api/filesystem/FSNodeParam');
|
||||
const StringParam = require('../../api/filesystem/StringParam');
|
||||
const { HLFilesystemOperation } = require('./definitions');
|
||||
const APIError = require('../../api/APIError');
|
||||
const { TYPE_DIRECTORY } = require('../FSNodeContext');
|
||||
|
||||
class HLMkLink extends HLFilesystemOperation {
|
||||
static PARAMETERS = {
|
||||
parent: new FSNodeParam('symlink'),
|
||||
name: new StringParam('name'),
|
||||
target: new StringParam('target'),
|
||||
}
|
||||
};
|
||||
|
||||
static MODULES = {
|
||||
path: require('node:path'),
|
||||
}
|
||||
};
|
||||
|
||||
async _run () {
|
||||
const { context, values } = this;
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const APIError = require("../../api/APIError");
|
||||
const FSNodeParam = require("../../api/filesystem/FSNodeParam");
|
||||
const FlagParam = require("../../api/filesystem/FlagParam");
|
||||
const StringParam = require("../../api/filesystem/StringParam");
|
||||
const { TYPE_DIRECTORY } = require("../FSNodeContext");
|
||||
const { HLFilesystemOperation } = require("./definitions");
|
||||
const APIError = require('../../api/APIError');
|
||||
const FSNodeParam = require('../../api/filesystem/FSNodeParam');
|
||||
const FlagParam = require('../../api/filesystem/FlagParam');
|
||||
const StringParam = require('../../api/filesystem/StringParam');
|
||||
const { TYPE_DIRECTORY } = require('../FSNodeContext');
|
||||
const { HLFilesystemOperation } = require('./definitions');
|
||||
|
||||
class HLMkShortcut extends HLFilesystemOperation {
|
||||
static PARAMETERS = {
|
||||
@@ -30,14 +30,14 @@ class HLMkShortcut extends HLFilesystemOperation {
|
||||
target: new FSNodeParam('target'),
|
||||
|
||||
dedupe_name: new FlagParam('dedupe_name', { optional: true }),
|
||||
}
|
||||
};
|
||||
|
||||
static MODULES = {
|
||||
path: require('node:path'),
|
||||
}
|
||||
};
|
||||
|
||||
async _run () {
|
||||
console.log('HLMKSHORTCUT IS HAPPENING')
|
||||
console.log('HLMKSHORTCUT IS HAPPENING');
|
||||
const { context, values } = this;
|
||||
const fs = context.get('services').get('filesystem');
|
||||
|
||||
@@ -50,7 +50,7 @@ class HLMkShortcut extends HLFilesystemOperation {
|
||||
|
||||
if ( ! name ) {
|
||||
dedupe_name = true;
|
||||
name = 'Shortcut to ' + await target.get('name');
|
||||
name = `Shortcut to ${ await target.get('name')}`;
|
||||
}
|
||||
|
||||
{
|
||||
@@ -79,7 +79,7 @@ class HLMkShortcut extends HLFilesystemOperation {
|
||||
|
||||
const name_ext = this.modules.path.extname(name);
|
||||
const name_noext = this.modules.path.basename(name, name_ext);
|
||||
for ( let i=1 ;; i++ ) {
|
||||
for ( let i = 1 ;; i++ ) {
|
||||
const try_new_name = `${name_noext} (${i})${name_ext}`;
|
||||
const try_dest = await parent.getChild(try_new_name);
|
||||
if ( ! await try_dest.exists() ) {
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const { DB_READ } = require("../../services/database/consts");
|
||||
const { Context } = require("../../util/context");
|
||||
const { NodeUIDSelector } = require("../node/selectors");
|
||||
const { HLFilesystemOperation } = require("./definitions");
|
||||
const { DB_READ } = require('../../services/database/consts');
|
||||
const { Context } = require('../../util/context');
|
||||
const { NodeUIDSelector } = require('../node/selectors');
|
||||
const { HLFilesystemOperation } = require('./definitions');
|
||||
|
||||
class HLNameSearch extends HLFilesystemOperation {
|
||||
async _run () {
|
||||
@@ -31,20 +31,18 @@ class HLNameSearch extends HLFilesystemOperation {
|
||||
.get(DB_READ, 'fs.namesearch');
|
||||
|
||||
term = term.replace(/%/g, '');
|
||||
term = '%' + term + '%';
|
||||
|
||||
term = `%${ term }%`;
|
||||
|
||||
// Only user actors can do this, because the permission
|
||||
// system would otherwise slow things down
|
||||
if ( ! actor.type.user ) return [];
|
||||
|
||||
const results = await db.read(
|
||||
`SELECT uuid FROM fsentries WHERE name LIKE ? AND ` +
|
||||
`user_id = ? LIMIT 50`,
|
||||
[term, actor.type.user.id]
|
||||
);
|
||||
|
||||
const results = await db.read('SELECT uuid FROM fsentries WHERE name LIKE ? AND ' +
|
||||
'user_id = ? LIMIT 50',
|
||||
[term, actor.type.user.id]);
|
||||
|
||||
const uuids = results.map(v => v.uuid);
|
||||
|
||||
|
||||
const fsnodes = await Promise.all(uuids.map(async uuid => {
|
||||
return await svc_fs.node(new NodeUIDSelector(uuid));
|
||||
}));
|
||||
|
||||
@@ -16,22 +16,22 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const APIError = require("../../api/APIError");
|
||||
const { LLRead } = require("../ll_operations/ll_read");
|
||||
const { HLFilesystemOperation } = require("./definitions");
|
||||
const APIError = require('../../api/APIError');
|
||||
const { LLRead } = require('../ll_operations/ll_read');
|
||||
const { HLFilesystemOperation } = require('./definitions');
|
||||
|
||||
class HLRead extends HLFilesystemOperation {
|
||||
static CONCERN = 'filesystem';
|
||||
static MODULES = {
|
||||
'stream': require('stream'),
|
||||
}
|
||||
};
|
||||
|
||||
async _run () {
|
||||
const {
|
||||
fsNode, actor,
|
||||
line_count, byte_count,
|
||||
offset,
|
||||
version_id, range
|
||||
version_id, range,
|
||||
} = this.values;
|
||||
|
||||
if ( ! await fsNode.exists() ) {
|
||||
@@ -40,12 +40,13 @@ class HLRead extends HLFilesystemOperation {
|
||||
|
||||
const ll_read = new LLRead();
|
||||
let stream = await ll_read.run({
|
||||
fsNode, actor,
|
||||
fsNode,
|
||||
actor,
|
||||
version_id,
|
||||
range,
|
||||
...(byte_count !== undefined ? {
|
||||
offset: offset ?? 0,
|
||||
length: byte_count
|
||||
length: byte_count,
|
||||
} : {}),
|
||||
});
|
||||
|
||||
@@ -64,8 +65,8 @@ class HLRead extends HLFilesystemOperation {
|
||||
_wrap_stream_line_count (stream, line_count) {
|
||||
const readline = require('readline');
|
||||
const rl = readline.createInterface({
|
||||
input: stream,
|
||||
terminal: false
|
||||
input: stream,
|
||||
terminal: false,
|
||||
});
|
||||
|
||||
const { PassThrough } = this.modules.stream;
|
||||
@@ -75,11 +76,11 @@ class HLRead extends HLFilesystemOperation {
|
||||
let lines_read = 0;
|
||||
new Promise((resolve, reject) => {
|
||||
rl.on('line', (line) => {
|
||||
if(lines_read++ >= line_count){
|
||||
if ( lines_read++ >= line_count ) {
|
||||
return rl.close();
|
||||
}
|
||||
|
||||
output_stream.write(lines_read > 1 ? '\r\n' + line : line);
|
||||
output_stream.write(lines_read > 1 ? `\r\n${ line}` : line);
|
||||
});
|
||||
rl.on('error', () => {
|
||||
console.log('error');
|
||||
@@ -94,5 +95,5 @@ class HLRead extends HLFilesystemOperation {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
HLRead
|
||||
HLRead,
|
||||
};
|
||||
|
||||
@@ -16,19 +16,19 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const APIError = require("../../api/APIError");
|
||||
const { Context } = require("../../util/context");
|
||||
const { stream_to_buffer } = require("../../util/streamutil");
|
||||
const { ECMAP } = require("../ECMAP");
|
||||
const { TYPE_DIRECTORY, TYPE_SYMLINK } = require("../FSNodeContext");
|
||||
const { LLListUsers } = require("../ll_operations/ll_listusers");
|
||||
const { LLReadDir } = require("../ll_operations/ll_readdir");
|
||||
const { LLReadShares } = require("../ll_operations/ll_readshares");
|
||||
const { HLFilesystemOperation } = require("./definitions");
|
||||
const APIError = require('../../api/APIError');
|
||||
const { Context } = require('../../util/context');
|
||||
const { stream_to_buffer } = require('../../util/streamutil');
|
||||
const { ECMAP } = require('../ECMAP');
|
||||
const { TYPE_DIRECTORY, TYPE_SYMLINK } = require('../FSNodeContext');
|
||||
const { LLListUsers } = require('../ll_operations/ll_listusers');
|
||||
const { LLReadDir } = require('../ll_operations/ll_readdir');
|
||||
const { LLReadShares } = require('../ll_operations/ll_readshares');
|
||||
const { HLFilesystemOperation } = require('./definitions');
|
||||
|
||||
class HLReadDir extends HLFilesystemOperation {
|
||||
static CONCERN = 'filesystem';
|
||||
async _run() {
|
||||
async _run () {
|
||||
return ECMAP.arun(async () => {
|
||||
const ecmap = Context.get(ECMAP.SYMBOL);
|
||||
ecmap.store_fsNodeContext(this.values.subject);
|
||||
@@ -61,15 +61,14 @@ class HLReadDir extends HLFilesystemOperation {
|
||||
}
|
||||
throw APIError.create('readdir_of_non_directory');
|
||||
}
|
||||
|
||||
|
||||
let children;
|
||||
|
||||
this.log.debug('READDIR',
|
||||
{
|
||||
userdir: await subject.isUserDirectory(),
|
||||
namediff: await subject.get('name') !== user.username
|
||||
}
|
||||
);
|
||||
{
|
||||
userdir: await subject.isUserDirectory(),
|
||||
namediff: await subject.get('name') !== user.username,
|
||||
});
|
||||
if ( subject.isRoot ) {
|
||||
const ll_listusers = new LLListUsers();
|
||||
children = await ll_listusers.run(this.values);
|
||||
@@ -100,7 +99,7 @@ class HLReadDir extends HLFilesystemOperation {
|
||||
]);
|
||||
}
|
||||
const entry = await child.getSafeEntry();
|
||||
if ( ! no_thumbs && entry.associated_app ) {
|
||||
if ( !no_thumbs && entry.associated_app ) {
|
||||
const svc_appIcon = this.context.get('services').get('app-icon');
|
||||
const icon_result = await svc_appIcon.get_icon_stream({
|
||||
app_icon: entry.associated_app.icon,
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const APIError = require("../../api/APIError");
|
||||
const { chkperm } = require("../../helpers");
|
||||
const { TYPE_DIRECTORY } = require("../FSNodeContext");
|
||||
const { LLRmDir } = require("../ll_operations/ll_rmdir");
|
||||
const { LLRmNode } = require("../ll_operations/ll_rmnode");
|
||||
const { HLFilesystemOperation } = require("./definitions");
|
||||
const APIError = require('../../api/APIError');
|
||||
const { chkperm } = require('../../helpers');
|
||||
const { TYPE_DIRECTORY } = require('../FSNodeContext');
|
||||
const { LLRmDir } = require('../ll_operations/ll_rmdir');
|
||||
const { LLRmNode } = require('../ll_operations/ll_rmnode');
|
||||
const { HLFilesystemOperation } = require('./definitions');
|
||||
|
||||
class HLRemove extends HLFilesystemOperation {
|
||||
static PARAMETERS = {
|
||||
@@ -29,7 +29,7 @@ class HLRemove extends HLFilesystemOperation {
|
||||
user: {},
|
||||
recursive: {},
|
||||
descendants_only: {},
|
||||
}
|
||||
};
|
||||
|
||||
async _run () {
|
||||
const { target, user } = this.values;
|
||||
|
||||
@@ -16,16 +16,16 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { Context } = require("../../util/context");
|
||||
const { HLFilesystemOperation } = require("./definitions");
|
||||
const { Context } = require('../../util/context');
|
||||
const { HLFilesystemOperation } = require('./definitions');
|
||||
const APIError = require('../../api/APIError');
|
||||
const { ECMAP } = require("../ECMAP");
|
||||
const { NodeUIDSelector } = require("../node/selectors");
|
||||
const { ECMAP } = require('../ECMAP');
|
||||
const { NodeUIDSelector } = require('../node/selectors');
|
||||
|
||||
class HLStat extends HLFilesystemOperation {
|
||||
static MODULES = {
|
||||
['mime-types']: require('mime-types'),
|
||||
}
|
||||
};
|
||||
|
||||
async _run () {
|
||||
return await ECMAP.arun(async () => {
|
||||
@@ -46,12 +46,12 @@ class HLStat extends HLFilesystemOperation {
|
||||
return_versions,
|
||||
return_size,
|
||||
} = this.values;
|
||||
|
||||
|
||||
const maybe_uid_selector = subject.get_selector_of_type(NodeUIDSelector);
|
||||
|
||||
|
||||
// users created before 2025-07-30 might have fsentries with NULL paths.
|
||||
// we can remove this check once that is fixed.
|
||||
const user_unix_ts = Number((''+Date.parse(Context.get('actor')?.type?.user?.timestamp)).slice(0, -3));
|
||||
const user_unix_ts = Number((`${Date.parse(Context.get('actor')?.type?.user?.timestamp)}`).slice(0, -3));
|
||||
const paths_are_fine = user_unix_ts >= 1722385593;
|
||||
|
||||
if ( maybe_uid_selector || paths_are_fine ) {
|
||||
@@ -67,7 +67,7 @@ class HLStat extends HLFilesystemOperation {
|
||||
}
|
||||
|
||||
// file not found
|
||||
if( ! subject.found ) throw APIError.create('subject_does_not_exist');
|
||||
if ( ! subject.found ) throw APIError.create('subject_does_not_exist');
|
||||
|
||||
await subject.fetchOwner();
|
||||
|
||||
@@ -80,20 +80,20 @@ class HLStat extends HLFilesystemOperation {
|
||||
|
||||
// TODO: why is this specific to stat?
|
||||
const mime = this.require('mime-types');
|
||||
const contentType = mime.contentType(subject.entry.name)
|
||||
const contentType = mime.contentType(subject.entry.name);
|
||||
subject.entry.type = contentType ? contentType : null;
|
||||
|
||||
if (return_size) await subject.fetchSize(user);
|
||||
if (return_subdomains) await subject.fetchSubdomains(user)
|
||||
if (return_shares || return_permissions) {
|
||||
if ( return_size ) await subject.fetchSize(user);
|
||||
if ( return_subdomains ) await subject.fetchSubdomains(user);
|
||||
if ( return_shares || return_permissions ) {
|
||||
await subject.fetchShares();
|
||||
}
|
||||
if (return_versions) await subject.fetchVersions();
|
||||
if ( return_versions ) await subject.fetchVersions();
|
||||
|
||||
return await subject.getSafeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
HLStat
|
||||
HLStat,
|
||||
};
|
||||
|
||||
@@ -20,10 +20,10 @@ const _path = require('path');
|
||||
|
||||
/**
|
||||
* Puter paths look like any of the following:
|
||||
*
|
||||
*
|
||||
* Absolute path: /user/dir1/dir2/file
|
||||
* From UID: AAAA-BBBB-CCCC-DDDD/../a/b/c
|
||||
*
|
||||
*
|
||||
* The difference between an absolute path and a UID-relative path
|
||||
* is the leading forward-slash character.
|
||||
*/
|
||||
@@ -44,9 +44,11 @@ class PuterPath {
|
||||
this.normUnix = _path.normalize(text);
|
||||
this.normFlat =
|
||||
(this.normUnix.endsWith('/') && this.normUnix.length > 1)
|
||||
? this.normUnix.slice(0, -1) : this.normUnix;
|
||||
? this.normUnix.slice(0, -1) : this.normUnix;
|
||||
}
|
||||
get text () {
|
||||
return this.text_;
|
||||
}
|
||||
get text () { return this.text_; }
|
||||
|
||||
isRoot () {
|
||||
if ( this.normFlat === '/' ) return true;
|
||||
@@ -61,7 +63,7 @@ class PuterPath {
|
||||
}
|
||||
|
||||
isFromUID () {
|
||||
return ! this.isAbsolute();
|
||||
return !this.isAbsolute();
|
||||
}
|
||||
|
||||
get reference () {
|
||||
|
||||
@@ -16,10 +16,11 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { BaseOperation } = require("../../services/OperationTraceService");
|
||||
const { BaseOperation } = require('../../services/OperationTraceService');
|
||||
|
||||
class LLFilesystemOperation extends BaseOperation {}
|
||||
class LLFilesystemOperation extends BaseOperation {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
LLFilesystemOperation
|
||||
LLFilesystemOperation,
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ class LLCopy extends LLFilesystemOperation {
|
||||
static MODULES = {
|
||||
_path: require('path'),
|
||||
uuidv4: require('uuid').v4,
|
||||
}
|
||||
};
|
||||
|
||||
async _run () {
|
||||
const { _path, uuidv4 } = this.modules;
|
||||
@@ -36,7 +36,7 @@ class LLCopy extends LLFilesystemOperation {
|
||||
const svc_event = svc.get('event');
|
||||
|
||||
const uuid = uuidv4();
|
||||
const ts = Math.round(Date.now()/1000);
|
||||
const ts = Math.round(Date.now() / 1000);
|
||||
|
||||
this.field('target-uid', uuid);
|
||||
this.field('source', source.selector.describe());
|
||||
|
||||
@@ -16,15 +16,15 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { RootNodeSelector, NodeChildSelector } = require("../node/selectors");
|
||||
const { LLFilesystemOperation } = require("./definitions");
|
||||
const { RootNodeSelector, NodeChildSelector } = require('../node/selectors');
|
||||
const { LLFilesystemOperation } = require('./definitions');
|
||||
|
||||
class LLListUsers extends LLFilesystemOperation {
|
||||
static description = `
|
||||
List user directories which are relevant to the
|
||||
current actor.
|
||||
`;
|
||||
|
||||
|
||||
async _run () {
|
||||
const { context } = this;
|
||||
const svc = context.get('services');
|
||||
@@ -35,19 +35,16 @@ class LLListUsers extends LLFilesystemOperation {
|
||||
const issuers = await svc_permission.list_user_permission_issuers(user);
|
||||
|
||||
const nodes = [];
|
||||
|
||||
nodes.push(await svc_fs.node(new NodeChildSelector(
|
||||
new RootNodeSelector(),
|
||||
user.username,
|
||||
)));
|
||||
|
||||
nodes.push(await svc_fs.node(new NodeChildSelector(new RootNodeSelector(),
|
||||
user.username)));
|
||||
|
||||
for ( const issuer of issuers ) {
|
||||
const node = await svc_fs.node(new NodeChildSelector(
|
||||
new RootNodeSelector(),
|
||||
issuer.username));
|
||||
const node = await svc_fs.node(new NodeChildSelector(new RootNodeSelector(),
|
||||
issuer.username));
|
||||
nodes.push(node);
|
||||
}
|
||||
|
||||
|
||||
return nodes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { LLFilesystemOperation } = require("./definitions");
|
||||
const { LLFilesystemOperation } = require('./definitions');
|
||||
|
||||
class LLMove extends LLFilesystemOperation {
|
||||
static MODULES = {
|
||||
_path: require('path'),
|
||||
}
|
||||
};
|
||||
|
||||
async _run () {
|
||||
const { context } = this;
|
||||
|
||||
@@ -31,7 +31,7 @@ const checkACLForRead = async (aclService, actor, fsNode, skip = false) => {
|
||||
if ( skip ) {
|
||||
return;
|
||||
}
|
||||
if ( !await aclService.check(actor, fsNode, 'read') ) {
|
||||
if ( ! await aclService.check(actor, fsNode, 'read') ) {
|
||||
throw await aclService.get_safe_acl_error(actor, fsNode, 'read');
|
||||
}
|
||||
};
|
||||
@@ -43,7 +43,7 @@ const typeCheckForRead = async (fsNode) => {
|
||||
|
||||
class LLRead extends LLFilesystemOperation {
|
||||
static CONCERN = 'filesystem';
|
||||
async _run({ fsNode, no_acl, actor, offset, length, range, version_id } = {}){
|
||||
async _run ({ fsNode, no_acl, actor, offset, length, range, version_id } = {}) {
|
||||
// extract services from context
|
||||
const aclService = Context.get('services').get('acl');
|
||||
const db = Context.get('services')
|
||||
@@ -51,7 +51,7 @@ class LLRead extends LLFilesystemOperation {
|
||||
const fileCacheService = Context.get('services').get('file-cache');
|
||||
|
||||
// validate input
|
||||
if ( !await fsNode.exists() ){
|
||||
if ( ! await fsNode.exists() ) {
|
||||
throw APIError.create('subject_does_not_exist');
|
||||
}
|
||||
// validate initial node
|
||||
@@ -86,7 +86,7 @@ class LLRead extends LLFilesystemOperation {
|
||||
[Date.now() / 1000, await fsNode.get('mysql-id')]);
|
||||
|
||||
const ownerId = await fsNode.get('user_id');
|
||||
const chargedActor = actor? actor: new Actor({
|
||||
const chargedActor = actor ? actor : new Actor({
|
||||
type: new UserActorType({
|
||||
user: await get_user({ id: ownerId }),
|
||||
}),
|
||||
@@ -141,7 +141,7 @@ class LLRead extends LLFilesystemOperation {
|
||||
|
||||
// Meter ingress
|
||||
const size = await (async () => {
|
||||
if ( range ){
|
||||
if ( range ) {
|
||||
const match = range.match(/bytes=(\d+)-(\d+)/);
|
||||
if ( match ) {
|
||||
const start = parseInt(match[1], 10);
|
||||
@@ -157,7 +157,7 @@ class LLRead extends LLFilesystemOperation {
|
||||
meteringService.incrementUsage(chargedActor, 'filesystem:egress:bytes', size);
|
||||
|
||||
// cache if whole file read
|
||||
if ( !has_range ) {
|
||||
if ( ! has_range ) {
|
||||
// only cache for non-memoryfs providers
|
||||
if ( ! (fsNode.provider instanceof MemoryFSProvider) ) {
|
||||
const res = await fileCacheService.maybe_store(fsNode, stream);
|
||||
|
||||
@@ -16,17 +16,17 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const APIError = require("../../api/APIError");
|
||||
const fsCapabilities = require("../definitions/capabilities");
|
||||
const { ECMAP } = require("../ECMAP");
|
||||
const { TYPE_SYMLINK } = require("../FSNodeContext");
|
||||
const { RootNodeSelector } = require("../node/selectors");
|
||||
const { NodeUIDSelector, NodeChildSelector } = require("../node/selectors");
|
||||
const { LLFilesystemOperation } = require("./definitions");
|
||||
const APIError = require('../../api/APIError');
|
||||
const fsCapabilities = require('../definitions/capabilities');
|
||||
const { ECMAP } = require('../ECMAP');
|
||||
const { TYPE_SYMLINK } = require('../FSNodeContext');
|
||||
const { RootNodeSelector } = require('../node/selectors');
|
||||
const { NodeUIDSelector, NodeChildSelector } = require('../node/selectors');
|
||||
const { LLFilesystemOperation } = require('./definitions');
|
||||
|
||||
class LLReadDir extends LLFilesystemOperation {
|
||||
static CONCERN = 'filesystem';
|
||||
async _run() {
|
||||
async _run () {
|
||||
return ECMAP.arun(async () => {
|
||||
return await this.__run();
|
||||
});
|
||||
@@ -65,10 +65,8 @@ class LLReadDir extends LLFilesystemOperation {
|
||||
if ( subject.isRoot ) {
|
||||
if ( ! actor.type.user ) return [];
|
||||
return [
|
||||
await svc_fs.node(new NodeChildSelector(
|
||||
new RootNodeSelector(),
|
||||
actor.type.user.username,
|
||||
))
|
||||
await svc_fs.node(new NodeChildSelector(new RootNodeSelector(),
|
||||
actor.type.user.username)),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -76,12 +74,12 @@ class LLReadDir extends LLFilesystemOperation {
|
||||
|
||||
// UUID Mode
|
||||
if ( capabilities.has(fsCapabilities.READDIR_UUID_MODE) ) {
|
||||
this.checkpoint('readdir uuid mode')
|
||||
this.checkpoint('readdir uuid mode');
|
||||
const child_uuids = await subject.provider.readdir({
|
||||
context,
|
||||
node: subject,
|
||||
});
|
||||
this.checkpoint('after get direct descendants')
|
||||
this.checkpoint('after get direct descendants');
|
||||
const children = await Promise.all(child_uuids.map(async uuid => {
|
||||
return await svc_fs.node(new NodeUIDSelector(uuid));
|
||||
}));
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { get_user } = require("../../helpers");
|
||||
const { MANAGE_PERM_PREFIX } = require("../../services/auth/permissionConts.mjs");
|
||||
const { PermissionUtil } = require("../../services/auth/permissionUtils.mjs");
|
||||
const { DB_WRITE } = require("../../services/database/consts");
|
||||
const { NodeUIDSelector } = require("../node/selectors");
|
||||
const { LLFilesystemOperation } = require("./definitions");
|
||||
const { LLReadDir } = require("./ll_readdir");
|
||||
const { get_user } = require('../../helpers');
|
||||
const { MANAGE_PERM_PREFIX } = require('../../services/auth/permissionConts.mjs');
|
||||
const { PermissionUtil } = require('../../services/auth/permissionUtils.mjs');
|
||||
const { DB_WRITE } = require('../../services/database/consts');
|
||||
const { NodeUIDSelector } = require('../node/selectors');
|
||||
const { LLFilesystemOperation } = require('./definitions');
|
||||
const { LLReadDir } = require('./ll_readdir');
|
||||
|
||||
class LLReadShares extends LLFilesystemOperation {
|
||||
static description = `
|
||||
@@ -34,7 +34,7 @@ class LLReadShares extends LLFilesystemOperation {
|
||||
will not be traversed.
|
||||
`;
|
||||
|
||||
async _run() {
|
||||
async _run () {
|
||||
const { subject, user, actor } = this.values;
|
||||
|
||||
const svc = this.context.get('services');
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const APIError = require("../../api/APIError");
|
||||
const { MemoryFSProvider } = require("../../modules/puterfs/customfs/MemoryFSProvider");
|
||||
const { ParallelTasks } = require("../../util/otelutil");
|
||||
const FSNodeContext = require("../FSNodeContext");
|
||||
const { NodeUIDSelector } = require("../node/selectors");
|
||||
const { LLFilesystemOperation } = require("./definitions");
|
||||
const APIError = require('../../api/APIError');
|
||||
const { MemoryFSProvider } = require('../../modules/puterfs/customfs/MemoryFSProvider');
|
||||
const { ParallelTasks } = require('../../util/otelutil');
|
||||
const FSNodeContext = require('../FSNodeContext');
|
||||
const { NodeUIDSelector } = require('../node/selectors');
|
||||
const { LLFilesystemOperation } = require('./definitions');
|
||||
const { LLRmNode } = require('./ll_rmnode');
|
||||
|
||||
class LLRmDir extends LLFilesystemOperation {
|
||||
@@ -32,7 +32,7 @@ class LLRmDir extends LLFilesystemOperation {
|
||||
actor,
|
||||
descendants_only,
|
||||
recursive,
|
||||
|
||||
|
||||
// internal use only - not for clients
|
||||
ignore_not_empty,
|
||||
|
||||
@@ -54,7 +54,7 @@ class LLRmDir extends LLFilesystemOperation {
|
||||
}
|
||||
}
|
||||
|
||||
if ( await target.get('immutable') && ! descendants_only ) {
|
||||
if ( await target.get('immutable') && !descendants_only ) {
|
||||
throw APIError.create('immutable');
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ class LLRmDir extends LLFilesystemOperation {
|
||||
node: target,
|
||||
});
|
||||
|
||||
if ( children.length > 0 && ! recursive && ! ignore_not_empty ) {
|
||||
if ( children.length > 0 && !recursive && !ignore_not_empty ) {
|
||||
throw APIError.create('not_empty');
|
||||
}
|
||||
|
||||
@@ -72,17 +72,13 @@ class LLRmDir extends LLFilesystemOperation {
|
||||
const tasks = new ParallelTasks({ tracer, max: max_tasks });
|
||||
|
||||
for ( const child_uuid of children ) {
|
||||
tasks.add(`fs:rm:rm-child`, async () => {
|
||||
const child_node = await fs.node(
|
||||
new NodeUIDSelector(child_uuid)
|
||||
);
|
||||
tasks.add('fs:rm:rm-child', async () => {
|
||||
const child_node = await fs.node(new NodeUIDSelector(child_uuid));
|
||||
const type = await child_node.get('type');
|
||||
if ( type === FSNodeContext.TYPE_DIRECTORY ) {
|
||||
const ll_rm = new LLRmDir();
|
||||
await ll_rm.run({
|
||||
target: await fs.node(
|
||||
new NodeUIDSelector(child_uuid),
|
||||
),
|
||||
target: await fs.node(new NodeUIDSelector(child_uuid)),
|
||||
user,
|
||||
recursive: true,
|
||||
descendants_only: false,
|
||||
@@ -92,9 +88,7 @@ class LLRmDir extends LLFilesystemOperation {
|
||||
} else {
|
||||
const ll_rm = new LLRmNode();
|
||||
await ll_rm.run({
|
||||
target: await fs.node(
|
||||
new NodeUIDSelector(child_uuid),
|
||||
),
|
||||
target: await fs.node(new NodeUIDSelector(child_uuid)),
|
||||
user,
|
||||
});
|
||||
}
|
||||
@@ -105,23 +99,23 @@ class LLRmDir extends LLFilesystemOperation {
|
||||
|
||||
// TODO (xiaochen): consolidate these two branches
|
||||
if ( target.provider instanceof MemoryFSProvider ) {
|
||||
await target.provider.rmdir( {
|
||||
await target.provider.rmdir({
|
||||
context,
|
||||
node: target,
|
||||
options: {
|
||||
recursive,
|
||||
descendants_only,
|
||||
},
|
||||
} );
|
||||
});
|
||||
} else {
|
||||
if ( ! descendants_only ) {
|
||||
await target.provider.rmdir( {
|
||||
await target.provider.rmdir({
|
||||
context,
|
||||
node: target,
|
||||
options: {
|
||||
ignore_not_empty: true,
|
||||
},
|
||||
} );
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { LLFilesystemOperation } = require("./definitions");
|
||||
const { LLFilesystemOperation } = require('./definitions');
|
||||
|
||||
class LLRmNode extends LLFilesystemOperation {
|
||||
async _run () {
|
||||
@@ -24,7 +24,7 @@ class LLRmNode extends LLFilesystemOperation {
|
||||
|
||||
const { context } = this;
|
||||
|
||||
const svc_event = context.get('services').get("event");
|
||||
const svc_event = context.get('services').get('event');
|
||||
|
||||
// Access Control
|
||||
{
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { LLFilesystemOperation } = require("./definitions");
|
||||
const APIError = require("../../api/APIError");
|
||||
const { LLFilesystemOperation } = require('./definitions');
|
||||
const APIError = require('../../api/APIError');
|
||||
|
||||
/**
|
||||
* The "overwrite" write operation.
|
||||
*
|
||||
*
|
||||
* This operation is used to write a file to an existing path.
|
||||
*
|
||||
*
|
||||
* @extends LLFilesystemOperation
|
||||
*/
|
||||
class LLOWrite extends LLFilesystemOperation {
|
||||
@@ -59,9 +59,9 @@ class LLOWrite extends LLFilesystemOperation {
|
||||
|
||||
/**
|
||||
* The "non-overwrite" write operation.
|
||||
*
|
||||
*
|
||||
* This operation is used to write a file to a non-existent path.
|
||||
*
|
||||
*
|
||||
* @extends LLFilesystemOperation
|
||||
*/
|
||||
class LLCWrite extends LLFilesystemOperation {
|
||||
@@ -69,7 +69,7 @@ class LLCWrite extends LLFilesystemOperation {
|
||||
_path: require('path'),
|
||||
uuidv4: require('uuid').v4,
|
||||
config: require('../../config.js'),
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes the create operation by writing a new file to the parent directory.
|
||||
|
||||
@@ -75,7 +75,7 @@ class NodeUIDSelector extends NodeSelector {
|
||||
|
||||
class NodeInternalIDSelector extends NodeSelector {
|
||||
constructor (service, id, debugInfo) {
|
||||
super();
|
||||
super();
|
||||
this.service = service;
|
||||
this.id = id;
|
||||
this.debugInfo = debugInfo;
|
||||
@@ -91,9 +91,9 @@ class NodeInternalIDSelector extends NodeSelector {
|
||||
if ( showDebug ) {
|
||||
return `[db:${this.id}] (${
|
||||
JSON.stringify(this.debugInfo, null, 2)
|
||||
})`
|
||||
})`;
|
||||
}
|
||||
return `[db:${this.id}]`
|
||||
return `[db:${this.id}]`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ class NodeChildSelector extends NodeSelector {
|
||||
}
|
||||
|
||||
describe () {
|
||||
return this.parent.describe() + '/' + this.name;
|
||||
return `${this.parent.describe() }/${ this.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ class NodeRawEntrySelector extends NodeSelector {
|
||||
constructor (entry) {
|
||||
super();
|
||||
// Fix entries from get_descendants
|
||||
if ( ! entry.uuid && entry.uid ) {
|
||||
if ( !entry.uuid && entry.uid ) {
|
||||
entry.uuid = entry.uid;
|
||||
if ( entry._id ) {
|
||||
entry.id = entry._id;
|
||||
@@ -206,7 +206,7 @@ const relativeSelector = (parent, path) => {
|
||||
}
|
||||
|
||||
return selector;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
NodeSelector,
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class NodeFoundState {}
|
||||
class NodeFoundState {
|
||||
}
|
||||
|
||||
class NodeDoesNotExistState {}
|
||||
class NodeDoesNotExistState {
|
||||
}
|
||||
|
||||
class NodeInitialState {}
|
||||
class NodeInitialState {
|
||||
}
|
||||
|
||||
@@ -67,8 +67,8 @@ class UploadProgressTracker {
|
||||
if ( idx !== -1 ) {
|
||||
listeners.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return det;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { BaseOperation } = require("../../../services/OperationTraceService");
|
||||
const { BaseOperation } = require('../../../services/OperationTraceService');
|
||||
|
||||
/**
|
||||
* Handles file upload operations to local disk storage.
|
||||
@@ -58,7 +58,7 @@ class LocalDiskUploadStrategy extends BaseOperation {
|
||||
on_progress: evt => {
|
||||
progress_tracker.set_total(file.size);
|
||||
progress_tracker.set(evt.uploaded);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,8 @@ class LocalDiskUploadStrategy extends BaseOperation {
|
||||
/**
|
||||
* Hook called after the operation is inserted into the trace.
|
||||
*/
|
||||
post_insert () {}
|
||||
post_insert () {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +106,8 @@ class LocalDiskCopyStrategy extends BaseOperation {
|
||||
/**
|
||||
* Hook called after the operation is inserted into the trace.
|
||||
*/
|
||||
post_insert () {}
|
||||
post_insert () {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,7 +24,7 @@ This module contains functions that validate filesystem operations.
|
||||
|
||||
/* eslint-disable no-control-regex */
|
||||
|
||||
const config = require("../config");
|
||||
const config = require('../config');
|
||||
|
||||
const path_excludes = () => /[\x00-\x1F]/g;
|
||||
const node_excludes = () => /[/\x00-\x1F]/g;
|
||||
@@ -38,8 +38,8 @@ const safety_excludes = [
|
||||
/[\u2066-\u2069]/, // RTL and LTR isolate
|
||||
/[\u2028-\u2029]/, // line and paragraph separator
|
||||
/[\uFF01-\uFF5E]/, // fullwidth ASCII
|
||||
/[\u2060]/, // word joiner
|
||||
/[\uFEFF]/, // zero width no-break space
|
||||
/[\u2060]/, // word joiner
|
||||
/[\uFEFF]/, // zero width no-break space
|
||||
/[\uFFFE-\uFFFF]/, // non-characters
|
||||
];
|
||||
|
||||
@@ -56,7 +56,7 @@ const is_valid_node_name = function is_valid_node_name (name) {
|
||||
if ( name_without_dots.length < 1 ) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const is_valid_path = function is_valid_path (path, {
|
||||
no_relative_components,
|
||||
@@ -69,8 +69,10 @@ const is_valid_path = function is_valid_path (path, {
|
||||
if ( exclude.test(path) ) return false;
|
||||
}
|
||||
|
||||
if ( ! allow_path_fragment ) if ( path[0] !== '/' && path[0] !== '.' ) {
|
||||
return false;
|
||||
if ( ! allow_path_fragment ) {
|
||||
if ( path[0] !== '/' && path[0] !== '.' ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( no_relative_components ) {
|
||||
@@ -83,7 +85,7 @@ const is_valid_path = function is_valid_path (path, {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
is_valid_node_name,
|
||||
|
||||
@@ -32,7 +32,7 @@ const surrounding_box = (col, lines, lengths) => {
|
||||
if ( ! lengths ) {
|
||||
lengths = lines.map(line => stringLength(line));
|
||||
}
|
||||
|
||||
|
||||
const probably_docker = (() => {
|
||||
try {
|
||||
// I don't know what the value of this is in Docker,
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
module.exports = [
|
||||
{
|
||||
sz:40,
|
||||
txt:`&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|
||||
{
|
||||
sz: 40,
|
||||
txt: `&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|
||||
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|
||||
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|
||||
&&&&&&&&&&&&&&&. ,& &&&&&&&&&&&
|
||||
@@ -34,11 +34,11 @@ txt:`&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|
||||
&&&&&&&&& .&&&&& & &&&&& &&&&&&&&
|
||||
&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&
|
||||
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|
||||
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&`
|
||||
},
|
||||
{
|
||||
sz:72,
|
||||
txt:`&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|
||||
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&`,
|
||||
},
|
||||
{
|
||||
sz: 72,
|
||||
txt: `&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|
||||
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|
||||
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|
||||
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|
||||
@@ -67,5 +67,5 @@ txt:`&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|
||||
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|
||||
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|
||||
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&`,
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,58 +18,58 @@
|
||||
*/
|
||||
const config = require('./config');
|
||||
|
||||
function html_footer(options) {
|
||||
let html = ``;
|
||||
if(options.show_footer ?? false){
|
||||
html += `<div class="container">`;
|
||||
html += `<footer class="row row-cols-5 py-5 my-5 border-top">`;
|
||||
html += `<div class="col">`;
|
||||
html += `<a href="/">`;
|
||||
html += `<img src="/assets/img/logo-128x128.png" style="width:60px; border-radius: 4px; margin-bottom: 10px;">`;
|
||||
html += `</a>`;
|
||||
html += `<div><a href="mailto:hi@puter.com">hi@puter.com</a></div>`;
|
||||
html += `</div>`;
|
||||
function html_footer (options) {
|
||||
let html = '';
|
||||
if ( options.show_footer ?? false ) {
|
||||
html += '<div class="container">';
|
||||
html += '<footer class="row row-cols-5 py-5 my-5 border-top">';
|
||||
html += '<div class="col">';
|
||||
html += '<a href="/">';
|
||||
html += '<img src="/assets/img/logo-128x128.png" style="width:60px; border-radius: 4px; margin-bottom: 10px;">';
|
||||
html += '</a>';
|
||||
html += '<div><a href="mailto:hi@puter.com">hi@puter.com</a></div>';
|
||||
html += '</div>';
|
||||
|
||||
html += `<div class="col">`;
|
||||
html += `</div>`;
|
||||
html += '<div class="col">';
|
||||
html += '</div>';
|
||||
|
||||
html += `<div class="col">`;
|
||||
html += `</div>`;
|
||||
html += '<div class="col">';
|
||||
html += '</div>';
|
||||
|
||||
html += `<div class="col">`;
|
||||
html += `</div>`;
|
||||
html += '<div class="col">';
|
||||
html += '</div>';
|
||||
|
||||
html += `<div class="col">`;
|
||||
html += `<h5>Quick Links</h5>`;
|
||||
html += `<ul class="nav flex-column">`;
|
||||
html += `<li class="nav-item mb-2"><a href="/" class="nav-link p-0 text-muted">Home</a></li>`;
|
||||
html += `<li class="nav-item mb-2"><a href="${ config.protocol + '://blog.' + config.domain}" class="nav-link p-0 text-muted">Blog</a></li>`;
|
||||
html += `<li class="nav-item mb-2"><a href="/login" class="nav-link p-0 text-muted">Log In</a></li>`;
|
||||
html += `<li class="nav-item mb-2"><a href="/terms" class="nav-link p-0 text-muted">Terms</a></li>`;
|
||||
html += `<li class="nav-item mb-2"><a href="/privacy" class="nav-link p-0 text-muted">Privacy Policy</a></li>`;
|
||||
html += `</ul>`;
|
||||
html += `</div>`;
|
||||
html += `</footer>`;
|
||||
// social
|
||||
html += `<div style="margin-top:20px; padding-bottom: 20px; padding-top:10px; overflow:hidden; border-top:1px solid #CCC;">`
|
||||
html += `<p class="text-muted" style="float:left;">Puter Technologies Inc. © ${new Date().getFullYear()}</p>`;
|
||||
html += `<a href="https://github.com/HeyPuter" target="_blank"><img src="/img/logo-github.svg" class="social-media-icon"></a>`;
|
||||
html += `<a href="https://www.facebook.com/HeyPuter"><img src="/img/logo-facebook.svg" target="_blank" class="social-media-icon"></a>`;
|
||||
html += `<a href="https://twitter.com/HeyPuter" target="_blank"><img src="/img/logo-twitter.svg" class="social-media-icon"></a>`;
|
||||
html += `</div>`;
|
||||
html += `</div>`;
|
||||
html += `</main>`;
|
||||
html += '<div class="col">';
|
||||
html += '<h5>Quick Links</h5>';
|
||||
html += '<ul class="nav flex-column">';
|
||||
html += '<li class="nav-item mb-2"><a href="/" class="nav-link p-0 text-muted">Home</a></li>';
|
||||
html += `<li class="nav-item mb-2"><a href="${ `${config.protocol }://blog.${ config.domain}`}" class="nav-link p-0 text-muted">Blog</a></li>`;
|
||||
html += '<li class="nav-item mb-2"><a href="/login" class="nav-link p-0 text-muted">Log In</a></li>';
|
||||
html += '<li class="nav-item mb-2"><a href="/terms" class="nav-link p-0 text-muted">Terms</a></li>';
|
||||
html += '<li class="nav-item mb-2"><a href="/privacy" class="nav-link p-0 text-muted">Privacy Policy</a></li>';
|
||||
html += '</ul>';
|
||||
html += '</div>';
|
||||
html += '</footer>';
|
||||
// social
|
||||
html += '<div style="margin-top:20px; padding-bottom: 20px; padding-top:10px; overflow:hidden; border-top:1px solid #CCC;">';
|
||||
html += `<p class="text-muted" style="float:left;">Puter Technologies Inc. © ${new Date().getFullYear()}</p>`;
|
||||
html += '<a href="https://github.com/HeyPuter" target="_blank"><img src="/img/logo-github.svg" class="social-media-icon"></a>';
|
||||
html += '<a href="https://www.facebook.com/HeyPuter"><img src="/img/logo-facebook.svg" target="_blank" class="social-media-icon"></a>';
|
||||
html += '<a href="https://twitter.com/HeyPuter" target="_blank"><img src="/img/logo-twitter.svg" class="social-media-icon"></a>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '</main>';
|
||||
}
|
||||
|
||||
html += `<script>window.page = "${options.page ?? ''}"</script>`;
|
||||
html += `<script src="/assets/js/jquery-3.6.0/jquery-3.6.0.min.js"></script>`;
|
||||
if(options.jsfiles && options.jsfiles.length > 0){
|
||||
options.jsfiles.forEach(jsfile => {
|
||||
html += `<script src="${jsfile}"></script>`;
|
||||
});
|
||||
}
|
||||
html += `<script src="/assets/js/app.js"></script>`;
|
||||
html += `</body>`;
|
||||
return html;
|
||||
html += `<script>window.page = "${options.page ?? ''}"</script>`;
|
||||
html += '<script src="/assets/js/jquery-3.6.0/jquery-3.6.0.min.js"></script>';
|
||||
if ( options.jsfiles && options.jsfiles.length > 0 ) {
|
||||
options.jsfiles.forEach(jsfile => {
|
||||
html += `<script src="${jsfile}"></script>`;
|
||||
});
|
||||
}
|
||||
html += '<script src="/assets/js/app.js"></script>';
|
||||
html += '</body>';
|
||||
return html;
|
||||
}
|
||||
module.exports = html_footer;
|
||||
@@ -16,24 +16,24 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const config = require('./config')
|
||||
const {encode} = require('html-entities');
|
||||
const config = require('./config');
|
||||
const { encode } = require('html-entities');
|
||||
|
||||
function html_head(options) {
|
||||
let canonical_url = `${config.origin}/${options.page === 'index' ? '' : options.page}`;
|
||||
let html = ``;
|
||||
html += `<!doctype html>`;
|
||||
html += `<html lang="en" id="html-${options.page ?? 'default'}">`;
|
||||
html += `<head>`;
|
||||
// meta tags
|
||||
html += `<meta charset="utf-8">`;
|
||||
html += `<meta name="viewport" content="width=device-width, initial-scale=1">`;
|
||||
html += `<meta name="description" content="${encode(options.meta_description ?? '')}">`;
|
||||
html += `<meta name="referrer" content="no-referrer">`;
|
||||
// title
|
||||
html += `<title>${encode(options.title ?? 'Puter')}</title>`;
|
||||
// favicons
|
||||
html += `<link rel="apple-touch-icon" sizes="57x57" href="/img/favicons/apple-icon-57x57.png">
|
||||
function html_head (options) {
|
||||
let canonical_url = `${config.origin}/${options.page === 'index' ? '' : options.page}`;
|
||||
let html = '';
|
||||
html += '<!doctype html>';
|
||||
html += `<html lang="en" id="html-${options.page ?? 'default'}">`;
|
||||
html += '<head>';
|
||||
// meta tags
|
||||
html += '<meta charset="utf-8">';
|
||||
html += '<meta name="viewport" content="width=device-width, initial-scale=1">';
|
||||
html += `<meta name="description" content="${encode(options.meta_description ?? '')}">`;
|
||||
html += '<meta name="referrer" content="no-referrer">';
|
||||
// title
|
||||
html += `<title>${encode(options.title ?? 'Puter')}</title>`;
|
||||
// favicons
|
||||
html += `<link rel="apple-touch-icon" sizes="57x57" href="/img/favicons/apple-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/img/favicons/apple-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/img/favicons/apple-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/img/favicons/apple-icon-76x76.png">
|
||||
@@ -51,58 +51,58 @@ function html_head(options) {
|
||||
<meta name="msapplication-TileImage" content="/img/favicons/ms-icon-144x144.png">
|
||||
<meta name="theme-color" content="#ffffff">`;
|
||||
|
||||
// Roboto font
|
||||
html += `<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:black,bold,medium,regular,light,thin">`;
|
||||
// Roboto font
|
||||
html += '<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:black,bold,medium,regular,light,thin">';
|
||||
|
||||
// canonical link
|
||||
html += `<link rel="canonical" href="${canonical_url}" />`;
|
||||
// canonical link
|
||||
html += `<link rel="canonical" href="${canonical_url}" />`;
|
||||
|
||||
// preload images
|
||||
if(options.page === 'index'){
|
||||
html += `<link rel="preload" as="image" href="/assets/img/lock.svg"></link>`;
|
||||
html += `<link rel="preload" as="image" href="/assets/img/screenshot.png"></link>`;
|
||||
}
|
||||
// preload images
|
||||
if ( options.page === 'index' ) {
|
||||
html += '<link rel="preload" as="image" href="/assets/img/lock.svg"></link>';
|
||||
html += '<link rel="preload" as="image" href="/assets/img/screenshot.png"></link>';
|
||||
}
|
||||
|
||||
// Facebook meta tags
|
||||
html += `<meta property="og:url" content="${canonical_url}">`;
|
||||
html += `<meta property="og:type" content="website">`;
|
||||
html += `<meta property="og:title" content="${encode(options.title ?? 'Puter')}">`;
|
||||
html += `<meta property="og:description" content="${encode(config.short_description)}">`;
|
||||
html += `<meta property="og:image" content="${config.social_card}">`;
|
||||
// Facebook meta tags
|
||||
html += `<meta property="og:url" content="${canonical_url}">`;
|
||||
html += '<meta property="og:type" content="website">';
|
||||
html += `<meta property="og:title" content="${encode(options.title ?? 'Puter')}">`;
|
||||
html += `<meta property="og:description" content="${encode(config.short_description)}">`;
|
||||
html += `<meta property="og:image" content="${config.social_card}">`;
|
||||
|
||||
// Twitter meta tags
|
||||
html += `<meta name="twitter:card" content="">`;
|
||||
html += `<meta property="twitter:domain" content="${config.domain}">`;
|
||||
html += `<meta property="twitter:url" content="${canonical_url}">`;
|
||||
html += `<meta name="twitter:title" content="${encode(options.title ?? 'Puter')}">`;
|
||||
html += `<meta name="twitter:description" content="${encode(config.short_description)}">`;
|
||||
html += `<meta name="twitter:image" content="${config.social_card}">`;
|
||||
// Twitter meta tags
|
||||
html += '<meta name="twitter:card" content="">';
|
||||
html += `<meta property="twitter:domain" content="${config.domain}">`;
|
||||
html += `<meta property="twitter:url" content="${canonical_url}">`;
|
||||
html += `<meta name="twitter:title" content="${encode(options.title ?? 'Puter')}">`;
|
||||
html += `<meta name="twitter:description" content="${encode(config.short_description)}">`;
|
||||
html += `<meta name="twitter:image" content="${config.social_card}">`;
|
||||
|
||||
// CSS
|
||||
html += `<link href="/assets/bootstrap-5.1.3/css/bootstrap.min.css" rel="stylesheet"></link>`;
|
||||
html += `<link href="/assets/css/style.css" rel="stylesheet"></link>`;
|
||||
// CSS
|
||||
html += '<link href="/assets/bootstrap-5.1.3/css/bootstrap.min.css" rel="stylesheet"></link>';
|
||||
html += '<link href="/assets/css/style.css" rel="stylesheet"></link>';
|
||||
|
||||
html += `</head>`;
|
||||
html += `<body id="body-${options.page ?? 'default'}">`;
|
||||
if(options.show_navbar ?? false){
|
||||
html += `<main>`;
|
||||
html += `<div class="container">`;
|
||||
html += `<header class="d-flex flex-wrap align-items-center justify-content-center justify-content-md-between py-3 mb-4 border-bottom">`;
|
||||
html +=`<div class="d-flex align-items-center col-md-3 mb-2 mb-md-0 text-dark text-decoration-none">`;
|
||||
html += `<a href="/" class="text-dark text-decoration-none">`;
|
||||
html += `<img class="bi me-2 logo" width="40" height="40" role="img" src="/assets/img/logo-128x128.png" style="margin-right: 0 !important;">`;
|
||||
html += `</a>`;
|
||||
html +=`</div>`;
|
||||
|
||||
html += `<ul class="nav col-12 col-md-auto mb-2 justify-content-center mb-md-0">`;
|
||||
html += `</ul>`;
|
||||
html += '</head>';
|
||||
html += `<body id="body-${options.page ?? 'default'}">`;
|
||||
if ( options.show_navbar ?? false ) {
|
||||
html += '<main>';
|
||||
html += '<div class="container">';
|
||||
html += '<header class="d-flex flex-wrap align-items-center justify-content-center justify-content-md-between py-3 mb-4 border-bottom">';
|
||||
html += '<div class="d-flex align-items-center col-md-3 mb-2 mb-md-0 text-dark text-decoration-none">';
|
||||
html += '<a href="/" class="text-dark text-decoration-none">';
|
||||
html += '<img class="bi me-2 logo" width="40" height="40" role="img" src="/assets/img/logo-128x128.png" style="margin-right: 0 !important;">';
|
||||
html += '</a>';
|
||||
html += '</div>';
|
||||
|
||||
html += `</header>`;
|
||||
|
||||
html += `</div>`;
|
||||
}
|
||||
|
||||
html += ``;
|
||||
return html;
|
||||
html += '<ul class="nav col-12 col-md-auto mb-2 justify-content-center mb-md-0">';
|
||||
html += '</ul>';
|
||||
|
||||
html += '</header>';
|
||||
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
html += '';
|
||||
return html;
|
||||
}
|
||||
module.exports = html_head;
|
||||
@@ -16,18 +16,17 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
"use strict"
|
||||
'use strict';
|
||||
|
||||
const { Kernel } = require("./Kernel");
|
||||
const CoreModule = require("./CoreModule");
|
||||
const { CaptchaModule } = require("./modules/captcha/CaptchaModule"); // Add CaptchaModule
|
||||
const { Kernel } = require('./Kernel');
|
||||
const CoreModule = require('./CoreModule');
|
||||
const { CaptchaModule } = require('./modules/captcha/CaptchaModule'); // Add CaptchaModule
|
||||
|
||||
const testlaunch = () => {
|
||||
const k = new Kernel();
|
||||
k.add_module(new CoreModule());
|
||||
k.add_module(new CaptchaModule()); // Register the CaptchaModule
|
||||
k.boot();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = { testlaunch };
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
async function prependToJSFiles(directory, snippet) {
|
||||
async function prependToJSFiles (directory, snippet) {
|
||||
const jsExtensions = new Set(['.js', '.cjs', '.mjs', '.ts']);
|
||||
|
||||
async function processDirectory(dir) {
|
||||
async function processDirectory (dir) {
|
||||
try {
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||
const promises = [];
|
||||
@@ -14,7 +14,7 @@ async function prependToJSFiles(directory, snippet) {
|
||||
|
||||
if ( entry.isDirectory() ) {
|
||||
// Skip common directories that shouldn't be modified
|
||||
if ( !shouldSkipDirectory(entry.name) ) {
|
||||
if ( ! shouldSkipDirectory(entry.name) ) {
|
||||
promises.push(processDirectory(fullPath));
|
||||
}
|
||||
} else if ( entry.isFile() && jsExtensions.has(path.extname(entry.name)) ) {
|
||||
@@ -30,7 +30,7 @@ async function prependToJSFiles(directory, snippet) {
|
||||
}
|
||||
}
|
||||
|
||||
function shouldSkipDirectory(dirName) {
|
||||
function shouldSkipDirectory (dirName) {
|
||||
const skipDirs = new Set([
|
||||
'node_modules',
|
||||
'gui',
|
||||
@@ -40,7 +40,7 @@ async function prependToJSFiles(directory, snippet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
async function prependToFile(filePath, snippet) {
|
||||
async function prependToFile (filePath, snippet) {
|
||||
try {
|
||||
const content = await fs.readFile(filePath, 'utf8');
|
||||
if ( content.startsWith('//!no-prepend') ) return;
|
||||
|
||||
@@ -18,35 +18,31 @@
|
||||
*/
|
||||
class ArrayUtil extends use.Library {
|
||||
/**
|
||||
*
|
||||
* @param {*} marked_map
|
||||
* @param {*} subject
|
||||
*
|
||||
* @param {*} marked_map
|
||||
* @param {*} subject
|
||||
*/
|
||||
remove_marked_items (marked_map, subject) {
|
||||
for ( let i=0 ; i < marked_map.length ; i++ ) {
|
||||
for ( let i = 0 ; i < marked_map.length ; i++ ) {
|
||||
let ii = marked_map[i];
|
||||
// track: type check
|
||||
if ( ! Number.isInteger(ii) ) {
|
||||
throw new Error(
|
||||
'marked_map can only contain integers'
|
||||
);
|
||||
throw new Error('marked_map can only contain integers');
|
||||
}
|
||||
// track: bounds check
|
||||
if ( ii < 0 && ii >= subject.length ) {
|
||||
throw new Error(
|
||||
'each item in `marked_map` must be within that bounds ' +
|
||||
'of `subject`'
|
||||
);
|
||||
throw new Error('each item in `marked_map` must be within that bounds ' +
|
||||
'of `subject`');
|
||||
}
|
||||
}
|
||||
|
||||
marked_map.sort((a, b) => b - a);
|
||||
|
||||
for ( let i=0 ; i < marked_map.length ; i++ ) {
|
||||
|
||||
for ( let i = 0 ; i < marked_map.length ; i++ ) {
|
||||
let ii = marked_map[i];
|
||||
subject.splice(ii, 1);
|
||||
}
|
||||
|
||||
|
||||
return subject;
|
||||
}
|
||||
|
||||
@@ -54,7 +50,8 @@ class ArrayUtil extends use.Library {
|
||||
// inner indices
|
||||
{
|
||||
const subject = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
|
||||
];
|
||||
// 0 1 2 3 4 5 6 7
|
||||
const marked_map = [2, 5];
|
||||
this.remove_marked_items(marked_map, subject);
|
||||
@@ -63,27 +60,30 @@ class ArrayUtil extends use.Library {
|
||||
// left edge
|
||||
{
|
||||
const subject = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
|
||||
];
|
||||
// 0 1 2 3 4 5 6 7
|
||||
const marked_map = [0]
|
||||
const marked_map = [0];
|
||||
this.remove_marked_items(marked_map, subject);
|
||||
assert(() => subject.join('') === 'bcdefgh');
|
||||
}
|
||||
// right edge
|
||||
{
|
||||
const subject = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
|
||||
];
|
||||
// 0 1 2 3 4 5 6 7
|
||||
const marked_map = [7]
|
||||
const marked_map = [7];
|
||||
this.remove_marked_items(marked_map, subject);
|
||||
assert(() => subject.join('') === 'abcdefg');
|
||||
}
|
||||
// both edges
|
||||
{
|
||||
const subject = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
|
||||
];
|
||||
// 0 1 2 3 4 5 6 7
|
||||
const marked_map = [0, 7]
|
||||
const marked_map = [0, 7];
|
||||
this.remove_marked_items(marked_map, subject);
|
||||
assert(() => subject.join('') === 'bcdefg');
|
||||
}
|
||||
|
||||
@@ -16,79 +16,87 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { whatis } = require("../util/langutil");
|
||||
const { whatis } = require('../util/langutil');
|
||||
|
||||
class LibTypeTagged extends use.Library {
|
||||
process (o) {
|
||||
const could_be = whatis(o) === 'object' || Array.isArray(o);
|
||||
if ( ! could_be ) return {
|
||||
$: 'error',
|
||||
code: 'invalid-type',
|
||||
message: 'should be object or array',
|
||||
};
|
||||
|
||||
if ( ! could_be ) {
|
||||
return {
|
||||
$: 'error',
|
||||
code: 'invalid-type',
|
||||
message: 'should be object or array',
|
||||
};
|
||||
}
|
||||
|
||||
const intermediate = this.get_intermediate_(o);
|
||||
|
||||
if ( ! intermediate.type ) return {
|
||||
$: 'error',
|
||||
code: 'missing-type-param',
|
||||
message: 'type parameter is missing',
|
||||
};
|
||||
|
||||
|
||||
if ( ! intermediate.type ) {
|
||||
return {
|
||||
$: 'error',
|
||||
code: 'missing-type-param',
|
||||
message: 'type parameter is missing',
|
||||
};
|
||||
}
|
||||
|
||||
return this.intermediate_to_standard_(intermediate);
|
||||
}
|
||||
|
||||
|
||||
intermediate_to_standard_ (intermediate) {
|
||||
const out = {};
|
||||
out.$ = intermediate.type;
|
||||
for ( const k in intermediate.meta ) {
|
||||
out['$' + k] = intermediate.meta[k];
|
||||
out[`$${ k}`] = intermediate.meta[k];
|
||||
}
|
||||
for ( const k in intermediate.body ) {
|
||||
out[k] = intermediate.body[k];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
get_intermediate_ (o) {
|
||||
if ( Array.isArray(o) ) {
|
||||
return this.process_array_(o);
|
||||
}
|
||||
|
||||
|
||||
if ( o['$'] === '$meta-body' ) {
|
||||
return this.process_structured_(o);
|
||||
}
|
||||
|
||||
|
||||
return this.process_standard_(o);
|
||||
}
|
||||
|
||||
|
||||
process_array_ (a) {
|
||||
if ( a.length <= 1 || a.length > 3 ) return {
|
||||
$: 'error',
|
||||
code: 'invalid-array-length',
|
||||
message: 'tag-typed arrays should have 1-3 elements',
|
||||
};
|
||||
|
||||
if ( a.length <= 1 || a.length > 3 ) {
|
||||
return {
|
||||
$: 'error',
|
||||
code: 'invalid-array-length',
|
||||
message: 'tag-typed arrays should have 1-3 elements',
|
||||
};
|
||||
}
|
||||
|
||||
const [type, body = {}, meta = {}] = a;
|
||||
|
||||
|
||||
return { $: '$', type, body, meta };
|
||||
}
|
||||
|
||||
|
||||
process_structured_ (o) {
|
||||
if ( ! o.hasOwnProperty('type') ) return {
|
||||
$: 'error',
|
||||
code: 'missing-type-property',
|
||||
message: 'missing "type" property'
|
||||
};
|
||||
|
||||
if ( ! o.hasOwnProperty('type') ) {
|
||||
return {
|
||||
$: 'error',
|
||||
code: 'missing-type-property',
|
||||
message: 'missing "type" property',
|
||||
};
|
||||
}
|
||||
|
||||
return { $: '$', ...o };
|
||||
}
|
||||
|
||||
|
||||
process_standard_ (o) {
|
||||
const type = o.$;
|
||||
const meta = {};
|
||||
const body = {};
|
||||
|
||||
|
||||
for ( const k in o ) {
|
||||
if ( k === '$' ) continue;
|
||||
if ( k.startsWith('$') ) {
|
||||
@@ -97,7 +105,7 @@ class LibTypeTagged extends use.Library {
|
||||
body[k] = o[k];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return { $: '$', type, meta, body };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const APIError = require("../api/APIError");
|
||||
const config = require("../config");
|
||||
const { Context } = require("../util/context");
|
||||
const APIError = require('../api/APIError');
|
||||
const config = require('../config');
|
||||
const { Context } = require('../util/context');
|
||||
|
||||
const abuse = options => (req, res, next) => {
|
||||
if ( config.disable_abuse_checks ) {
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const APIError = require("../api/APIError");
|
||||
const APIError = require('../api/APIError');
|
||||
|
||||
/**
|
||||
* Creates an anti-CSRF middleware that validates CSRF tokens in incoming requests.
|
||||
* This middleware protects against Cross-Site Request Forgery attacks by verifying
|
||||
* that requests contain a valid anti-CSRF token in the request body.
|
||||
*
|
||||
*
|
||||
* @param {Object} options - Configuration options for the middleware
|
||||
* @returns {Function} Express middleware function that validates CSRF tokens
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* // Apply anti-CSRF protection to a route
|
||||
* app.post('/api/secure-endpoint', anticsrf(), (req, res) => {
|
||||
@@ -46,7 +46,7 @@ const anticsrf = options => async (req, res, next) => {
|
||||
err.write(res);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
|
||||
@@ -16,16 +16,18 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
"use strict"
|
||||
'use strict';
|
||||
const APIError = require('../api/APIError');
|
||||
const { UserActorType } = require('../services/auth/Actor');
|
||||
const auth2 = require('./auth2');
|
||||
|
||||
const auth = async (req, res, next)=>{
|
||||
const auth = async (req, res, next) => {
|
||||
let auth2_ok = false;
|
||||
try{
|
||||
try {
|
||||
// Delegate to new middleware
|
||||
await auth2(req, res, () => { auth2_ok = true; });
|
||||
await auth2(req, res, () => {
|
||||
auth2_ok = true;
|
||||
});
|
||||
if ( ! auth2_ok ) return;
|
||||
|
||||
// Everything using the old reference to the auth middleware
|
||||
@@ -37,9 +39,9 @@ const auth = async (req, res, next)=>{
|
||||
next();
|
||||
}
|
||||
// auth failed
|
||||
catch(e){
|
||||
catch (e) {
|
||||
return res.status(401).send(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = auth
|
||||
module.exports = auth;
|
||||
@@ -16,7 +16,7 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const configurable_auth = require("./configurable_auth");
|
||||
const configurable_auth = require('./configurable_auth');
|
||||
|
||||
const auth2 = configurable_auth({ optional: false });
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const APIError = require('../api/APIError');
|
||||
const config = require("../config");
|
||||
const { LegacyTokenError } = require("../services/auth/AuthService");
|
||||
const { Context } = require("../util/context");
|
||||
const config = require('../config');
|
||||
const { LegacyTokenError } = require('../services/auth/AuthService');
|
||||
const { Context } = require('../util/context');
|
||||
|
||||
// The "/whoami" endpoint is a special case where we want to allow
|
||||
// a legacy token to be used for authentication. The "/whoami"
|
||||
@@ -33,7 +33,7 @@ const is_whoami = (req) => {
|
||||
// const subdomain = req.subdomains[res.subdomains.length - 1];
|
||||
// if ( subdomain !== 'api' ) return;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Allow auth middleware to be used without requiring
|
||||
// authentication. This will allow us to use the auth middleware
|
||||
@@ -56,40 +56,48 @@ const configurable_auth = options => async (req, res, next) => {
|
||||
|
||||
let token;
|
||||
// Auth token in body
|
||||
if(req.body && req.body.auth_token)
|
||||
if ( req.body && req.body.auth_token )
|
||||
{
|
||||
token = req.body.auth_token;
|
||||
}
|
||||
// HTTML Auth header
|
||||
else if (req.header && req.header('Authorization') && !req.header('Authorization').startsWith("Basic ") && req.header('Authorization') !== "Bearer") { // Bearer with no space is something office does
|
||||
else if ( req.header && req.header('Authorization') && !req.header('Authorization').startsWith('Basic ') && req.header('Authorization') !== 'Bearer' ) { // Bearer with no space is something office does
|
||||
token = req.header('Authorization');
|
||||
token = token.replace('Bearer ', '').trim();
|
||||
if ( token === 'undefined' ) {
|
||||
APIError.create('unexpected_undefined', null, {
|
||||
msg: `The Authorization token cannot be the string "undefined"`
|
||||
msg: 'The Authorization token cannot be the string "undefined"',
|
||||
});
|
||||
}
|
||||
}
|
||||
// Cookie
|
||||
else if(req.cookies && req.cookies[config.cookie_name])
|
||||
else if ( req.cookies && req.cookies[config.cookie_name] )
|
||||
{
|
||||
token = req.cookies[config.cookie_name];
|
||||
}
|
||||
// Auth token in URL
|
||||
else if(req.query && req.query.auth_token)
|
||||
else if ( req.query && req.query.auth_token )
|
||||
{
|
||||
token = req.query.auth_token;
|
||||
}
|
||||
// Socket
|
||||
else if(req.handshake && req.handshake.query && req.handshake.query.auth_token)
|
||||
else if ( req.handshake && req.handshake.query && req.handshake.query.auth_token )
|
||||
{
|
||||
token = req.handshake.query.auth_token;
|
||||
|
||||
if(!token || token.startsWith("Basic ")) {
|
||||
}
|
||||
|
||||
if ( !token || token.startsWith('Basic ') ) {
|
||||
if ( optional ) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
APIError.create('token_missing').write(res);
|
||||
return;
|
||||
} else if (typeof token !== 'string') {
|
||||
} else if ( typeof token !== 'string' ) {
|
||||
APIError.create('token_auth_failed').write(res);
|
||||
return;
|
||||
} else {
|
||||
token = token.replace('Bearer ', '')
|
||||
token = token.replace('Bearer ', '');
|
||||
}
|
||||
|
||||
// === Delegate to AuthService ===
|
||||
@@ -113,14 +121,14 @@ const configurable_auth = options => async (req, res, next) => {
|
||||
const new_info = await svc_auth.check_session(token, {
|
||||
req,
|
||||
from_upgrade: true,
|
||||
})
|
||||
});
|
||||
context.set('actor', new_info.actor);
|
||||
context.set('user', new_info.user);
|
||||
req.new_token = new_info.token;
|
||||
req.token = new_info.token;
|
||||
req.user = new_info.user;
|
||||
req.actor = new_info.actor;
|
||||
|
||||
|
||||
if ( req.user?.suspended ) {
|
||||
throw APIError.create('forbidden');
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const APIError = require("../api/APIError");
|
||||
const { Context } = require("../util/context");
|
||||
const APIError = require('../api/APIError');
|
||||
const { Context } = require('../util/context');
|
||||
|
||||
const featureflag = options => async (req, res, next) => {
|
||||
const { feature } = options;
|
||||
|
||||
|
||||
const context = Context.get();
|
||||
const services = context.get('services');
|
||||
const svc_featureFlag = services.get('feature-flag');
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user