narasiahli-be/app/module/articles/repository/articles.repository.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
}