Skip to content

Commit

Permalink
initial suppliments conversion and move of perms manager to a utility.
Browse files Browse the repository at this point in the history
  • Loading branch information
Morgul committed May 22, 2024
1 parent 56f1f84 commit ac6d340
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 245 deletions.
243 changes: 9 additions & 234 deletions src/server/managers/supplement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,146 +2,21 @@
// SupplementManager
//----------------------------------------------------------------------------------------------------------------------

import { inspect } from 'node:util';
import { Knex } from 'knex';
import logging from '@strata-js/util-logging';

// Managers
import * as permMan from './permissions';

// Models
import { Account } from '../../common/interfaces/models/account';
import { Supplement } from '../models/supplement';

// Resource Access
import * as suppRA from '../resource-access/supplement';

// Utilities
import { getDB } from '../utils/database';
import { applyFilters } from '../knex/utils';
import { FilterToken } from '../routes/utils';
import { camelCaseKeys } from '../utils/misc';

// Errors
import { MultipleResultsError, DuplicateSupplementError, NotFoundError, NotAuthorizedError } from '../errors';

//----------------------------------------------------------------------------------------------------------------------

const logger = logging.getLogger(module.filename);

//----------------------------------------------------------------------------------------------------------------------

async function $checkViewAccess(
query : Knex.QueryBuilder,
systemPrefix ?: string,
account ?: Account
) : Promise<Knex.QueryBuilder>
{
if(account && systemPrefix)
{
// Generally, this is just going to be admins; but hey, why not let admins see everything?
if(!permMan.hasPerm(account, `${ systemPrefix }/canViewContent`))
{
// Add scoping in
query = query.where(function()
{
this.where({ scope: 'public' }).orWhere({ scope: 'user', owner: account.id });
});
}
}

return query;
}

async function $checkModAccess(
supplement : Supplement,
systemPrefix : string,
type : string,
account ?: Account
) : Promise<void>
{
if(account)
{
// Check if we have permission to remove
const hasRight = permMan.hasPerm(account, `${ systemPrefix }/canModifyContent`);
const isOwner = supplement.scope === 'user' && account.id === supplement.owner;
if(!hasRight && !isOwner)
{
throw new NotAuthorizedError(
'modify',
`${ systemPrefix }/${ type }/${ supplement.name }/${ supplement.id }`
);
}
}
}

async function $ensureOfficialAllowed(
supplement : Supplement,
systemPrefix : string,
account ?: Account
) : Promise<void>
{
if(account)
{
// Check if we have permission to set official
const hasRight = permMan.hasPerm(account, `${ systemPrefix }/canSetOfficial`);
if(!hasRight)
{
supplement.official = false;
}
}
}

async function $ensureCorrectOwner(
supplement : Supplement,
systemPrefix ?: string,
account ?: Account
) : Promise<Supplement>
{
if(account && systemPrefix)
{
const hasRight = permMan.hasPerm(account, `${ systemPrefix }/canModifyContent`);
const isOwner = account.id === supplement.owner;

// If we're not an admin user, and therefore allowed to add/edit content for other people, we have to make sure
// that the owner is set to the account making this call. (Assuming one was passed in, of course.)
if(supplement.scope === 'user' && (isOwner || hasRight))
{
supplement.owner = account.id;
}

if(supplement.scope === 'public')
{
supplement.owner = undefined;
}
}

return supplement;
}

//----------------------------------------------------------------------------------------------------------------------

export async function get(id : number, type : string, systemPrefix : string, account ?: Account) : Promise<Supplement>
{
const tableName = `${ systemPrefix }_${ type }`;
const db = await getDB();
const query = db(`${ tableName } as t`)
.select('t.*', 'a.account_id as ownerHash')
.leftJoin('account as a', 'a.account_id', '=', 't.owner')
.where({ id });

// Handle retrieval
const supplements = await $checkViewAccess(query, systemPrefix, account);
if(supplements.length > 1)
{
throw new MultipleResultsError(type);
}
else if(supplements.length === 0)
{
throw new NotFoundError(`No ${ type } with id '${ id }' found.`);
}
else
{
const { ownerHash, ...restSupp } = supplements[0];
return Supplement.fromDB(systemPrefix, type, { ...camelCaseKeys(restSupp), owner: ownerHash });
}
return suppRA.get(id, type, systemPrefix, account);
}

export async function list(
Expand All @@ -150,34 +25,12 @@ export async function list(
account ?: Account
) : Promise<Supplement[]>
{
const tableName = `${ systemPrefix }_${ type }`;
const db = await getDB();
let query = db(`${ tableName } as t`)
.select('t.*', 'a.account_id as ownerHash')
.leftJoin('account as a', 'a.account_id', '=', 't.owner');

// Add filters for only what we have access to
query = await $checkViewAccess(query, systemPrefix, account);

// Apply any filters
query = applyFilters(query, filters);

return (await query).map((supp) =>
{
const { ownerHash, ...restSupp } = supp;
return Supplement.fromDB(systemPrefix, type, { ...camelCaseKeys(restSupp), owner: ownerHash });
});
return suppRA.list(filters, type, systemPrefix, account);
}

export async function exists(id : number, type : string, systemPrefix : string, account ?: Account) : Promise<boolean>
{
// If you're paying attention, you'll realize we also return 'undefined' (and hence false for existence) if there's
// a duplicate. This is fine, it means the DB is somehow screwed, so all bets are off, better to err on the side of
// saying this doesn't exist, rather than allowing for it to be referenced.
const supp = await get(id, type, systemPrefix, account).catch(() => undefined);

// We only need a boolean.
return !!supp;
return suppRA.exists(id, type, systemPrefix, account);
}

export async function add(
Expand All @@ -186,39 +39,7 @@ export async function add(
account ?: Account
) : Promise<Supplement>
{
const db = await getDB();
const tableName = `${ systemPrefix }_${ type }`;
const supplement = Supplement.fromJSON(systemPrefix, type, newSupplement);

// Ensure the supplement's ownership is valid.
await $ensureCorrectOwner(supplement, systemPrefix, account);

// Ensure that official is allowed to be set.
await $ensureOfficialAllowed(supplement, systemPrefix, account);

// Make sure we have permission to modify
await $checkModAccess(supplement, systemPrefix, type, account);

// First, we check to see if we already have one that matches the unique constraint. We do this manually, because
// it's very hard to catch specific sqlite errors reliably, so we do the check explicitly.
const suppExists = (await db(tableName)
.select()
.where({ scope: supplement.scope, owner: supplement.owner ?? null, name: supplement.name })).length > 0;

if(suppExists)
{
logger.warn(
'Attempted to add supplement with the same name, scope and owner as an existing one:',
inspect(supplement.toJSON(), { depth: null })
);
throw new DuplicateSupplementError(`${ systemPrefix }/${ type }/${ supplement.name }`);
}

// Now, we insert the supplement
const [ id ] = await db(tableName).insert(supplement.toDB());

// Return the inserted supplement
return get(id, type, systemPrefix, account);
return suppRA.add(newSupplement, type, systemPrefix, account);
}

export async function update(
Expand All @@ -228,38 +49,7 @@ export async function update(
systemPrefix : string, account ?: Account
) : Promise<Supplement>
{
const db = await getDB();
const supplement = await get(id, type, systemPrefix, account);
const tableName = `${ systemPrefix }_${ type }`;

// Mix the current character with the allowed updates. Note: because we don't know what properties to allow to be
// updatable, we assume everything but ID is. Instead of trying to destructure just id out, we apply everything,
// and re-apply id. It's less efficient, but more explicit.
const allowedUpdate = {
...supplement.toJSON(),
...updateSup,
id
};

// Make a new character object
const newSupplement = Supplement.fromJSON(systemPrefix, type, allowedUpdate);

// Ensure the supplement's ownership is valid.
await $ensureCorrectOwner(supplement, systemPrefix, account);

// Ensure that official is allowed to be set.
await $ensureOfficialAllowed(supplement, systemPrefix, account);

// Make sure we have permission to modify
await $checkModAccess(newSupplement, systemPrefix, type, account);

// Now, we update the supplement
await db(tableName)
.update(newSupplement.toDB())
.where({ id });

// Return the updated supplement
return get(id, type, systemPrefix, account);
return suppRA.update(id, updateSup, type, systemPrefix, account);
}

export async function remove(
Expand All @@ -269,22 +59,7 @@ export async function remove(
account ?: Account
) : Promise<{ status : 'ok' }>
{
const db = await getDB();
const supplement = await get(id, type, systemPrefix, account).catch(() => undefined);
const tableName = `${ systemPrefix }_${ type }`;

if(supplement)
{
// Make sure we have permission to modify
await $checkModAccess(supplement, systemPrefix, type, account);

// Delete the supplement
await db(tableName)
.delete()
.where({ id });
}

return { status: 'ok' };
return suppRA.remove(id, type, systemPrefix, account);
}

//----------------------------------------------------------------------------------------------------------------------
Loading

0 comments on commit ac6d340

Please sign in to comment.