kontenhumas-be/app/module/articles/service/articles.service.go

1532 lines
49 KiB
Go

package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"math/rand"
"mime"
"netidhub-saas-be/app/database/entity"
approvalWorkflowsRepository "netidhub-saas-be/app/module/approval_workflows/repository"
articleApprovalFlowsRepository "netidhub-saas-be/app/module/article_approval_flows/repository"
articleApprovalFlowsService "netidhub-saas-be/app/module/article_approval_flows/service"
articleApprovalsRepository "netidhub-saas-be/app/module/article_approvals/repository"
articleCategoriesRepository "netidhub-saas-be/app/module/article_categories/repository"
articleCategoryDetailsRepository "netidhub-saas-be/app/module/article_category_details/repository"
articleCategoryDetailsReq "netidhub-saas-be/app/module/article_category_details/request"
articleFilesRepository "netidhub-saas-be/app/module/article_files/repository"
"netidhub-saas-be/app/module/articles/mapper"
"netidhub-saas-be/app/module/articles/repository"
"netidhub-saas-be/app/module/articles/request"
"netidhub-saas-be/app/module/articles/response"
clientsRepository "netidhub-saas-be/app/module/clients/repository"
usersRepository "netidhub-saas-be/app/module/users/repository"
config "netidhub-saas-be/config/config"
minioStorage "netidhub-saas-be/config/config"
"netidhub-saas-be/utils/paginator"
utilSvc "netidhub-saas-be/utils/service"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"github.com/minio/minio-go/v7"
"github.com/rs/zerolog"
)
// ArticlesService
type articlesService struct {
Repo repository.ArticlesRepository
ArticleCategoriesRepo articleCategoriesRepository.ArticleCategoriesRepository
ArticleFilesRepo articleFilesRepository.ArticleFilesRepository
ArticleApprovalsRepo articleApprovalsRepository.ArticleApprovalsRepository
ArticleCategoryDetailsRepo articleCategoryDetailsRepository.ArticleCategoryDetailsRepository
Log zerolog.Logger
Cfg *config.Config
UsersRepo usersRepository.UsersRepository
ClientsRepo clientsRepository.ClientsRepository
MinioStorage *minioStorage.MinioStorage
// Dynamic approval system dependencies
ArticleApprovalFlowsRepo articleApprovalFlowsRepository.ArticleApprovalFlowsRepository
ApprovalWorkflowsRepo approvalWorkflowsRepository.ApprovalWorkflowsRepository
ArticleApprovalFlowsSvc articleApprovalFlowsService.ArticleApprovalFlowsService
}
// ArticlesService define interface of IArticlesService
type ArticlesService interface {
All(authToken string, req request.ArticlesQueryRequest) (articles []*response.ArticlesResponse, paging paginator.Pagination, err error)
Show(authToken string, id uint) (articles *response.ArticlesResponse, err error)
ShowByOldId(authToken string, oldId uint) (articles *response.ArticlesResponse, err error)
Save(authToken string, req request.ArticlesCreateRequest) (articles *entity.Articles, err error)
SaveThumbnail(authToken string, c *fiber.Ctx) (err error)
Update(authToken string, id uint, req request.ArticlesUpdateRequest) (err error)
Delete(authToken string, id uint) error
UpdateActivityCount(authToken string, id uint, activityTypeId int) (err error)
UpdateApproval(authToken string, id uint, statusId int, userLevelId int, userLevelNumber int, userParentLevelId int) (err error)
UpdateBanner(authToken string, id uint, isBanner bool) error
Viewer(authToken string, c *fiber.Ctx) error
SummaryStats(authToken string) (summaryStats *response.ArticleSummaryStats, err error)
ArticlePerUserLevelStats(authToken string, startDate *string, endDate *string) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error)
ArticleMonthlyStats(authToken string, year *int) (articleMonthlyStats []*response.ArticleMonthlyStats, err error)
PublishScheduling(authToken string, id uint, publishSchedule string) error
ExecuteScheduling() error
// Dynamic approval system methods
SubmitForApproval(authToken string, articleId uint, workflowId *uint) error
GetApprovalStatus(authToken string, articleId uint) (*response.ArticleApprovalStatusResponse, error)
GetArticlesWaitingForApproval(authToken string, page, limit int) ([]*response.ArticleApprovalQueueResponse, paginator.Pagination, error)
GetPendingApprovals(authToken string, page, limit int, typeId *int) ([]*response.ArticleApprovalQueueResponse, paginator.Pagination, error) // Updated with typeId filter
// No-approval system methods
CheckApprovalRequired(authToken string, articleId uint, userId uint, userLevelId uint) (bool, error)
AutoApproveArticle(authToken string, articleId uint, reason string) error
GetClientApprovalSettings(authToken string) (*response.ClientApprovalSettingsResponse, error)
SetArticleApprovalExempt(authToken string, articleId uint, exempt bool, reason string) error
}
// NewArticlesService init ArticlesService
func NewArticlesService(
repo repository.ArticlesRepository,
articleCategoriesRepo articleCategoriesRepository.ArticleCategoriesRepository,
articleCategoryDetailsRepo articleCategoryDetailsRepository.ArticleCategoryDetailsRepository,
articleFilesRepo articleFilesRepository.ArticleFilesRepository,
articleApprovalsRepo articleApprovalsRepository.ArticleApprovalsRepository,
articleApprovalFlowsRepo articleApprovalFlowsRepository.ArticleApprovalFlowsRepository,
approvalWorkflowsRepo approvalWorkflowsRepository.ApprovalWorkflowsRepository,
articleApprovalFlowsSvc articleApprovalFlowsService.ArticleApprovalFlowsService,
log zerolog.Logger,
cfg *config.Config,
usersRepo usersRepository.UsersRepository,
clientsRepo clientsRepository.ClientsRepository,
minioStorage *minioStorage.MinioStorage,
) ArticlesService {
return &articlesService{
Repo: repo,
ArticleCategoriesRepo: articleCategoriesRepo,
ArticleCategoryDetailsRepo: articleCategoryDetailsRepo,
ArticleFilesRepo: articleFilesRepo,
ArticleApprovalsRepo: articleApprovalsRepo,
ArticleApprovalFlowsRepo: articleApprovalFlowsRepo,
ApprovalWorkflowsRepo: approvalWorkflowsRepo,
ArticleApprovalFlowsSvc: articleApprovalFlowsSvc,
Log: log,
UsersRepo: usersRepo,
ClientsRepo: clientsRepo,
MinioStorage: minioStorage,
Cfg: cfg,
}
}
// All implement interface of ArticlesService
func (_i *articlesService) All(authToken string, req request.ArticlesQueryRequest) (articless []*response.ArticlesResponse, paging paginator.Pagination, err error) {
// Extract clientId, userLevelId, and userId from authToken
var clientId *uuid.UUID
var userLevelId *uint
var userId *uint
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil {
if user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
userLevelId = &user.UserLevelId
userId = &user.ID
_i.Log.Info().Interface("userLevelId", userLevelId).Msg("Extracted userLevelId from auth token")
_i.Log.Info().Interface("userId", userId).Msg("Extracted userId from auth token")
}
}
if req.Category != nil {
findCategory, err := _i.ArticleCategoriesRepo.FindOneBySlug(clientId, *req.Category)
if err != nil {
return nil, paging, err
}
req.CategoryId = &findCategory.ID
}
results, paging, err := _i.Repo.GetAll(clientId, userLevelId, userId, req)
if err != nil {
return
}
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:articlesService", "Methods:All").
Interface("results", results).Msg("")
host := _i.Cfg.App.Domain
for _, result := range results {
articleRes := mapper.ArticlesResponseMapper(_i.Log, host, clientId, result, _i.ArticleCategoriesRepo, _i.ArticleCategoryDetailsRepo, _i.ArticleFilesRepo, _i.UsersRepo, _i.ClientsRepo)
articless = append(articless, articleRes)
}
return
}
func (_i *articlesService) Show(authToken string, id uint) (articles *response.ArticlesResponse, err error) {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
result, err := _i.Repo.FindOne(clientId, id)
if err != nil {
return nil, err
}
host := _i.Cfg.App.Domain
return mapper.ArticlesResponseMapper(_i.Log, host, clientId, result, _i.ArticleCategoriesRepo, _i.ArticleCategoryDetailsRepo, _i.ArticleFilesRepo, _i.UsersRepo, _i.ClientsRepo), nil
}
func (_i *articlesService) ShowByOldId(authToken string, oldId uint) (articles *response.ArticlesResponse, err error) {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
result, err := _i.Repo.FindByOldId(clientId, oldId)
if err != nil {
return nil, err
}
host := _i.Cfg.App.Domain
return mapper.ArticlesResponseMapper(_i.Log, host, clientId, result, _i.ArticleCategoriesRepo, _i.ArticleCategoryDetailsRepo, _i.ArticleFilesRepo, _i.UsersRepo, _i.ClientsRepo), nil
}
func (_i *articlesService) Save(authToken string, req request.ArticlesCreateRequest) (articles *entity.Articles, err error) {
_i.Log.Info().Interface("data", req).Msg("")
newReq := req.ToEntity()
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
var userLevelNumber int
var approvalLevelId int
if req.CreatedById != nil {
createdBy, err := _i.UsersRepo.FindOne(clientId, *req.CreatedById)
if err != nil {
return nil, fmt.Errorf("User not found")
}
newReq.CreatedById = &createdBy.ID
userLevelNumber = createdBy.UserLevel.LevelNumber
// Find the next higher level for approval (level_number should be smaller)
approvalLevelId = _i.findNextApprovalLevel(clientId, userLevelNumber)
} else {
createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
newReq.CreatedById = &createdBy.ID
userLevelNumber = createdBy.UserLevel.LevelNumber
// Find the next higher level for approval (level_number should be smaller)
approvalLevelId = _i.findNextApprovalLevel(clientId, userLevelNumber)
}
isDraft := true
if req.IsDraft == &isDraft {
draftedAt := time.Now()
newReq.IsDraft = &isDraft
newReq.DraftedAt = &draftedAt
isPublishFalse := false
newReq.IsPublish = &isPublishFalse
newReq.PublishedAt = nil
}
isPublish := true
if req.IsPublish == &isPublish {
publishedAt := time.Now()
newReq.IsPublish = &isPublish
newReq.PublishedAt = &publishedAt
isDraftFalse := false
newReq.IsDraft = &isDraftFalse
newReq.DraftedAt = nil
}
if req.CreatedAt != nil {
layout := "2006-01-02 15:04:05"
parsedTime, err := time.Parse(layout, *req.CreatedAt)
if err != nil {
return nil, fmt.Errorf("Error parsing time:", err)
}
newReq.CreatedAt = parsedTime
}
// Dynamic Approval Workflow System
statusIdOne := 1
statusIdTwo := 2
isPublishFalse := false
// Get user info for approval logic
createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
// Check if user level requires approval
if createdBy != nil && *createdBy.UserLevel.IsApprovalActive == false {
_i.Log.Info().
Uint("userId", createdBy.ID).
Uint("userLevelId", createdBy.UserLevel.ID).
Str("userLevelName", createdBy.UserLevel.Name).
Bool("isApprovalActive", *createdBy.UserLevel.IsApprovalActive).
Msg("User level does not require approval - auto publishing")
// User level doesn't require approval - auto publish
newReq.NeedApprovalFrom = nil
newReq.StatusId = &statusIdTwo
newReq.IsPublish = &isPublishFalse
newReq.PublishedAt = nil
newReq.BypassApproval = &[]bool{true}[0]
} else {
_i.Log.Info().
Uint("userId", createdBy.ID).
Uint("userLevelId", createdBy.UserLevel.ID).
Str("userLevelName", createdBy.UserLevel.Name).
Bool("isApprovalActive", func() bool {
if createdBy != nil && createdBy.UserLevel.IsApprovalActive != nil {
return *createdBy.UserLevel.IsApprovalActive
}
return false
}()).
Msg("User level requires approval - setting to pending")
// User level requires approval - set to pending
newReq.NeedApprovalFrom = &approvalLevelId
newReq.StatusId = &statusIdOne
newReq.IsPublish = &isPublishFalse
newReq.PublishedAt = nil
newReq.BypassApproval = &[]bool{false}[0]
}
saveArticleRes, err := _i.Repo.Create(clientId, newReq)
if err != nil {
return nil, err
}
// Dynamic Approval Workflow Assignment with Multi-Branch Support
if createdBy != nil && *createdBy.UserLevel.IsApprovalActive == true {
_i.Log.Info().
Uint("userId", createdBy.ID).
Uint("userLevelId", createdBy.UserLevel.ID).
Str("userLevelName", createdBy.UserLevel.Name).
Bool("isApprovalActive", *createdBy.UserLevel.IsApprovalActive).
Msg("User level requires approval - proceeding with workflow assignment")
// Get default workflow for the client
defaultWorkflow, err := _i.ApprovalWorkflowsRepo.GetDefault(clientId)
if err != nil {
_i.Log.Error().Err(err).Msg("Failed to get default workflow")
} else if defaultWorkflow == nil {
_i.Log.Warn().Msg("No default workflow found for client")
} else {
_i.Log.Info().
Uint("workflowId", defaultWorkflow.ID).
Str("workflowName", defaultWorkflow.Name).
Bool("isActive", *defaultWorkflow.IsActive).
Bool("isDefault", *defaultWorkflow.IsDefault).
Msg("Found default workflow")
// Assign workflow to article
saveArticleRes.WorkflowId = &defaultWorkflow.ID
saveArticleRes.CurrentApprovalStep = &[]int{1}[0] // Start at step 1
// Update article with workflow info
err = _i.Repo.Update(clientId, saveArticleRes.ID, saveArticleRes)
if err != nil {
_i.Log.Error().Err(err).Msg("Failed to update article with workflow")
} else {
_i.Log.Info().
Uint("articleId", saveArticleRes.ID).
Uint("workflowId", defaultWorkflow.ID).
Msg("Article updated with workflow successfully")
}
// Create approval flow with multi-branch support
approvalFlow := &entity.ArticleApprovalFlows{
ArticleId: saveArticleRes.ID,
WorkflowId: defaultWorkflow.ID,
CurrentStep: 1,
StatusId: 1, // In Progress
SubmittedById: *newReq.CreatedById,
SubmittedAt: time.Now(),
ClientId: clientId,
// Multi-branch fields will be set by the service
CurrentBranch: nil, // Will be determined by first applicable step
BranchPath: nil, // Will be populated as flow progresses
IsParallelFlow: &[]bool{false}[0], // Default to sequential
}
createdFlow, err := _i.ArticleApprovalFlowsRepo.Create(clientId, approvalFlow)
if err != nil {
_i.Log.Error().Err(err).Msg("Failed to create approval flow")
} else {
_i.Log.Info().
Uint("flowId", createdFlow.ID).
Uint("articleId", saveArticleRes.ID).
Uint("workflowId", defaultWorkflow.ID).
Msg("Approval flow created successfully")
// Initialize the multi-branch flow by determining the first applicable step
err = _i.initializeMultiBranchFlow(authToken, createdFlow.ID, createdBy.UserLevel.ID)
if err != nil {
_i.Log.Error().Err(err).Msg("Failed to initialize multi-branch flow")
} else {
_i.Log.Info().
Uint("flowId", createdFlow.ID).
Uint("userLevelId", createdBy.UserLevel.ID).
Msg("Multi-branch flow initialized successfully")
}
}
}
// Create legacy approval record for backward compatibility
articleApproval := &entity.ArticleApprovals{
ArticleId: saveArticleRes.ID,
ApprovalBy: *newReq.CreatedById,
StatusId: statusIdOne,
Message: "Need Approval",
ApprovalAtLevel: &approvalLevelId,
}
_, err = _i.ArticleApprovalsRepo.Create(articleApproval)
if err != nil {
_i.Log.Error().Err(err).Msg("Failed to create legacy approval record")
}
} else {
// Auto-publish for users who don't require approval
articleApproval := &entity.ArticleApprovals{
ArticleId: saveArticleRes.ID,
ApprovalBy: *newReq.CreatedById,
StatusId: statusIdTwo,
Message: "Publish Otomatis",
ApprovalAtLevel: nil,
}
_, err = _i.ArticleApprovalsRepo.Create(articleApproval)
if err != nil {
_i.Log.Error().Err(err).Msg("Failed to create auto-approval record")
}
}
var categoryIds []string
if req.CategoryIds != "" {
categoryIds = strings.Split(req.CategoryIds, ",")
}
_i.Log.Info().Interface("categoryIds", categoryIds).Msg("")
for _, categoryId := range categoryIds {
categoryIdInt, _ := strconv.Atoi(categoryId)
_i.Log.Info().Interface("categoryIdUint", uint(categoryIdInt)).Msg("")
findCategory, err := _i.ArticleCategoriesRepo.FindOne(clientId, uint(categoryIdInt))
_i.Log.Info().Interface("findCategory", findCategory).Msg("")
if err != nil {
return nil, err
}
if findCategory == nil {
return nil, errors.New("category not found")
}
categoryReq := articleCategoryDetailsReq.ArticleCategoryDetailsCreateRequest{
ArticleId: saveArticleRes.ID,
CategoryId: categoryIdInt,
IsActive: true,
}
newCategoryReq := categoryReq.ToEntity()
err = _i.ArticleCategoryDetailsRepo.Create(newCategoryReq)
if err != nil {
return nil, err
}
}
return saveArticleRes, nil
}
func (_i *articlesService) SaveThumbnail(authToken string, c *fiber.Ctx) (err error) {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
if err != nil {
return err
}
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:articlesService", "Methods:SaveThumbnail").
Interface("id", id).Msg("")
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
form, err := c.MultipartForm()
if err != nil {
return err
}
files := form.File["files"]
// Create minio connection.
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
// Return status 500 and minio connection error.
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": true,
"msg": err.Error(),
})
}
// Iterasi semua file yang diunggah
for _, file := range files {
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop1").
Interface("data", file).Msg("")
src, err := file.Open()
if err != nil {
return err
}
defer src.Close()
filename := filepath.Base(file.Filename)
filename = strings.ReplaceAll(filename, " ", "")
filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))])
extension := filepath.Ext(file.Filename)[1:]
now := time.Now()
rand.New(rand.NewSource(now.UnixNano()))
randUniqueId := rand.Intn(1000000)
newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId)
newFilename := newFilenameWithoutExt + "." + extension
objectName := fmt.Sprintf("articles/thumbnail/%d/%d/%s", now.Year(), now.Month(), newFilename)
findCategory, err := _i.Repo.FindOne(clientId, uint(id))
findCategory.ThumbnailName = &newFilename
findCategory.ThumbnailPath = &objectName
err = _i.Repo.Update(clientId, uint(id), findCategory)
if err != nil {
return err
}
// Upload file ke MinIO
_, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, file.Size, minio.PutObjectOptions{})
if err != nil {
return err
}
}
return
}
func (_i *articlesService) Update(authToken string, id uint, req request.ArticlesUpdateRequest) (err error) {
_i.Log.Info().Interface("data", req).Msg("")
newReq := req.ToEntity()
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
if req.CreatedAt != nil {
layout := "2006-01-02 15:04:05"
parsedTime, err := time.Parse(layout, *req.CreatedAt)
if err != nil {
return fmt.Errorf("Error parsing time:", err)
}
newReq.CreatedAt = parsedTime
}
return _i.Repo.UpdateSkipNull(clientId, id, newReq)
}
func (_i *articlesService) Delete(authToken string, id uint) error {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
return _i.Repo.Delete(clientId, id)
}
func (_i *articlesService) Viewer(authToken string, c *fiber.Ctx) (err error) {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
thumbnailName := c.Params("thumbnailName")
emptyImage := "empty-image.jpg"
searchThumbnail := emptyImage
if thumbnailName != emptyImage {
result, err := _i.Repo.FindByFilename(clientId, thumbnailName)
if err != nil {
return err
}
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "articlesService:Viewer").
Interface("resultThumbnail", result.ThumbnailPath).Msg("")
if result.ThumbnailPath != nil {
searchThumbnail = *result.ThumbnailPath
} else {
searchThumbnail = "articles/thumbnail/" + emptyImage
}
} else {
searchThumbnail = "articles/thumbnail/" + emptyImage
}
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "articlesService:Viewer").
Interface("searchThumbnail", searchThumbnail).Msg("")
ctx := context.Background()
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
objectName := searchThumbnail
// Create minio connection.
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
// Return status 500 and minio connection error.
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": true,
"msg": err.Error(),
})
}
fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
if err != nil {
log.Fatalln(err)
}
defer fileContent.Close()
contentType := mime.TypeByExtension("." + getFileExtension(objectName))
if contentType == "" {
contentType = "application/octet-stream"
}
c.Set("Content-Type", contentType)
if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil {
return err
}
return
}
func (_i *articlesService) UpdateActivityCount(authToken string, id uint, activityTypeId int) error {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
result, err := _i.Repo.FindOne(clientId, id)
if err != nil {
return err
}
viewCount := 0
if result.ViewCount != nil {
viewCount = *result.ViewCount
}
shareCount := 0
if result.ShareCount != nil {
shareCount = *result.ShareCount
}
commentCount := 0
if result.CommentCount != nil {
commentCount = *result.CommentCount
}
if activityTypeId == 2 {
viewCount++
} else if activityTypeId == 3 {
shareCount++
} else if activityTypeId == 4 {
commentCount++
}
result.ViewCount = &viewCount
result.ShareCount = &shareCount
result.CommentCount = &commentCount
return _i.Repo.Update(clientId, id, result)
}
func (_i *articlesService) SummaryStats(authToken string) (summaryStats *response.ArticleSummaryStats, err error) {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
result, err := _i.Repo.SummaryStats(clientId, user.ID)
if err != nil {
return nil, err
}
return result, nil
}
func (_i *articlesService) ArticlePerUserLevelStats(authToken string, startDate *string, endDate *string) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error) {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats").
Interface("startDate", startDate).Msg("")
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats").
Interface("endDate", endDate).Msg("")
var userLevelId *uint
var userLevelNumber *int
if user != nil {
userLevelId = &user.UserLevelId
userLevelNumber = &user.UserLevel.LevelNumber
}
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats").
Interface("userLevelId", userLevelId).Msg("")
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats").
Interface("userLevelNumber", userLevelNumber).Msg("")
result, err := _i.Repo.ArticlePerUserLevelStats(clientId, userLevelId, userLevelNumber, nil, nil)
if err != nil {
return nil, err
}
return result, nil
}
func (_i *articlesService) ArticleMonthlyStats(authToken string, year *int) (articleMonthlyStats []*response.ArticleMonthlyStats, err error) {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
var userLevelId *uint
var userLevelNumber *int
if user != nil {
userLevelId = &user.UserLevelId
userLevelNumber = &user.UserLevel.LevelNumber
}
result, err := _i.Repo.ArticleMonthlyStats(clientId, userLevelId, userLevelNumber, *year)
if err != nil {
return nil, err
}
return result, nil
}
func (_i *articlesService) UpdateApproval(authToken string, id uint, statusId int, userLevelId int, userLevelNumber int, userParentLevelId int) (err error) {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
result, err := _i.Repo.FindOne(clientId, id)
if err != nil {
return err
}
_i.Log.Info().Interface("statusId", statusId).Msg("")
statusIdOne := 1
statusIdTwo := 2
statusIdThree := 3
isPublish := true
isDraftFalse := false
if statusId == 2 {
if userLevelNumber == 2 || userLevelNumber == 3 {
result.NeedApprovalFrom = &userParentLevelId
result.StatusId = &statusIdOne
} else {
result.NeedApprovalFrom = nil
result.StatusId = &statusIdTwo
result.IsPublish = &isPublish
publishedAt := time.Now()
result.PublishedAt = &publishedAt
result.IsDraft = &isDraftFalse
result.DraftedAt = nil
}
userLevelIdStr := strconv.Itoa(userLevelId)
if result.HasApprovedBy == nil {
result.HasApprovedBy = &userLevelIdStr
} else {
hasApprovedBySlice := strings.Split(*result.HasApprovedBy, ",")
hasApprovedBySlice = append(hasApprovedBySlice, userLevelIdStr)
hasApprovedByJoin := strings.Join(hasApprovedBySlice, ",")
result.HasApprovedBy = &hasApprovedByJoin
}
} else if statusId == 3 {
result.StatusId = &statusIdThree
result.NeedApprovalFrom = nil
result.HasApprovedBy = nil
}
err = _i.Repo.Update(clientId, id, result)
if err != nil {
return err
}
return
}
func (_i *articlesService) PublishScheduling(authToken string, id uint, publishSchedule string) error {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
result, err := _i.Repo.FindOne(clientId, id)
if err != nil {
return err
}
result.PublishSchedule = &publishSchedule
return _i.Repo.Update(clientId, id, result)
}
func (_i *articlesService) UpdateBanner(authToken string, id uint, isBanner bool) error {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
result, err := _i.Repo.FindOne(clientId, id)
if err != nil {
return err
}
result.IsBanner = &isBanner
return _i.Repo.Update(clientId, id, result)
}
func (_i *articlesService) ExecuteScheduling() error {
// For background jobs, we don't have context, so pass nil for clientId
articles, err := _i.Repo.GetAllPublishSchedule(nil)
if err != nil {
return err
}
layout := "2006-01-02"
now := time.Now()
today := now.Truncate(24 * time.Hour)
for _, article := range articles { // Looping setiap artikel
if article.PublishSchedule == nil {
continue
}
parsedDate, err := time.Parse(layout, *article.PublishSchedule)
if err != nil {
continue
}
if parsedDate.Equal(today) {
isPublish := true
statusIdTwo := 2
article.PublishSchedule = nil
article.IsPublish = &isPublish
article.PublishedAt = &now
article.StatusId = &statusIdTwo
// For background jobs, we don't have context, so pass nil for clientId
if err := _i.Repo.UpdateSkipNull(nil, article.ID, article); err != nil {
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:articlesService", "Methods:ExecuteScheduling").
Interface("Failed to publish Article ID : ", article.ID).Msg("")
}
}
}
return err
}
func getFileExtension(filename string) string {
// split file name
parts := strings.Split(filename, ".")
// jika tidak ada ekstensi, kembalikan string kosong
if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") {
return ""
}
// ambil ekstensi terakhir
return parts[len(parts)-1]
}
// SubmitForApproval submits an article for approval using the dynamic workflow system
func (_i *articlesService) SubmitForApproval(authToken string, articleId uint, workflowId *uint) error {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
// Extract user info from auth token
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user == nil {
return errors.New("user not found from auth token")
}
// Check if article exists
article, err := _i.Repo.FindOne(clientId, articleId)
if err != nil {
return err
}
// If no workflow specified, get the default workflow
if workflowId == nil {
defaultWorkflow, err := _i.ApprovalWorkflowsRepo.GetDefault(clientId)
if err != nil {
return err
}
workflowId = &defaultWorkflow.ID
}
// Validate workflow exists and is active
_, err = _i.ApprovalWorkflowsRepo.FindOne(clientId, *workflowId)
if err != nil {
return err
}
// Create approval flow
approvalFlow := &entity.ArticleApprovalFlows{
ArticleId: articleId,
WorkflowId: *workflowId,
CurrentStep: 1,
StatusId: 1, // 1 = In Progress
ClientId: clientId,
SubmittedById: user.ID,
}
_, err = _i.ArticleApprovalFlowsRepo.Create(clientId, approvalFlow)
if err != nil {
return err
}
// Update article status to pending approval
statusId := 1 // Pending approval
article.StatusId = &statusId
article.WorkflowId = workflowId
err = _i.Repo.Update(clientId, articleId, article)
if err != nil {
return err
}
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:articlesService", "Methods:SubmitForApproval").
Interface("Article submitted for approval", articleId).Msg("")
return nil
}
// GetApprovalStatus gets the current approval status of an article
func (_i *articlesService) GetApprovalStatus(authToken string, articleId uint) (*response.ArticleApprovalStatusResponse, error) {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
// Check if article exists
_, err := _i.Repo.FindOne(clientId, articleId)
if err != nil {
return nil, err
}
// Get approval flow
approvalFlow, err := _i.ArticleApprovalFlowsRepo.FindByArticleId(clientId, articleId)
if err != nil {
// Article might not be in approval process
return &response.ArticleApprovalStatusResponse{
ArticleId: articleId,
Status: "not_submitted",
CurrentStep: 0,
TotalSteps: 0,
Progress: 0,
}, nil
}
// Get workflow details
workflow, err := _i.ApprovalWorkflowsRepo.FindOne(clientId, approvalFlow.WorkflowId)
if err != nil {
return nil, err
}
// Get workflow steps
workflowSteps, err := _i.ApprovalWorkflowsRepo.GetWorkflowSteps(clientId, approvalFlow.WorkflowId)
if err != nil {
return nil, err
}
totalSteps := len(workflowSteps)
progress := 0.0
if totalSteps > 0 {
progress = float64(approvalFlow.CurrentStep-1) / float64(totalSteps) * 100
}
// Determine status
status := "in_progress"
if approvalFlow.StatusId == 2 {
status = "approved"
} else if approvalFlow.StatusId == 3 {
status = "rejected"
} else if approvalFlow.StatusId == 4 {
status = "revision_requested"
}
// Get current approver info
var currentApprover *string
var nextStep *string
if approvalFlow.CurrentStep <= totalSteps && approvalFlow.StatusId == 1 {
if approvalFlow.CurrentStep < totalSteps {
// Array indexing starts from 0, so subtract 1 from CurrentStep
nextStepIndex := approvalFlow.CurrentStep - 1
if nextStepIndex >= 0 && nextStepIndex < len(workflowSteps) {
nextStepInfo := workflowSteps[nextStepIndex]
nextStep = &nextStepInfo.RequiredUserLevel.Name
}
}
}
return &response.ArticleApprovalStatusResponse{
ArticleId: articleId,
WorkflowId: &workflow.ID,
WorkflowName: &workflow.Name,
CurrentStep: approvalFlow.CurrentStep,
TotalSteps: totalSteps,
Status: status,
CurrentApprover: currentApprover,
SubmittedAt: &approvalFlow.CreatedAt,
LastActionAt: &approvalFlow.UpdatedAt,
Progress: progress,
CanApprove: false, // TODO: Implement based on user permissions
NextStep: nextStep,
}, nil
}
// GetPendingApprovals gets articles pending approval for a specific user level
func (_i *articlesService) GetPendingApprovals(authToken string, page, limit int, typeId *int) ([]*response.ArticleApprovalQueueResponse, paginator.Pagination, error) {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
// Extract user info from auth token
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user == nil {
return nil, paginator.Pagination{}, errors.New("user not found from auth token")
}
// Prepare filters
filters := make(map[string]interface{})
if typeId != nil {
filters["type_id"] = *typeId
}
// Get pending approvals for the user level
approvalFlows, paging, err := _i.ArticleApprovalFlowsRepo.GetPendingApprovals(clientId, user.UserLevelId, page, limit, filters)
if err != nil {
return nil, paging, err
}
var responses []*response.ArticleApprovalQueueResponse
for _, flow := range approvalFlows {
// Get article details
article, err := _i.Repo.FindOne(clientId, flow.ArticleId)
if err != nil {
continue
}
// Get workflow details
workflow, err := _i.ApprovalWorkflowsRepo.FindOne(clientId, flow.WorkflowId)
if err != nil {
continue
}
// Get workflow steps
workflowSteps, err := _i.ApprovalWorkflowsRepo.GetWorkflowSteps(clientId, flow.WorkflowId)
if err != nil {
continue
}
// Calculate days in queue
daysInQueue := int(time.Since(flow.CreatedAt).Hours() / 24)
// Determine priority based on days in queue
priority := "low"
if daysInQueue > 7 {
priority = "urgent"
} else if daysInQueue > 3 {
priority = "high"
} else if daysInQueue > 1 {
priority = "medium"
}
// Get author name
var authorName string
if article.CreatedById != nil {
user, err := _i.UsersRepo.FindOne(clientId, *article.CreatedById)
if err == nil && user != nil {
authorName = user.Fullname
}
}
// Get category name
var categoryName string
if article.CategoryId != 0 {
category, err := _i.ArticleCategoriesRepo.FindOne(clientId, uint(article.CategoryId))
if err == nil && category != nil {
categoryName = category.Title
}
}
response := &response.ArticleApprovalQueueResponse{
ID: article.ID,
Title: article.Title,
Slug: article.Slug,
Description: article.Description,
CategoryName: categoryName,
AuthorName: authorName,
SubmittedAt: flow.CreatedAt,
CurrentStep: flow.CurrentStep,
TotalSteps: len(workflowSteps),
Priority: priority,
DaysInQueue: daysInQueue,
WorkflowName: workflow.Name,
CanApprove: true, // TODO: Implement based on user permissions
EstimatedTime: "2-3 days", // TODO: Calculate based on historical data
}
responses = append(responses, response)
}
return responses, paging, nil
}
// GetArticlesWaitingForApproval gets articles that are waiting for approval by a specific user level
func (_i *articlesService) GetArticlesWaitingForApproval(authToken string, page, limit int) ([]*response.ArticleApprovalQueueResponse, paginator.Pagination, error) {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
// Extract user info from auth token
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user == nil {
return nil, paginator.Pagination{}, errors.New("user not found from auth token")
}
// Use the existing repository method with proper filtering
pagination := paginator.Pagination{
Page: page,
Limit: limit,
}
req := request.ArticlesQueryRequest{
Pagination: &pagination,
}
articles, paging, err := _i.Repo.GetAll(clientId, &user.UserLevelId, &user.ID, req)
if err != nil {
return nil, paging, err
}
// Build response
var responses []*response.ArticleApprovalQueueResponse
for _, article := range articles {
response := &response.ArticleApprovalQueueResponse{
ID: article.ID,
Title: article.Title,
Slug: article.Slug,
Description: article.Description,
SubmittedAt: article.CreatedAt,
CurrentStep: 1, // Will be updated with actual step
CanApprove: true,
}
responses = append(responses, response)
}
return responses, paging, nil
}
// CheckApprovalRequired checks if an article requires approval based on client settings
func (_i *articlesService) CheckApprovalRequired(authToken string, articleId uint, userId uint, userLevelId uint) (bool, error) {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
// Get article to check category and other properties
article, err := _i.Repo.FindOne(clientId, articleId)
if err != nil {
return true, err // Default to requiring approval on error
}
// Check if article is already exempt
if article.ApprovalExempt != nil && *article.ApprovalExempt {
return false, nil
}
// Check if article should bypass approval
if article.BypassApproval != nil && *article.BypassApproval {
return false, nil
}
// Check client-level settings (this would require the client approval settings service)
// For now, we'll use a simple check
// TODO: Integrate with ClientApprovalSettingsService
// Check if workflow is set to no approval
if article.WorkflowId != nil {
workflow, err := _i.ApprovalWorkflowsRepo.FindOne(clientId, *article.WorkflowId)
if err == nil && workflow != nil {
if workflow.RequiresApproval != nil && !*workflow.RequiresApproval {
return false, nil
}
if workflow.AutoPublish != nil && *workflow.AutoPublish {
return false, nil
}
}
}
// Default to requiring approval
return true, nil
}
// AutoApproveArticle automatically approves an article (for no-approval scenarios)
func (_i *articlesService) AutoApproveArticle(authToken string, articleId uint, reason string) error {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
article, err := _i.Repo.FindOne(clientId, articleId)
if err != nil {
return err
}
// Update article status to approved
updates := map[string]interface{}{
"status_id": 2, // Assuming 2 = approved
"is_publish": true,
"published_at": time.Now(),
"current_approval_step": 0, // Reset approval step
}
// Convert updates map to article entity
articleUpdate := &entity.Articles{}
if isPublish, ok := updates["is_publish"].(bool); ok {
articleUpdate.IsPublish = &isPublish
}
if publishedAt, ok := updates["published_at"].(time.Time); ok {
articleUpdate.PublishedAt = &publishedAt
}
if currentApprovalStep, ok := updates["current_approval_step"].(int); ok {
articleUpdate.CurrentApprovalStep = &currentApprovalStep
}
err = _i.Repo.Update(clientId, articleId, articleUpdate)
if err != nil {
return err
}
// Create approval flow record for audit trail
approvalFlow := &entity.ArticleApprovalFlows{
ArticleId: articleId,
WorkflowId: *article.WorkflowId,
CurrentStep: 0,
StatusId: 2, // approved
SubmittedById: *article.CreatedById,
SubmittedAt: time.Now(),
CompletedAt: &[]time.Time{time.Now()}[0],
ClientId: clientId,
}
_, err = _i.ArticleApprovalFlowsRepo.Create(clientId, approvalFlow)
if err != nil {
_i.Log.Error().Err(err).Msg("Failed to create approval flow for auto-approved article")
// Don't return error as article was already updated
}
_i.Log.Info().
Str("article_id", fmt.Sprintf("%d", articleId)).
Str("client_id", clientId.String()).
Str("reason", reason).
Msg("Article auto-approved")
return nil
}
// GetClientApprovalSettings gets the approval settings for a client
func (_i *articlesService) GetClientApprovalSettings(authToken string) (*response.ClientApprovalSettingsResponse, error) {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
// This would require the ClientApprovalSettingsService
// For now, return default settings
return &response.ClientApprovalSettingsResponse{
ClientId: clientId.String(),
RequiresApproval: true, // Default to requiring approval
AutoPublishArticles: false,
IsActive: true,
}, nil
}
// SetArticleApprovalExempt sets whether an article is exempt from approval
func (_i *articlesService) SetArticleApprovalExempt(authToken string, articleId uint, exempt bool, reason string) error {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
updates := map[string]interface{}{
"approval_exempt": &exempt,
}
if exempt {
// If exempt, also set bypass approval
bypass := true
updates["bypass_approval"] = &bypass
updates["current_approval_step"] = 0
}
// Convert updates map to article entity
articleUpdate := &entity.Articles{}
if approvalExempt, ok := updates["approval_exempt"].(*bool); ok {
articleUpdate.ApprovalExempt = approvalExempt
}
if bypassApproval, ok := updates["bypass_approval"].(*bool); ok {
articleUpdate.BypassApproval = bypassApproval
}
if currentApprovalStep, ok := updates["current_approval_step"].(int); ok {
articleUpdate.CurrentApprovalStep = &currentApprovalStep
}
err := _i.Repo.Update(clientId, articleId, articleUpdate)
if err != nil {
return err
}
_i.Log.Info().
Str("article_id", fmt.Sprintf("%d", articleId)).
Str("client_id", clientId.String()).
Bool("exempt", exempt).
Str("reason", reason).
Msg("Article approval exemption updated")
return nil
}
// findNextApprovalLevel finds the next higher level for approval
func (_i *articlesService) findNextApprovalLevel(clientId *uuid.UUID, currentLevelNumber int) int {
// For now, we'll use a simple logic based on level numbers
// Level 3 (POLRES) -> Level 2 (POLDAS) -> Level 1 (POLDAS)
switch currentLevelNumber {
case 3: // POLRES
return 2 // Should be approved by POLDAS (Level 2)
case 2: // POLDAS
return 1 // Should be approved by Level 1
case 1: // Highest level
return 0 // No approval needed, can publish directly
default:
_i.Log.Warn().Int("currentLevel", currentLevelNumber).Msg("Unknown level, no approval needed")
return 0
}
}
// initializeMultiBranchFlow initializes the multi-branch flow by determining the first applicable step
func (_i *articlesService) initializeMultiBranchFlow(authToken string, flowId uint, submitterLevelId uint) error {
// Get the approval flow
flow, err := _i.ArticleApprovalFlowsRepo.FindOne(nil, flowId)
if err != nil {
return fmt.Errorf("failed to find approval flow: %w", err)
}
// Find the first applicable step based on submitter's user level
nextSteps, err := _i.ArticleApprovalFlowsSvc.FindNextStepsForBranch(authToken, flow.WorkflowId, 0, submitterLevelId)
if err != nil {
return fmt.Errorf("failed to find next steps: %w", err)
}
if len(nextSteps) == 0 {
// No applicable steps found - this shouldn't happen with proper workflow configuration
_i.Log.Warn().Uint("flowId", flowId).Uint("submitterLevelId", submitterLevelId).Msg("No applicable steps found for multi-branch flow")
return nil
}
// Update the flow with the first applicable step and branch information
firstStep := nextSteps[0]
flow.CurrentStep = firstStep.StepOrder
// Set branch information if available
if firstStep.BranchName != nil {
flow.CurrentBranch = firstStep.BranchName
// Initialize branch path
branchPath := []string{*firstStep.BranchName}
branchPathJSON, err := json.Marshal(branchPath)
if err == nil {
branchPathStr := string(branchPathJSON)
flow.BranchPath = &branchPathStr
}
}
// Update the flow
err = _i.ArticleApprovalFlowsRepo.Update(flowId, flow)
if err != nil {
return fmt.Errorf("failed to update approval flow: %w", err)
}
_i.Log.Info().
Uint("flowId", flowId).
Uint("submitterLevelId", submitterLevelId).
Int("currentStep", flow.CurrentStep).
Str("currentBranch", func() string {
if flow.CurrentBranch != nil {
return *flow.CurrentBranch
}
return "none"
}()).
Msg("Multi-branch flow initialized successfully")
return nil
}