feat: Add core services for user, category, item, and restaurant management, including caching and ownership verification.
This commit is contained in:
Vendored
+6
-1
@@ -1,7 +1,12 @@
|
|||||||
export const keys = {
|
export const keys = {
|
||||||
restaurantBySlug: (slug: string) => `restaurant:slug:${slug}`,
|
restaurantBySlug: (slug: string) => `restaurant:slug:${slug}`,
|
||||||
restaurantCategories: (restaurantId: string) => `restaurant:${restaurantId}:categories`,
|
restaurantById: (id: string) => `restaurant:id:${id}`,
|
||||||
|
restaurantCategories: (restaurantId: string) =>
|
||||||
|
`restaurant:${restaurantId}:categories`,
|
||||||
restaurantItems: (restaurantId: string) => `restaurant:${restaurantId}:items`,
|
restaurantItems: (restaurantId: string) => `restaurant:${restaurantId}:items`,
|
||||||
|
userRestaurants: (ownerId: string) => `user:${ownerId}:restaurants`,
|
||||||
category: (categoryId: string) => `category:${categoryId}`,
|
category: (categoryId: string) => `category:${categoryId}`,
|
||||||
|
categoryItems: (categoryId: string) => `category:${categoryId}:items`,
|
||||||
item: (itemId: string) => `item:${itemId}`,
|
item: (itemId: string) => `item:${itemId}`,
|
||||||
|
user: (userId: string) => `user:${userId}`,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,20 +16,29 @@ export class CategoryService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async _getCategorie(categoryId: string) {
|
async _getCategorie(categoryId: string) {
|
||||||
const category = await this.prisma.category.findFirstOrThrow({
|
const cacheKey = keys.category(categoryId);
|
||||||
|
const cached = await this.cache.get(cacheKey);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
|
const category = await this.prisma.category
|
||||||
|
.findFirstOrThrow({
|
||||||
where: { id: categoryId },
|
where: { id: categoryId },
|
||||||
include: { items: true, restaurant: true },
|
include: { items: true, restaurant: true },
|
||||||
}).catch(() => {
|
})
|
||||||
|
.catch(() => {
|
||||||
throw new NotFoundException('Category not found');
|
throw new NotFoundException('Category not found');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.cache.set(cacheKey, category, 600);
|
||||||
return category;
|
return category;
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(dto: CreateCategoryDto, userId: string) {
|
async create(dto: CreateCategoryDto, userId: string) {
|
||||||
const restaurant = await this.prisma.restaurant.findUniqueOrThrow({
|
const restaurant = await this.prisma.restaurant
|
||||||
|
.findUniqueOrThrow({
|
||||||
where: { id: dto.restaurantId },
|
where: { id: dto.restaurantId },
|
||||||
}).catch(() => {
|
})
|
||||||
|
.catch(() => {
|
||||||
throw new NotFoundException('Restaurant not found');
|
throw new NotFoundException('Restaurant not found');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -52,18 +61,30 @@ export class CategoryService {
|
|||||||
async findAllForRestaurant(restaurantId: string, userId: string) {
|
async findAllForRestaurant(restaurantId: string, userId: string) {
|
||||||
await this.restaurantService.verifyOwnership(userId, restaurantId);
|
await this.restaurantService.verifyOwnership(userId, restaurantId);
|
||||||
|
|
||||||
|
const cacheKey = keys.restaurantCategories(restaurantId);
|
||||||
|
const cached = await this.cache.get(cacheKey);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
const categories = await this.prisma.category.findMany({
|
const categories = await this.prisma.category.findMany({
|
||||||
where: { restaurantId },
|
where: { restaurantId },
|
||||||
include: { items: true },
|
include: { items: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
return categories.map(CategoryDto.fromEntity);
|
const result = categories.map(CategoryDto.fromEntity);
|
||||||
|
await this.cache.set(cacheKey, result, 300);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(id: string) {
|
async findOne(id: string) {
|
||||||
const category = await this._getCategorie(id);
|
const cacheKey = keys.category(id);
|
||||||
|
const cached = await this.cache.get(cacheKey);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
return CategoryDto.fromEntity(category);
|
const category = await this._getCategorie(id);
|
||||||
|
const result = CategoryDto.fromEntity(category);
|
||||||
|
|
||||||
|
await this.cache.set(cacheKey, result, 600);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, dto: UpdateCategoryDto, userId: string) {
|
async update(id: string, dto: UpdateCategoryDto, userId: string) {
|
||||||
|
|||||||
@@ -36,33 +36,54 @@ export class ItemService {
|
|||||||
await this.cache.del(keys.restaurantBySlug(category.restaurant.slug));
|
await this.cache.del(keys.restaurantBySlug(category.restaurant.slug));
|
||||||
}
|
}
|
||||||
await this.cache.del(keys.restaurantCategories(category.restaurantId));
|
await this.cache.del(keys.restaurantCategories(category.restaurantId));
|
||||||
|
await this.cache.del(keys.restaurantItems(category.restaurantId));
|
||||||
|
await this.cache.del(keys.categoryItems(category.id));
|
||||||
|
await this.cache.del(keys.category(category.id));
|
||||||
|
|
||||||
return MenuItemDto.fromEntity(item);
|
return MenuItemDto.fromEntity(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllByCategory(categoryId: string) {
|
async findAllByCategory(categoryId: string) {
|
||||||
|
const cacheKey = keys.categoryItems(categoryId);
|
||||||
|
const cached = await this.cache.get(cacheKey);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
const items = await this.prisma.menuItem.findMany({
|
const items = await this.prisma.menuItem.findMany({
|
||||||
where: { categoryId },
|
where: { categoryId },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
});
|
});
|
||||||
|
|
||||||
return items.map(MenuItemDto.fromEntity);
|
const result = items.map(MenuItemDto.fromEntity);
|
||||||
|
await this.cache.set(cacheKey, result, 300);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
async findAllForRestaurant(restaurantId: string) {
|
async findAllForRestaurant(restaurantId: string) {
|
||||||
|
const cacheKey = keys.restaurantItems(restaurantId);
|
||||||
|
const cached = await this.cache.get(cacheKey);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
const items = await this.prisma.menuItem.findMany({
|
const items = await this.prisma.menuItem.findMany({
|
||||||
where: { restaurantId },
|
where: { restaurantId },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
});
|
});
|
||||||
|
|
||||||
return items.map(MenuItemDto.fromEntity);
|
const result = items.map(MenuItemDto.fromEntity);
|
||||||
|
await this.cache.set(cacheKey, result, 300);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(id: string) {
|
async findOne(id: string) {
|
||||||
|
const cacheKey = keys.item(id);
|
||||||
|
const cached = await this.cache.get(cacheKey);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
const item = await this.prisma.menuItem.findUnique({ where: { id } });
|
const item = await this.prisma.menuItem.findUnique({ where: { id } });
|
||||||
|
|
||||||
if (!item) throw new NotFoundException('Menu item not found');
|
if (!item) throw new NotFoundException('Menu item not found');
|
||||||
|
|
||||||
return MenuItemDto.fromEntity(item);
|
const result = MenuItemDto.fromEntity(item);
|
||||||
|
await this.cache.set(cacheKey, result, 600);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, dto: UpdateItemDto, userId: string) {
|
async update(id: string, dto: UpdateItemDto, userId: string) {
|
||||||
@@ -89,6 +110,16 @@ export class ItemService {
|
|||||||
await this.cache.del(keys.restaurantBySlug(category.restaurant.slug));
|
await this.cache.del(keys.restaurantBySlug(category.restaurant.slug));
|
||||||
}
|
}
|
||||||
await this.cache.del(keys.restaurantCategories(category.restaurantId));
|
await this.cache.del(keys.restaurantCategories(category.restaurantId));
|
||||||
|
await this.cache.del(keys.restaurantItems(category.restaurantId));
|
||||||
|
await this.cache.del(keys.categoryItems(category.id));
|
||||||
|
await this.cache.del(keys.category(category.id));
|
||||||
|
await this.cache.del(keys.item(id));
|
||||||
|
|
||||||
|
// If category changed, invalidate old category caches too
|
||||||
|
if (dto.categoryId && dto.categoryId !== item.categoryId) {
|
||||||
|
await this.cache.del(keys.categoryItems(item.categoryId));
|
||||||
|
await this.cache.del(keys.category(item.categoryId));
|
||||||
|
}
|
||||||
|
|
||||||
return MenuItemDto.fromEntity(updated);
|
return MenuItemDto.fromEntity(updated);
|
||||||
}
|
}
|
||||||
@@ -106,6 +137,10 @@ export class ItemService {
|
|||||||
await this.cache.del(keys.restaurantBySlug(category.restaurant.slug));
|
await this.cache.del(keys.restaurantBySlug(category.restaurant.slug));
|
||||||
}
|
}
|
||||||
await this.cache.del(keys.restaurantCategories(category.restaurantId));
|
await this.cache.del(keys.restaurantCategories(category.restaurantId));
|
||||||
|
await this.cache.del(keys.restaurantItems(category.restaurantId));
|
||||||
|
await this.cache.del(keys.categoryItems(category.id));
|
||||||
|
await this.cache.del(keys.category(category.id));
|
||||||
|
await this.cache.del(keys.item(id));
|
||||||
|
|
||||||
return deleted;
|
return deleted;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,15 +40,23 @@ export class RestaurantService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.cache.del(keys.userRestaurants(ownerId));
|
||||||
|
|
||||||
return RestaurantDto.fromEntity(restaurant);
|
return RestaurantDto.fromEntity(restaurant);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findByOwner(ownerId: string) {
|
async findByOwner(ownerId: string) {
|
||||||
|
const cacheKey = keys.userRestaurants(ownerId);
|
||||||
|
const cached = await this.cache.get(cacheKey);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
const restaurants = await this.prisma.restaurant.findMany({
|
const restaurants = await this.prisma.restaurant.findMany({
|
||||||
where: { ownerId },
|
where: { ownerId },
|
||||||
});
|
});
|
||||||
|
|
||||||
return restaurants.map(RestaurantDto.fromEntity);
|
const result = restaurants.map(RestaurantDto.fromEntity);
|
||||||
|
await this.cache.set(cacheKey, result, 300);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll() {
|
async findAll() {
|
||||||
@@ -57,6 +65,10 @@ export class RestaurantService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async findOne(id: string) {
|
async findOne(id: string) {
|
||||||
|
const cacheKey = keys.restaurantById(id);
|
||||||
|
const cached = await this.cache.get(cacheKey);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
const restaurant = await this.prisma.restaurant.findUnique({
|
const restaurant = await this.prisma.restaurant.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
});
|
});
|
||||||
@@ -65,7 +77,9 @@ export class RestaurantService {
|
|||||||
throw new NotFoundException('Restaurant not found');
|
throw new NotFoundException('Restaurant not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
return RestaurantDto.fromEntity(restaurant);
|
const result = RestaurantDto.fromEntity(restaurant);
|
||||||
|
await this.cache.set(cacheKey, result, 600);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findMyRestaurant(id: string, userId: string) {
|
async findMyRestaurant(id: string, userId: string) {
|
||||||
@@ -113,8 +127,10 @@ export class RestaurantService {
|
|||||||
|
|
||||||
// Invalidate cache by slug and by id
|
// Invalidate cache by slug and by id
|
||||||
await this.cache.del(keys.restaurantBySlug(updated.slug));
|
await this.cache.del(keys.restaurantBySlug(updated.slug));
|
||||||
|
await this.cache.del(keys.restaurantById(updated.id));
|
||||||
await this.cache.del(keys.restaurantCategories(updated.id));
|
await this.cache.del(keys.restaurantCategories(updated.id));
|
||||||
await this.cache.del(keys.restaurantItems(updated.id));
|
await this.cache.del(keys.restaurantItems(updated.id));
|
||||||
|
await this.cache.del(keys.userRestaurants(userId));
|
||||||
|
|
||||||
return RestaurantDto.fromEntity(updated);
|
return RestaurantDto.fromEntity(updated);
|
||||||
}
|
}
|
||||||
@@ -125,8 +141,10 @@ export class RestaurantService {
|
|||||||
|
|
||||||
// invalidate
|
// invalidate
|
||||||
await this.cache.del(keys.restaurantBySlug(deleted.slug));
|
await this.cache.del(keys.restaurantBySlug(deleted.slug));
|
||||||
|
await this.cache.del(keys.restaurantById(deleted.id));
|
||||||
await this.cache.del(keys.restaurantCategories(deleted.id));
|
await this.cache.del(keys.restaurantCategories(deleted.id));
|
||||||
await this.cache.del(keys.restaurantItems(deleted.id));
|
await this.cache.del(keys.restaurantItems(deleted.id));
|
||||||
|
await this.cache.del(keys.userRestaurants(userId));
|
||||||
|
|
||||||
return deleted;
|
return deleted;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
|
|||||||
import { UserController } from './user.controller';
|
import { UserController } from './user.controller';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
import { PrismaModule } from 'src/prisma/prisma.module';
|
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||||
|
import { CacheModule } from 'src/cache/cache.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrismaModule],
|
imports: [PrismaModule, CacheModule],
|
||||||
controllers: [UserController],
|
controllers: [UserController],
|
||||||
providers: [UserService],
|
providers: [UserService],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { PrismaService } from 'src/prisma/prisma.service';
|
import { PrismaService } from 'src/prisma/prisma.service';
|
||||||
|
import { CacheService } from 'src/cache/cache.service';
|
||||||
|
import { keys } from 'src/cache/cache.keys';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
constructor(
|
||||||
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly cache: CacheService,
|
||||||
|
) {}
|
||||||
|
|
||||||
async getAllUsers() {
|
async getAllUsers() {
|
||||||
return this.prisma.user.findMany({
|
return this.prisma.user.findMany({
|
||||||
@@ -12,11 +17,17 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getUserById(id: string) {
|
async getUserById(id: string) {
|
||||||
|
const cacheKey = keys.user(id);
|
||||||
|
const cached = await this.cache.get(cacheKey);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
const user = await this.prisma.user.findUnique({
|
const user = await this.prisma.user.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { accounts: true, sessions: true, restaurants: true },
|
include: { accounts: true, sessions: true, restaurants: true },
|
||||||
});
|
});
|
||||||
if (!user) throw new NotFoundException('User not found');
|
if (!user) throw new NotFoundException('User not found');
|
||||||
|
|
||||||
|
await this.cache.set(cacheKey, user, 600);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +36,14 @@ export class UserService {
|
|||||||
await this.prisma.session.deleteMany({ where: { userId: id } });
|
await this.prisma.session.deleteMany({ where: { userId: id } });
|
||||||
await this.prisma.account.deleteMany({ where: { userId: id } });
|
await this.prisma.account.deleteMany({ where: { userId: id } });
|
||||||
await this.prisma.restaurant.deleteMany({ where: { ownerId: id } });
|
await this.prisma.restaurant.deleteMany({ where: { ownerId: id } });
|
||||||
return this.prisma.user.delete({ where: { id } });
|
|
||||||
|
const deleted = await this.prisma.user.delete({ where: { id } });
|
||||||
|
|
||||||
|
// Invalidate caches
|
||||||
|
await this.cache.del(keys.user(id));
|
||||||
|
await this.cache.del(keys.userRestaurants(id));
|
||||||
|
|
||||||
|
return deleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSessionsByUserId(id: string) {
|
async getSessionsByUserId(id: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user