backend/app/models/api/Page.model.js

169 lines
5.0 KiB
JavaScript

const Model = require("flitter-orm/src/model/Model");
const { ObjectId } = require("mongodb");
const uuid = require('uuid/v4');
const ActiveScope = require('../scopes/Active.scope')
/*
* Page Model
* -------------------------------------------------------------
* Put some description here!
*/
class Page extends Model {
static get services() {
return [...super.services, "models"];
}
static get schema() {
// Return a flitter-orm schema here.
return {
UUID: {type: String, default: () => uuid()},
Name: String,
OrgUserId: ObjectId,
IsPublic: {type: Boolean, default: true},
IsVisibleInMenu: {type: Boolean, default: true},
ParentId: String,
NodeIds: [String],
CreatedAt: {type: Date, default: () => new Date},
UpdatedAt: {type: Date, default: () => new Date},
DeletedAt: Date,
Active: {type: Boolean, default: true},
CreatedUserId: {type: String},
UpdateUserId: {type: String},
ChildPageIds: [String],
// Menu flags
noDelete: { type: Boolean, default: false },
virtual: { type: Boolean, default: false },
// Sharing properties
shared_users_view: [ObjectId],
shared_users_update: [ObjectId],
shared_users_manage: [ObjectId],
};
}
static scopes = [new ActiveScope]
is_shared() {
return this.shared_users_view.length > 0 || this.shared_users_update.length > 0 || this.shared_users_manage.length > 0
}
// ================= RELATIONSHIPS =================
get user() {
const User = this.models.get("auth:User")
return this.belongs_to_one(User, "OrgUserId", "_id")
}
get nodes() {
const Node = this.models.get("api:Node")
return this.has_many(Node, "NodeIds", "UUID")
}
get childPages() {
const Page = this.models.get("api:Page")
return this.has_many(Page, "ChildPageIds", "UUID")
}
get parent() {
const Parent = this.models.get("api:Page")
return this.belongs_to_one(Parent, "ParentId", "UUID")
}
get view_users() {
const User = this.models.get('auth:User')
return this.has_many(User, 'shared_users_view', '_id')
}
get update_users() {
const User = this.models.get('auth:User')
return this.has_many(User, 'shared_users_update', '_id')
}
get manage_users() {
const User = this.models.get('auth:User')
return this.has_many(User, 'shared_users_manage', '_id')
}
// ================= SECURITY =================
accessible_by(user, mode = 'view') {
const base_access = user.can(`page:${this.UUID}:${mode}`)
}
async is_accessible_by(user, mode = 'view') {
const can_manage = user.can(`page:${this.UUID}:manage`)
const can_update = user.can(`page:${this.UUID}:update`)
const can_view = user.can(`page:${this.UUID}:view`)
const can_all = user.can(`page:${this.UUID}`)
// Allow universal access
if ( can_all ) return true
// deny if blocked
else if ( user.can(`page:${this.UUID}:block`) ) return false
// manage, update, view can view
else if ( mode === 'view' && (can_manage || can_update || can_view) ) return true
// manage, update can update
else if ( mode === 'update' ) {
if ( can_manage || can_update ) return true
// If other permissions are explicitly set for this page, use those
else if ( can_view ) return false
}
// manage can manage
else if ( mode === 'manage' ) {
if ( can_manage ) return true
// If other permissions are explicitly set for this page, use those
else if ( can_update || can_view ) return false
}
// allow universal access
// deny blocked users
// If there are no explicit permissions set for this node, check the parent
const parent = await this.parent
if ( parent ) return (await this.parent).is_accessible_by(user, mode)
else return false
}
async access_level_for(user) {
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, 'view') ) return 'view'
else return false
}
async share_with(user, level = 'view') {
if ( !['view', 'update', 'manage'].includes(level) ) {
throw new Error(`Invalid share level: ${level}`)
}
// Remove existing sharing info
await this.unshare_with(user)
// Add the page to the user's permissions:
user.allow(`page:${this.UUID}:${level}`)
// Add the user to the appropriate access list
this[`shared_users_${level}`].push(user._id)
await this.save()
await user.save()
}
async unshare_with(user) {
// Remove this page from the user's permissions
if ( user.can(`page:${this.UUID}`) ) user.disallow(`page:${this.UUID}`)
for ( const level of ['view', 'update', 'manage'] ) {
if ( user.can(`page:${this.UUID}:${level}`) ) user.disallow(`page:${this.UUID}:${level}`)
}
// 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_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)
await this.save()
await user.save()
}
}
module.exports = exports = Page;