#78 - include file boxes in page children for sidebar
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Garrett Mills 2021-02-04 15:15:39 -06:00
parent e32d38e9b6
commit 1758340a7b
Signed by: garrettmills
GPG Key ID: D2BF5FBA8298F246

View File

@ -11,323 +11,341 @@ const { PageType } = require('../../enum');
* Put some description here! * Put some description here!
*/ */
class Page extends VersionedModel { class Page extends VersionedModel {
static get services() { static get services() {
return [...super.services, 'models']; return [...super.services, 'models'];
} }
static get schema() { static get schema() {
// Return a flitter-orm schema here. // Return a flitter-orm schema here.
return { return {
...super.schema, ...super.schema,
UUID: {type: String, default: () => uuid()}, UUID: {type: String, default: () => uuid()},
Name: String, Name: String,
OrgUserId: ObjectId, OrgUserId: ObjectId,
IsPublic: {type: Boolean, default: true}, IsPublic: {type: Boolean, default: true},
IsVisibleInMenu: {type: Boolean, default: true}, IsVisibleInMenu: {type: Boolean, default: true},
ParentId: String, ParentId: String,
NodeIds: [String], NodeIds: [String],
CreatedAt: {type: Date, default: () => new Date}, CreatedAt: {type: Date, default: () => new Date},
UpdatedAt: {type: Date, default: () => new Date}, UpdatedAt: {type: Date, default: () => new Date},
DeletedAt: Date, DeletedAt: Date,
Active: {type: Boolean, default: true}, Active: {type: Boolean, default: true},
CreatedUserId: {type: String}, CreatedUserId: {type: String},
UpdateUserId: {type: String}, UpdateUserId: {type: String},
ChildPageIds: [String], ChildPageIds: [String],
PageType: {type: String, default: PageType.Note}, // PageType PageType: {type: String, default: PageType.Note}, // PageType
AdditionalData: Object, AdditionalData: Object,
// Menu flags // Menu flags
noDelete: { type: Boolean, default: false }, noDelete: { type: Boolean, default: false },
virtual: { type: Boolean, default: false }, virtual: { type: Boolean, default: false },
// Sharing properties // Sharing properties
shared_users_view: [ObjectId], shared_users_view: [ObjectId],
shared_users_update: [ObjectId], shared_users_update: [ObjectId],
shared_users_manage: [ObjectId], shared_users_manage: [ObjectId],
}; };
} }
static scopes = [new ActiveScope] static scopes = [new ActiveScope]
static async visible_by_user(user) { static async visible_by_user(user) {
const user_root = await user.get_root_page() const user_root = await user.get_root_page()
const user_root_children = await user_root.visible_flat_children(user) const user_root_children = await user_root.visible_flat_children(user)
const view_only_trees = await this.find({ shared_users_view: user._id }) const view_only_trees = await this.find({ shared_users_view: user._id })
let view_only_children = [] let view_only_children = []
for ( const tree of view_only_trees ) { for ( const tree of view_only_trees ) {
if ( await tree.is_accessible_by(user) ) { if ( await tree.is_accessible_by(user) ) {
view_only_children.push(tree) view_only_children.push(tree)
view_only_children = [...view_only_children, await tree.visible_flat_children(user)] view_only_children = [...view_only_children, await tree.visible_flat_children(user)]
} }
} }
const update_trees = await this.find({ shared_users_update: user._id }) const update_trees = await this.find({ shared_users_update: user._id })
let update_children = [] let update_children = []
for ( const tree of update_trees ) { for ( const tree of update_trees ) {
if ( await tree.is_accessible_by(user) ) { if ( await tree.is_accessible_by(user) ) {
update_children.push(tree) update_children.push(tree)
update_children = [...update_children, await tree.visible_flat_children(user)] update_children = [...update_children, await tree.visible_flat_children(user)]
} }
} }
const manage_trees = await this.find({ shared_users_manage: user._id }) const manage_trees = await this.find({ shared_users_manage: user._id })
let manage_children = [] let manage_children = []
for ( const tree of manage_trees ) { for ( const tree of manage_trees ) {
if ( await tree.is_accessible_by(user) ) { if ( await tree.is_accessible_by(user) ) {
manage_children.push(tree) manage_children.push(tree)
manage_children = [...manage_children, await tree.visible_flat_children(user)] manage_children = [...manage_children, await tree.visible_flat_children(user)]
} }
} }
const all_children = [...user_root_children, ...view_only_children, ...update_children, ...manage_children] const all_children = [...user_root_children, ...view_only_children, ...update_children, ...manage_children]
const unique_children = [] const unique_children = []
const seen_UUIDs = [] const seen_UUIDs = []
all_children.forEach(child => { all_children.forEach(child => {
if ( !seen_UUIDs.includes(child.UUID) ) { if ( !seen_UUIDs.includes(child.UUID) ) {
unique_children.push(child) unique_children.push(child)
seen_UUIDs.push(child.UUID) seen_UUIDs.push(child.UUID)
} }
}) })
return unique_children return unique_children
} }
async visible_flat_children(user) { async visible_flat_children(user) {
const children = await this.childPages const children = await this.childPages
let visible = [] let visible = []
if ( children ) { if ( children ) {
for ( const child of children ) { for ( const child of children ) {
if ( !(await child.is_accessible_by(user)) ) continue if ( !(await child.is_accessible_by(user)) ) continue
visible.push(child) visible.push(child)
visible = [...visible, ...(await child.visible_flat_children(user))] visible = [...visible, ...(await child.visible_flat_children(user))]
} }
} }
return visible return visible
} }
is_shared() { is_shared() {
return this.shared_users_view.length > 0 || this.shared_users_update.length > 0 || this.shared_users_manage.length > 0 return this.shared_users_view.length > 0 || this.shared_users_update.length > 0 || this.shared_users_manage.length > 0
} }
// ================= RELATIONSHIPS ================= // ================= RELATIONSHIPS =================
get user() { get user() {
const User = this.models.get("auth:User") const User = this.models.get("auth:User")
return this.belongs_to_one(User, "OrgUserId", "_id") return this.belongs_to_one(User, "OrgUserId", "_id")
} }
get nodes() { get nodes() {
const Node = this.models.get("api:Node") const Node = this.models.get("api:Node")
const node_promise = this.has_many(Node, "NodeIds", "UUID") const node_promise = this.has_many(Node, "NodeIds", "UUID")
if ( this.is_a_version() ) { if ( this.is_a_version() ) {
// return the nodes for this version! // return the nodes for this version!
return (async () => { return (async () => {
const nodes = await node_promise const nodes = await node_promise
const version_data = this.raw_version_data() const version_data = this.raw_version_data()
const return_nodes = [] const return_nodes = []
if ( version_data?.node_version_nums ) { if ( version_data?.node_version_nums ) {
const node_x_version_num = {} const node_x_version_num = {}
for ( const item of version_data.node_version_nums ) { for ( const item of version_data.node_version_nums ) {
node_x_version_num[item.NodeId] = item.version_num node_x_version_num[item.NodeId] = item.version_num
} }
for ( const node of nodes ) { for ( const node of nodes ) {
if ( node_x_version_num[node.UUID] ) { if ( node_x_version_num[node.UUID] ) {
return_nodes.push(await node.as_version(node_x_version_num[node.UUID])) return_nodes.push(await node.as_version(node_x_version_num[node.UUID]))
} else { } else {
return_nodes.push(node) return_nodes.push(node)
} }
} }
} }
return return_nodes return return_nodes
})() })()
} else { } else {
return node_promise return node_promise
} }
} }
get childPages() { get childPages() {
const Page = this.models.get("api:Page") const Page = this.models.get("api:Page")
return this.has_many(Page, "ChildPageIds", "UUID") return this.has_many(Page, "ChildPageIds", "UUID")
} }
get parent() { get parent() {
const Parent = this.models.get("api:Page") const Parent = this.models.get("api:Page")
return this.belongs_to_one(Parent, "ParentId", "UUID") return this.belongs_to_one(Parent, "ParentId", "UUID")
} }
get view_users() { get view_users() {
const User = this.models.get('auth:User') const User = this.models.get('auth:User')
return this.has_many(User, 'shared_users_view', '_id') return this.has_many(User, 'shared_users_view', '_id')
} }
get update_users() { get update_users() {
const User = this.models.get('auth:User') const User = this.models.get('auth:User')
return this.has_many(User, 'shared_users_update', '_id') return this.has_many(User, 'shared_users_update', '_id')
} }
get manage_users() { get manage_users() {
const User = this.models.get('auth:User') const User = this.models.get('auth:User')
return this.has_many(User, 'shared_users_manage', '_id') return this.has_many(User, 'shared_users_manage', '_id')
} }
// ================= SECURITY ================= // ================= SECURITY =================
async is_accessible_by(user, mode = 'view') { async is_accessible_by(user, mode = 'view') {
const can_manage = await user.can(`page:${this.UUID}:manage`) const can_manage = await user.can(`page:${this.UUID}:manage`)
const can_update = await user.can(`page:${this.UUID}:update`) const can_update = await user.can(`page:${this.UUID}:update`)
const can_view = await user.can(`page:${this.UUID}:view`) const can_view = await user.can(`page:${this.UUID}:view`)
const can_all = await user.can(`page:${this.UUID}`) const can_all = await user.can(`page:${this.UUID}`)
// Allow universal access // Allow universal access
if ( can_all ) return true if ( can_all ) return true
// deny if blocked // deny if blocked
else if ( await user.can(`page:${this.UUID}:block`) ) return false else if ( await user.can(`page:${this.UUID}:block`) ) return false
// manage, update, view can view // manage, update, view can view
else if ( mode === 'view' && (can_manage || can_update || can_view) ) return true else if ( mode === 'view' && (can_manage || can_update || can_view) ) return true
// manage, update can update // manage, update can update
else if ( mode === 'update' ) { else if ( mode === 'update' ) {
if ( can_manage || can_update ) return true if ( can_manage || can_update ) return true
// If other permissions are explicitly set for this page, use those // If other permissions are explicitly set for this page, use those
else if ( can_view ) return false else if ( can_view ) return false
} }
// manage can manage // manage can manage
else if ( mode === 'manage' ) { else if ( mode === 'manage' ) {
if ( can_manage ) return true if ( can_manage ) return true
// If other permissions are explicitly set for this page, use those // If other permissions are explicitly set for this page, use those
else if ( can_update || can_view ) return false else if ( can_update || can_view ) return false
} }
// allow universal access // allow universal access
// deny blocked users // deny blocked users
// If there are no explicit permissions set for this node, check the parent // If there are no explicit permissions set for this node, check the parent
const parent = await this.parent const parent = await this.parent
if ( parent ) return (await this.parent).is_accessible_by(user, mode) if ( parent ) return (await this.parent).is_accessible_by(user, mode)
else return false else return false
} }
async access_level_for(user) { async access_level_for(user) {
if ( await this.is_accessible_by(user, 'manage') ) return 'manage' if ( await this.is_accessible_by(user, 'manage') ) return 'manage'
else if ( await this.is_accessible_by(user, 'update') ) return 'update' else if ( await this.is_accessible_by(user, 'update') ) return 'update'
else if ( await this.is_accessible_by(user, 'view') ) return 'view' else if ( await this.is_accessible_by(user, 'view') ) return 'view'
else return false else return false
} }
async share_with(user, level = 'view') { async share_with(user, level = 'view') {
if ( !['view', 'update', 'manage'].includes(level) ) { if ( !['view', 'update', 'manage'].includes(level) ) {
throw new Error(`Invalid share level: ${level}`) throw new Error(`Invalid share level: ${level}`)
} }
// Remove existing sharing info // Remove existing sharing info
await this.unshare_with(user) await this.unshare_with(user)
// Add the page to the user's permissions: // Add the page to the user's permissions:
user.allow(`page:${this.UUID}:${level}`) user.allow(`page:${this.UUID}:${level}`)
// Add the user to the appropriate access list // Add the user to the appropriate access list
this[`shared_users_${level}`].push(user._id) this[`shared_users_${level}`].push(user._id)
// TODO replace user.uid with name of user when we support that // TODO replace user.uid with name of user when we support that
await this.version_save(`Shared with ${user.uid} (${level} access)`, user.id) await this.version_save(`Shared with ${user.uid} (${level} access)`, user.id)
await user.save() await user.save()
} }
async unshare_with(user) { async unshare_with(user) {
// Remove this page from the user's permissions // Remove this page from the user's permissions
if ( await user.can(`page:${this.UUID}`) ) user.disallow(`page:${this.UUID}`) if ( await user.can(`page:${this.UUID}`) ) user.disallow(`page:${this.UUID}`)
for ( const level of ['view', 'update', 'manage'] ) { for ( const level of ['view', 'update', 'manage'] ) {
if ( await user.can(`page:${this.UUID}:${level}`) ) user.disallow(`page:${this.UUID}:${level}`) if ( await user.can(`page:${this.UUID}:${level}`) ) user.disallow(`page:${this.UUID}:${level}`)
} }
// Remove the user from this page's access lists // Remove the user from this page's access lists
this.shared_users_view = this.shared_users_view.filter(x => String(x) !== user.id) this.shared_users_view = this.shared_users_view.filter(x => String(x) !== user.id)
this.shared_users_update = this.shared_users_update.filter(x => String(x) !== user.id) this.shared_users_update = this.shared_users_update.filter(x => String(x) !== user.id)
this.shared_users_manage = this.shared_users_manage.filter(x => String(x) !== user.id) this.shared_users_manage = this.shared_users_manage.filter(x => String(x) !== user.id)
await this.version_save(`Unshared with ${user.uid}`, user.id) await this.version_save(`Unshared with ${user.uid}`, user.id)
await user.save() await user.save()
} }
async get_menu_items(page_only) { async get_menu_items(page_only) {
if ( page_only ) return []; if ( page_only ) return [];
// { // {
// id: child.UUID, // id: child.UUID,
// name: child.is_shared() ? child.Name + ' ⁽ˢʰᵃʳᵉᵈ⁾' : child.Name, // name: child.is_shared() ? child.Name + ' ⁽ˢʰᵃʳᵉᵈ⁾' : child.Name,
// shared: child.is_shared(), // shared: child.is_shared(),
// children: await this._build_menu_object(child), // children: await this._build_menu_object(child),
// type: 'page', // type: 'page',
// } // }
// Databases & Code Snips // Databases & Code Snips
const children = [] const children = []
const Database = this.models.get('api:db:Database') const Database = this.models.get('api:db:Database')
const dbs = await Database.find({ const dbs = await Database.find({
Active: true, Active: true,
PageId: this.UUID, PageId: this.UUID,
}) })
for ( const db of dbs ) { for ( const db of dbs ) {
children.push({ children.push({
id: db.PageId, id: db.PageId,
node_id: db.NodeId, node_id: db.NodeId,
children: [], children: [],
type: 'db', type: 'db',
name: db.Name, name: db.Name,
}) })
} }
const Codium = this.models.get('api:Codium') const FileBox = this.models.get('api:files:FileBox')
const codiums = await Codium.find({ const boxes = await FileBox.find({
PageId: this.UUID, active: true,
}) pageId: this.UUID,
})
for ( const codium of codiums ) { for ( const box of boxes ) {
children.push({ if ( box.parentUUID ) continue; // only show top-level
id: codium.PageId,
node_id: codium.NodeId,
children: [],
type: 'code',
name: codium.code.slice(0, 25) + (codium.code.length > 25 ? '...' : ''),
})
}
return children children.push({
} id: box.pageId,
// node_id // FIXME need to track this w/ the file boxes
children: [],
type: 'file_box',
name: box.name,
})
}
async cast_to_version_data() { const Codium = this.models.get('api:Codium')
const data = await super.cast_to_version_data() const codiums = await Codium.find({
const node_version_nums = [] PageId: this.UUID,
const nodes = await this.nodes })
for ( const node of nodes ) { for ( const codium of codiums ) {
node_version_nums.push({ NodeId: node.UUID, version_num: node.version_num }) children.push({
} id: codium.PageId,
node_id: codium.NodeId,
children: [],
type: 'code',
name: codium.code.slice(0, 25) + (codium.code.length > 25 ? '...' : ''),
})
}
data.node_version_nums = node_version_nums return children
return data }
}
async revert_to_version(version_num, user_id = undefined) { async cast_to_version_data() {
const Node = this.models.get('api:Node') const data = await super.cast_to_version_data()
const reverted = await super.revert_to_version(version_num, user_id) const node_version_nums = []
const data = await this.get_version_data(version_num) const nodes = await this.nodes
// Revert the nodes to the given versions for ( const node of nodes ) {
for ( const node_data of data.node_version_nums ) { node_version_nums.push({ NodeId: node.UUID, version_num: node.version_num })
const node = await Node.findOne({ UUID: node_data.NodeId }) }
if ( node ) {
const reverted_node = await node.revert_to_version(node_data.version_num)
await reverted_node.save()
}
}
await reverted.save() data.node_version_nums = node_version_nums
return reverted return data
} }
async revert_to_version(version_num, user_id = undefined) {
const Node = this.models.get('api:Node')
const reverted = await super.revert_to_version(version_num, user_id)
const data = await this.get_version_data(version_num)
// Revert the nodes to the given versions
for ( const node_data of data.node_version_nums ) {
const node = await Node.findOne({ UUID: node_data.NodeId })
if ( node ) {
const reverted_node = await node.revert_to_version(node_data.version_num)
await reverted_node.save()
}
}
await reverted.save()
return reverted
}
} }
module.exports = exports = Page; module.exports = exports = Page;