2025-09-28 01:53:09 +00:00
package service
import (
"context"
"errors"
"fmt"
"io"
"log"
"math/rand"
"mime"
2025-09-30 13:34:56 +00:00
"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"
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"
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"
2025-09-28 01:53:09 +00:00
"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
MinioStorage * minioStorage . MinioStorage
// Dynamic approval system dependencies
ArticleApprovalFlowsRepo articleApprovalFlowsRepository . ArticleApprovalFlowsRepository
ApprovalWorkflowsRepo approvalWorkflowsRepository . ApprovalWorkflowsRepository
}
// ArticlesService define interface of IArticlesService
type ArticlesService interface {
2025-10-01 03:10:18 +00:00
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
2025-09-28 01:53:09 +00:00
ExecuteScheduling ( ) error
// Dynamic approval system methods
2025-10-01 03:10:18 +00:00
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
2025-09-28 01:53:09 +00:00
// No-approval system methods
2025-10-01 03:10:18 +00:00
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
2025-09-28 01:53:09 +00:00
}
// 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 ,
log zerolog . Logger ,
cfg * config . Config ,
usersRepo usersRepository . UsersRepository ,
minioStorage * minioStorage . MinioStorage ,
) ArticlesService {
return & articlesService {
Repo : repo ,
ArticleCategoriesRepo : articleCategoriesRepo ,
ArticleCategoryDetailsRepo : articleCategoryDetailsRepo ,
ArticleFilesRepo : articleFilesRepo ,
ArticleApprovalsRepo : articleApprovalsRepo ,
ArticleApprovalFlowsRepo : articleApprovalFlowsRepo ,
ApprovalWorkflowsRepo : approvalWorkflowsRepo ,
Log : log ,
UsersRepo : usersRepo ,
MinioStorage : minioStorage ,
Cfg : cfg ,
}
}
// All implement interface of ArticlesService
2025-10-01 03:10:18 +00:00
func ( _i * articlesService ) All ( authToken string , req request . ArticlesQueryRequest ) ( articless [ ] * response . ArticlesResponse , paging paginator . Pagination , err error ) {
// Extract clientId and userLevelId from authToken
var clientId * uuid . UUID
2025-09-28 01:53:09 +00:00
var userLevelId * uint
if authToken != "" {
user := utilSvc . GetUserInfo ( _i . Log , _i . UsersRepo , authToken )
if user != nil {
2025-10-01 03:10:18 +00:00
if user . ClientId != nil {
clientId = user . ClientId
_i . Log . Info ( ) . Interface ( "clientId" , clientId ) . Msg ( "Extracted clientId from auth token" )
}
2025-09-28 01:53:09 +00:00
userLevelId = & user . UserLevelId
_i . Log . Info ( ) . Interface ( "userLevelId" , userLevelId ) . Msg ( "Extracted userLevelId 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 , 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 )
articless = append ( articless , articleRes )
}
return
}
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
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 ) , nil
}
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
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 ) , nil
}
2025-10-01 03:10:18 +00:00
func ( _i * articlesService ) Save ( authToken string , req request . ArticlesCreateRequest ) ( articles * entity . Articles , err error ) {
2025-09-28 01:53:09 +00:00
_i . Log . Info ( ) . Interface ( "data" , req ) . Msg ( "" )
newReq := req . ToEntity ( )
2025-10-01 03:10:18 +00:00
// 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" )
}
}
2025-09-28 01:53:09 +00:00
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 {
// 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 {
// 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
if createdBy != nil && * createdBy . UserLevel . IsApprovalActive == true {
// Get default workflow for the client
defaultWorkflow , err := _i . ApprovalWorkflowsRepo . GetDefault ( clientId )
if err == nil && defaultWorkflow != nil {
// 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" )
}
// Create approval flow
approvalFlow := & entity . ArticleApprovalFlows {
ArticleId : saveArticleRes . ID ,
WorkflowId : defaultWorkflow . ID ,
CurrentStep : 1 ,
StatusId : 1 , // In Progress
SubmittedById : * newReq . CreatedById ,
SubmittedAt : time . Now ( ) ,
ClientId : clientId ,
}
_ , err = _i . ArticleApprovalFlowsRepo . Create ( clientId , approvalFlow )
if err != nil {
_i . Log . Error ( ) . Err ( err ) . Msg ( "Failed to create approval flow" )
}
}
// 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
}
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
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
}
2025-10-01 03:10:18 +00:00
func ( _i * articlesService ) Update ( authToken string , id uint , req request . ArticlesUpdateRequest ) ( err error ) {
2025-09-28 01:53:09 +00:00
_i . Log . Info ( ) . Interface ( "data" , req ) . Msg ( "" )
newReq := req . ToEntity ( )
2025-10-01 03:10:18 +00:00
// 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" )
}
}
2025-09-28 01:53:09 +00:00
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 )
}
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
return _i . Repo . Delete ( clientId , id )
}
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
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
}
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
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 )
}
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
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
}
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
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
}
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
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
}
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
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
}
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
result , err := _i . Repo . FindOne ( clientId , id )
if err != nil {
return err
}
result . PublishSchedule = & publishSchedule
return _i . Repo . Update ( clientId , id , result )
}
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
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
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
// 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
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
// 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
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
// 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
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
// 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 , 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
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
// 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)
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
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
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
// 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
2025-10-01 03:10:18 +00:00
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" )
}
}
2025-09-28 01:53:09 +00:00
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
}
}