Aller au contenu principal

Service Layer Usage Examples

Basic Controller Using CrudService

All controllers extending BaseController automatically get access to CRUD operations through the service layer:

import { IGallery } from "../interfaces/gallery.interface";
import Gallery from "../models/gallery.model";
import { BaseController } from "./base.controller";

class GalleryController extends BaseController<IGallery> {
constructor() {
super(Gallery);
// this.crudService is automatically available
}

/**
* Generate SEO-optimized slug for galleries
*/
protected generateSlug(item: IGallery): string | undefined {
const year = item.createdAt
? new Date(item.createdAt).getFullYear()
: new Date().getFullYear();

return this.SlugGenerator.generateGallerySlug({
title: item.title,
year: year,
});
}
}

Using Services Directly in Custom Methods

import { Request, Response, NextFunction } from "express";
import { BaseController } from "./base.controller";
import { loggingService } from "../services/logging.service";
import { fileManagementService } from "../services/fileManagement.service";

class CustomController extends BaseController<ICustom> {
// Custom method using services directly
async customOperation(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
// Use CRUD service
const items = await this.crudService.getAll({
module_id: req.body.module_id,
page: 1,
limit: 10,
});

// Use logging service
await loggingService.logAction(
"Custom operation performed",
200,
req.user?._id,
req.ip,
req.get("User-Agent")
);

res.status(200).json(items);
} catch (error) {
next(error);
}
}

// Custom file handling
async customFileOperation(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const { fileId } = req.params;

// Regenerate thumbnail with custom size
const result = await fileManagementService.regenerateThumbnail(
fileId,
{ width: 500, height: 500 }
);

res.status(200).json(result);
} catch (error) {
next(error);
}
}
}

Using Module Service for Complex Operations

import { Request, Response, NextFunction } from "express";
import { moduleService } from "../services/module.service";
import errorService from "../utils/error.util";

class AdminController {
async bulkModuleOperation(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const { moduleIds } = req.body;

// Delete multiple modules with all their content
const result = await moduleService.deleteModules(
moduleIds,
req.user?._id,
req.ip,
req.get("User-Agent")
);

res.status(200).json({
message: "Modules deleted successfully",
...result,
});
} catch (error) {
next(errorService.internal("Error deleting modules", error));
}
}

async regenerateAllThumbnails(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const modules = await moduleService.getAllModules();
const results = [];

for (const module of modules) {
try {
const result = await moduleService.regenerateThumbnails(
module._id.toString(),
req.user?._id,
req.ip,
req.get("User-Agent")
);
results.push({ moduleId: module._id, ...result });
} catch (error) {
results.push({
moduleId: module._id,
error: error.message,
});
}
}

res.status(200).json({
message: "Thumbnail regeneration complete",
results,
});
} catch (error) {
next(errorService.internal("Error regenerating thumbnails", error));
}
}
}

Creating Custom Services

// src/services/myCustom.service.ts
import { loggingService } from "./logging.service";
import { fileManagementService } from "./fileManagement.service";
import MyModel from "../models/myModel.model";

export class MyCustomService {
async customBusinessLogic(data: any): Promise<any> {
// Implement your business logic
const result = await MyModel.find(data);

// Use other services
await loggingService.logAction(
"Custom business logic executed",
200
);

return result;
}

async processFiles(files: string[]): Promise<void> {
// Use file management service
for (const fileId of files) {
await fileManagementService.regenerateThumbnail(fileId, {
width: 300,
height: 300,
});
}
}
}

export const myCustomService = new MyCustomService();

Service Composition

// Compose multiple services for complex operations
import { CrudService } from "../services/crud.service";
import { loggingService } from "../services/logging.service";
import { fileManagementService } from "../services/fileManagement.service";
import { tableOrderService } from "../services/tableOrder.service";

class ComplexOperationService {
private crudService: CrudService<any>;

constructor(model: Model<any>) {
this.crudService = new CrudService(model);
}

async performComplexOperation(data: any): Promise<any> {
// Step 1: Create item
const item = await this.crudService.create(data);

// Step 2: Handle files
if (data.files) {
await fileManagementService.handleFileUpdates(
[],
data.files,
this.crudService.model,
item._id.toString(),
data.module_id
);
}

// Step 3: Update table order
await tableOrderService.addItemToTableOrder(
data.module_id,
item._id,
false
);

// Step 4: Log operation
await loggingService.logCreation(
item.title,
"ModuleName",
data.credential,
data.ip,
data.userAgent
);

return item;
}
}

Testing Services

// Example unit test for a service
import { CrudService } from "../services/crud.service";
import { Model } from "mongoose";

describe("CrudService", () => {
let crudService: CrudService<any>;
let mockModel: jest.Mocked<Model<any>>;

beforeEach(() => {
// Create mock model
mockModel = {
find: jest.fn(),
findById: jest.fn(),
create: jest.fn(),
// ... other methods
} as any;

crudService = new CrudService(mockModel);
});

it("should get items by id", async () => {
const mockItem = { _id: "123", title: "Test" };
mockModel.findById.mockResolvedValue(mockItem);

const result = await crudService.getById("123");

expect(mockModel.findById).toHaveBeenCalledWith("123");
expect(result).toBeDefined();
});

// More tests...
});

Benefits Summary

  1. Code Reusability: Services can be used across multiple controllers
  2. Testability: Services are easily testable in isolation
  3. Maintainability: Business logic is centralized and easier to modify
  4. Separation of Concerns: Controllers handle HTTP, services handle business logic
  5. Type Safety: Full TypeScript support with proper interfaces
  6. Error Handling: Centralized error handling in services
  7. Logging: Consistent logging patterns across the application