generated from alphane/template
Initial commit
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import { ModelStatic, Model, Attributes, FindOptions, ScopeOptions, literal } from "@sequelize/core"
|
||||
|
||||
import { User } from "@/models"
|
||||
import { Path, deepPick } from "@/utils/deep-pick"
|
||||
import { isInteger, isNil } from "lodash"
|
||||
|
||||
export type Actions = "show" | "create" | "update" | "destroy"
|
||||
|
||||
export const NO_RECORDS_SCOPE = { where: literal("1 = 0") }
|
||||
export const ALL_RECORDS_SCOPE = {}
|
||||
|
||||
/**
|
||||
* See PolicyFactory below for policy with scope helpers
|
||||
*/
|
||||
export class BasePolicy<M extends Model> {
|
||||
protected user: User
|
||||
protected record: M
|
||||
|
||||
constructor(user: User, record: M) {
|
||||
this.user = user
|
||||
this.record = record
|
||||
}
|
||||
|
||||
show(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
create(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
update(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
destroy(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
static policyScope<M extends Model>(user: User, ...args: unknown[]): FindOptions<Attributes<M>> {
|
||||
throw new Error("Derived classes must implement policyScope method")
|
||||
}
|
||||
|
||||
permitAttributes(record: Partial<M>): Partial<M> {
|
||||
return deepPick(record, this.permittedAttributes())
|
||||
}
|
||||
|
||||
permitAttributesForCreate(record: Partial<M>): Partial<M> {
|
||||
if (this.permittedAttributesForCreate !== BasePolicy.prototype.permittedAttributesForCreate) {
|
||||
return deepPick(record, this.permittedAttributesForCreate())
|
||||
} else {
|
||||
return deepPick(record, this.permittedAttributes())
|
||||
}
|
||||
}
|
||||
|
||||
permitAttributesForUpdate(record: Partial<M>): Partial<M> {
|
||||
if (this.permittedAttributesForUpdate !== BasePolicy.prototype.permittedAttributesForUpdate) {
|
||||
return deepPick(record, this.permittedAttributesForUpdate())
|
||||
} else {
|
||||
return deepPick(record, this.permittedAttributes())
|
||||
}
|
||||
}
|
||||
|
||||
permittedAttributes(): Path[] {
|
||||
throw new Error("Not Implemented")
|
||||
}
|
||||
|
||||
permittedAttributesForCreate(): Path[] {
|
||||
throw new Error("Not Implemented")
|
||||
}
|
||||
|
||||
permittedAttributesForUpdate(): Path[] {
|
||||
throw new Error("Not Implemented")
|
||||
}
|
||||
|
||||
setNumberNullIfEmpty(input: number | null | undefined): number | null | undefined {
|
||||
let output = input
|
||||
if (!isNil(input) && !isInteger(input)) output = undefined
|
||||
if (!isNil(input) && input == 0) output = undefined
|
||||
return output
|
||||
}
|
||||
|
||||
setNumberZeroIfEmpty(input: number | null | undefined): number {
|
||||
let output = input
|
||||
if (!isNil(input) && !isInteger(input)) output = 0
|
||||
if (!isNil(input) && input == 0) output = 0
|
||||
return output ?? 0
|
||||
}
|
||||
/**
|
||||
* Add to support return policy information via this.reponse.json({ someObject, policy })
|
||||
*
|
||||
* If this method becomes complex, it should be broken out into a serializer.
|
||||
*
|
||||
* @returns a JSON representation of the policy
|
||||
*/
|
||||
toJSON(): Record<Actions, boolean> {
|
||||
return {
|
||||
show: this.show(),
|
||||
create: this.create(),
|
||||
update: this.update(),
|
||||
destroy: this.destroy(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See api/node_modules/sequelize/types/model.d.ts -> Model -> scope
|
||||
export type BaseScopeOptions = string | ScopeOptions
|
||||
|
||||
export const POLICY_SCOPE_NAME = "policyScope"
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type AllArgsButFirstOne<T extends any[]> = T extends [any, ...infer Rest] ? Rest : never
|
||||
|
||||
export function PolicyFactory<M extends Model, T extends Model = M>(modelClass: ModelStatic<M>) {
|
||||
const policyClass = class Policy extends BasePolicy<T> {
|
||||
static applyScope<P extends typeof Policy>(
|
||||
this: P,
|
||||
scopes: BaseScopeOptions[],
|
||||
user: User,
|
||||
...extraPolicyScopeArgs: AllArgsButFirstOne<Parameters<P["policyScope"]>>
|
||||
): ModelStatic<M> {
|
||||
this.ensurePolicyScope()
|
||||
return modelClass.withScope([
|
||||
...scopes,
|
||||
{ method: [POLICY_SCOPE_NAME, user, ...extraPolicyScopeArgs] },
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* Just in time scope creation for model class.
|
||||
* TODO: to have scope creation occur at definition time, instead of execution time.
|
||||
*/
|
||||
static ensurePolicyScope() {
|
||||
if (Object.prototype.hasOwnProperty.call(modelClass.options.scopes, POLICY_SCOPE_NAME)) {
|
||||
return
|
||||
}
|
||||
|
||||
modelClass.addScope(POLICY_SCOPE_NAME, this.policyScope.bind(modelClass))
|
||||
}
|
||||
}
|
||||
|
||||
return policyClass
|
||||
}
|
||||
|
||||
export default PolicyFactory
|
||||
Reference in New Issue
Block a user