social share up and running
This commit is contained in:
1 parent
000ebd388a
commit
b8779e5a35
22 files changed
+1935
-41
No files matched your search
@@ -0,0 +1,266 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { ShareEvent, SharePlatform } from './analytics.entity';
|
||||
import { Article } from '../entities';
|
||||
import {
|
||||
TrackShareDto,
|
||||
GetShareStatsDto,
|
||||
ShareStatsResponse,
|
||||
} from './analytics.dto';
|
||||
|
||||
@Injectable()
|
||||
export class AnalyticsService {
|
||||
private readonly logger = new Logger(AnalyticsService.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(ShareEvent)
|
||||
private readonly shareEventRepository: Repository<ShareEvent>,
|
||||
@InjectRepository(Article)
|
||||
private readonly articleRepository: Repository<Article>,
|
||||
) {}
|
||||
|
||||
async trackShare(trackShareDto: TrackShareDto): Promise<ShareEvent> {
|
||||
const shareEvent = this.shareEventRepository.create(trackShareDto);
|
||||
|
||||
// Also update the article's share counters
|
||||
await this.incrementArticleShareCounter(
|
||||
trackShareDto.articleId,
|
||||
trackShareDto.platform,
|
||||
);
|
||||
|
||||
return await this.shareEventRepository.save(shareEvent);
|
||||
}
|
||||
|
||||
private async incrementArticleShareCounter(
|
||||
articleId: string,
|
||||
platform: SharePlatform,
|
||||
): Promise<void> {
|
||||
const updateField = this.getShareCounterField(platform);
|
||||
if (!updateField) return;
|
||||
|
||||
await this.articleRepository
|
||||
.createQueryBuilder()
|
||||
.update(Article)
|
||||
.set({ [updateField]: () => `${updateField} + 1` })
|
||||
.where('id = :id', { id: articleId })
|
||||
.execute();
|
||||
}
|
||||
|
||||
private getShareCounterField(platform: SharePlatform): string | null {
|
||||
switch (platform) {
|
||||
case 'facebook':
|
||||
return 'facebookShares';
|
||||
case 'twitter':
|
||||
return 'twitterShares';
|
||||
case 'whatsapp':
|
||||
return 'whatsappShares';
|
||||
case 'telegram':
|
||||
return 'telegramShares';
|
||||
default:
|
||||
return null; // 'link' shares don't increment counters
|
||||
}
|
||||
}
|
||||
|
||||
async getShareStats(
|
||||
getShareStatsDto: GetShareStatsDto,
|
||||
): Promise<ShareStatsResponse[]> {
|
||||
const query = this.articleRepository
|
||||
.createQueryBuilder('article')
|
||||
.select([
|
||||
'article.id as "articleId"',
|
||||
'article.title as "articleTitle"',
|
||||
'article.facebookShares as "facebookShares"',
|
||||
'article.twitterShares as "twitterShares"',
|
||||
'article.whatsappShares as "whatsappShares"',
|
||||
'article.telegramShares as "telegramShares"',
|
||||
'article.views as "views"',
|
||||
'article.createdAt as "createdAt"',
|
||||
'article.updatedAt as "updatedAt"',
|
||||
])
|
||||
.addSelect(
|
||||
`(article.facebookShares + article.twitterShares + article.whatsappShares + article.telegramShares) as "totalShares"`,
|
||||
)
|
||||
.addSelect(
|
||||
`CASE
|
||||
WHEN article.views > 0
|
||||
THEN ROUND(
|
||||
(article.facebookShares + article.twitterShares + article.whatsappShares + article.telegramShares)::decimal / article.views * 100,
|
||||
2
|
||||
)
|
||||
ELSE 0
|
||||
END as "shareRate"`,
|
||||
);
|
||||
|
||||
if (getShareStatsDto.articleId) {
|
||||
query.where('article.id = :articleId', {
|
||||
articleId: getShareStatsDto.articleId,
|
||||
});
|
||||
}
|
||||
|
||||
if (getShareStatsDto.startDate) {
|
||||
query.andWhere('article.createdAt >= :startDate', {
|
||||
startDate: getShareStatsDto.startDate,
|
||||
});
|
||||
}
|
||||
|
||||
if (getShareStatsDto.endDate) {
|
||||
query.andWhere('article.createdAt <= :endDate', {
|
||||
endDate: getShareStatsDto.endDate,
|
||||
});
|
||||
}
|
||||
|
||||
query.orderBy('"totalShares"', 'DESC');
|
||||
|
||||
const rawResults = await query.getRawMany<{
|
||||
articleId: string;
|
||||
articleTitle: string;
|
||||
facebookShares: string;
|
||||
twitterShares: string;
|
||||
whatsappShares: string;
|
||||
telegramShares: string;
|
||||
views: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
totalShares: string;
|
||||
shareRate: string;
|
||||
}>();
|
||||
|
||||
// Get link shares from share_events table
|
||||
const results: ShareStatsResponse[] = [];
|
||||
for (const rawResult of rawResults) {
|
||||
const linkShares = await this.shareEventRepository.count({
|
||||
where: {
|
||||
articleId: rawResult.articleId,
|
||||
platform: 'link',
|
||||
},
|
||||
});
|
||||
|
||||
const facebookShares = parseInt(rawResult.facebookShares) || 0;
|
||||
const twitterShares = parseInt(rawResult.twitterShares) || 0;
|
||||
const whatsappShares = parseInt(rawResult.whatsappShares) || 0;
|
||||
const telegramShares = parseInt(rawResult.telegramShares) || 0;
|
||||
const views = parseInt(rawResult.views) || 0;
|
||||
const baseTotalShares =
|
||||
facebookShares + twitterShares + whatsappShares + telegramShares;
|
||||
const totalShares = baseTotalShares + linkShares;
|
||||
const shareRate =
|
||||
views > 0 ? parseFloat(((totalShares / views) * 100).toFixed(2)) : 0;
|
||||
|
||||
results.push({
|
||||
articleId: rawResult.articleId,
|
||||
articleTitle: rawResult.articleTitle,
|
||||
facebookShares,
|
||||
twitterShares,
|
||||
whatsappShares,
|
||||
telegramShares,
|
||||
linkShares,
|
||||
totalShares,
|
||||
views,
|
||||
shareRate,
|
||||
createdAt: new Date(rawResult.createdAt),
|
||||
updatedAt: new Date(rawResult.updatedAt),
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async getTopSharedArticles(
|
||||
limit: number = 10,
|
||||
): Promise<ShareStatsResponse[]> {
|
||||
const stats = await this.getShareStats({});
|
||||
return stats.slice(0, limit);
|
||||
}
|
||||
|
||||
async getShareTrends(
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
interval: 'day' | 'week' | 'month' = 'day',
|
||||
): Promise<
|
||||
Array<{ period: string; platform: SharePlatform; count: number }>
|
||||
> {
|
||||
const dateFormat =
|
||||
interval === 'day'
|
||||
? 'YYYY-MM-DD'
|
||||
: interval === 'week'
|
||||
? 'YYYY-WW'
|
||||
: 'YYYY-MM';
|
||||
|
||||
const query = this.shareEventRepository
|
||||
.createQueryBuilder('share_event')
|
||||
.select([
|
||||
`TO_CHAR(share_event.createdAt, '${dateFormat}') as period`,
|
||||
'share_event.platform as platform',
|
||||
'COUNT(*) as count',
|
||||
])
|
||||
.where('share_event.createdAt >= :startDate', { startDate })
|
||||
.andWhere('share_event.createdAt <= :endDate', { endDate })
|
||||
.groupBy('period, platform')
|
||||
.orderBy('period', 'ASC');
|
||||
|
||||
const rawResults = await query.getRawMany<{
|
||||
period: string;
|
||||
platform: string;
|
||||
count: string;
|
||||
}>();
|
||||
|
||||
// Convert platform strings to SharePlatform type
|
||||
return rawResults.map((result) => ({
|
||||
period: result.period,
|
||||
platform: result.platform as SharePlatform,
|
||||
count: parseInt(result.count) || 0,
|
||||
}));
|
||||
}
|
||||
|
||||
async getTotalShareStats(): Promise<{
|
||||
totalShares: number;
|
||||
facebookShares: number;
|
||||
twitterShares: number;
|
||||
whatsappShares: number;
|
||||
telegramShares: number;
|
||||
linkShares: number;
|
||||
}> {
|
||||
interface ArticleStatsRaw {
|
||||
facebookShares: string;
|
||||
twitterShares: string;
|
||||
whatsappShares: string;
|
||||
telegramShares: string;
|
||||
}
|
||||
|
||||
const articleStats = (await this.articleRepository
|
||||
.createQueryBuilder('article')
|
||||
.select([
|
||||
'SUM(article.facebookShares) as facebookShares',
|
||||
'SUM(article.twitterShares) as twitterShares',
|
||||
'SUM(article.whatsappShares) as whatsappShares',
|
||||
'SUM(article.telegramShares) as telegramShares',
|
||||
])
|
||||
.getRawOne()) as ArticleStatsRaw;
|
||||
|
||||
const linkShares = await this.shareEventRepository.count({
|
||||
where: { platform: 'link' },
|
||||
});
|
||||
|
||||
const facebookShares = parseInt(articleStats?.facebookShares || '0') || 0;
|
||||
const twitterShares = parseInt(articleStats?.twitterShares || '0') || 0;
|
||||
const whatsappShares = parseInt(articleStats?.whatsappShares || '0') || 0;
|
||||
const telegramShares = parseInt(articleStats?.telegramShares || '0') || 0;
|
||||
|
||||
const totalShares =
|
||||
facebookShares +
|
||||
twitterShares +
|
||||
whatsappShares +
|
||||
telegramShares +
|
||||
linkShares;
|
||||
|
||||
return {
|
||||
totalShares,
|
||||
facebookShares,
|
||||
twitterShares,
|
||||
whatsappShares,
|
||||
telegramShares,
|
||||
linkShares,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user