Appearance
Resource Module Architecture
DeepVision employs a consistent architecture for NestJS resource modules to ensure better support and maintainability across all applications within our ecosystem. NestKit provides numerous utilities to minimize boilerplate code in module development.
Module Structure
Each resource module should be placed in the src/modules
folder and follow this structure:
plaintext
📦resource-name
┣ 📂types
┃ ┣ 📂resolver
┃ ┃ ┣ 📜create-resource.ts
┃ ┃ ┣ 📜delete-resource.ts
┃ ┃ ┣ 📜fetch-resources.ts
┃ ┃ ┣ 📜index.ts
┃ ┃ ┗ 📜update-resource.ts
┃ ┣ 📂service
┃ ┃ ┣ 📜create-resource.ts
┃ ┃ ┣ 📜get-many-resources.ts
┃ ┃ ┣ 📜index.ts
┃ ┃ ┗ 📜update-resource.ts
┃ ┗ 📜common.ts
┣ 📜resource.entity.ts
┣ 📜resource.module.ts
┣ 📜resource.resolver.ts
┗ 📜resource.service.ts
Replace resource-name
and resource
with the specific name of the resource module you are creating. This structure ensures a consistent approach to resource module development, making it easier to maintain and support the applications within the DeepVision ecosystem.
Using the SeasonGroups
example, the module structure would look like this:
plaintext
📦season-groups
┣ 📂types
┃ ┣ 📂resolver
┃ ┃ ┣ 📜create-season-group.ts
┃ ┃ ┣ 📜delete-season-group.ts
┃ ┃ ┣ 📜fetch-season-groups.ts
┃ ┃ ┣ 📜index.ts
┃ ┃ ┗ 📜update-season-group.ts
┃ ┣ 📂service
┃ ┃ ┣ 📜create-season-group.ts
┃ ┃ ┣ 📜get-many-season-groups.ts
┃ ┃ ┣ 📜index.ts
┃ ┃ ┗ 📜update-season-group.ts
┃ ┗ 📜common.ts
┣ 📜season-group.entity.ts
┣ 📜season-groups.module.ts
┣ 📜season-groups.resolver.ts
┗ 📜season-groups.service.ts
In this example, the resource-name
and resource
placeholders from the previous structure have been replaced with season-groups
and season-group
, respectively.
In the types
directory, we store type definitions related to the entity properties, options, and inputs used by service methods and GraphQL resolvers. This helps maintain a clear separation of concerns and keeps the codebase organized, making it easier to work with and understand.
The updated SeasonGroups
module structure, including the purpose of the types
directory, is as follows:
plaintext
📦season-groups
┣ 📂types
┃ ┣ 📂resolver
┃ ┃ ┣ 📜create-season-group.ts // Input types for creating a season group
┃ ┃ ┣ 📜delete-season-group.ts // Input types for deleting a season group
┃ ┃ ┣ 📜fetch-season-groups.ts // Input types for fetching season groups
┃ ┃ ┣ 📜index.ts // Exports all resolver types
┃ ┃ ┗ 📜update-season-group.ts // Input types for updating a season group
┃ ┣ 📂service
┃ ┃ ┣ 📜create-season-group.ts // Options for creating a season group
┃ ┃ ┣ 📜get-many-season-groups.ts // Options for fetching season groups
┃ ┃ ┣ 📜index.ts // Exports all service types
┃ ┃ ┗ 📜update-season-group.ts // Options for updating a season group
┃ ┗ 📜common.ts // Shared types for the entity properties
By organizing type definitions in the types
directory, it becomes easier to navigate and understand the relationships between various components in the resource module.
For each GraphQL mutation or query, it's a good practice to create separate files where you define the input types and the response payloads. This approach helps to keep your codebase clean, modular, and easy to maintain.
In the SeasonGroups
example, the GraphQL mutation and query inputs and payloads can be organized within the types/resolver
directory.
Resource Entity Definition
We use TypeORM entities to describe the resource schema in the database. TypeORM entities provide a convenient way to define and interact with database tables in an object-oriented manner.
Here's an example of a SeasonGroup
entity definition:
ts
import { Field, ObjectType } from '@nestjs/graphql';
import {
BaseEntity, Column, Entity, PrimaryColumn, CreateDateColumn, UpdateDateColumn, OneToOne, JoinColumn, ManyToOne,
} from 'typeorm';
import { User } from '@modules/users/user.entity';
import { SeasonGroupStatus } from './types/common';
import { Image } from '@/modules/images/image.entity.ts';
@ObjectType()
@Entity()
export class SeasonGroup extends BaseEntity {
@Field()
@PrimaryColumn()
id!: string;
@Field(() => SeasonGroupStatus)
@Column({
type: 'enum',
enum: SeasonGroupStatus,
default: SeasonGroupStatus.ACTIVE,
})
status!: SeasonGroupStatus;
@Field()
@Column()
title!: string;
@Field()
@Column()
imageId!: string;
@Field(() => Image)
@OneToOne(() => Image, {
lazy: true,
onDelete: 'RESTRICT', // DELETE, SET NULL, rely on business logic
})
@JoinColumn()
image!: Promise<Image>;
@Field()
@Column()
creatorId!: string;
@ManyToOne(() => User, {
lazy: true,
onDelete: 'RESTRICT',
nullable: true,
})
@JoinColumn()
creator!: Promise<User>;
@CreateDateColumn({
type: 'timestamptz',
precision: 3,
})
createdAt!: Date;
@UpdateDateColumn({
type: 'timestamptz',
precision: 3,
})
updatedAt!: Date;
}
In this example, the SeasonGroup
entity is defined using TypeORM decorators such as @Entity
,@PrimaryColumn
, @Column
, @OneToOne
,@ManyToOne
, @CreateDateColumn
, and @UpdateDateColumn
. The corresponding GraphQL schema is automatically generated using NestJS GraphQL decorators like @ObjectType
and @Field
. This combination allows you to define the database schema and GraphQL schema in one place, making it easier to manage and maintain.
We expose the creatorId
with @Field()
decorator to declare the creatorId
field directly in your entity class. This will allow clients to request just the creatorId
without needing to fetch the entire User
object.
graphql
query {
seasonGroup(id: "xxx") {
id
creatorId
}
}
Resource GraphQL Resolver
The GraphQL Resolver for a resource provides a CRUD interface for the end user, utilizing the resource service to perform operations with the database.
ts
import {
Args, Mutation, Query, Resolver,
} from '@nestjs/graphql';
import { NotFoundException, UseGuards } from '@nestjs/common';
import { ErrorKeys } from '@/enums/error-keys';
import {
GraphQLAuthGuard,
GraphQLPermissionsGuard,
createOffsetPaginationOptions,
MaybeNull,
offsetPaginatedOutput,
} from '@deeepvision/nest-kit';
import { SeasonGroup } from './season-group.entity';
import { SeasonGroupsService } from './season-groups.service';
import { ActionContext, IActionContext } from '@/decorators';
import {
CreateSeasonGroupInput,
DeleteSeasonGroupPayload, FetchSeasonGroupsInput,
PaginatedSeasonGroups, UpdateSeasonGroupInput,
} from './types/resolver';
import { UsePermission } from '@/decorators';
@Resolver(() => SeasonGroup)
// Guards are required. For shared endpoints use service accounts
@UseGuards(GraphQLAuthGuard, GraphQLPermissionsGuard)
export class SeasonGroupsResolver {
constructor(private readonly seasonGroupsService: SeasonGroupsService) {}
@Query(() => SeasonGroup)
@UsePermission('js:core:season-groups:get') // project:module:resource:action
async seasonGroup(
@Args('id') id: string,
@ActionContext() ctx: IActionContext,
): Promise<SeasonGroup> {
return await this.seasonGroupsService.getOnOrFail(id, ctx);
}
@Query(() => PaginatedSeasonGroups)
@UsePermission('js:core:season-groups:list')
async seasonGroups(
@Args() input: FetchSeasonGroupsInput,
@ActionContext() ctx: IActionContext,
): Promise<PaginatedSeasonGroups> {
const [seasonGroups, meta] = await this.seasonGroupsService.getMany(
{
filter: input.filter ?? {
},
orderBy: input.orderBy,
...createOffsetPaginationOptions(input),
},
ctx,
);
return offsetPaginatedOutput(seasonGroups, meta);
}
@Mutation(() => SeasonGroup)
@UsePermission('js:core:season-groups:create')
async createSeasonGroup(
@Args('input') input: CreateSeasonGroupInput,
@ActionContext() ctx: IActionContext,
): Promise<SeasonGroup> {
return await this.seasonGroupsService.create(input, ctx);
}
@Mutation(() => SeasonGroup)
async updateSeasonGroup(
@Args('input') input: UpdateSeasonGroupInput,
@ActionContext() ctx: IActionContext,
): Promise<SeasonGroup> {
return await this.seasonGroupsService.update(input, ctx);
}
@Mutation(() => DeleteSeasonGroupPayload)
@UsePermission('js:core:season-groups:delete')
async deleteSeasonGroup(
@Args('input') input: DeleteSeasonGroupInput,
@ActionContext() ctx: IActionContext,
): Promise<DeleteSeasonGroupPayload> {
await this.seasonGroupsService.delete(id, ctx);
return {
id,
};
}
}
This example demonstrates a SeasonGroupsResolver
for the SeasonGroup
resource. It includes the necessary imports and decorators to protect the resolver with guards and permissions. The resolver uses the SeasonGroupsService
to perform CRUD operations, such as creating, updating, and deleting season groups, as well as fetching a single season group or a paginated list of season groups.
The GraphQLAuthGuard from NestKit is used to authenticate the user making the request. For each query and mutation, the @ActionContext
decorator is defined, which provides the ctx
decorator that contains the resolved user making the request. This makes it easy to attach the creator to the SeasonGroup
.
The GraphQLPermissionsGuard from NestKit is used for access control to queries and mutations. It works in conjunction with the @UsePermission decorator to manage and enforce role-based permissions for the API.
When you apply the @UsePermission decorator to a query or mutation, it takes an argument that represents the required permission string for the user to access that specific endpoint. This permission string is typically in the format of project:module:resource:action
. The GraphQLPermissionsGuard then checks whether the authenticated user has the necessary permission to perform the requested operation.
Each query and mutation, except for simple queries, has input parameters. These input parameters are typed in separate files for better organization and maintainability.
Create mutation
Create input is stored in a file named types/resolver/create-season-group.ts
.
Here's an example of a create input for the SeasonGroup
resource:
ts
import { Field, InputType } from '@nestjs/graphql';
@InputType()
export class CreateSeasonGroupInput {
@Field()
title!: string;
@Field()
imageId!: string;
}
Create mutation returns the created entity as a result. This allows the client to receive the latest state of the resource after performing the mutation.
Update mutation
Update input is stored in a file named types/resolver/update-season-group.ts
. You must provide the id of the resource you want to update. Other fields are optional, allowing for atomic updates.
ts
import { Field, InputType } from '@nestjs/graphql';
@InputType()
export class UpdateSeasonGroupInput {
@Field()
id!: string;
@Field({ nullable: true })
title?: string;
@Field({ nullable: true })
imageId?: string;
}
Update mutation returns the updated entity as a result. This allows the client to receive the latest state of the resource after performing the mutation.
Delete mutation
Delete input and response payload are stored in a file named types/resolver/delete-season-group.ts
.
ts
import { Field, InputType, ObjectType } from '@nestjs/graphql';
@InputType()
export class DeleteSeasonGroupInput {
@Field()
id!: string;
}
@ObjectType()
export class DeleteSeasonGroupPayload {
@Field()
id!: string;
}
Delete mutations can use input instead of a simple ID to provide additional data for performing optional actions. For example, you can include a deleteFileFromRemoteStorage
field in the input to specify whether a file should be deleted from remote storage when the associated resource is deleted. As a result, delete mutations return a DeletePayload
that contains information about the deleted resource and any additional actions performed.
Here's an example of a delete mutation with additional input:
ts
import { Field, ObjectType } from '@nestjs/graphql';
@InputType()
export class DeleteResourceInput {
@Field()
id!: string;
@Field()
deleteFileFromRemoteStorage!: boolean;
}
Fetch items
The seasonGroups
query is a complex query that returns a list of offset-paginated items. This list supports the offset and limit parameters and can be filtered by special parameters.
The types for this query are stored in the types/resolver/fetch-season-groups.ts
file:
ts
import { SeasonGroup } from '../../season-group.entity';
import {
ExtractSortFields,
OffsetPaginated,
OffsetPaginationInput,
} from '@deeepvision/nest-kit';
import {
ArgsType,
Field,
InputType,
ObjectType,
registerEnumType,
} from '@nestjs/graphql';
import { SeasonGroupStatus } from '../common';
@InputType()
export class SeasonGroupsFilter {
@Field(() => [String], {
nullable: true,
})
ids?: SeasonGroupStatus[];
@Field(() => String, {
nullable: true,
})
search?: String;
@Field(() => [SeasonGroupStatus], {
nullable: true,
})
statuses?: SeasonGroupStatus[];
}
export enum SeasonGroupsOrderBy {
createdAt_ASC = 'createdAt_ASC',
createdAt_DESC = 'createdAt_DESC',
status_ASC = 'status_ASC',
status_DESC = 'status_DESC',
}
export type SeasonGroupsOrderFields = ExtractSortFields<SeasonGroupsOrderBy>;
registerEnumType(SeasonGroupsOrderBy, {
name: 'SeasonGroupsOrderBy',
});
@ArgsType()
export class FetchSeasonGroupsInput extends OffsetPaginationInput {
@Field(() => SeasonGroupsOrderBy, {
defaultValue: SeasonGroupsOrderBy.createdAt_DESC,
})
orderBy!: SeasonGroupsOrderBy;
@Field(() => SeasonGroupsFilter, {
nullable: true,
})
filter?: SeasonGroupsFilter;
}
@ObjectType()
export class PaginatedSeasonGroups extends OffsetPaginated(SeasonGroup) {}
In the FetchSeasonGroupsInput
, the @Args()
decorator is used. This means that all the properties within the class will be treated as individual arguments for the seasonGroups
query.
Here's an example GraphQL query for fetching seasonGroups
using the defined arguments:
graphql
query SeasonGroups($limit: Int!, $offset: Int!, $orderBy: SeasonGroupsOrderBy!, $filter: SeasonGroupsFilter) {
seasonGroups(limit: $limit, offset: $offset, orderBy: $orderBy, filter: $filter) {
...
}
}
In this query, the limit
, offset
, orderBy
, and filter
variables are passed as arguments to the seasonGroups
query.
Filtering
SeasonGroupsFilter
is an input type used to define the filter criteria for fetching season groups. It allows you to specify the params you want to filter the season groups by.
Yes, you can extend the filter based on your specific business logic requirements. To add more filter parameters, update the SeasonGroupsFilter
input type by adding new fields with appropriate types and decorators. For example, if you want to add a filter parameter for filtering season groups by brandId
, you can do so like this:
ts
import { Field, InputType } from '@nestjs/graphql';
import { SeasonGroupStatus } from '../common';
@InputType()
export class SeasonGroupsFilter {
@Field(() => [SeasonGroupStatus], {
nullable: true,
})
statuses?: SeasonGroupStatus[];
// Add a new filter parameter for brandId
@Field({ nullable: true })
brandId?: string;
}
Remember to update your service to handle the new filter parameter in your filtering logic as well. Here's an example of how you can update your service to incorporate the new brandId
filter parameter.
Ordering
SeasonGroupsOrderBy
is an enumeration that defines the possible sorting options for the seasonGroups
query. It allows the user to sort the fetched season groups based on different criteria, such as creation date or status.
In the given example, the SeasonGroupsOrderBy
enum has four possible values:
ts
export enum SeasonGroupsOrderBy {
createdAt_ASC = 'createdAt_ASC',
createdAt_DESC = 'createdAt_DESC',
status_ASC = 'status_ASC',
status_DESC = 'status_DESC',
}
These values represent the different ways the user can order the fetched season groups:
createdAt_ASC
: Sort season groups by creation date in ascending order (from oldest to newest).createdAt_DESC
: Sort season groups by creation date in descending order (from newest to oldest).status_ASC
: Sort season groups by status in ascending order (e.g., from ACTIVE to INACTIVE).status_DESC
: Sort season groups by status in descending order (e.g., from INACTIVE to ACTIVE).
By providing this enumeration, you allow users to easily customize the order in which the season groups are fetched.
The SeasonGroupsOrderBy
enum follows a special format {param}_{order}
. This format makes it easy to understand and extend the sorting options. The format consists of two parts:
{param}
: This represents the field or property of the resource.{order}
: This indicates the sorting order, either ascending (ASC
) or descending (DESC
).
Pagination
PaginatedSeasonGroups
utilizes the OffsetPaginated
utility from NestKit to create an ObjectType of paginated items. This makes it easy to handle pagination in GraphQL queries, while providing a consistent output structure for paginated data.
Here's a sample GraphQL query using PaginatedSeasonGroups
:
graphql
query SeasonGroups($limit: Int!, $offset: Int!, $orderBy: SeasonGroupsOrderBy!, $filter: SeasonGroupsFilter) {
seasonGroups(limit: $limit, offset: $offset, orderBy: $orderBy, filter: $filter) {
items {
title
}
pageInfo {
limit
page
totalItems
totalPages
}
}
}
This query accepts arguments such as limit
, offset
, orderBy
, and filter
to control pagination, sorting, and filtering. The response is structured with two main properties:
items
: This contains an array of the actualSeasonGroup
items. You can request specific fields (e.g.,title
) within the items.pageInfo
: This provides metadata about the pagination, including thelimit
,page
,totalItems
, andtotalPages
.
By using PaginatedSeasonGroups
and the OffsetPaginated
utility, you can easily manage pagination for any resource while maintaining a consistent response structure across your application.
Fetch item by id
The season
query is a simple query that retrieves a specific SeasonGroup
entity based on its id
. This query is useful when you want to fetch detailed information about a single SeasonGroup
.
Here's an example of the season
query in the GraphQL Resolver:
ts
@Query(() => SeasonGroup)
@UsePermission('js:core:season-groups:get') // project:module:resource:action
async seasonGroup(
@Args('id') id: string,
@ActionContext() ctx: IActionContext,
): Promise<SeasonGroup> {
return await this.seasonGroupsService.getOnOrFail(id, ctx);
}
In the SeasonGroupsResolver
, the seasonGroup
method is decorated with the @Query
decorator, which indicates that it's a GraphQL query. It also uses the @UsePermission
decorator to handle access control.
The seasonGroup
method accepts two arguments:
id
: Theid
of theSeasonGroup
you want to fetch.ctx
: TheActionContext
, which provides context about the user who made the request.
The method calls the getOnOrFail
function from the seasonGroupsService
, passing the id
and ctx
as arguments. This function returns the SeasonGroup
entity if found; otherwise, it throws an error.
Here's a sample GraphQL query for the season
query:
graphql
query SeasonGroup($id: String!) {
seasonGroup(id: $id) {
id
title
status
createdAt
updatedAt
}
}
This query accepts the id
parameter and requests specific fields of the SeasonGroup
, such as id
, title
, status
, createdAt
, and updatedAt
.
Resource Service
The SeasonGroupsService
class in the code below defines a CRUD (Create, Read, Update, Delete) interface for working with SeasonGroup
entities. This service is meant for internal use within your application, allowing for easy interaction with the SeasonGroup
entities in your database.
ts
import {
Injectable,
InternalServerErrorException,
NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ErrorKeys } from '@/enums/error-keys';
import {
createListMeta,
defineStatuses,
extractSortParams,
InjectWinstonLoggerFactory,
ListMeta,
MaybeNull,
WinstonLogger,
WinstonLoggerFactory,
} from '@deeepvision/nest-kit';
import { SeasonGroup } from './season-group.entity';
import { SeasonGroupsOrderBy, SeasonGroupsOrderFields } from './types/resolver';
import {
CreateSeasonGroupOptions,
GetManySeasonGroupsOptions,
UpdateSeasonGroupOptions,
} from './types/service';
import { SeasonGroupStatus, defaultSeasonGroupStatuses } from './types/common';
import { IdPrefix } from '@/enums/id-prefix';
import { ServiceMethodContext } from '@/types';
@Injectable()
export class SeasonGroupsService {
private readonly logger = this.loggerFactory.create({
scope: SeasonGroupsService.name,
});
constructor(
@InjectRepository(SeasonGroup) private readonly seasonGroupRepository: Repository<SeasonGroup>,
@InjectWinstonLoggerFactory() private readonly loggerFactory: WinstonLoggerFactory,
private readonly idService: IdService,
) {}
async getOneBy(opts: GetSeasonGroupBy) {
return await this.seasonGroupRepository.findOneBy({
...opts,
status: Not(SeasonGroupStatus.DELETED),
})
}
async getOne(id: string): Promise<MaybeNull<SeasonGroup>> {
return await this.getOneBy({
id,
});
}
async getOneByOrFail(opts: GetSeasonGroupBy, ctx: ServiceMethodContext) {
const logger = this.logger.forMethod(this.getOneByOrFail.name, ctx, {
id,
});
const seasonGroup = await this.getOneBy(opts);
if (!seasonGroup) {
throw new NotFoundException({
message: `Season group with ${stringifyOpts(opts)} not found`,
key: ErrorKeys.JS_SEASON_GROUPS_NOT_FOUND,
context: logger.getContext(),
});
}
return seasonGroup;
}
async getOneOrFile(id: string, ctx: ServiceMethodContext): Promise<MaybeNull<SeasonGroup>> {
return await this.getOneByOrFail({
id,
}, ctx);
}
async getMany(
{
filter,
orderBy = SeasonGroupsOrderBy.createdAt_DESC,
limit,
offset,
needCountTotal,
}: GetManySeasonGroupsOptions,
): Promise<[SeasonGroup[], ListMeta]> {
const sort = extractSortParams<SeasonGroupsOrderFields>(orderBy);
const statuses = defineStatuses(filter.statuses, defaultSeasonGroupStatuses);
const alias = 'sg';
const query = this.seasonGroupRepository
.createQueryBuilder(alias)
.andWhere(`${alias}.status IN(:...statuses)`, {
statuses,
});
query.addOrderBy(`${alias}.${sort.columnName}`, sort.direction);
query.limit(limit);
query.offset(offset);
return Promise.all([
query.getMany(),
createListMeta<SeasonGroup>({
query,
needCountTotal,
limit,
offset,
}),
]);
}
async create(
opts: CreateSeasonGroupOptions,
ctx: ServiceMethodContext,
): Promise<SeasonGroup> {
const logger = this.logger.forMethod(this.create.name, ctx);
const seasonGroup = this.seasonGroupRepository.create({
id: this.idService.generateEntityId(IdPrefix.SEASON_GROUP),
creatorId: ctx.user.id,
...opts,
});
try {
return await this.seasonGroupRepository.save(seasonGroup);
} catch (error) {
throw new InternalServerErrorException({
message: 'Failed to create season group',
key: ErrorKeys.JS_SEASON_GROUPS_FAILED_TO_CREATE,
context: logger.getContext(),
error,
});
}
}
async update(
{
id,
...opts
}: UpdateSeasonGroupOptions,
ctx: ServiceMethodContext,
): Promise<SeasonGroup> {
const logger = this.logger.forMethod(this.update.name, ctx, {
id,
});
const seasonGroup = await this.getOneOrFail(id, ctx);
this.seasonGroupRepository.merge(seasonGroup, opts);
try {
return await this.seasonGroupRepository.save(seasonGroup);
} catch (error) {
throw new InternalServerErrorException({
message: `Failed to update season group with id "${id}"`,
key: ErrorKeys.JS_SEASON_GROUPS_FAILED_TO_UPDATE,
context: logger.getContext(),
error,
});
}
}
async delete(id: string, ctx: ServiceMethodContext): Promise<void> {
const logger = this.logger.forMethod(this.delete.name, ctx, {
id,
});
const seasonGroup = await this.getOne(id);
if (!seasonGroup || seasonGroup.isDeleted) {
return;
}
seasonGroup.status = SeasonGroupStatus.DELETED;
try {
await this.seasonGroupRepository.save(seasonGroup);
} catch (error) {
throw new InternalServerErrorException({
message: `Failed to delete season group with id ${id}`,
key: ErrorKeys.JS_SEASON_GROUPS_FAILED_TO_DELETE,
context: logger.getContext(),
error,
});
}
}
}
Here's a brief overview of the methods provided by this service:
getOneBy(opts: GetSeasonGroupBy)
: Fetches a singleSeasonGroup
entity based on the specified options. The method filters out the deleted items.getOne(id: string)
: Fetches a singleSeasonGroup
entity by itsid
.getOneByOrFail(opts: GetSeasonGroupBy, ctx: ServiceMethodContext)
: Fetches a singleSeasonGroup
entity based on the specified options, throwing aNotFoundException
if the item is not found.getOneOrFile(id: string, ctx: ServiceMethodContext)
: Fetches a singleSeasonGroup
entity by itsid
, throwing aNotFoundException
if the item is not found.getMany(options: GetManySeasonGroupsOptions)
: Fetches a list ofSeasonGroup
entities based on the specified options, such as filters, ordering, limit, and offset. It returns the list of items along with pagination metadata.create(opts: CreateSeasonGroupOptions, ctx: ServiceMethodContext)
: Creates a newSeasonGroup
entity based on the specified options and the user's context.update(options: UpdateSeasonGroupOptions, ctx: ServiceMethodContext)
: Updates an existingSeasonGroup
entity based on the specified options and the user's context.delete(id: string, ctx: ServiceMethodContext)
: Deletes aSeasonGroup
entity by itsid
. The method marks the entity as deleted by setting itsstatus
toSeasonGroupStatus.DELETED
.
These methods can be used within your application's resolvers to implement the GraphQL queries and mutations that interact with the SeasonGroup
entities. The service utilizes the TypeORM repository for SeasonGroup
to perform database operations, and it also uses a logger for error handling and debugging purposes.