... | ... | @@ -8,15 +8,18 @@ This document governs best practices for writing CFML for inLeague applications |
|
|
2. [Writing API Endpoints](#writing-api-endpoints)
|
|
|
3. [Documenting API Endpoints](#documenting-api-endpoints)
|
|
|
4. [Request Security Object](#request-security-object)
|
|
|
5. Report Builder
|
|
|
|
|
|
## Function Architecture
|
|
|
|
|
|
### Function definitions
|
|
|
|
|
|
* **Return Type Mapping**: Functions should declare a return type without an extended path name (e.g. `public Response function doSomething()` and not `public api.models.Response function doSomething()`. This is because the full mapping for a component may not be consistent across different versions of Lucee.
|
|
|
* **Return One Thing Or Don't Specify**: Functions should return a single type, but may return null. If a function can return either something or null, don't specify a return type in the method signature. Functions should never return two different, non-null types (e.g. a struct or an array)
|
|
|
* **Returning Metadata and Data**: If a function is returning both data and metadata about what the function did, use a **Response@api** object, as it has plumbing for error status, messages, and data, and it maps very well to either cbmessagebox results or API endpoints. When in doubt, return a `Response`.
|
|
|
|
|
|
### What to put in Handlers, Models, or Services
|
|
|
|
|
|
Handlers' job is to handle input. Their job is not to apply business rules, or even be aware of business rules. They are concerned with:
|
|
|
|
|
|
* processing incoming data in the request scope
|
... | ... | @@ -51,19 +54,17 @@ The inLeague API supports several styles of authentication, but only one of them |
|
|
|
|
|
API Handler endpoints should be annotated with the **secured** (or **x-secured**) values matching the AuthService context desired, e,g.
|
|
|
|
|
|
`function index ( event, rc, prc ) secured="PlayerAdmin,DivisionDirector" {
|
|
|
....function
|
|
|
}`
|
|
|
`function index ( event, rc, prc ) secured="PlayerAdmin,DivisionDirector" { ....function }`
|
|
|
|
|
|
The above endpoint will reject any request from a user not authorized in the PlayerAdmin or DivisionDirector roles.
|
|
|
|
|
|
*Neglecting to include the secured attribute in an API function definition will make the endpoint available to any authenticated user.*
|
|
|
_Neglecting to include the secured attribute in an API function definition will make the endpoint available to any authenticated user._
|
|
|
|
|
|
### API Endpoint Routing
|
|
|
|
|
|
Prior to April 2020, the current API routing scheme was simple but disorganized due to all API routes living in the `ModuleConfig` for the `v1` module. For instance, the API endpoint to list all available seasons using the `seasons` handler with the `list` function is:
|
|
|
|
|
|
```.route( "/seasons/list" )
|
|
|
```.route(:"/seasons/list" )
|
|
|
.withAction( {
|
|
|
GET = "list"
|
|
|
} )
|
... | ... | @@ -105,7 +106,6 @@ This module's routing has a traditional Coldbox handler of `Home` handling regul |
|
|
There are two considerations to keep in mind when using event caching:
|
|
|
|
|
|
1. **Every unique combination of arguments is cached separately**. In our `lookupUser` example, passing a different `username` argument to the event will create a new cache key for that event and that username. Changing any part of the query string or event arguments will result in a new cache key.
|
|
|
|
|
|
2. **Cache Eviction**. Whenever the underlying data changes, it is essential to update the cache. This is best done through the convention of an interceptor, e.g. a Quick `postSave()` interception point that will clear all related API events. The example of `lookupUser` has a scenario (however unlikely) where a request is made for a login that doesn't yet exist, but is then added before the ten minute timeout expires. We would clear the relevant cache with this snipper:
|
|
|
|
|
|
`cachebox.getCache( "template" ).clearEvent('v1:authenticate.lookupUser','username=' & arguments.username )`
|
... | ... | @@ -177,6 +177,7 @@ The default behavior of `getExpandedIncludes()` is to append any values from the |
|
|
`getExpandedIncludes()` is defined on the inLegaue base Quick entity.
|
|
|
|
|
|
#### Expandables with custom authorization
|
|
|
|
|
|
By default, there are no authorization checks performed when requesting expandables; the API assumes that, if you have access to the endpoint invoking the expandable, you have access to the expandable. A simple, additional mechanism is available by specifying a value in the `expandableAuth` struct on the memento whose key matches the expandable name, e.g.
|
|
|
|
|
|
```
|
... | ... | @@ -232,3 +233,12 @@ In the event that the `qUser` object is needed at any point in the request on th |
|
|
## API Result Pagination
|
|
|
|
|
|
The inLeague API Response object was originally based on Coldbox 5's HMVC template. We have back-ported the addition of the pagination struct in CB6. The handler must have `prc.pagination` enabled ((`true`)), and then any data-execution functions must use Quick or QB's `.paginate()` functions, and `setDataWithPagination()` on the Response object instead of `setData()`. See this commit for an example in `invoice.cfc`: https://gitlab.inleague.io/inLeague/inLeague/-/commit/d8557f871fa3ac6cefdb3fa01b3d36976d1c0b23
|
|
|
|
|
|
# Report Builder (v4: 2024+)
|
|
|
|
|
|
The Report Builder is the library service for all reportable entities within inLeague and consists of:
|
|
|
|
|
|
* **Common Entities** that form the basis of each report: Players, Registered Players, Volunteers, Teams, Games, Volunteer Points, Score Transactions (e.g. yellow cards), and financial transactions. Some entities share data (e.g. **PlayerCommon** contains filters and output columns common to both Players and Registered Players).
|
|
|
* Entities map to a Quick 'root entity' (e.g. `/reportBuilder/CommonEntities/Player.cfc:getFreshEntity()` upon which all operations are performed, before dropping to `.asQuery()` and returning an array of structs
|
|
|
* **Resolvers** on each entity that map to data fields containing one or both of a **where resolver** (aka 'filter') that governs the criteria of the report (e.g. 'all (entities) where (field) is (value) and a **select resolver** that governs the display.
|
|
|
* **Utilities** for generating and evaluating resolvers and populating select lists |
|
|
\ No newline at end of file |