Untitled

mail@pastecode.io avatar
unknown
plain_text
13 days ago
4.1 kB
2
Indexable
Never
import { Model, Document, FilterQuery, UpdateQuery, PopulateOptions } from 'mongoose';
import { createClient } from 'redis';

// Initialize Redis client
const redisClient = createClient();

redisClient.on('error', (err) => console.error('Redis Client Error', err));

(async () => {
  await redisClient.connect();
})();

interface PaginationOptions {
  page?: number;
  limit?: number;
}

interface ReadAllOptions<T> {
  query?: FilterQuery<T>;
  projection?: Record<string, unknown>;
  sort?: Record<string, 1 | -1>;
  filter?: FilterQuery<T>;
  pagination?: PaginationOptions;
  includes?: string[] | PopulateOptions[];
}

interface PaginatedResult<T> {
  docs: T[];
  totalDocs: number;
  totalPages: number;
  currentPage: number;
  hasNextPage: boolean;
  hasPrevPage: boolean;
  nextPage: number | null;
  prevPage: number | null;
}

interface DbOperation<T extends Document> {
  create: (model: Model<T>, data: Partial<T>) => Promise<T>;
  read: (model: Model<T>, options: { query?: FilterQuery<T>; includes?: string[] | PopulateOptions[] }) => Promise<T | null>;
  readAll: (model: Model<T>, options: ReadAllOptions<T>) => Promise<PaginatedResult<T>>;
  update: (model: Model<T>, options: { query: FilterQuery<T>; update: UpdateQuery<T> }) => Promise<T | null>;
  delete: (model: Model<T>, options: { query: FilterQuery<T> }) => Promise<T | null>;
  invalidateCache: (keyPattern: string) => Promise<void>;
}

export const dbOperation: DbOperation<any> = {
  async create(model, data) {
    const newDocument = await model.create(data);
    await this.invalidateCache(`*${model.modelName}*`);
    return newDocument;
  },

  async read(model, { query = {}, includes = [] }) {
    const result = await model.findOne(query).populate(includes).exec();
    return result;
  },

  async readAll(model, { query = {}, projection = {}, sort = {}, filter = {}, pagination = {}, includes = [] }) {
    const { page = 1, limit = 10 } = pagination;

    // Combine query and filter for better optimization
    const combinedQuery = { ...query, ...filter };

    const cacheKey = `${model.modelName}:${JSON.stringify(combinedQuery)}:page:${page}:limit:${limit}:sort:${JSON.stringify(sort)}`;

    // Check cache
    const cachedData = await redisClient.get(cacheKey);
    if (cachedData) {
      console.log('Returning data from Redis cache');
      return JSON.parse(cachedData);
    }

    // Total document count for pagination
    const totalDocs = await model.countDocuments(combinedQuery).exec();
    const totalPages = Math.ceil(totalDocs / limit);
    const hasNextPage = page < totalPages;
    const hasPrevPage = page > 1;
    const nextPage = hasNextPage ? page + 1 : null;
    const prevPage = hasPrevPage ? page - 1 : null;

    // Query MongoDB
    const docs = await model.find(combinedQuery)
      .select(projection)
      .sort(sort)
      .skip((page - 1) * limit)
      .limit(limit)
      .populate(includes)
      .exec();

    const result: PaginatedResult<any> = {
      docs,
      totalDocs,
      totalPages,
      currentPage: page,
      hasNextPage,
      hasPrevPage,
      nextPage,
      prevPage,
    };

    // Cache the results
    await redisClient.set(cacheKey, JSON.stringify(result), {
      EX: 3600, // Cache expiration time (1 hour)
    });

    console.log('Returning data from MongoDB and caching in Redis');
    return result;
  },

  async update(model, { query, update }) {
    const updatedDocument = await model.findOneAndUpdate(query, update, { new: true }).exec();
    await this.invalidateCache(`*${model.modelName}*`);
    return updatedDocument;
  },

  async delete(model, { query }) {
    const deletedDocument = await model.findOneAndDelete(query).exec();
    await this.invalidateCache(`*${model.modelName}*`);
    return deletedDocument;
  },

  async invalidateCache(keyPattern: string) {
    const keys = await redisClient.keys(keyPattern);
    if (keys.length > 0) {
      await redisClient.del(keys);
      console.log(`Invalidated cache for keys: ${keys}`);
    }
  },
};

(async () => {
  process.on('exit', async () => {
    await redisClient.disconnect();
  });
})();
Leave a Comment