mirror of
https://github.com/HeyPuter/puter.git
synced 2025-02-02 23:28:39 +08:00
feat: update share endpoint to support more things
This commit is contained in:
parent
18f9959776
commit
dd5fde5130
@ -30,9 +30,10 @@ anywhere.
|
|||||||
## Specification
|
## Specification
|
||||||
|
|
||||||
- The `"$"` key indicates a type (or class) of object
|
- The `"$"` key indicates a type (or class) of object
|
||||||
- Any key beginning with `$` is a **meta-key**
|
- Any other key beginning with `$` is a **meta-key**
|
||||||
- Other keys are not allowed to contain `$`
|
- Other keys are not allowed to contain `$`
|
||||||
- `"$version"` must follow [semver](https://semver.org/)
|
- `"$version"` must follow [semver](https://semver.org/)
|
||||||
|
- Keys with multiple `"$"` symbols are reserved for future use
|
||||||
|
|
||||||
## Alternative Representations
|
## Alternative Representations
|
||||||
|
|
||||||
|
@ -57,6 +57,9 @@ const install = async ({ services, app, useapi }) => {
|
|||||||
const ArrayUtil = require('./libraries/ArrayUtil');
|
const ArrayUtil = require('./libraries/ArrayUtil');
|
||||||
services.registerService('util-array', ArrayUtil);
|
services.registerService('util-array', ArrayUtil);
|
||||||
|
|
||||||
|
const LibTypeTagged = require('./libraries/LibTypeTagged');
|
||||||
|
services.registerService('lib-type-tagged', LibTypeTagged);
|
||||||
|
|
||||||
// === SERVICES ===
|
// === SERVICES ===
|
||||||
|
|
||||||
// /!\ IMPORTANT /!\
|
// /!\ IMPORTANT /!\
|
||||||
|
@ -33,6 +33,29 @@ module.exports = class APIError {
|
|||||||
status: 500,
|
status: 500,
|
||||||
message: () => `An unknown error occurred`,
|
message: () => `An unknown error occurred`,
|
||||||
},
|
},
|
||||||
|
'format_error': {
|
||||||
|
status: 400,
|
||||||
|
message: ({ message }) => `format error: ${message}`,
|
||||||
|
},
|
||||||
|
'temp_error': {
|
||||||
|
status: 400,
|
||||||
|
message: ({ message }) => `error: ${message}`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Things
|
||||||
|
'disallowed_thing': {
|
||||||
|
status: 400,
|
||||||
|
message: ({ thing_type, accepted }) =>
|
||||||
|
`Request contained a ${quot(thing_type)} in a ` +
|
||||||
|
`place where ${quot(thing_type)} isn't accepted` +
|
||||||
|
(
|
||||||
|
accepted
|
||||||
|
? '; ' +
|
||||||
|
'accepted types are: ' +
|
||||||
|
accepted.map(v => quot(v)).join(', ')
|
||||||
|
: ''
|
||||||
|
) + '.'
|
||||||
|
},
|
||||||
|
|
||||||
// Unorganized
|
// Unorganized
|
||||||
'item_with_same_name_exists': {
|
'item_with_same_name_exists': {
|
||||||
|
88
packages/backend/src/libraries/LibTypeTagged.js
Normal file
88
packages/backend/src/libraries/LibTypeTagged.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
const Library = require("../definitions/Library");
|
||||||
|
const { whatis } = require("../util/langutil");
|
||||||
|
|
||||||
|
class LibTypeTagged extends 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',
|
||||||
|
};
|
||||||
|
|
||||||
|
const intermediate = this.get_intermediate_(o);
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
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',
|
||||||
|
};
|
||||||
|
|
||||||
|
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'
|
||||||
|
};
|
||||||
|
|
||||||
|
return { $: '$', ...o };
|
||||||
|
}
|
||||||
|
|
||||||
|
process_standard_ (o) {
|
||||||
|
const type = o.$;
|
||||||
|
const meta = {};
|
||||||
|
const body = {};
|
||||||
|
|
||||||
|
for ( const k in o ) {
|
||||||
|
if ( k === '$' ) continue;
|
||||||
|
if ( k.startsWith('$') ) {
|
||||||
|
meta[k.slice(1)] = o[k];
|
||||||
|
} else {
|
||||||
|
body[k] = o[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { $: '$', type, meta, body };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = LibTypeTagged;
|
@ -3,7 +3,7 @@ const { Endpoint } = require('../util/expressutil');
|
|||||||
|
|
||||||
const validator = require('validator');
|
const validator = require('validator');
|
||||||
const APIError = require('../api/APIError');
|
const APIError = require('../api/APIError');
|
||||||
const { get_user } = require('../helpers');
|
const { get_user, get_app } = require('../helpers');
|
||||||
const { Context } = require('../util/context');
|
const { Context } = require('../util/context');
|
||||||
const auth2 = require('../middleware/auth2');
|
const auth2 = require('../middleware/auth2');
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
@ -140,6 +140,8 @@ const v0_2 = async (req, res) => {
|
|||||||
const svc_permission = req.services.get('permission');
|
const svc_permission = req.services.get('permission');
|
||||||
const svc_notification = req.services.get('notification');
|
const svc_notification = req.services.get('notification');
|
||||||
|
|
||||||
|
const lib_typeTagged = req.services.get('lib-type-tagged');
|
||||||
|
|
||||||
const actor = Context.get('actor');
|
const actor = Context.get('actor');
|
||||||
|
|
||||||
// === Request Validators ===
|
// === Request Validators ===
|
||||||
@ -170,12 +172,12 @@ const v0_2 = async (req, res) => {
|
|||||||
return recipients;
|
return recipients;
|
||||||
});
|
});
|
||||||
|
|
||||||
const validate_paths = UtilFn(paths => {
|
const validate_shares = UtilFn(shares => {
|
||||||
// Single-values get adapted into an array
|
// Single-values get adapted into an array
|
||||||
if ( ! Array.isArray(paths) ) {
|
if ( ! Array.isArray(shares) ) {
|
||||||
paths = [paths];
|
shares = [shares];
|
||||||
}
|
}
|
||||||
return paths;
|
return shares;
|
||||||
})
|
})
|
||||||
|
|
||||||
// === Request Values ===
|
// === Request Values ===
|
||||||
@ -184,8 +186,8 @@ const v0_2 = async (req, res) => {
|
|||||||
validate_mode.if(req.body.mode) ?? false;
|
validate_mode.if(req.body.mode) ?? false;
|
||||||
const req_recipients =
|
const req_recipients =
|
||||||
validate_recipients.if(req.body.recipients) ?? [];
|
validate_recipients.if(req.body.recipients) ?? [];
|
||||||
const req_paths =
|
const req_shares =
|
||||||
validate_paths.if(req.body.paths) ?? [];
|
validate_shares.if(req.body.shares) ?? [];
|
||||||
|
|
||||||
// === State Values ===
|
// === State Values ===
|
||||||
|
|
||||||
@ -198,10 +200,10 @@ const v0_2 = async (req, res) => {
|
|||||||
// Results
|
// Results
|
||||||
status: null,
|
status: null,
|
||||||
recipients: Array(req_recipients.length).fill(null),
|
recipients: Array(req_recipients.length).fill(null),
|
||||||
paths: Array(req_paths.length).fill(null),
|
shares: Array(req_shares.length).fill(null),
|
||||||
}
|
}
|
||||||
const recipients_work = new WorkList();
|
const recipients_work = new WorkList();
|
||||||
const fsitems_work = new WorkList();
|
const shares_work = new WorkList();
|
||||||
|
|
||||||
// const assert_work_item = (wut, item) => {
|
// const assert_work_item = (wut, item) => {
|
||||||
// if ( item.$ !== wut ) {
|
// if ( item.$ !== wut ) {
|
||||||
@ -221,11 +223,11 @@ const v0_2 = async (req, res) => {
|
|||||||
result.recipients[i] = result.recipients[i].serialize();
|
result.recipients[i] = result.recipients[i].serialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for ( let i=0 ; i < result.paths.length ; i++ ) {
|
for ( let i=0 ; i < result.shares.length ; i++ ) {
|
||||||
if ( ! result.paths[i] ) continue;
|
if ( ! result.shares[i] ) continue;
|
||||||
if ( result.paths[i] instanceof APIError ) {
|
if ( result.shares[i] instanceof APIError ) {
|
||||||
result.status = 'mixed';
|
result.status = 'mixed';
|
||||||
result.paths[i] = result.paths[i].serialize();
|
result.shares[i] = result.shares[i].serialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -234,7 +236,7 @@ const v0_2 = async (req, res) => {
|
|||||||
console.log('OK');
|
console.log('OK');
|
||||||
if (
|
if (
|
||||||
result.recipients.some(v => v !== null) ||
|
result.recipients.some(v => v !== null) ||
|
||||||
result.paths.some(v => v !== null)
|
result.shares.some(v => v !== null)
|
||||||
) {
|
) {
|
||||||
console.log('DOESNT THIS??')
|
console.log('DOESNT THIS??')
|
||||||
serialize_result();
|
serialize_result();
|
||||||
@ -329,74 +331,104 @@ const v0_2 = async (req, res) => {
|
|||||||
// --- Process Paths ---
|
// --- Process Paths ---
|
||||||
|
|
||||||
// Expect: at least one path
|
// Expect: at least one path
|
||||||
if ( req_paths.length < 1 ) {
|
if ( req_shares.length < 1 ) {
|
||||||
throw APIError.create('field_invalid', null, {
|
throw APIError.create('field_invalid', null, {
|
||||||
key: 'paths',
|
key: 'shares',
|
||||||
expected: 'at least one',
|
expected: 'at least one',
|
||||||
got: 'none',
|
got: 'none',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( let i=0 ; i < req_paths.length ; i++ ) {
|
for ( let i=0 ; i < req_shares.length ; i++ ) {
|
||||||
const value = req_paths[i];
|
const value = req_shares[i];
|
||||||
fsitems_work.push({ i, value });
|
shares_work.push({ i, value });
|
||||||
}
|
}
|
||||||
fsitems_work.lockin();
|
shares_work.lockin();
|
||||||
|
|
||||||
for ( const item of fsitems_work.list() ) {
|
for ( const item of shares_work.list() ) {
|
||||||
const { i } = item;
|
const { i } = item;
|
||||||
let { value } = item;
|
let { value } = item;
|
||||||
|
|
||||||
// adapt all strings to objects
|
const thing = lib_typeTagged.process(value);
|
||||||
if ( typeof value === 'string' ) {
|
if ( thing.$ === 'error' ) {
|
||||||
value = { path: value };
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( whatis(value) !== 'object' ) {
|
|
||||||
item.invalid = true;
|
item.invalid = true;
|
||||||
result.paths[i] =
|
result.shares[i] =
|
||||||
APIError.create('invalid_path', null, {
|
APIError.create('format_error', null, {
|
||||||
path: item.path,
|
message: thing.message
|
||||||
value,
|
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const errors = [];
|
console.log('thing?', thing);
|
||||||
if ( ! value.path ) {
|
|
||||||
errors.push('`path` is required');
|
const allowed_things = ['fs-share', 'app-share'];
|
||||||
|
if ( ! allowed_things.includes(thing.$) ) {
|
||||||
|
APIError.create('disallowed_thing', null, {
|
||||||
|
thing: thing.$,
|
||||||
|
accepted: allowed_things,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
let access = value.access;
|
|
||||||
if ( access ) {
|
if ( thing.$ === 'fs-share' ) {
|
||||||
if ( ! ['read','write'].includes(access) ) {
|
item.type = 'fs';
|
||||||
errors.push('`access` should be `read` or `write`');
|
const errors = [];
|
||||||
|
if ( ! thing.path ) {
|
||||||
|
errors.push('`path` is required');
|
||||||
}
|
}
|
||||||
} else access = 'read';
|
let access = thing.access;
|
||||||
|
if ( access ) {
|
||||||
|
if ( ! ['read','write'].includes(access) ) {
|
||||||
|
errors.push('`access` should be `read` or `write`');
|
||||||
|
}
|
||||||
|
} else access = 'read';
|
||||||
|
|
||||||
if ( errors.length ) {
|
if ( errors.length ) {
|
||||||
item.invalid = true;
|
item.invalid = true;
|
||||||
result.paths[item.i] =
|
result.shares[item.i] =
|
||||||
APIError.create('field_errors', null, {
|
APIError.create('field_errors', null, {
|
||||||
path: item.path,
|
key: `shares[${item.i}]`,
|
||||||
errors
|
errors
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.path = thing.path;
|
||||||
|
item.permission = PermissionUtil.join('fs', thing.path, access);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.path = value.path;
|
if ( thing.$ === 'app-share' ) {
|
||||||
item.permission = PermissionUtil.join('fs', value.path, access);
|
item.type = 'app';
|
||||||
|
const errors = [];
|
||||||
|
if ( ! thing.uid && thing.name ) {
|
||||||
|
errors.push('`uid` or `name` is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( errors.length ) {
|
||||||
|
item.invalid = true;
|
||||||
|
result.shares[item.i] =
|
||||||
|
APIError.create('field_errors', null, {
|
||||||
|
key: `shares[${item.i}]`,
|
||||||
|
errors
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.permission = PermissionUtil.join('app', thing.path, 'access');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fsitems_work.clear_invalid();
|
shares_work.clear_invalid();
|
||||||
|
|
||||||
for ( const item of fsitems_work.list() ) {
|
for ( const item of shares_work.list() ) {
|
||||||
|
if ( item.type !== 'fs' ) continue;
|
||||||
const node = await (new FSNodeParam('path')).consolidate({
|
const node = await (new FSNodeParam('path')).consolidate({
|
||||||
req, getParam: () => item.path
|
req, getParam: () => item.path
|
||||||
});
|
});
|
||||||
|
|
||||||
if ( ! await node.exists() ) {
|
if ( ! await node.exists() ) {
|
||||||
item.invalid = true;
|
item.invalid = true;
|
||||||
result.paths[item.i] = APIError.create('subject_does_not_exist', {
|
result.shares[item.i] = APIError.create('subject_does_not_exist', {
|
||||||
path: item.path,
|
path: item.path,
|
||||||
})
|
})
|
||||||
continue;
|
continue;
|
||||||
@ -417,12 +449,12 @@ const v0_2 = async (req, res) => {
|
|||||||
item.email_link = email_link;
|
item.email_link = email_link;
|
||||||
}
|
}
|
||||||
|
|
||||||
fsitems_work.clear_invalid();
|
shares_work.clear_invalid();
|
||||||
|
|
||||||
// Mark files as successful; further errors will be
|
// Mark files as successful; further errors will be
|
||||||
// reported on recipients instead.
|
// reported on recipients instead.
|
||||||
for ( const item of fsitems_work.list() ) {
|
for ( const item of shares_work.list() ) {
|
||||||
result.paths[item.i] =
|
result.shares[item.i] =
|
||||||
{
|
{
|
||||||
$: 'api:status-report',
|
$: 'api:status-report',
|
||||||
status: 'success',
|
status: 'success',
|
||||||
@ -452,11 +484,11 @@ const v0_2 = async (req, res) => {
|
|||||||
|
|
||||||
const username = recipient_item.user.username;
|
const username = recipient_item.user.username;
|
||||||
|
|
||||||
for ( const path_item of fsitems_work.list() ) {
|
for ( const share_item of shares_work.list() ) {
|
||||||
await svc_permission.grant_user_user_permission(
|
await svc_permission.grant_user_user_permission(
|
||||||
actor,
|
actor,
|
||||||
username,
|
username,
|
||||||
path_item.permission,
|
share_item.permission,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -478,7 +510,7 @@ const v0_2 = async (req, res) => {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const files = []; {
|
const files = []; {
|
||||||
for ( const path_item of fsitems_work.list() ) {
|
for ( const path_item of shares_work.list() ) {
|
||||||
files.push(
|
files.push(
|
||||||
await path_item.node.getSafeEntry(),
|
await path_item.node.getSafeEntry(),
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user