Skip to content

Commit

Permalink
feat(api): add views for products
Browse files Browse the repository at this point in the history
  • Loading branch information
fieztazica committed Jun 6, 2024
1 parent 0782a31 commit bc1ba35
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 11 deletions.
5 changes: 3 additions & 2 deletions apps/api/prisma/seeders/products.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ export function createRandomProduct(): Prisma.ProductCreateInput {
max: 100,
}),
);
const name = faker.commerce.productName();
return {
name: faker.commerce.productName(),
displayName: faker.commerce.productName(),
name,
displayName: name,
description: faker.commerce.productDescription(),
imageUrl: faker.image.url({
width: 300,
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/admin/admin.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ export class AdminService {
): Promise<ReturnType<Service['findOne']>> {
const entityService = this.getServiceFromEntityName(entityName);
try {
const entity = await entityService.findOne(id);
const entity = await entityService.findOne(id, 0);
return this.beautifyEntity(entity);
} catch (error) {
console.error(error);
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/products/products.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { forwardRef, Module } from '@nestjs/common';
import { ProductsService } from './products.service';
import { ProductsResolver } from './products.resolver';
import { FeedbacksModule } from 'src/feedbacks/feedbacks.module';
import { RedisModule } from 'src/redis/redis.module';

@Module({
imports: [forwardRef(() => FeedbacksModule)],
imports: [forwardRef(() => FeedbacksModule), RedisModule],
providers: [ProductsResolver, ProductsService],
exports: [ProductsService],
})
Expand Down
66 changes: 59 additions & 7 deletions apps/api/src/products/products.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,20 @@ import { FeedbacksService } from 'src/feedbacks/feedbacks.service';
import { PrismaService } from '../common/database/prisma.service';
import { CreateProductInput } from './dto/create-product.input';
import { UpdateProductInput } from './dto/update-product.input';
import { RedisService } from 'src/redis/redis.service';

@Injectable()
export class ProductsService {
cacheKey = 'products';
// 7 days in milliseconds
cacheTtl = 7 * 24 * 60 * 60 * 1000;
private readonly logger = new Logger(ProductsService.name);
isTesting: boolean;
constructor(
@Inject(ENHANCED_PRISMA) private readonly prisma: PrismaService,
private readonly feedbacksService: FeedbacksService,
private readonly configService: ConfigService,
private readonly redisService: RedisService,
) {
this.isTesting = this.configService.get('testing');
}
Expand Down Expand Up @@ -49,10 +54,40 @@ export class ProductsService {
});
}

findOne(id: string): Promise<Product> {
return this.prisma.product.findUnique({
async findOne(id: string, addView: number = 1) {
let product = await this.redisService.hGetJson<Product>(this.cacheKey, id);

// if product found in cache store
if (product) {
if (addView > 0) {
product = { ...product, views: product.views + addView };
await this.redisService.hSetJson(
this.cacheKey,
id,
product,
this.cacheTtl,
);
}
return product;
}

product = await this.prisma.product.findUnique({
where: { id, deleted: false },
});

if (product) {
if (addView > 0) {
product = { ...product, views: product.views + addView };
}
await this.redisService.hSetJson(
this.cacheKey,
id,
product,
this.cacheTtl,
);
}

return product;
}

getProductFeedbacks(productId: string) {
Expand Down Expand Up @@ -98,7 +133,7 @@ export class ProductsService {

@Cron(
process.env.NODE_ENV !== 'production'
? CronExpression.EVERY_MINUTE
? CronExpression.EVERY_5_MINUTES
: CronExpression.EVERY_DAY_AT_MIDNIGHT,
)
async calculateProductsRatings() {
Expand All @@ -121,6 +156,26 @@ export class ProductsService {
);
}

@Cron(
process.env.NODE_ENV !== 'production'
? CronExpression.EVERY_10_SECONDS
: CronExpression.EVERY_DAY_AT_MIDNIGHT,
)
async updateProductsViews() {
this.logger.log('[TASK] Updating products views...');
const products = await this.redisService.hGetAllJson<Product>(
this.cacheKey,
);
const productsArray = Object.values(products);
for await (const product of productsArray) {
await this.update(product.id, {
id: product.id,
views: product.views,
});
}
this.logger.log('[TASK] Updated products views...');
}

async calculateRatingsByProductId(productId: string, product?: Product) {
if (product) {
const diff = this.isTesting
Expand All @@ -136,10 +191,7 @@ export class ProductsService {
ratings: ratings.averageRatings,
});
this.logger.log(
`triggered calculate ratings for `,
productId,
productRatings,
new Date(),
`triggered calculate ratings for ${productId}: ${productRatings}`,
);
return productRatings;
}
Expand Down
27 changes: 27 additions & 0 deletions apps/api/src/redis/redis.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,31 @@ export class RedisService implements OnModuleInit {
}
return value;
}

keys() {
return this.cacheManager.store.keys();
}

async getAll(key: string) {
const keys = (await this.keys()).filter((k) => k.startsWith(key));
const result: {
[key: string]: string;
} = {};
for await (const key of keys) {
result[key] = await this.get(key);
}
return result;
}

async hGetAllJson<T>(key: string) {
const all = await this.getAll(key);
const result: {
[key: string]: T;
} = {};
for (const key in all) {
const field = key.split(':')[1];
result[field] = JSON.parse(all[key]) as T;
}
return result;
}
}

0 comments on commit bc1ba35

Please sign in to comment.