|
/** |
|
* This file contains shared type definitions for this app. |
|
* |
|
* Our type-organization methodology is straightforward and simplistic. |
|
* |
|
* 1. Common types that should be shared across the app live in this file. |
|
* - There should be no (or VERY limited) local imports in this file. This is to reduce nasty dependency cycles. |
|
* - Imports from external 3rd party packages are allowed if needed. |
|
* |
|
* 2. Unique types should be defined as close to where they are consumed as possible. |
|
* |
|
* 3. If an unavoidable circular type dependency exists, it can be over-ridden but must include a comment explaining the override. |
|
* ! We do not want to disable circular warnings often and when we do we must be certain there are no runtime consequences. |
|
* ! Type-specific circ warnings are generally safe to bypass and are sometimes unavoidable. |
|
*/ |
|
|
|
import { BaseListTypeInfo, FieldTypeFunc, KeystoneContext } from '@keystone-6/core/types'; |
|
|
|
// ! We are disabling circular dependency warnings here because we HAVE to have permission fields references to build the correct shared type in this file. |
|
// eslint-disable-next-line import/no-cycle |
|
import { permissionFields } from '../access-control/fields'; |
|
|
|
/** |
|
******************************************************************************* |
|
* Enum Type Constants |
|
******************************************************************************* |
|
*/ |
|
|
|
/** |
|
* Environments our app needs to account for |
|
*/ |
|
export enum ENV { |
|
DEVELOPMENT = 'development', |
|
// Provided by render.com at runtime: https://render.com/docs/environment-variables#node |
|
PRODUCTION = 'production', |
|
} |
|
|
|
/** |
|
* CRUD-type operations our application handles |
|
*/ |
|
export enum CRUD { |
|
CREATE = 'create', |
|
QUERY = 'query', |
|
UPDATE = 'update', |
|
DELETE = 'delete', |
|
} |
|
|
|
/** |
|
* Available access control types on Lists (Schemas) |
|
*/ |
|
export enum LIST_ACCESS_CONTROLS { |
|
OPERATION = 'operation', |
|
FILTER = 'filter', |
|
ITEM = 'item', |
|
} |
|
|
|
/** |
|
* Registered Schemas in our app. We use these enums as dynamic keys directly when |
|
* defining a new schema list. New Schemas should have their key/value added here |
|
* before anything else is done. |
|
* |
|
* * These are title cased because that's what Keystone expects. This is a little odd |
|
* * for object keys typically and i'm not a fan, but we do what we gotta do. |
|
*/ |
|
export enum SCHEMAS { |
|
// AVATAR = 'Avatar', |
|
REGION = 'Region', |
|
RESOURCE = 'Resource', |
|
ROLE = 'Role', |
|
SCHOOL = 'School', |
|
SPONSOR = 'Sponsor', |
|
STATE = 'State', |
|
STORY = 'Story', |
|
USER = 'User', |
|
} |
|
|
|
/** |
|
* Registered Roles in our App. These key/values pairs are based on the `Role.key` Schema field |
|
* |
|
* * Theoretically this it isn't a true constant. At present the role names are arbitrary and are |
|
* * defined in the CMS. We need to manually add them here if a new one is added. |
|
* |
|
* ! enum naming convention overridden because we needed to match our defined `role.key` field format in the database. |
|
*/ |
|
export enum ROLES { |
|
// eslint-disable-next-line @typescript-eslint/naming-convention |
|
'super-admin' = 'super-admin', |
|
// eslint-disable-next-line @typescript-eslint/naming-convention |
|
admin = 'admin', |
|
// eslint-disable-next-line @typescript-eslint/naming-convention |
|
'school-admin' = 'school-admin', |
|
// eslint-disable-next-line @typescript-eslint/naming-convention |
|
// student = 'student', |
|
} |
|
|
|
/** |
|
* Generates a resolved value list from enums. |
|
*/ |
|
export type SchemaName = `${SCHEMAS}`; |
|
export type RoleName = `${ROLES}`; |
|
export type CrudName = `${CRUD}`; |
|
export type ListAccessControlName = `${LIST_ACCESS_CONTROLS}`; |
|
|
|
/** |
|
******************************************************************************* |
|
* User Session Types |
|
******************************************************************************* |
|
*/ |
|
|
|
export interface UserSessionData { |
|
id: string; |
|
email: string; |
|
name?: string; |
|
isBanned?: boolean; |
|
isTestAccount?: boolean; |
|
createdAt: string; |
|
lastUpdatedAt: string; |
|
// TODO: add type for School |
|
adminOfSchool: any; |
|
studentOfSchool: any; |
|
role?: { |
|
id: string; |
|
name?: string; |
|
key?: RoleName; |
|
} & { |
|
[key in PermissionName]: boolean; |
|
}; |
|
} |
|
|
|
export interface Session { |
|
itemId: string; |
|
listKey: SCHEMAS.USER; |
|
data: UserSessionData; |
|
} |
|
|
|
export interface SessionContext { |
|
session?: Session; |
|
} |
|
|
|
/** |
|
******************************************************************************* |
|
* Access Control - Permission Field Types |
|
******************************************************************************* |
|
*/ |
|
|
|
// This is slightly brittle as it is reproducing a camelCase key format. This should probably get moved to a utility type. |
|
export type PermissionKey = `${Uncapitalize<SCHEMAS>}${Capitalize<CRUD>}`; |
|
export type PermissionName = keyof typeof permissionFields; |
|
export type ListOperationPermissionFields = { |
|
[key in PermissionKey]: FieldTypeFunc<BaseListTypeInfo>; |
|
}; |
|
export type PermissionCheckFunc = <T extends SessionContext>({ session }: T) => boolean; |
|
|
|
/** |
|
******************************************************************************* |
|
* Access Control - Base Types |
|
******************************************************************************* |
|
*/ |
|
|
|
// TODO: Look into leveraging built-in Keystone types instead of making our own... OR... determine which ones we HAVE to make at which ones we can leverage |
|
export type BaseAccessArgs = { |
|
session?: Session; |
|
context: KeystoneContext; |
|
listKey: SchemaName; |
|
}; |
|
|
|
export type ListAccessOperationCRUDName = CrudName; |
|
export type ListAccessFilterCRUDName = Exclude<CrudName, 'create'>; |
|
export type ListAccessItemCRUDName = Exclude<CrudName, 'query'>; |
|
|
|
/** |
|
* A conditional type that changes the available CRUD operations based |
|
* on the provided List Access control (operation | filter | item). |
|
*/ |
|
export type ListAccessArgs<Control> = Control extends 'filter' |
|
? BaseAccessArgs & { |
|
operation: ListAccessFilterCRUDName; |
|
} |
|
: Control extends 'item' |
|
? BaseAccessArgs & { |
|
operation: ListAccessItemCRUDName; |
|
} |
|
: never; |
|
|
|
/** |
|
******************************************************************************* |
|
* Access Control - List Operation Types |
|
******************************************************************************* |
|
*/ |
|
|
|
/** |
|
* The tuple signature for operation checks where: |
|
* 1. the fist item is a condition function |
|
* 2. the second is the return value that indicates if a user can perform the operation if the given condition evaluates to true |
|
*/ |
|
export type ListAccessOperationCheck = [PermissionCheckFunc, boolean]; |
|
|
|
/** |
|
******************************************************************************* |
|
* Access Control - List Filter Types |
|
******************************************************************************* |
|
*/ |
|
|
|
export type ListAccessFilterReturn = boolean | BaseListTypeInfo['inputs']['where']; |
|
export type ListAccessFilter = <T extends SessionContext>({ session }: T) => ListAccessFilterReturn; |