331 lines
11 KiB
Go
331 lines
11 KiB
Go
package repository
|
|
|
|
import (
|
|
"fmt"
|
|
"narasi-ahli-be/app/database"
|
|
"narasi-ahli-be/app/database/entity"
|
|
"narasi-ahli-be/app/module/articles/request"
|
|
"narasi-ahli-be/app/module/articles/response"
|
|
"narasi-ahli-be/utils/paginator"
|
|
utilSvc "narasi-ahli-be/utils/service"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
type articlesRepository struct {
|
|
DB *database.Database
|
|
Log zerolog.Logger
|
|
}
|
|
|
|
// ArticlesRepository define interface of IArticlesRepository
|
|
type ArticlesRepository interface {
|
|
GetAll(req request.ArticlesQueryRequest) (articless []*entity.Articles, paging paginator.Pagination, err error)
|
|
GetAllPublishSchedule() (articless []*entity.Articles, err error)
|
|
FindOne(id uint) (articles *entity.Articles, err error)
|
|
FindByFilename(thumbnailName string) (articleReturn *entity.Articles, err error)
|
|
FindByOldId(oldId uint) (articles *entity.Articles, err error)
|
|
Create(articles *entity.Articles) (articleReturn *entity.Articles, err error)
|
|
Update(id uint, articles *entity.Articles) (err error)
|
|
UpdateSkipNull(id uint, articles *entity.Articles) (err error)
|
|
Delete(id uint) (err error)
|
|
SummaryStats(userID uint) (articleSummaryStats *response.ArticleSummaryStats, err error)
|
|
ArticlePerUserLevelStats(userLevelId *uint, levelNumber *int, startDate *time.Time, endDate *time.Time) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error)
|
|
ArticleMonthlyStats(userLevelId *uint, levelNumber *int, year int) (articleMontlyStats []*response.ArticleMonthlyStats, err error)
|
|
}
|
|
|
|
func NewArticlesRepository(db *database.Database, log zerolog.Logger) ArticlesRepository {
|
|
return &articlesRepository{
|
|
DB: db,
|
|
Log: log,
|
|
}
|
|
}
|
|
|
|
// implement interface of IArticlesRepository
|
|
func (_i *articlesRepository) GetAll(req request.ArticlesQueryRequest) (articless []*entity.Articles, paging paginator.Pagination, err error) {
|
|
var count int64
|
|
|
|
query := _i.DB.DB.Model(&entity.Articles{})
|
|
|
|
if req.CategoryId != nil {
|
|
query = query.Joins("JOIN article_category_details acd ON acd.article_id = articles.id").
|
|
Where("acd.category_id = ?", req.CategoryId)
|
|
}
|
|
query = query.Where("articles.is_active = ?", true)
|
|
|
|
if req.Title != nil && *req.Title != "" {
|
|
title := strings.ToLower(*req.Title)
|
|
query = query.Where("LOWER(articles.title) LIKE ?", "%"+strings.ToLower(title)+"%")
|
|
}
|
|
if req.Description != nil && *req.Description != "" {
|
|
description := strings.ToLower(*req.Description)
|
|
query = query.Where("LOWER(articles.description) LIKE ?", "%"+strings.ToLower(description)+"%")
|
|
}
|
|
if req.Tags != nil && *req.Tags != "" {
|
|
tags := strings.ToLower(*req.Tags)
|
|
query = query.Where("LOWER(articles.tags) LIKE ?", "%"+strings.ToLower(tags)+"%")
|
|
}
|
|
if req.TypeId != nil {
|
|
query = query.Where("articles.type_id = ?", req.TypeId)
|
|
}
|
|
if req.IsPublish != nil {
|
|
query = query.Where("articles.is_publish = ?", req.IsPublish)
|
|
}
|
|
if req.IsBanner != nil {
|
|
query = query.Where("articles.is_banner = ?", req.IsBanner)
|
|
}
|
|
if req.IsDraft != nil {
|
|
query = query.Where("articles.is_draft = ?", req.IsDraft)
|
|
}
|
|
if req.StatusId != nil {
|
|
query = query.Where("articles.status_id = ?", req.StatusId)
|
|
}
|
|
if req.CreatedById != nil {
|
|
query = query.Where("articles.created_by_id = ?", req.CreatedById)
|
|
}
|
|
query.Count(&count)
|
|
|
|
if req.Pagination.SortBy != "" {
|
|
direction := "ASC"
|
|
if req.Pagination.Sort == "desc" {
|
|
direction = "DESC"
|
|
}
|
|
query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction))
|
|
} else {
|
|
direction := "DESC"
|
|
sortBy := "articles.created_at"
|
|
query.Order(fmt.Sprintf("%s %s", sortBy, direction))
|
|
}
|
|
|
|
req.Pagination.Count = count
|
|
req.Pagination = paginator.Paging(req.Pagination)
|
|
|
|
err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articless).Error
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
paging = *req.Pagination
|
|
|
|
return
|
|
}
|
|
|
|
func (_i *articlesRepository) GetAllPublishSchedule() (articles []*entity.Articles, err error) {
|
|
query := _i.DB.DB.Where("publish_schedule IS NOT NULL")
|
|
err = query.Find(&articles).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return articles, nil
|
|
}
|
|
|
|
func (_i *articlesRepository) FindOne(id uint) (articles *entity.Articles, err error) {
|
|
query := _i.DB.DB
|
|
if err := query.First(&articles, id).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return articles, nil
|
|
}
|
|
|
|
func (_i *articlesRepository) FindByFilename(thumbnailName string) (articles *entity.Articles, err error) {
|
|
query := _i.DB.DB.Where("thumbnail_name = ?", thumbnailName)
|
|
|
|
if err := query.First(&articles).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return articles, nil
|
|
}
|
|
|
|
func (_i *articlesRepository) FindByOldId(oldId uint) (articles *entity.Articles, err error) {
|
|
query := _i.DB.DB.Where("old_id = ?", oldId)
|
|
|
|
if err := query.First(&articles).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return articles, nil
|
|
}
|
|
|
|
func (_i *articlesRepository) Create(articles *entity.Articles) (articleReturn *entity.Articles, err error) {
|
|
result := _i.DB.DB.Create(articles)
|
|
return articles, result.Error
|
|
}
|
|
|
|
func (_i *articlesRepository) Update(id uint, articles *entity.Articles) (err error) {
|
|
articlesMap, err := utilSvc.StructToMap(articles)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return _i.DB.DB.Model(&entity.Articles{}).
|
|
Where(&entity.Articles{ID: id}).
|
|
Updates(articlesMap).Error
|
|
}
|
|
|
|
func (_i *articlesRepository) UpdateSkipNull(id uint, articles *entity.Articles) (err error) {
|
|
return _i.DB.DB.Model(&entity.Articles{}).
|
|
Where(&entity.Articles{ID: id}).
|
|
Updates(articles).Error
|
|
}
|
|
|
|
func (_i *articlesRepository) Delete(id uint) error {
|
|
return _i.DB.DB.Delete(&entity.Articles{}, id).Error
|
|
}
|
|
|
|
func (_i *articlesRepository) SummaryStats(userID uint) (articleSummaryStats *response.ArticleSummaryStats, err error) {
|
|
now := time.Now()
|
|
startOfDay := now.Truncate(24 * time.Hour)
|
|
startOfWeek := now.AddDate(0, 0, -int(now.Weekday())+1).Truncate(24 * time.Hour)
|
|
|
|
// Query
|
|
query := _i.DB.DB.Model(&entity.Articles{}).
|
|
Select(
|
|
"COUNT(*) AS total_all, "+
|
|
"COALESCE(SUM(view_count), 0) AS total_views, "+
|
|
"COALESCE(SUM(share_count), 0) AS total_shares, "+
|
|
"COALESCE(SUM(comment_count), 0) AS total_comments, "+
|
|
"COUNT(CASE WHEN created_at >= ? THEN 1 END) AS total_today, "+
|
|
"COUNT(CASE WHEN created_at >= ? THEN 1 END) AS total_this_week",
|
|
startOfDay, startOfWeek).
|
|
Where("created_by_id = ?", userID)
|
|
|
|
err = query.Scan(&articleSummaryStats).Error
|
|
|
|
return articleSummaryStats, err
|
|
}
|
|
|
|
func (_i *articlesRepository) ArticlePerUserLevelStats(userLevelId *uint, levelNumber *int, startDate *time.Time, endDate *time.Time) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error) {
|
|
|
|
levelNumberTop := 1
|
|
|
|
query := _i.DB.DB.Model(&entity.Articles{}).
|
|
Select("user_levels.id as user_level_id", "user_levels.name as user_level_name", "COUNT(articles.id) as total_article").
|
|
Joins("LEFT JOIN users ON articles.created_by_id = users.id").
|
|
Joins("LEFT JOIN user_levels ON users.user_level_id = user_levels.id").
|
|
Where("articles.is_active = true")
|
|
|
|
if userLevelId != nil && *levelNumber != levelNumberTop {
|
|
query = query.Where("user_levels.id = ? or user_levels.parent_level_id = ?", *userLevelId, *userLevelId)
|
|
} else {
|
|
query = _i.DB.DB.Raw(`
|
|
WITH LevelHierarchy AS (
|
|
SELECT
|
|
id,
|
|
name,
|
|
level_number,
|
|
parent_level_id,
|
|
CASE
|
|
WHEN level_number = 1 THEN id
|
|
WHEN level_number = 2 and name ILIKE '%polda%' THEN id
|
|
WHEN level_number = 2 and name NOT ILIKE '%polda%' THEN parent_level_id
|
|
WHEN level_number = 3 THEN parent_level_id
|
|
END AS level_2_id,
|
|
CASE
|
|
WHEN level_number = 1 THEN name
|
|
WHEN level_number = 2 and name ILIKE '%polda%' THEN name
|
|
WHEN level_number = 2 and name NOT ILIKE '%polda%' THEN (SELECT name FROM user_levels ul2 WHERE ul2.id = user_levels.parent_level_id)
|
|
WHEN level_number = 3 THEN (SELECT name FROM user_levels ul2 WHERE ul2.id = user_levels.parent_level_id)
|
|
END AS level_2_name
|
|
FROM user_levels
|
|
)
|
|
SELECT
|
|
lh.level_2_id AS user_level_id,
|
|
UPPER(lh.level_2_name) AS user_level_name,
|
|
COUNT(articles.id) AS total_article
|
|
FROM articles
|
|
JOIN users ON articles.created_by_id = users.id
|
|
JOIN LevelHierarchy lh ON users.user_level_id = lh.id
|
|
WHERE articles.is_active = true AND lh.level_2_id > 0`)
|
|
|
|
query = query.Group("lh.level_2_id, lh.level_2_name").Order("total_article DESC")
|
|
}
|
|
|
|
// Apply date filters if provided
|
|
if startDate != nil {
|
|
query = query.Where("articles.created_at >= ?", *startDate)
|
|
}
|
|
if endDate != nil {
|
|
query = query.Where("articles.created_at <= ?", *endDate)
|
|
}
|
|
|
|
// Group by all non-aggregated columns
|
|
err = query.Group("user_levels.id, user_levels.name").
|
|
Order("total_article DESC").
|
|
Scan(&articlePerUserLevelStats).Error
|
|
|
|
return articlePerUserLevelStats, err
|
|
}
|
|
|
|
func (_i *articlesRepository) ArticleMonthlyStats(userLevelId *uint, levelNumber *int, year int) (articleMontlyStats []*response.ArticleMonthlyStats, err error) {
|
|
levelNumberTop := 1
|
|
|
|
if year < 1900 || year > 2100 {
|
|
return nil, fmt.Errorf("invalid year")
|
|
}
|
|
|
|
var results []struct {
|
|
Month int
|
|
Day int
|
|
TotalView int
|
|
TotalComment int
|
|
TotalShare int
|
|
}
|
|
|
|
query := _i.DB.DB.Model(&entity.Articles{}).
|
|
Select("EXTRACT(MONTH FROM created_at) as month, EXTRACT(DAY FROM created_at) as day, "+
|
|
"SUM(view_count) as total_view, "+
|
|
"SUM(comment_count) as total_comment, "+
|
|
"SUM(share_count) as total_share").
|
|
Where("EXTRACT(YEAR FROM created_at) = ?", year)
|
|
|
|
if userLevelId != nil && *levelNumber != levelNumberTop {
|
|
query = _i.DB.DB.Model(&entity.Articles{}).
|
|
Select("EXTRACT(MONTH FROM articles.created_at) as month, EXTRACT(DAY FROM articles.created_at) as day, "+
|
|
"SUM(articles.view_count) as total_view, "+
|
|
"SUM(articles.comment_count) as total_comment, "+
|
|
"SUM(articles.share_count) as total_share").
|
|
Joins("LEFT JOIN users ON articles.created_by_id = users.id").
|
|
Joins("LEFT JOIN user_levels ON users.user_level_id = user_levels.id").
|
|
Where("articles.is_active = true").
|
|
Where("EXTRACT(YEAR FROM articles.created_at) = ?", year).
|
|
Where("(user_levels.id = ? OR user_levels.parent_level_id = ?)", *userLevelId, *userLevelId)
|
|
|
|
}
|
|
|
|
err = query.Group("month, day").Scan(&results).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Siapkan struktur untuk menyimpan data bulanan
|
|
monthlyAnalytics := make([]*response.ArticleMonthlyStats, 12)
|
|
for i := 0; i < 12; i++ {
|
|
daysInMonth := time.Date(year, time.Month(i+1), 0, 0, 0, 0, 0, time.UTC).Day()
|
|
monthlyAnalytics[i] = &response.ArticleMonthlyStats{
|
|
Year: year,
|
|
Month: i + 1,
|
|
View: make([]int, daysInMonth),
|
|
Comment: make([]int, daysInMonth),
|
|
Share: make([]int, daysInMonth),
|
|
}
|
|
}
|
|
|
|
// Isi data dari hasil agregasi
|
|
for _, result := range results {
|
|
monthIndex := result.Month - 1
|
|
dayIndex := result.Day - 1
|
|
|
|
if monthIndex >= 0 && monthIndex < 12 {
|
|
if dayIndex >= 0 && dayIndex < len(monthlyAnalytics[monthIndex].View) {
|
|
monthlyAnalytics[monthIndex].View[dayIndex] = result.TotalView
|
|
monthlyAnalytics[monthIndex].Comment[dayIndex] = result.TotalComment
|
|
monthlyAnalytics[monthIndex].Share[dayIndex] = result.TotalShare
|
|
}
|
|
}
|
|
}
|
|
|
|
return monthlyAnalytics, nil
|
|
}
|