generated from alphane/template
Initial commit
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# Serializers
|
||||
|
||||
Serializers take model data, and add or remove fields. They are used to convert a database representation of a model to a front-end representation of a model. This might include removing fields that should not be exposed to the front-end, or adding fields that are derived from the database representation.
|
||||
|
||||
Serializers are used in controllers to convert from a database representation to a front-end data packet. Serializers should not be used for general data formating such as date or money formatting, as formatting those kinds of things in the front-end is generally more flexible.
|
||||
|
||||
e.g. Usage in a Controller might look like this
|
||||
Note that the BaseSerializer supports passing either an array or a single model to the `perform` method.
|
||||
|
||||
```typescript
|
||||
import { isNil } from "lodash"
|
||||
|
||||
import logger from "@/utils/logger"
|
||||
import { User } from "@/models"
|
||||
import { UsersPolicy } from "@/policies"
|
||||
import { CreateService } from "@/services/users"
|
||||
import { IndexSerializer } from "@/serializers/users"
|
||||
import BaseController from "@/controllers/base-controller"
|
||||
|
||||
export class FormsController extends BaseController {
|
||||
async index() {
|
||||
try {
|
||||
const where = this.buildWhere()
|
||||
const scopes = this.buildFilterScopes()
|
||||
const scopedUsers = UsersPolicy.applyScope(scopes, this.currentUser)
|
||||
|
||||
const totalCount = await scopedUsers.count({ where })
|
||||
const users = await scopedUsers.findAll({
|
||||
where,
|
||||
limit: this.pagination.limit,
|
||||
offset: this.pagination.offset,
|
||||
})
|
||||
const serializedUsers = IndexSerializer.perform(users)
|
||||
return this.response.json({
|
||||
users: serializedUsers,
|
||||
totalCount,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error("Error fetching users" + error)
|
||||
return this.response.status(400).json({
|
||||
message: `Error fetching users: ${error}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async show() {
|
||||
try {
|
||||
const user = await this.loadUser()
|
||||
if (isNil(user)) {
|
||||
return this.response.status(404).json({
|
||||
message: "User not found",
|
||||
})
|
||||
}
|
||||
|
||||
const policy = this.buildPolicy(user)
|
||||
if (!policy.show()) {
|
||||
return this.response.status(403).json({
|
||||
message: "You are not authorized to view this user",
|
||||
})
|
||||
}
|
||||
|
||||
return this.response.json({ user, policy })
|
||||
} catch (error) {
|
||||
logger.error("Error fetching user" + error)
|
||||
return this.response.status(400).json({
|
||||
message: `Error fetching user: ${error}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,78 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
type RemainingConstructorParameters<C extends new (...args: any[]) => any> = C extends new (
|
||||
head: any,
|
||||
...tail: infer TT
|
||||
) => any
|
||||
? TT
|
||||
: []
|
||||
|
||||
/**
|
||||
* BaseSerializer is a generic class that provides a common interface for all serializers.
|
||||
* It is designed to be extended by other serializers, and provides a static `perform` method
|
||||
* that can be used to serialize a single record or an array of records.
|
||||
*
|
||||
* The `perform` method is overloaded to handle both cases, and will return the serialized
|
||||
* record or records based on the input. The return type is determined as the return type of the
|
||||
* `perform` instance method of the subclass as a single value or an array of values.
|
||||
*
|
||||
* The `perform` takes its signature from the constructor of the subclass, while also allowing
|
||||
* for an array of records to be passed in as the first argument.
|
||||
*
|
||||
* @param M The model type that the serializer is designed to handle
|
||||
*
|
||||
* @example
|
||||
* class TableSerializer extends BaseSerializer<Dataset> {
|
||||
* constructor(
|
||||
* protected record: Dataset,
|
||||
* protected currentUser: User
|
||||
* ) {
|
||||
* super(record)
|
||||
* }
|
||||
*
|
||||
* perform(): DatasetTableView {}
|
||||
* }
|
||||
*
|
||||
* TableSerializer.perform(dataset, currentUser) // => DatasetTableView
|
||||
* TableSerializer.perform([dataset1, dataset2], currentUser) // => [DatasetTableView, DatasetTableView]
|
||||
*/
|
||||
export class BaseSerializer<Model> {
|
||||
constructor(protected record: Model) {}
|
||||
|
||||
// Overload for handling a single record
|
||||
static perform<T extends BaseSerializer<any>, C extends new (...args: any[]) => T>(
|
||||
this: C,
|
||||
...args: ConstructorParameters<C>
|
||||
): ReturnType<InstanceType<C>["perform"]>
|
||||
|
||||
// Overload for handling an array of records
|
||||
static perform<T extends BaseSerializer<any>, C extends new (...args: any[]) => T>(
|
||||
this: C,
|
||||
...args: [ConstructorParameters<C>[0][], ...RemainingConstructorParameters<C>]
|
||||
): ReturnType<InstanceType<C>["perform"]>[]
|
||||
|
||||
// Implementation of the perform method
|
||||
static perform<T extends BaseSerializer<any>, C extends new (...args: any[]) => T>(
|
||||
this: C,
|
||||
...args:
|
||||
| ConstructorParameters<C>
|
||||
| [ConstructorParameters<C>[0][], ...RemainingConstructorParameters<C>]
|
||||
): ReturnType<InstanceType<C>["perform"]> | ReturnType<InstanceType<C>["perform"]>[] {
|
||||
if (Array.isArray(args[0])) {
|
||||
const records = args[0] as ConstructorParameters<C>[0][]
|
||||
return records.map((record) => {
|
||||
const instance = new this(record, ...args.slice(1))
|
||||
return instance.perform()
|
||||
}) as ReturnType<InstanceType<C>["perform"]>[]
|
||||
} else {
|
||||
const instance = new this(...args)
|
||||
return instance.perform() as ReturnType<InstanceType<C>["perform"]>
|
||||
}
|
||||
}
|
||||
|
||||
perform(): any {
|
||||
throw new Error("Not Implemented")
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseSerializer
|
||||
@@ -0,0 +1 @@
|
||||
export { ShowSerializer } from "./show-serializer"
|
||||
@@ -0,0 +1,28 @@
|
||||
import { pick } from "lodash"
|
||||
|
||||
import { User } from "@/models"
|
||||
import BaseSerializer from "@/serializers/base-serializer"
|
||||
|
||||
export type UserShowView = Pick<
|
||||
User,
|
||||
"id" | "email" | "firstName" | "lastName" | "displayName" | "roles" | "createdAt" | "updatedAt"
|
||||
>
|
||||
|
||||
export class ShowSerializer extends BaseSerializer<User> {
|
||||
perform(): UserShowView {
|
||||
return {
|
||||
...pick(this.record, [
|
||||
"id",
|
||||
"email",
|
||||
"firstName",
|
||||
"lastName",
|
||||
"displayName",
|
||||
"roles",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ShowSerializer
|
||||
@@ -0,0 +1,2 @@
|
||||
// Bundled exports
|
||||
export * as Users from "./users"
|
||||
@@ -0,0 +1,19 @@
|
||||
import { pick } from "lodash"
|
||||
|
||||
import { User } from "@/models"
|
||||
import BaseSerializer from "@/serializers/base-serializer"
|
||||
|
||||
export type UserIndexView = Pick<
|
||||
User,
|
||||
"id" | "email" | "firstName" | "lastName" | "displayName" | "roles"
|
||||
>
|
||||
|
||||
export class IndexSerializer extends BaseSerializer<User> {
|
||||
perform(): UserIndexView {
|
||||
return {
|
||||
...pick(this.record, ["id", "email", "firstName", "lastName", "displayName", "roles"]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default IndexSerializer
|
||||
@@ -0,0 +1,2 @@
|
||||
export { ShowSerializer } from "./show-serializer"
|
||||
export { IndexSerializer } from "./index-serializer"
|
||||
@@ -0,0 +1,28 @@
|
||||
import { pick } from "lodash"
|
||||
|
||||
import { User } from "@/models"
|
||||
import BaseSerializer from "@/serializers/base-serializer"
|
||||
|
||||
export type UserShowView = Pick<
|
||||
User,
|
||||
"id" | "email" | "firstName" | "lastName" | "displayName" | "roles" | "createdAt" | "updatedAt"
|
||||
>
|
||||
|
||||
export class ShowSerializer extends BaseSerializer<User> {
|
||||
perform(): UserShowView {
|
||||
return {
|
||||
...pick(this.record, [
|
||||
"id",
|
||||
"email",
|
||||
"firstName",
|
||||
"lastName",
|
||||
"displayName",
|
||||
"roles",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ShowSerializer
|
||||
Reference in New Issue
Block a user