feat : update major approval workflow process
This commit is contained in:
parent
8d0e6d81c0
commit
95cc9df933
|
|
@ -1,8 +1,9 @@
|
||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ActivityLogs struct {
|
type ActivityLogs struct {
|
||||||
|
|
@ -14,4 +15,7 @@ type ActivityLogs struct {
|
||||||
UserId *uint `json:"user_id" gorm:"type:int4"`
|
UserId *uint `json:"user_id" gorm:"type:int4"`
|
||||||
ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
Article *Articles `json:"article" gorm:"foreignKey:ArticleId;constraint:OnDelete:CASCADE"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ArticleApprovals struct {
|
type ArticleApprovals struct {
|
||||||
|
|
@ -14,4 +15,7 @@ type ArticleApprovals struct {
|
||||||
ApprovalAtLevel *int `json:"approval_at_level" gorm:"type:int4"`
|
ApprovalAtLevel *int `json:"approval_at_level" gorm:"type:int4"`
|
||||||
ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
Article Articles `json:"article" gorm:"foreignKey:ArticleId;constraint:OnDelete:CASCADE"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
package article_category_details
|
package article_category_details
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
|
||||||
"time"
|
"time"
|
||||||
entity "web-medols-be/app/database/entity"
|
entity "web-medols-be/app/database/entity"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ArticleCategoryDetails struct {
|
type ArticleCategoryDetails struct {
|
||||||
|
|
@ -15,4 +16,7 @@ type ArticleCategoryDetails struct {
|
||||||
IsActive bool `json:"is_active" gorm:"type:bool"`
|
IsActive bool `json:"is_active" gorm:"type:bool"`
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
Article entity.Articles `json:"article" gorm:"foreignKey:ArticleId;constraint:OnDelete:CASCADE"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ArticleComments struct {
|
type ArticleComments struct {
|
||||||
|
|
@ -18,6 +19,9 @@ type ArticleComments struct {
|
||||||
IsActive bool `json:"is_active" gorm:"type:bool"`
|
IsActive bool `json:"is_active" gorm:"type:bool"`
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
Article Articles `json:"article" gorm:"foreignKey:ArticleId;constraint:OnDelete:CASCADE"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// statusId => 0: waiting, 1: accepted, 2: replied, 3: rejected
|
// statusId => 0: waiting, 1: accepted, 2: replied, 3: rejected
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ArticleFiles struct {
|
type ArticleFiles struct {
|
||||||
|
|
@ -26,4 +27,7 @@ type ArticleFiles struct {
|
||||||
IsActive bool `json:"is_active" gorm:"type:bool;default:true"`
|
IsActive bool `json:"is_active" gorm:"type:bool;default:true"`
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
Article Articles `json:"article" gorm:"foreignKey:ArticleId;constraint:OnDelete:CASCADE"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,57 @@
|
||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// StringArray is a custom type for handling string arrays with JSON serialization
|
||||||
|
type StringArray []string
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface
|
||||||
|
func (s *StringArray) Scan(value interface{}) error {
|
||||||
|
if value == nil {
|
||||||
|
*s = StringArray{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case []byte:
|
||||||
|
return json.Unmarshal(v, s)
|
||||||
|
case string:
|
||||||
|
return json.Unmarshal([]byte(v), s)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface
|
||||||
|
func (s StringArray) Value() (driver.Value, error) {
|
||||||
|
if s == nil || len(s) == 0 {
|
||||||
|
return "[]", nil
|
||||||
|
}
|
||||||
|
return json.Marshal(s)
|
||||||
|
}
|
||||||
|
|
||||||
type ClientApprovalSettings struct {
|
type ClientApprovalSettings struct {
|
||||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
ClientId uuid.UUID `json:"client_id" gorm:"type:UUID;not null;uniqueIndex"`
|
ClientId uuid.UUID `json:"client_id" gorm:"type:UUID;not null;uniqueIndex"`
|
||||||
RequiresApproval *bool `json:"requires_approval" gorm:"type:bool;default:true"` // false = no approval needed
|
RequiresApproval *bool `json:"requires_approval" gorm:"type:bool;default:true"` // false = no approval needed
|
||||||
DefaultWorkflowId *uint `json:"default_workflow_id" gorm:"type:int4"` // default workflow for this client
|
DefaultWorkflowId *uint `json:"default_workflow_id" gorm:"type:int4"` // default workflow for this client
|
||||||
AutoPublishArticles *bool `json:"auto_publish_articles" gorm:"type:bool;default:false"` // auto publish after creation
|
AutoPublishArticles *bool `json:"auto_publish_articles" gorm:"type:bool;default:false"` // auto publish after creation
|
||||||
ApprovalExemptUsers []uint `json:"approval_exempt_users" gorm:"type:int4[]"` // user IDs exempt from approval
|
ApprovalExemptUsers []uint `json:"approval_exempt_users" gorm:"type:int4[]"` // user IDs exempt from approval
|
||||||
ApprovalExemptRoles []uint `json:"approval_exempt_roles" gorm:"type:int4[]"` // role IDs exempt from approval
|
ApprovalExemptRoles []uint `json:"approval_exempt_roles" gorm:"type:int4[]"` // role IDs exempt from approval
|
||||||
ApprovalExemptCategories []uint `json:"approval_exempt_categories" gorm:"type:int4[]"` // category IDs exempt from approval
|
ApprovalExemptCategories []uint `json:"approval_exempt_categories" gorm:"type:int4[]"` // category IDs exempt from approval
|
||||||
RequireApprovalFor []string `json:"require_approval_for" gorm:"type:varchar[]"` // specific content types that need approval
|
RequireApprovalFor []string `json:"require_approval_for" gorm:"type:jsonb"` // specific content types that need approval
|
||||||
SkipApprovalFor []string `json:"skip_approval_for" gorm:"type:varchar[]"` // specific content types that skip approval
|
SkipApprovalFor []string `json:"skip_approval_for" gorm:"type:jsonb"` // specific content types that skip approval
|
||||||
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
|
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
Client Clients `json:"client" gorm:"foreignKey:ClientId;constraint:OnDelete:CASCADE"`
|
Client Clients `json:"client" gorm:"foreignKey:ClientId;constraint:OnDelete:CASCADE"`
|
||||||
Workflow *ApprovalWorkflows `json:"workflow" gorm:"foreignKey:DefaultWorkflowId"`
|
Workflow *ApprovalWorkflows `json:"workflow" gorm:"foreignKey:DefaultWorkflowId"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"web-medols-be/app/database/entity"
|
||||||
|
"web-medols-be/app/database/entity/article_category_details"
|
||||||
|
"web-medols-be/config/config"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
||||||
"web-medols-be/app/database/entity"
|
|
||||||
"web-medols-be/app/database/entity/article_category_details"
|
|
||||||
"web-medols-be/config/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Database setup database with gorm
|
// Database setup database with gorm
|
||||||
|
|
@ -99,6 +100,7 @@ func Models() []interface{} {
|
||||||
entity.AuditTrails{},
|
entity.AuditTrails{},
|
||||||
entity.Cities{},
|
entity.Cities{},
|
||||||
entity.Clients{},
|
entity.Clients{},
|
||||||
|
entity.ClientApprovalSettings{},
|
||||||
entity.CsrfTokenRecords{},
|
entity.CsrfTokenRecords{},
|
||||||
entity.CustomStaticPages{},
|
entity.CustomStaticPages{},
|
||||||
entity.Districts{},
|
entity.Districts{},
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gofiber/fiber/v2/middleware/csrf"
|
|
||||||
"github.com/gofiber/fiber/v2/middleware/session"
|
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
"web-medols-be/app/database"
|
"web-medols-be/app/database"
|
||||||
"web-medols-be/config/config"
|
"web-medols-be/config/config"
|
||||||
utilsSvc "web-medols-be/utils"
|
utilsSvc "web-medols-be/utils"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/csrf"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/session"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/gofiber/fiber/v2/middleware/compress"
|
"github.com/gofiber/fiber/v2/middleware/compress"
|
||||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||||
|
|
@ -129,7 +130,7 @@ func (m *Middleware) Register(db *database.Database) {
|
||||||
m.App.Use(ClientMiddleware(db.DB))
|
m.App.Use(ClientMiddleware(db.DB))
|
||||||
|
|
||||||
m.App.Use(AuditTrailsMiddleware(db.DB))
|
m.App.Use(AuditTrailsMiddleware(db.DB))
|
||||||
StartAuditTrailCleanup(db.DB, m.Cfg.Middleware.AuditTrails.Retention)
|
// StartAuditTrailCleanup(db.DB, m.Cfg.Middleware.AuditTrails.Retention)
|
||||||
|
|
||||||
//m.App.Use(filesystem.New(filesystem.Config{
|
//m.App.Use(filesystem.New(filesystem.Config{
|
||||||
// Next: utils.IsEnabled(m.Cfg.Middleware.FileSystem.Enable),
|
// Next: utils.IsEnabled(m.Cfg.Middleware.FileSystem.Enable),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"web-medols-be/app/database/entity/users"
|
||||||
|
"web-medols-be/app/module/users/repository"
|
||||||
|
utilSvc "web-medols-be/utils/service"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UserContextKey = "user"
|
||||||
|
UserLevelContextKey = "user_level_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserMiddleware extracts user information from JWT token and stores in context
|
||||||
|
func UserMiddleware(usersRepo repository.UsersRepository) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
// Skip if no Authorization header
|
||||||
|
authHeader := c.Get("Authorization")
|
||||||
|
if authHeader == "" {
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user info from token
|
||||||
|
// Create a default logger if not available in context
|
||||||
|
log := zerolog.Nop()
|
||||||
|
if logFromCtx, ok := c.Locals("log").(zerolog.Logger); ok {
|
||||||
|
log = logFromCtx
|
||||||
|
}
|
||||||
|
user := utilSvc.GetUserInfo(log, usersRepo, authHeader)
|
||||||
|
if user != nil {
|
||||||
|
// Store user in context
|
||||||
|
c.Locals(UserContextKey, user)
|
||||||
|
c.Locals(UserLevelContextKey, user.UserLevelId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUser retrieves the user from the context
|
||||||
|
func GetUser(c *fiber.Ctx) *users.Users {
|
||||||
|
if user, ok := c.Locals(UserContextKey).(*users.Users); ok {
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserLevelID retrieves the user level ID from the context
|
||||||
|
func GetUserLevelID(c *fiber.Ctx) *uint {
|
||||||
|
if userLevelId, ok := c.Locals(UserLevelContextKey).(uint); ok {
|
||||||
|
return &userLevelId
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -2,12 +2,13 @@ package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"web-medols-be/app/database"
|
"web-medols-be/app/database"
|
||||||
"web-medols-be/app/database/entity"
|
"web-medols-be/app/database/entity"
|
||||||
"web-medols-be/app/module/approval_workflow_steps/request"
|
"web-medols-be/app/module/approval_workflow_steps/request"
|
||||||
"web-medols-be/utils/paginator"
|
"web-medols-be/utils/paginator"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type approvalWorkflowStepsRepository struct {
|
type approvalWorkflowStepsRepository struct {
|
||||||
|
|
@ -79,7 +80,7 @@ func (_i *approvalWorkflowStepsRepository) GetAll(clientId *uuid.UUID, req reque
|
||||||
query = query.Where("step_name ILIKE ?", "%"+*req.StepName+"%")
|
query = query.Where("step_name ILIKE ?", "%"+*req.StepName+"%")
|
||||||
}
|
}
|
||||||
|
|
||||||
query = query.Preload("ApprovalWorkflow").Preload("RequiredUserLevel")
|
query = query.Preload("Workflow").Preload("RequiredUserLevel")
|
||||||
query = query.Order("workflow_id ASC, step_order ASC")
|
query = query.Order("workflow_id ASC, step_order ASC")
|
||||||
|
|
||||||
err = query.Count(&count).Error
|
err = query.Count(&count).Error
|
||||||
|
|
@ -120,7 +121,7 @@ func (_i *approvalWorkflowStepsRepository) FindOne(clientId *uuid.UUID, id uint)
|
||||||
query = query.Where("client_id = ?", clientId)
|
query = query.Where("client_id = ?", clientId)
|
||||||
}
|
}
|
||||||
|
|
||||||
query = query.Preload("ApprovalWorkflow").Preload("RequiredUserLevel")
|
query = query.Preload("Workflow").Preload("RequiredUserLevel")
|
||||||
|
|
||||||
err = query.First(&step, id).Error
|
err = query.First(&step, id).Error
|
||||||
return step, err
|
return step, err
|
||||||
|
|
@ -157,7 +158,7 @@ func (_i *approvalWorkflowStepsRepository) GetByWorkflowId(clientId *uuid.UUID,
|
||||||
}
|
}
|
||||||
|
|
||||||
query = query.Where("workflow_id = ?", workflowId)
|
query = query.Where("workflow_id = ?", workflowId)
|
||||||
query = query.Preload("ApprovalWorkflow").Preload("RequiredUserLevel")
|
query = query.Preload("Workflow").Preload("RequiredUserLevel")
|
||||||
query = query.Order("step_order ASC")
|
query = query.Order("step_order ASC")
|
||||||
|
|
||||||
err = query.Find(&steps).Error
|
err = query.Find(&steps).Error
|
||||||
|
|
@ -172,7 +173,7 @@ func (_i *approvalWorkflowStepsRepository) GetActiveByWorkflowId(clientId *uuid.
|
||||||
}
|
}
|
||||||
|
|
||||||
query = query.Where("workflow_id = ? AND is_active = ?", workflowId, true)
|
query = query.Where("workflow_id = ? AND is_active = ?", workflowId, true)
|
||||||
query = query.Preload("ApprovalWorkflow").Preload("RequiredUserLevel")
|
query = query.Preload("Workflow").Preload("RequiredUserLevel")
|
||||||
query = query.Order("step_order ASC")
|
query = query.Order("step_order ASC")
|
||||||
|
|
||||||
err = query.Find(&steps).Error
|
err = query.Find(&steps).Error
|
||||||
|
|
@ -187,7 +188,7 @@ func (_i *approvalWorkflowStepsRepository) FindByWorkflowAndStep(clientId *uuid.
|
||||||
}
|
}
|
||||||
|
|
||||||
query = query.Where("workflow_id = ? AND step_order = ?", workflowId, stepOrder)
|
query = query.Where("workflow_id = ? AND step_order = ?", workflowId, stepOrder)
|
||||||
query = query.Preload("ApprovalWorkflow").Preload("RequiredUserLevel")
|
query = query.Preload("Workflow").Preload("RequiredUserLevel")
|
||||||
|
|
||||||
err = query.First(&step).Error
|
err = query.First(&step).Error
|
||||||
return step, err
|
return step, err
|
||||||
|
|
@ -201,7 +202,7 @@ func (_i *approvalWorkflowStepsRepository) GetNextStep(clientId *uuid.UUID, work
|
||||||
}
|
}
|
||||||
|
|
||||||
query = query.Where("workflow_id = ? AND step_order > ? AND is_active = ?", workflowId, currentStep, true)
|
query = query.Where("workflow_id = ? AND step_order > ? AND is_active = ?", workflowId, currentStep, true)
|
||||||
query = query.Preload("ApprovalWorkflow").Preload("RequiredUserLevel")
|
query = query.Preload("Workflow").Preload("RequiredUserLevel")
|
||||||
query = query.Order("step_order ASC")
|
query = query.Order("step_order ASC")
|
||||||
|
|
||||||
err = query.First(&step).Error
|
err = query.First(&step).Error
|
||||||
|
|
@ -216,7 +217,7 @@ func (_i *approvalWorkflowStepsRepository) GetPreviousStep(clientId *uuid.UUID,
|
||||||
}
|
}
|
||||||
|
|
||||||
query = query.Where("workflow_id = ? AND step_order < ? AND is_active = ?", workflowId, currentStep, true)
|
query = query.Where("workflow_id = ? AND step_order < ? AND is_active = ?", workflowId, currentStep, true)
|
||||||
query = query.Preload("ApprovalWorkflow").Preload("RequiredUserLevel")
|
query = query.Preload("Workflow").Preload("RequiredUserLevel")
|
||||||
query = query.Order("step_order DESC")
|
query = query.Order("step_order DESC")
|
||||||
|
|
||||||
err = query.First(&step).Error
|
err = query.First(&step).Error
|
||||||
|
|
@ -279,7 +280,7 @@ func (_i *approvalWorkflowStepsRepository) GetStepsByUserLevel(clientId *uuid.UU
|
||||||
}
|
}
|
||||||
|
|
||||||
query = query.Where("required_user_level_id = ? AND is_active = ?", userLevelId, true)
|
query = query.Where("required_user_level_id = ? AND is_active = ?", userLevelId, true)
|
||||||
query = query.Preload("ApprovalWorkflow").Preload("RequiredUserLevel")
|
query = query.Preload("Workflow").Preload("RequiredUserLevel")
|
||||||
query = query.Order("workflow_id ASC, step_order ASC")
|
query = query.Order("workflow_id ASC, step_order ASC")
|
||||||
|
|
||||||
err = query.Find(&steps).Error
|
err = query.Find(&steps).Error
|
||||||
|
|
@ -369,4 +370,4 @@ func (_i *approvalWorkflowStepsRepository) CheckStepDependencies(clientId *uuid.
|
||||||
|
|
||||||
canDelete = len(dependencies) == 0
|
canDelete = len(dependencies) == 0
|
||||||
return canDelete, dependencies, nil
|
return canDelete, dependencies, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,52 +20,73 @@ type ApprovalWorkflowsQueryRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApprovalWorkflowsCreateRequest struct {
|
type ApprovalWorkflowsCreateRequest struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Description string `json:"description" validate:"required"`
|
Description string `json:"description" validate:"required"`
|
||||||
IsActive *bool `json:"isActive"`
|
IsActive *bool `json:"isActive"`
|
||||||
IsDefault *bool `json:"isDefault"`
|
IsDefault *bool `json:"isDefault"`
|
||||||
|
RequiresApproval *bool `json:"requiresApproval"`
|
||||||
|
AutoPublish *bool `json:"autoPublish"`
|
||||||
|
Steps []ApprovalWorkflowStepRequest `json:"steps"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req ApprovalWorkflowsCreateRequest) ToEntity() *entity.ApprovalWorkflows {
|
func (req ApprovalWorkflowsCreateRequest) ToEntity() *entity.ApprovalWorkflows {
|
||||||
return &entity.ApprovalWorkflows{
|
return &entity.ApprovalWorkflows{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Description: &req.Description,
|
Description: &req.Description,
|
||||||
IsActive: req.IsActive,
|
IsActive: req.IsActive,
|
||||||
IsDefault: req.IsDefault,
|
IsDefault: req.IsDefault,
|
||||||
CreatedAt: time.Now(),
|
RequiresApproval: req.RequiresApproval,
|
||||||
UpdatedAt: time.Now(),
|
AutoPublish: req.AutoPublish,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req ApprovalWorkflowsCreateRequest) ToStepsEntity() []*entity.ApprovalWorkflowSteps {
|
func (req ApprovalWorkflowsCreateRequest) ToStepsEntity() []*entity.ApprovalWorkflowSteps {
|
||||||
// Return empty slice since basic create request doesn't include steps
|
steps := make([]*entity.ApprovalWorkflowSteps, len(req.Steps))
|
||||||
return []*entity.ApprovalWorkflowSteps{}
|
for i, stepReq := range req.Steps {
|
||||||
|
steps[i] = &entity.ApprovalWorkflowSteps{
|
||||||
|
StepOrder: stepReq.StepOrder,
|
||||||
|
StepName: stepReq.StepName,
|
||||||
|
RequiredUserLevelId: stepReq.RequiredUserLevelId,
|
||||||
|
CanSkip: stepReq.CanSkip,
|
||||||
|
AutoApproveAfterHours: stepReq.AutoApproveAfterHours,
|
||||||
|
IsActive: stepReq.IsActive,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return steps
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApprovalWorkflowsUpdateRequest struct {
|
type ApprovalWorkflowsUpdateRequest struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Description string `json:"description" validate:"required"`
|
Description string `json:"description" validate:"required"`
|
||||||
IsActive *bool `json:"isActive"`
|
IsActive *bool `json:"isActive"`
|
||||||
IsDefault *bool `json:"isDefault"`
|
IsDefault *bool `json:"isDefault"`
|
||||||
|
RequiresApproval *bool `json:"requiresApproval"`
|
||||||
|
AutoPublish *bool `json:"autoPublish"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req ApprovalWorkflowsUpdateRequest) ToEntity() *entity.ApprovalWorkflows {
|
func (req ApprovalWorkflowsUpdateRequest) ToEntity() *entity.ApprovalWorkflows {
|
||||||
return &entity.ApprovalWorkflows{
|
return &entity.ApprovalWorkflows{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Description: &req.Description,
|
Description: &req.Description,
|
||||||
IsActive: req.IsActive,
|
IsActive: req.IsActive,
|
||||||
IsDefault: req.IsDefault,
|
IsDefault: req.IsDefault,
|
||||||
UpdatedAt: time.Now(),
|
RequiresApproval: req.RequiresApproval,
|
||||||
|
AutoPublish: req.AutoPublish,
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApprovalWorkflowStepRequest struct {
|
type ApprovalWorkflowStepRequest struct {
|
||||||
StepOrder int `json:"stepOrder" validate:"required"`
|
StepOrder int `json:"stepOrder" validate:"required"`
|
||||||
StepName string `json:"stepName" validate:"required"`
|
StepName string `json:"stepName" validate:"required"`
|
||||||
RequiredUserLevelId uint `json:"requiredUserLevelId" validate:"required"`
|
RequiredUserLevelId uint `json:"requiredUserLevelId" validate:"required"`
|
||||||
CanSkip *bool `json:"canSkip"`
|
CanSkip *bool `json:"canSkip"`
|
||||||
AutoApproveAfterHours *int `json:"autoApproveAfterHours"`
|
AutoApproveAfterHours *int `json:"autoApproveAfterHours"`
|
||||||
IsActive *bool `json:"isActive"`
|
IsActive *bool `json:"isActive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req ApprovalWorkflowStepRequest) ToEntity(workflowId uint) *entity.ApprovalWorkflowSteps {
|
func (req ApprovalWorkflowStepRequest) ToEntity(workflowId uint) *entity.ApprovalWorkflowSteps {
|
||||||
|
|
@ -83,21 +104,25 @@ func (req ApprovalWorkflowStepRequest) ToEntity(workflowId uint) *entity.Approva
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApprovalWorkflowsWithStepsCreateRequest struct {
|
type ApprovalWorkflowsWithStepsCreateRequest struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Description string `json:"description" validate:"required"`
|
Description string `json:"description" validate:"required"`
|
||||||
IsActive *bool `json:"isActive"`
|
IsActive *bool `json:"isActive"`
|
||||||
IsDefault *bool `json:"isDefault"`
|
IsDefault *bool `json:"isDefault"`
|
||||||
Steps []ApprovalWorkflowStepRequest `json:"steps" validate:"required,min=1"`
|
RequiresApproval *bool `json:"requiresApproval"`
|
||||||
|
AutoPublish *bool `json:"autoPublish"`
|
||||||
|
Steps []ApprovalWorkflowStepRequest `json:"steps" validate:"required,min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req ApprovalWorkflowsWithStepsCreateRequest) ToEntity() *entity.ApprovalWorkflows {
|
func (req ApprovalWorkflowsWithStepsCreateRequest) ToEntity() *entity.ApprovalWorkflows {
|
||||||
return &entity.ApprovalWorkflows{
|
return &entity.ApprovalWorkflows{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Description: &req.Description,
|
Description: &req.Description,
|
||||||
IsActive: req.IsActive,
|
IsActive: req.IsActive,
|
||||||
IsDefault: req.IsDefault,
|
IsDefault: req.IsDefault,
|
||||||
CreatedAt: time.Now(),
|
RequiresApproval: req.RequiresApproval,
|
||||||
UpdatedAt: time.Now(),
|
AutoPublish: req.AutoPublish,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,11 +144,13 @@ func (req ApprovalWorkflowsWithStepsCreateRequest) ToStepsEntity() []*entity.App
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApprovalWorkflowsWithStepsUpdateRequest struct {
|
type ApprovalWorkflowsWithStepsUpdateRequest struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Description string `json:"description" validate:"required"`
|
Description string `json:"description" validate:"required"`
|
||||||
IsActive *bool `json:"isActive"`
|
IsActive *bool `json:"isActive"`
|
||||||
IsDefault *bool `json:"isDefault"`
|
IsDefault *bool `json:"isDefault"`
|
||||||
Steps []ApprovalWorkflowStepRequest `json:"steps" validate:"required,min=1"`
|
RequiresApproval *bool `json:"requiresApproval"`
|
||||||
|
AutoPublish *bool `json:"autoPublish"`
|
||||||
|
Steps []ApprovalWorkflowStepRequest `json:"steps" validate:"required,min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApprovalWorkflowsQueryRequestContext struct {
|
type ApprovalWorkflowsQueryRequestContext struct {
|
||||||
|
|
@ -165,4 +192,4 @@ func (req ApprovalWorkflowsQueryRequestContext) ToParamRequest() ApprovalWorkflo
|
||||||
IsActive: isActive,
|
IsActive: isActive,
|
||||||
IsDefault: isDefault,
|
IsDefault: isDefault,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"web-medols-be/app/middleware"
|
"web-medols-be/app/middleware"
|
||||||
"web-medols-be/app/module/article_approval_flows/request"
|
"web-medols-be/app/module/article_approval_flows/request"
|
||||||
"web-medols-be/app/module/article_approval_flows/service"
|
"web-medols-be/app/module/article_approval_flows/service"
|
||||||
|
usersRepository "web-medols-be/app/module/users/repository"
|
||||||
"web-medols-be/utils/paginator"
|
"web-medols-be/utils/paginator"
|
||||||
|
utilSvc "web-medols-be/utils/service"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
utilRes "web-medols-be/utils/response"
|
utilRes "web-medols-be/utils/response"
|
||||||
utilVal "web-medols-be/utils/validator"
|
utilVal "web-medols-be/utils/validator"
|
||||||
|
|
@ -15,6 +18,7 @@ import (
|
||||||
|
|
||||||
type articleApprovalFlowsController struct {
|
type articleApprovalFlowsController struct {
|
||||||
articleApprovalFlowsService service.ArticleApprovalFlowsService
|
articleApprovalFlowsService service.ArticleApprovalFlowsService
|
||||||
|
UsersRepo usersRepository.UsersRepository
|
||||||
Log zerolog.Logger
|
Log zerolog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,9 +38,10 @@ type ArticleApprovalFlowsController interface {
|
||||||
GetApprovalAnalytics(c *fiber.Ctx) error
|
GetApprovalAnalytics(c *fiber.Ctx) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewArticleApprovalFlowsController(articleApprovalFlowsService service.ArticleApprovalFlowsService, log zerolog.Logger) ArticleApprovalFlowsController {
|
func NewArticleApprovalFlowsController(articleApprovalFlowsService service.ArticleApprovalFlowsService, usersRepo usersRepository.UsersRepository, log zerolog.Logger) ArticleApprovalFlowsController {
|
||||||
return &articleApprovalFlowsController{
|
return &articleApprovalFlowsController{
|
||||||
articleApprovalFlowsService: articleApprovalFlowsService,
|
articleApprovalFlowsService: articleApprovalFlowsService,
|
||||||
|
UsersRepo: usersRepo,
|
||||||
Log: log,
|
Log: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -61,13 +66,13 @@ func (_i *articleApprovalFlowsController) All(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
reqContext := request.ArticleApprovalFlowsQueryRequestContext{
|
reqContext := request.ArticleApprovalFlowsQueryRequestContext{
|
||||||
ArticleId: c.Query("articleId"),
|
ArticleId: c.Query("articleId"),
|
||||||
WorkflowId: c.Query("workflowId"),
|
WorkflowId: c.Query("workflowId"),
|
||||||
StatusId: c.Query("statusId"),
|
StatusId: c.Query("statusId"),
|
||||||
SubmittedBy: c.Query("submittedBy"),
|
SubmittedBy: c.Query("submittedBy"),
|
||||||
CurrentStep: c.Query("currentStep"),
|
CurrentStep: c.Query("currentStep"),
|
||||||
DateFrom: c.Query("dateFrom"),
|
DateFrom: c.Query("dateFrom"),
|
||||||
DateTo: c.Query("dateTo"),
|
DateTo: c.Query("dateTo"),
|
||||||
}
|
}
|
||||||
req := reqContext.ToParamRequest()
|
req := reqContext.ToParamRequest()
|
||||||
req.Pagination = paginate
|
req.Pagination = paginate
|
||||||
|
|
@ -129,6 +134,7 @@ func (_i *articleApprovalFlowsController) Show(c *fiber.Ctx) error {
|
||||||
// @Tags ArticleApprovalFlows
|
// @Tags ArticleApprovalFlows
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
// @Param req body request.SubmitForApprovalRequest true "Submit for approval data"
|
// @Param req body request.SubmitForApprovalRequest true "Submit for approval data"
|
||||||
// @Success 201 {object} response.Response
|
// @Success 201 {object} response.Response
|
||||||
// @Failure 400 {object} response.BadRequestError
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
|
@ -148,12 +154,23 @@ func (_i *articleApprovalFlowsController) SubmitForApproval(c *fiber.Ctx) error
|
||||||
// Get ClientId from context
|
// Get ClientId from context
|
||||||
clientId := middleware.GetClientID(c)
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
|
// Get Authorization token from header and extract user ID
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
if authToken == "" {
|
||||||
|
return utilRes.ErrorBadRequest(c, "Authorization token required")
|
||||||
|
}
|
||||||
|
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user == nil {
|
||||||
|
return utilRes.ErrorBadRequest(c, "Invalid authorization token")
|
||||||
|
}
|
||||||
|
|
||||||
var workflowId *uint
|
var workflowId *uint
|
||||||
if req.WorkflowId != nil {
|
if req.WorkflowId != nil {
|
||||||
workflowIdVal := uint(*req.WorkflowId)
|
workflowIdVal := uint(*req.WorkflowId)
|
||||||
workflowId = &workflowIdVal
|
workflowId = &workflowIdVal
|
||||||
}
|
}
|
||||||
articleApprovalFlowsData, err := _i.articleApprovalFlowsService.SubmitArticleForApproval(clientId, uint(req.ArticleId), 0, workflowId)
|
articleApprovalFlowsData, err := _i.articleApprovalFlowsService.SubmitArticleForApproval(clientId, uint(req.ArticleId), user.ID, workflowId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -171,6 +188,7 @@ func (_i *articleApprovalFlowsController) SubmitForApproval(c *fiber.Ctx) error
|
||||||
// @Tags ArticleApprovalFlows
|
// @Tags ArticleApprovalFlows
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
// @Param id path int true "ArticleApprovalFlows ID"
|
// @Param id path int true "ArticleApprovalFlows ID"
|
||||||
// @Param req body request.ApprovalActionRequest true "Approval action data"
|
// @Param req body request.ApprovalActionRequest true "Approval action data"
|
||||||
// @Success 200 {object} response.Response
|
// @Success 200 {object} response.Response
|
||||||
|
|
@ -196,7 +214,18 @@ func (_i *articleApprovalFlowsController) Approve(c *fiber.Ctx) error {
|
||||||
// Get ClientId from context
|
// Get ClientId from context
|
||||||
clientId := middleware.GetClientID(c)
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
err = _i.articleApprovalFlowsService.ApproveStep(clientId, uint(id), 0, req.Message)
|
// Get Authorization token from header and extract user ID
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
if authToken == "" {
|
||||||
|
return utilRes.ErrorBadRequest(c, "Authorization token required")
|
||||||
|
}
|
||||||
|
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user == nil {
|
||||||
|
return utilRes.ErrorBadRequest(c, "Invalid authorization token")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.articleApprovalFlowsService.ApproveStep(clientId, uint(id), user.ID, req.Message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -214,6 +243,7 @@ func (_i *articleApprovalFlowsController) Approve(c *fiber.Ctx) error {
|
||||||
// @Tags ArticleApprovalFlows
|
// @Tags ArticleApprovalFlows
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
// @Param id path int true "ArticleApprovalFlows ID"
|
// @Param id path int true "ArticleApprovalFlows ID"
|
||||||
// @Param req body request.RejectionRequest true "Rejection data"
|
// @Param req body request.RejectionRequest true "Rejection data"
|
||||||
// @Success 200 {object} response.Response
|
// @Success 200 {object} response.Response
|
||||||
|
|
@ -239,7 +269,18 @@ func (_i *articleApprovalFlowsController) Reject(c *fiber.Ctx) error {
|
||||||
// Get ClientId from context
|
// Get ClientId from context
|
||||||
clientId := middleware.GetClientID(c)
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
err = _i.articleApprovalFlowsService.RejectArticle(clientId, uint(id), 0, req.Reason)
|
// Get Authorization token from header and extract user ID
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
if authToken == "" {
|
||||||
|
return utilRes.ErrorBadRequest(c, "Authorization token required")
|
||||||
|
}
|
||||||
|
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user == nil {
|
||||||
|
return utilRes.ErrorBadRequest(c, "Invalid authorization token")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.articleApprovalFlowsService.RejectArticle(clientId, uint(id), user.ID, req.Reason)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -257,6 +298,7 @@ func (_i *articleApprovalFlowsController) Reject(c *fiber.Ctx) error {
|
||||||
// @Tags ArticleApprovalFlows
|
// @Tags ArticleApprovalFlows
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
// @Param id path int true "ArticleApprovalFlows ID"
|
// @Param id path int true "ArticleApprovalFlows ID"
|
||||||
// @Param req body request.RevisionRequest true "Revision request data"
|
// @Param req body request.RevisionRequest true "Revision request data"
|
||||||
// @Success 200 {object} response.Response
|
// @Success 200 {object} response.Response
|
||||||
|
|
@ -282,7 +324,18 @@ func (_i *articleApprovalFlowsController) RequestRevision(c *fiber.Ctx) error {
|
||||||
// Get ClientId from context
|
// Get ClientId from context
|
||||||
clientId := middleware.GetClientID(c)
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
err = _i.articleApprovalFlowsService.RequestRevision(clientId, uint(id), 0, req.Message)
|
// Get Authorization token from header and extract user ID
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
if authToken == "" {
|
||||||
|
return utilRes.ErrorBadRequest(c, "Authorization token required")
|
||||||
|
}
|
||||||
|
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user == nil {
|
||||||
|
return utilRes.ErrorBadRequest(c, "Invalid authorization token")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.articleApprovalFlowsService.RequestRevision(clientId, uint(id), user.ID, req.Message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -300,6 +353,7 @@ func (_i *articleApprovalFlowsController) RequestRevision(c *fiber.Ctx) error {
|
||||||
// @Tags ArticleApprovalFlows
|
// @Tags ArticleApprovalFlows
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
// @Param id path int true "ArticleApprovalFlows ID"
|
// @Param id path int true "ArticleApprovalFlows ID"
|
||||||
// @Param req body request.ResubmitRequest true "Resubmit data"
|
// @Param req body request.ResubmitRequest true "Resubmit data"
|
||||||
// @Success 200 {object} response.Response
|
// @Success 200 {object} response.Response
|
||||||
|
|
@ -325,7 +379,18 @@ func (_i *articleApprovalFlowsController) Resubmit(c *fiber.Ctx) error {
|
||||||
// Get ClientId from context
|
// Get ClientId from context
|
||||||
clientId := middleware.GetClientID(c)
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
err = _i.articleApprovalFlowsService.ResubmitAfterRevision(clientId, uint(id), 0)
|
// Get Authorization token from header and extract user ID
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
if authToken == "" {
|
||||||
|
return utilRes.ErrorBadRequest(c, "Authorization token required")
|
||||||
|
}
|
||||||
|
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user == nil {
|
||||||
|
return utilRes.ErrorBadRequest(c, "Invalid authorization token")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.articleApprovalFlowsService.ResubmitAfterRevision(clientId, uint(id), user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -343,6 +408,9 @@ func (_i *articleApprovalFlowsController) Resubmit(c *fiber.Ctx) error {
|
||||||
// @Tags ArticleApprovalFlows
|
// @Tags ArticleApprovalFlows
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
|
// @Param includePreview query bool false "Include article preview"
|
||||||
|
// @Param urgentOnly query bool false "Show only urgent articles"
|
||||||
// @Param req query paginator.Pagination false "pagination parameters"
|
// @Param req query paginator.Pagination false "pagination parameters"
|
||||||
// @Success 200 {object} response.Response
|
// @Success 200 {object} response.Response
|
||||||
// @Failure 400 {object} response.BadRequestError
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
|
@ -358,7 +426,22 @@ func (_i *articleApprovalFlowsController) GetMyApprovalQueue(c *fiber.Ctx) error
|
||||||
// Get ClientId from context
|
// Get ClientId from context
|
||||||
clientId := middleware.GetClientID(c)
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
approvalQueueData, paging, err := _i.articleApprovalFlowsService.GetMyApprovalQueue(clientId, uint(0), paginate.Page, paginate.Limit, false, false)
|
// Get Authorization token from header and extract user level ID
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
if authToken == "" {
|
||||||
|
return utilRes.ErrorBadRequest(c, "Authorization token required")
|
||||||
|
}
|
||||||
|
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user == nil {
|
||||||
|
return utilRes.ErrorBadRequest(c, "Invalid authorization token")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional parameters
|
||||||
|
includePreview := c.QueryBool("includePreview", false)
|
||||||
|
urgentOnly := c.QueryBool("urgentOnly", false)
|
||||||
|
|
||||||
|
approvalQueueData, paging, err := _i.articleApprovalFlowsService.GetMyApprovalQueue(clientId, user.UserLevelId, paginate.Page, paginate.Limit, includePreview, urgentOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -377,7 +460,7 @@ func (_i *articleApprovalFlowsController) GetMyApprovalQueue(c *fiber.Ctx) error
|
||||||
// @Tags ArticleApprovalFlows
|
// @Tags ArticleApprovalFlows
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
// @Param userLevelId query int false "User Level ID filter"
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
// @Param req query paginator.Pagination false "pagination parameters"
|
// @Param req query paginator.Pagination false "pagination parameters"
|
||||||
// @Success 200 {object} response.Response
|
// @Success 200 {object} response.Response
|
||||||
// @Failure 400 {object} response.BadRequestError
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
|
@ -390,20 +473,22 @@ func (_i *articleApprovalFlowsController) GetPendingApprovals(c *fiber.Ctx) erro
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// userLevelId parameter is not used in current implementation
|
|
||||||
// userLevelId := 0
|
|
||||||
// if userLevelIdStr := c.Query("userLevelId"); userLevelIdStr != "" {
|
|
||||||
// userLevelId, err = strconv.Atoi(userLevelIdStr)
|
|
||||||
// if err != nil {
|
|
||||||
// return utilRes.ErrorBadRequest(c, "Invalid userLevelId format")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Get ClientId from context
|
// Get ClientId from context
|
||||||
clientId := middleware.GetClientID(c)
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
|
// Get Authorization token from header and extract user level ID
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
if authToken == "" {
|
||||||
|
return utilRes.ErrorBadRequest(c, "Authorization token required")
|
||||||
|
}
|
||||||
|
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user == nil {
|
||||||
|
return utilRes.ErrorBadRequest(c, "Invalid authorization token")
|
||||||
|
}
|
||||||
|
|
||||||
filters := make(map[string]interface{})
|
filters := make(map[string]interface{})
|
||||||
pendingApprovalsData, paging, err := _i.articleApprovalFlowsService.GetPendingApprovals(clientId, uint(0), paginate.Page, paginate.Limit, filters)
|
pendingApprovalsData, paging, err := _i.articleApprovalFlowsService.GetPendingApprovals(clientId, user.UserLevelId, paginate.Page, paginate.Limit, filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -475,28 +560,31 @@ func (_i *articleApprovalFlowsController) GetApprovalHistory(c *fiber.Ctx) error
|
||||||
// @Tags ArticleApprovalFlows
|
// @Tags ArticleApprovalFlows
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
// @Param userLevelId query int false "User Level ID filter"
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
// @Success 200 {object} response.Response
|
// @Success 200 {object} response.Response
|
||||||
// @Failure 400 {object} response.BadRequestError
|
// @Failure 400 {object} response.BadRequestError
|
||||||
// @Failure 401 {object} response.UnauthorizedError
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
// @Failure 500 {object} response.InternalServerError
|
// @Failure 500 {object} response.InternalServerError
|
||||||
// @Router /article-approval-flows/dashboard-stats [get]
|
// @Router /article-approval-flows/dashboard-stats [get]
|
||||||
func (_i *articleApprovalFlowsController) GetDashboardStats(c *fiber.Ctx) error {
|
func (_i *articleApprovalFlowsController) GetDashboardStats(c *fiber.Ctx) error {
|
||||||
userLevelId := 0
|
// Get ClientId from context
|
||||||
var err error
|
clientId := middleware.GetClientID(c)
|
||||||
if userLevelIdStr := c.Query("userLevelId"); userLevelIdStr != "" {
|
|
||||||
userLevelId, err = strconv.Atoi(userLevelIdStr)
|
// Get Authorization token from header and extract user level ID
|
||||||
if err != nil {
|
authToken := c.Get("Authorization")
|
||||||
return utilRes.ErrorBadRequest(c, "Invalid userLevelId format")
|
if authToken == "" {
|
||||||
}
|
return utilRes.ErrorBadRequest(c, "Authorization token required")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get ClientId from context
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
// clientId := middleware.GetClientID(c)
|
if user == nil {
|
||||||
|
return utilRes.ErrorBadRequest(c, "Invalid authorization token")
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Implement GetDashboardStats method in service
|
// TODO: Implement GetDashboardStats method in service
|
||||||
_ = userLevelId // suppress unused variable warning
|
_ = clientId // suppress unused variable warning
|
||||||
// dashboardStatsData, err := _i.articleApprovalFlowsService.GetDashboardStats(clientId, userLevelId)
|
_ = user.UserLevelId // suppress unused variable warning
|
||||||
|
// dashboardStatsData, err := _i.articleApprovalFlowsService.GetDashboardStats(clientId, user.UserLevelId)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// return err
|
// return err
|
||||||
// }
|
// }
|
||||||
|
|
@ -514,6 +602,7 @@ func (_i *articleApprovalFlowsController) GetDashboardStats(c *fiber.Ctx) error
|
||||||
// @Tags ArticleApprovalFlows
|
// @Tags ArticleApprovalFlows
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
// @Success 200 {object} response.Response
|
// @Success 200 {object} response.Response
|
||||||
// @Failure 400 {object} response.BadRequestError
|
// @Failure 400 {object} response.BadRequestError
|
||||||
// @Failure 401 {object} response.UnauthorizedError
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
|
@ -543,6 +632,7 @@ func (_i *articleApprovalFlowsController) GetWorkloadStats(c *fiber.Ctx) error {
|
||||||
// @Tags ArticleApprovalFlows
|
// @Tags ArticleApprovalFlows
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
// @Param period query string false "Period filter (daily, weekly, monthly)"
|
// @Param period query string false "Period filter (daily, weekly, monthly)"
|
||||||
// @Param startDate query string false "Start date filter (YYYY-MM-DD)"
|
// @Param startDate query string false "Start date filter (YYYY-MM-DD)"
|
||||||
// @Param endDate query string false "End date filter (YYYY-MM-DD)"
|
// @Param endDate query string false "End date filter (YYYY-MM-DD)"
|
||||||
|
|
@ -571,4 +661,4 @@ func (_i *articleApprovalFlowsController) GetApprovalAnalytics(c *fiber.Ctx) err
|
||||||
Messages: utilRes.Messages{"Approval analytics successfully retrieved"},
|
Messages: utilRes.Messages{"Approval analytics successfully retrieved"},
|
||||||
Data: nil,
|
Data: nil,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,26 +2,29 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"time"
|
"time"
|
||||||
"web-medols-be/app/database/entity"
|
"web-medols-be/app/database/entity"
|
||||||
|
approvalWorkflowStepsRepo "web-medols-be/app/module/approval_workflow_steps/repository"
|
||||||
|
approvalWorkflowsRepo "web-medols-be/app/module/approval_workflows/repository"
|
||||||
"web-medols-be/app/module/article_approval_flows/repository"
|
"web-medols-be/app/module/article_approval_flows/repository"
|
||||||
"web-medols-be/app/module/article_approval_flows/request"
|
"web-medols-be/app/module/article_approval_flows/request"
|
||||||
approvalWorkflowsRepo "web-medols-be/app/module/approval_workflows/repository"
|
|
||||||
approvalWorkflowStepsRepo "web-medols-be/app/module/approval_workflow_steps/repository"
|
|
||||||
approvalStepLogsRepo "web-medols-be/app/module/article_approval_step_logs/repository"
|
approvalStepLogsRepo "web-medols-be/app/module/article_approval_step_logs/repository"
|
||||||
articlesRepo "web-medols-be/app/module/articles/repository"
|
articlesRepo "web-medols-be/app/module/articles/repository"
|
||||||
|
usersRepo "web-medols-be/app/module/users/repository"
|
||||||
"web-medols-be/utils/paginator"
|
"web-medols-be/utils/paginator"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type articleApprovalFlowsService struct {
|
type articleApprovalFlowsService struct {
|
||||||
ArticleApprovalFlowsRepository repository.ArticleApprovalFlowsRepository
|
ArticleApprovalFlowsRepository repository.ArticleApprovalFlowsRepository
|
||||||
ApprovalWorkflowsRepository approvalWorkflowsRepo.ApprovalWorkflowsRepository
|
ApprovalWorkflowsRepository approvalWorkflowsRepo.ApprovalWorkflowsRepository
|
||||||
ApprovalWorkflowStepsRepository approvalWorkflowStepsRepo.ApprovalWorkflowStepsRepository
|
ApprovalWorkflowStepsRepository approvalWorkflowStepsRepo.ApprovalWorkflowStepsRepository
|
||||||
ArticleApprovalStepLogsRepository approvalStepLogsRepo.ArticleApprovalStepLogsRepository
|
ArticleApprovalStepLogsRepository approvalStepLogsRepo.ArticleApprovalStepLogsRepository
|
||||||
ArticlesRepository articlesRepo.ArticlesRepository
|
ArticlesRepository articlesRepo.ArticlesRepository
|
||||||
Log zerolog.Logger
|
UsersRepository usersRepo.UsersRepository
|
||||||
|
Log zerolog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// ArticleApprovalFlowsService define interface of IArticleApprovalFlowsService
|
// ArticleApprovalFlowsService define interface of IArticleApprovalFlowsService
|
||||||
|
|
@ -63,15 +66,17 @@ func NewArticleApprovalFlowsService(
|
||||||
approvalWorkflowStepsRepository approvalWorkflowStepsRepo.ApprovalWorkflowStepsRepository,
|
approvalWorkflowStepsRepository approvalWorkflowStepsRepo.ApprovalWorkflowStepsRepository,
|
||||||
articleApprovalStepLogsRepository approvalStepLogsRepo.ArticleApprovalStepLogsRepository,
|
articleApprovalStepLogsRepository approvalStepLogsRepo.ArticleApprovalStepLogsRepository,
|
||||||
articlesRepository articlesRepo.ArticlesRepository,
|
articlesRepository articlesRepo.ArticlesRepository,
|
||||||
|
usersRepository usersRepo.UsersRepository,
|
||||||
log zerolog.Logger,
|
log zerolog.Logger,
|
||||||
) ArticleApprovalFlowsService {
|
) ArticleApprovalFlowsService {
|
||||||
return &articleApprovalFlowsService{
|
return &articleApprovalFlowsService{
|
||||||
ArticleApprovalFlowsRepository: articleApprovalFlowsRepository,
|
ArticleApprovalFlowsRepository: articleApprovalFlowsRepository,
|
||||||
ApprovalWorkflowsRepository: approvalWorkflowsRepository,
|
ApprovalWorkflowsRepository: approvalWorkflowsRepository,
|
||||||
ApprovalWorkflowStepsRepository: approvalWorkflowStepsRepository,
|
ApprovalWorkflowStepsRepository: approvalWorkflowStepsRepository,
|
||||||
ArticleApprovalStepLogsRepository: articleApprovalStepLogsRepository,
|
ArticleApprovalStepLogsRepository: articleApprovalStepLogsRepository,
|
||||||
ArticlesRepository: articlesRepository,
|
ArticlesRepository: articlesRepository,
|
||||||
Log: log,
|
UsersRepository: usersRepository,
|
||||||
|
Log: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,7 +143,7 @@ func (_i *articleApprovalFlowsService) SubmitArticleForApproval(clientId *uuid.U
|
||||||
ArticleId: articleId,
|
ArticleId: articleId,
|
||||||
WorkflowId: workflow.ID,
|
WorkflowId: workflow.ID,
|
||||||
CurrentStep: 1,
|
CurrentStep: 1,
|
||||||
StatusId: 1, // pending
|
StatusId: 1, // pending
|
||||||
SubmittedById: submittedById,
|
SubmittedById: submittedById,
|
||||||
SubmittedAt: time.Now(),
|
SubmittedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
@ -148,30 +153,24 @@ func (_i *articleApprovalFlowsService) SubmitArticleForApproval(clientId *uuid.U
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update article status and workflow info
|
// Get current article data first
|
||||||
articleUpdate := &entity.Articles{
|
currentArticle, err := _i.ArticlesRepository.FindOne(clientId, articleId)
|
||||||
WorkflowId: &workflow.ID,
|
|
||||||
CurrentApprovalStep: &flow.CurrentStep,
|
|
||||||
StatusId: &[]int{1}[0], // pending approval
|
|
||||||
}
|
|
||||||
|
|
||||||
err = _i.ArticlesRepository.Update(clientId, articleId, articleUpdate)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create initial step log
|
// Update only the necessary fields
|
||||||
stepLog := &entity.ArticleApprovalStepLogs{
|
currentArticle.WorkflowId = &workflow.ID
|
||||||
ApprovalFlowId: flow.ID,
|
currentArticle.CurrentApprovalStep = &flow.CurrentStep
|
||||||
StepOrder: 1,
|
currentArticle.StatusId = &[]int{1}[0] // pending approval
|
||||||
StepName: firstStep.StepName,
|
|
||||||
Action: "submitted",
|
err = _i.ArticlesRepository.UpdateSkipNull(clientId, articleId, currentArticle)
|
||||||
Message: &[]string{"Article submitted for approval"}[0],
|
if err != nil {
|
||||||
ProcessedAt: time.Now(),
|
return nil, err
|
||||||
UserLevelId: firstStep.RequiredUserLevelId,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = _i.ArticleApprovalStepLogsRepository.Create(clientId, stepLog)
|
// Process auto-skip logic based on user level
|
||||||
|
err = _i.processAutoSkipSteps(clientId, flow, submittedById)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -179,6 +178,160 @@ func (_i *articleApprovalFlowsService) SubmitArticleForApproval(clientId *uuid.U
|
||||||
return flow, nil
|
return flow, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processAutoSkipSteps handles automatic step skipping based on user level
|
||||||
|
func (_i *articleApprovalFlowsService) processAutoSkipSteps(clientId *uuid.UUID, flow *entity.ArticleApprovalFlows, submittedById uint) error {
|
||||||
|
// Get user level of the submitter
|
||||||
|
userLevelId, err := _i.getUserLevelId(clientId, submittedById)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all workflow steps
|
||||||
|
steps, err := _i.ApprovalWorkflowStepsRepository.GetByWorkflowId(clientId, flow.WorkflowId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort steps by step order
|
||||||
|
sortStepsByOrder(steps)
|
||||||
|
|
||||||
|
// Process each step to determine if it should be auto-skipped
|
||||||
|
for _, step := range steps {
|
||||||
|
shouldSkip := _i.shouldSkipStep(userLevelId, step.RequiredUserLevelId)
|
||||||
|
|
||||||
|
if shouldSkip {
|
||||||
|
// Create skip log
|
||||||
|
stepLog := &entity.ArticleApprovalStepLogs{
|
||||||
|
ApprovalFlowId: flow.ID,
|
||||||
|
StepOrder: step.StepOrder,
|
||||||
|
StepName: step.StepName,
|
||||||
|
ApprovedById: &submittedById,
|
||||||
|
Action: "auto_skip",
|
||||||
|
Message: &[]string{"Step auto-skipped due to user level"}[0],
|
||||||
|
ProcessedAt: time.Now(),
|
||||||
|
UserLevelId: step.RequiredUserLevelId,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = _i.ArticleApprovalStepLogsRepository.Create(clientId, stepLog)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update flow to next step (handle step order starting from 0)
|
||||||
|
nextStepOrder := step.StepOrder + 1
|
||||||
|
flow.CurrentStep = nextStepOrder
|
||||||
|
} else {
|
||||||
|
// Stop at first step that cannot be skipped
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update flow with final current step
|
||||||
|
err = _i.ArticleApprovalFlowsRepository.Update(flow.ID, flow)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current article data first
|
||||||
|
currentArticle, err := _i.ArticlesRepository.FindOne(clientId, flow.ArticleId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update only the necessary fields
|
||||||
|
currentArticle.CurrentApprovalStep = &flow.CurrentStep
|
||||||
|
|
||||||
|
err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all steps were skipped (workflow complete)
|
||||||
|
// Find the highest step order
|
||||||
|
maxStepOrder := 0
|
||||||
|
for _, step := range steps {
|
||||||
|
if step.StepOrder > maxStepOrder {
|
||||||
|
maxStepOrder = step.StepOrder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if flow.CurrentStep > maxStepOrder {
|
||||||
|
// All steps completed, mark as approved
|
||||||
|
flow.StatusId = 2 // approved
|
||||||
|
flow.CompletedAt = &[]time.Time{time.Now()}[0]
|
||||||
|
|
||||||
|
err = _i.ArticleApprovalFlowsRepository.Update(flow.ID, flow)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current article data first
|
||||||
|
currentArticle, err := _i.ArticlesRepository.FindOne(clientId, flow.ArticleId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update only the necessary fields
|
||||||
|
currentArticle.StatusId = &[]int{2}[0] // approved
|
||||||
|
currentArticle.CurrentApprovalStep = nil
|
||||||
|
|
||||||
|
err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUserLevelId gets the user level ID for a given user
|
||||||
|
func (_i *articleApprovalFlowsService) getUserLevelId(clientId *uuid.UUID, userId uint) (uint, error) {
|
||||||
|
// Get user from database to retrieve user level
|
||||||
|
user, err := _i.UsersRepository.FindOne(clientId, userId)
|
||||||
|
if err != nil {
|
||||||
|
_i.Log.Error().Err(err).Uint("userId", userId).Msg("Failed to find user")
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.UserLevel == nil {
|
||||||
|
_i.Log.Error().Uint("userId", userId).Msg("User has no user level")
|
||||||
|
return 0, errors.New("user has no user level")
|
||||||
|
}
|
||||||
|
|
||||||
|
_i.Log.Info().
|
||||||
|
Uint("userId", userId).
|
||||||
|
Uint("userLevelId", user.UserLevel.ID).
|
||||||
|
Str("userLevelName", user.UserLevel.Name).
|
||||||
|
Msg("Retrieved user level from database")
|
||||||
|
|
||||||
|
return user.UserLevel.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldSkipStep determines if a step should be auto-skipped based on user level
|
||||||
|
func (_i *articleApprovalFlowsService) shouldSkipStep(userLevelId, requiredLevelId uint) bool {
|
||||||
|
// Get user level details to compare level numbers
|
||||||
|
// User level with lower level_number (higher authority) can skip steps requiring higher level_number
|
||||||
|
// For now, we'll use a simple comparison based on IDs
|
||||||
|
// In production, this should compare level_number fields
|
||||||
|
|
||||||
|
// Simple logic: if user level ID is less than required level ID, they can skip
|
||||||
|
// This assumes level 1 (ID=1) has higher authority than level 2 (ID=2), etc.
|
||||||
|
return userLevelId < requiredLevelId
|
||||||
|
}
|
||||||
|
|
||||||
|
// sortStepsByOrder sorts workflow steps by their step order
|
||||||
|
func sortStepsByOrder(steps []*entity.ApprovalWorkflowSteps) {
|
||||||
|
// Simple bubble sort for step order
|
||||||
|
n := len(steps)
|
||||||
|
for i := 0; i < n-1; i++ {
|
||||||
|
for j := 0; j < n-i-1; j++ {
|
||||||
|
if steps[j].StepOrder > steps[j+1].StepOrder {
|
||||||
|
steps[j], steps[j+1] = steps[j+1], steps[j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (_i *articleApprovalFlowsService) ApproveStep(clientId *uuid.UUID, flowId uint, approvedById uint, message string) (err error) {
|
func (_i *articleApprovalFlowsService) ApproveStep(clientId *uuid.UUID, flowId uint, approvedById uint, message string) (err error) {
|
||||||
// Get approval flow
|
// Get approval flow
|
||||||
flow, err := _i.ArticleApprovalFlowsRepository.FindOne(clientId, flowId)
|
flow, err := _i.ArticleApprovalFlowsRepository.FindOne(clientId, flowId)
|
||||||
|
|
@ -239,13 +392,17 @@ func (_i *articleApprovalFlowsService) ApproveStep(clientId *uuid.UUID, flowId u
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update article status
|
// Get current article data first
|
||||||
articleUpdate := &entity.Articles{
|
currentArticle, err := _i.ArticlesRepository.FindOne(clientId, flow.ArticleId)
|
||||||
StatusId: &[]int{2}[0], // approved
|
if err != nil {
|
||||||
CurrentApprovalStep: nil,
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = _i.ArticlesRepository.Update(clientId, flow.ArticleId, articleUpdate)
|
// Update only the necessary fields
|
||||||
|
currentArticle.StatusId = &[]int{2}[0] // approved
|
||||||
|
currentArticle.CurrentApprovalStep = nil
|
||||||
|
|
||||||
|
err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -261,12 +418,16 @@ func (_i *articleApprovalFlowsService) ApproveStep(clientId *uuid.UUID, flowId u
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update article current step
|
// Get current article data first
|
||||||
articleUpdate := &entity.Articles{
|
currentArticle, err := _i.ArticlesRepository.FindOne(clientId, flow.ArticleId)
|
||||||
CurrentApprovalStep: &nextStep.StepOrder,
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = _i.ArticlesRepository.Update(clientId, flow.ArticleId, articleUpdate)
|
// Update only the necessary fields
|
||||||
|
currentArticle.CurrentApprovalStep = &nextStep.StepOrder
|
||||||
|
|
||||||
|
err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -325,13 +486,17 @@ func (_i *articleApprovalFlowsService) RejectArticle(clientId *uuid.UUID, flowId
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update article status
|
// Get current article data first
|
||||||
articleUpdate := &entity.Articles{
|
currentArticle, err := _i.ArticlesRepository.FindOne(clientId, flow.ArticleId)
|
||||||
StatusId: &[]int{3}[0], // rejected
|
if err != nil {
|
||||||
CurrentApprovalStep: nil,
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = _i.ArticlesRepository.Update(clientId, flow.ArticleId, articleUpdate)
|
// Update only the necessary fields
|
||||||
|
currentArticle.StatusId = &[]int{3}[0] // rejected
|
||||||
|
currentArticle.CurrentApprovalStep = nil
|
||||||
|
|
||||||
|
err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -379,9 +544,9 @@ func (_i *articleApprovalFlowsService) RequestRevision(clientId *uuid.UUID, flow
|
||||||
|
|
||||||
// Update approval flow status
|
// Update approval flow status
|
||||||
flowUpdate := &entity.ArticleApprovalFlows{
|
flowUpdate := &entity.ArticleApprovalFlows{
|
||||||
StatusId: 4, // revision_requested
|
StatusId: 4, // revision_requested
|
||||||
RevisionRequested: &[]bool{true}[0],
|
RevisionRequested: &[]bool{true}[0],
|
||||||
RevisionMessage: &revisionMessage,
|
RevisionMessage: &revisionMessage,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = _i.ArticleApprovalFlowsRepository.Update(flowId, flowUpdate)
|
err = _i.ArticleApprovalFlowsRepository.Update(flowId, flowUpdate)
|
||||||
|
|
@ -389,12 +554,16 @@ func (_i *articleApprovalFlowsService) RequestRevision(clientId *uuid.UUID, flow
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update article status
|
// Get current article data first
|
||||||
articleUpdate := &entity.Articles{
|
currentArticle, err := _i.ArticlesRepository.FindOne(clientId, flow.ArticleId)
|
||||||
StatusId: &[]int{4}[0], // revision_requested
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = _i.ArticlesRepository.Update(clientId, flow.ArticleId, articleUpdate)
|
// Update only the necessary fields
|
||||||
|
currentArticle.StatusId = &[]int{4}[0] // revision_requested
|
||||||
|
|
||||||
|
err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -420,7 +589,7 @@ func (_i *articleApprovalFlowsService) ResubmitAfterRevision(clientId *uuid.UUID
|
||||||
// Reset approval flow to pending
|
// Reset approval flow to pending
|
||||||
flowUpdate := &entity.ArticleApprovalFlows{
|
flowUpdate := &entity.ArticleApprovalFlows{
|
||||||
StatusId: 1, // pending
|
StatusId: 1, // pending
|
||||||
RevisionRequested: &[]bool{false}[0],
|
RevisionRequested: &[]bool{false}[0],
|
||||||
RevisionMessage: nil,
|
RevisionMessage: nil,
|
||||||
CurrentStep: 1, // restart from first step
|
CurrentStep: 1, // restart from first step
|
||||||
}
|
}
|
||||||
|
|
@ -430,13 +599,17 @@ func (_i *articleApprovalFlowsService) ResubmitAfterRevision(clientId *uuid.UUID
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update article status
|
// Get current article data first
|
||||||
articleUpdate := &entity.Articles{
|
currentArticle, err := _i.ArticlesRepository.FindOne(clientId, flow.ArticleId)
|
||||||
StatusId: &[]int{1}[0], // pending approval
|
if err != nil {
|
||||||
CurrentApprovalStep: &[]int{1}[0],
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = _i.ArticlesRepository.Update(clientId, flow.ArticleId, articleUpdate)
|
// Update only the necessary fields
|
||||||
|
currentArticle.StatusId = &[]int{1}[0] // pending approval
|
||||||
|
currentArticle.CurrentApprovalStep = &[]int{1}[0]
|
||||||
|
|
||||||
|
err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -603,4 +776,4 @@ func (_i *articleApprovalFlowsService) GetNextStepPreview(clientId *uuid.UUID, f
|
||||||
}
|
}
|
||||||
|
|
||||||
return nextStep, nil
|
return nextStep, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,21 @@
|
||||||
package articles
|
package articles
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gofiber/fiber/v2"
|
"web-medols-be/app/middleware"
|
||||||
"go.uber.org/fx"
|
|
||||||
"web-medols-be/app/module/articles/controller"
|
"web-medols-be/app/module/articles/controller"
|
||||||
"web-medols-be/app/module/articles/repository"
|
"web-medols-be/app/module/articles/repository"
|
||||||
"web-medols-be/app/module/articles/service"
|
"web-medols-be/app/module/articles/service"
|
||||||
|
usersRepo "web-medols-be/app/module/users/repository"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/fx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ArticlesRouter struct of ArticlesRouter
|
// ArticlesRouter struct of ArticlesRouter
|
||||||
type ArticlesRouter struct {
|
type ArticlesRouter struct {
|
||||||
App fiber.Router
|
App fiber.Router
|
||||||
Controller *controller.Controller
|
Controller *controller.Controller
|
||||||
|
UsersRepo usersRepo.UsersRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewArticlesModule register bulky of Articles module
|
// NewArticlesModule register bulky of Articles module
|
||||||
|
|
@ -30,10 +34,11 @@ var NewArticlesModule = fx.Options(
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewArticlesRouter init ArticlesRouter
|
// NewArticlesRouter init ArticlesRouter
|
||||||
func NewArticlesRouter(fiber *fiber.App, controller *controller.Controller) *ArticlesRouter {
|
func NewArticlesRouter(fiber *fiber.App, controller *controller.Controller, usersRepo usersRepo.UsersRepository) *ArticlesRouter {
|
||||||
return &ArticlesRouter{
|
return &ArticlesRouter{
|
||||||
App: fiber,
|
App: fiber,
|
||||||
Controller: controller,
|
Controller: controller,
|
||||||
|
UsersRepo: usersRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,6 +49,8 @@ func (_i *ArticlesRouter) RegisterArticlesRoutes() {
|
||||||
|
|
||||||
// define routes
|
// define routes
|
||||||
_i.App.Route("/articles", func(router fiber.Router) {
|
_i.App.Route("/articles", func(router fiber.Router) {
|
||||||
|
// Add user middleware to extract user level from JWT token
|
||||||
|
router.Use(middleware.UserMiddleware(_i.UsersRepo))
|
||||||
router.Get("/", articlesController.All)
|
router.Get("/", articlesController.All)
|
||||||
router.Get("/old-id/:id", articlesController.ShowByOldId)
|
router.Get("/old-id/:id", articlesController.ShowByOldId)
|
||||||
router.Get("/:id", articlesController.Show)
|
router.Get("/:id", articlesController.Show)
|
||||||
|
|
@ -57,10 +64,11 @@ func (_i *ArticlesRouter) RegisterArticlesRoutes() {
|
||||||
router.Get("/statistic/summary", articlesController.SummaryStats)
|
router.Get("/statistic/summary", articlesController.SummaryStats)
|
||||||
router.Get("/statistic/user-levels", articlesController.ArticlePerUserLevelStats)
|
router.Get("/statistic/user-levels", articlesController.ArticlePerUserLevelStats)
|
||||||
router.Get("/statistic/monthly", articlesController.ArticleMonthlyStats)
|
router.Get("/statistic/monthly", articlesController.ArticleMonthlyStats)
|
||||||
|
|
||||||
// Dynamic approval system routes
|
// Dynamic approval system routes
|
||||||
router.Post("/:id/submit-approval", articlesController.SubmitForApproval)
|
router.Post("/:id/submit-approval", articlesController.SubmitForApproval)
|
||||||
router.Get("/:id/approval-status", articlesController.GetApprovalStatus)
|
router.Get("/:id/approval-status", articlesController.GetApprovalStatus)
|
||||||
router.Get("/pending-approval", articlesController.GetPendingApprovals)
|
router.Get("/pending-approval", articlesController.GetPendingApprovals)
|
||||||
|
router.Get("/waiting-for-approval", articlesController.GetArticlesWaitingForApproval)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ type ArticlesController interface {
|
||||||
SubmitForApproval(c *fiber.Ctx) error
|
SubmitForApproval(c *fiber.Ctx) error
|
||||||
GetApprovalStatus(c *fiber.Ctx) error
|
GetApprovalStatus(c *fiber.Ctx) error
|
||||||
GetPendingApprovals(c *fiber.Ctx) error
|
GetPendingApprovals(c *fiber.Ctx) error
|
||||||
|
GetArticlesWaitingForApproval(c *fiber.Ctx) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewArticlesController(articlesService service.ArticlesService, log zerolog.Logger) ArticlesController {
|
func NewArticlesController(articlesService service.ArticlesService, log zerolog.Logger) ArticlesController {
|
||||||
|
|
@ -53,6 +54,7 @@ func NewArticlesController(articlesService service.ArticlesService, log zerolog.
|
||||||
// @Tags Articles
|
// @Tags Articles
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
// @Param req query request.ArticlesQueryRequest false "query parameters"
|
// @Param req query request.ArticlesQueryRequest false "query parameters"
|
||||||
// @Param req query paginator.Pagination false "pagination parameters"
|
// @Param req query paginator.Pagination false "pagination parameters"
|
||||||
// @Success 200 {object} response.Response
|
// @Success 200 {object} response.Response
|
||||||
|
|
@ -84,9 +86,12 @@ func (_i *articlesController) All(c *fiber.Ctx) error {
|
||||||
// Get ClientId from context
|
// Get ClientId from context
|
||||||
clientId := middleware.GetClientID(c)
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
|
// Get Authorization token from header
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
_i.Log.Info().Interface("clientId", clientId).Msg("")
|
_i.Log.Info().Interface("clientId", clientId).Msg("")
|
||||||
|
_i.Log.Info().Str("authToken", authToken).Msg("")
|
||||||
|
|
||||||
articlesData, paging, err := _i.articlesService.All(clientId, req)
|
articlesData, paging, err := _i.articlesService.All(clientId, authToken, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -188,6 +193,9 @@ func (_i *articlesController) Save(c *fiber.Ctx) error {
|
||||||
// Get ClientId from context
|
// Get ClientId from context
|
||||||
clientId := middleware.GetClientID(c)
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
|
_i.Log.Info().Interface("clientId", clientId).Msg("")
|
||||||
|
_i.Log.Info().Interface("authToken", authToken).Msg("")
|
||||||
|
|
||||||
dataResult, err := _i.articlesService.Save(clientId, *req, authToken)
|
dataResult, err := _i.articlesService.Save(clientId, *req, authToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -613,3 +621,46 @@ func (_i *articlesController) GetPendingApprovals(c *fiber.Ctx) error {
|
||||||
Meta: paging,
|
Meta: paging,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetArticlesWaitingForApproval
|
||||||
|
// @Summary Get articles waiting for approval by current user level
|
||||||
|
// @Description API for getting articles that are waiting for approval by the current user's level
|
||||||
|
// @Tags Articles
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string true "Client Key"
|
||||||
|
// @Param page query int false "Page number" default(1)
|
||||||
|
// @Param limit query int false "Items per page" default(10)
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /articles/waiting-for-approval [get]
|
||||||
|
func (_i *articlesController) GetArticlesWaitingForApproval(c *fiber.Ctx) error {
|
||||||
|
page, err := strconv.Atoi(c.Query("page", "1"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
limit, err := strconv.Atoi(c.Query("limit", "10"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get ClientId from context
|
||||||
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
|
// Get user level from middleware
|
||||||
|
userLevelId := middleware.GetUserLevelID(c)
|
||||||
|
|
||||||
|
responses, paging, err := _i.articlesService.GetArticlesWaitingForApproval(clientId, *userLevelId, page, limit)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Articles waiting for approval retrieved successfully"},
|
||||||
|
Data: responses,
|
||||||
|
Meta: paging,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ type articlesRepository struct {
|
||||||
|
|
||||||
// ArticlesRepository define interface of IArticlesRepository
|
// ArticlesRepository define interface of IArticlesRepository
|
||||||
type ArticlesRepository interface {
|
type ArticlesRepository interface {
|
||||||
GetAll(clientId *uuid.UUID, req request.ArticlesQueryRequest) (articless []*entity.Articles, paging paginator.Pagination, err error)
|
GetAll(clientId *uuid.UUID, userLevelId *uint, req request.ArticlesQueryRequest) (articless []*entity.Articles, paging paginator.Pagination, err error)
|
||||||
GetAllPublishSchedule(clientId *uuid.UUID) (articless []*entity.Articles, err error)
|
GetAllPublishSchedule(clientId *uuid.UUID) (articless []*entity.Articles, err error)
|
||||||
FindOne(clientId *uuid.UUID, id uint) (articles *entity.Articles, err error)
|
FindOne(clientId *uuid.UUID, id uint) (articles *entity.Articles, err error)
|
||||||
FindByFilename(clientId *uuid.UUID, thumbnailName string) (articleReturn *entity.Articles, err error)
|
FindByFilename(clientId *uuid.UUID, thumbnailName string) (articleReturn *entity.Articles, err error)
|
||||||
|
|
@ -44,7 +44,7 @@ func NewArticlesRepository(db *database.Database, log zerolog.Logger) ArticlesRe
|
||||||
}
|
}
|
||||||
|
|
||||||
// implement interface of IArticlesRepository
|
// implement interface of IArticlesRepository
|
||||||
func (_i *articlesRepository) GetAll(clientId *uuid.UUID, req request.ArticlesQueryRequest) (articless []*entity.Articles, paging paginator.Pagination, err error) {
|
func (_i *articlesRepository) GetAll(clientId *uuid.UUID, userLevelId *uint, req request.ArticlesQueryRequest) (articless []*entity.Articles, paging paginator.Pagination, err error) {
|
||||||
var count int64
|
var count int64
|
||||||
|
|
||||||
query := _i.DB.DB.Model(&entity.Articles{})
|
query := _i.DB.DB.Model(&entity.Articles{})
|
||||||
|
|
@ -54,6 +54,59 @@ func (_i *articlesRepository) GetAll(clientId *uuid.UUID, req request.ArticlesQu
|
||||||
query = query.Where("client_id = ?", clientId)
|
query = query.Where("client_id = ?", clientId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add approval workflow filtering based on user level
|
||||||
|
if userLevelId != nil {
|
||||||
|
// Complete filtering logic for article visibility
|
||||||
|
query = query.Where(`
|
||||||
|
(
|
||||||
|
-- Articles that don't require approval
|
||||||
|
(bypass_approval = true OR approval_exempt = true)
|
||||||
|
OR
|
||||||
|
-- Articles that are published
|
||||||
|
(is_publish = true)
|
||||||
|
OR
|
||||||
|
-- Articles where this user level is an approver in current step
|
||||||
|
(
|
||||||
|
workflow_id IS NOT NULL
|
||||||
|
AND current_approval_step > 0
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1 FROM article_approval_flows aaf
|
||||||
|
JOIN approval_workflow_steps aws ON aaf.workflow_id = aws.workflow_id
|
||||||
|
WHERE aaf.article_id = articles.id
|
||||||
|
AND aaf.status_id = 1
|
||||||
|
AND aws.required_user_level_id = ?
|
||||||
|
AND aws.step_order = aaf.current_step
|
||||||
|
)
|
||||||
|
)
|
||||||
|
OR
|
||||||
|
-- Articles created by users at same or lower hierarchy
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1 FROM users u
|
||||||
|
JOIN user_levels ul ON u.user_level_id = ul.id
|
||||||
|
WHERE u.id = articles.created_by_id
|
||||||
|
AND ul.level_number >= (
|
||||||
|
SELECT ul2.level_number FROM user_levels ul2 WHERE ul2.id = ?
|
||||||
|
)
|
||||||
|
)
|
||||||
|
OR
|
||||||
|
-- Articles where this user level is ANY approver in the workflow
|
||||||
|
(
|
||||||
|
workflow_id IS NOT NULL
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1 FROM article_approval_flows aaf
|
||||||
|
WHERE aaf.article_id = articles.id
|
||||||
|
AND aaf.status_id IN (1, 4)
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1 FROM approval_workflow_steps aws
|
||||||
|
WHERE aws.workflow_id = aaf.workflow_id
|
||||||
|
AND aws.required_user_level_id = ?
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
`, *userLevelId, *userLevelId, *userLevelId)
|
||||||
|
}
|
||||||
|
|
||||||
if req.CategoryId != nil {
|
if req.CategoryId != nil {
|
||||||
query = query.Joins("JOIN article_category_details acd ON acd.article_id = articles.id").
|
query = query.Joins("JOIN article_category_details acd ON acd.article_id = articles.id").
|
||||||
Where("acd.category_id = ?", req.CategoryId)
|
Where("acd.category_id = ?", req.CategoryId)
|
||||||
|
|
@ -207,6 +260,12 @@ func (_i *articlesRepository) Update(clientId *uuid.UUID, id uint, articles *ent
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove fields that could cause foreign key constraint violations
|
||||||
|
// delete(articlesMap, "workflow_id")
|
||||||
|
// delete(articlesMap, "id")
|
||||||
|
// delete(articlesMap, "created_at")
|
||||||
|
|
||||||
return _i.DB.DB.Model(&entity.Articles{}).
|
return _i.DB.DB.Model(&entity.Articles{}).
|
||||||
Where(&entity.Articles{ID: id}).
|
Where(&entity.Articles{ID: id}).
|
||||||
Updates(articlesMap).Error
|
Updates(articlesMap).Error
|
||||||
|
|
@ -224,9 +283,16 @@ func (_i *articlesRepository) UpdateSkipNull(clientId *uuid.UUID, id uint, artic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a copy to avoid modifying the original struct
|
||||||
|
updateData := *articles
|
||||||
|
// Clear fields that could cause foreign key constraint violations
|
||||||
|
updateData.WorkflowId = nil
|
||||||
|
updateData.ID = 0
|
||||||
|
updateData.CreatedAt = time.Time{}
|
||||||
|
|
||||||
return _i.DB.DB.Model(&entity.Articles{}).
|
return _i.DB.DB.Model(&entity.Articles{}).
|
||||||
Where(&entity.Articles{ID: id}).
|
Where(&entity.Articles{ID: id}).
|
||||||
Updates(articles).Error
|
Updates(&updateData).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *articlesRepository) Delete(clientId *uuid.UUID, id uint) error {
|
func (_i *articlesRepository) Delete(clientId *uuid.UUID, id uint) error {
|
||||||
|
|
@ -241,7 +307,9 @@ func (_i *articlesRepository) Delete(clientId *uuid.UUID, id uint) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return _i.DB.DB.Delete(&entity.Articles{}, id).Error
|
// Use soft delete by setting is_active to false
|
||||||
|
isActive := false
|
||||||
|
return _i.DB.DB.Model(&entity.Articles{}).Where("id = ?", id).Update("is_active", isActive).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *articlesRepository) SummaryStats(clientId *uuid.UUID, userID uint) (articleSummaryStats *response.ArticleSummaryStats, err error) {
|
func (_i *articlesRepository) SummaryStats(clientId *uuid.UUID, userID uint) (articleSummaryStats *response.ArticleSummaryStats, err error) {
|
||||||
|
|
|
||||||
|
|
@ -47,15 +47,15 @@ type articlesService struct {
|
||||||
Cfg *config.Config
|
Cfg *config.Config
|
||||||
UsersRepo usersRepository.UsersRepository
|
UsersRepo usersRepository.UsersRepository
|
||||||
MinioStorage *minioStorage.MinioStorage
|
MinioStorage *minioStorage.MinioStorage
|
||||||
|
|
||||||
// Dynamic approval system dependencies
|
// Dynamic approval system dependencies
|
||||||
ArticleApprovalFlowsRepo articleApprovalFlowsRepository.ArticleApprovalFlowsRepository
|
ArticleApprovalFlowsRepo articleApprovalFlowsRepository.ArticleApprovalFlowsRepository
|
||||||
ApprovalWorkflowsRepo approvalWorkflowsRepository.ApprovalWorkflowsRepository
|
ApprovalWorkflowsRepo approvalWorkflowsRepository.ApprovalWorkflowsRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
// ArticlesService define interface of IArticlesService
|
// ArticlesService define interface of IArticlesService
|
||||||
type ArticlesService interface {
|
type ArticlesService interface {
|
||||||
All(clientId *uuid.UUID, req request.ArticlesQueryRequest) (articles []*response.ArticlesResponse, paging paginator.Pagination, err error)
|
All(clientId *uuid.UUID, authToken string, req request.ArticlesQueryRequest) (articles []*response.ArticlesResponse, paging paginator.Pagination, err error)
|
||||||
Show(clientId *uuid.UUID, id uint) (articles *response.ArticlesResponse, err error)
|
Show(clientId *uuid.UUID, id uint) (articles *response.ArticlesResponse, err error)
|
||||||
ShowByOldId(clientId *uuid.UUID, oldId uint) (articles *response.ArticlesResponse, err error)
|
ShowByOldId(clientId *uuid.UUID, oldId uint) (articles *response.ArticlesResponse, err error)
|
||||||
Save(clientId *uuid.UUID, req request.ArticlesCreateRequest, authToken string) (articles *entity.Articles, err error)
|
Save(clientId *uuid.UUID, req request.ArticlesCreateRequest, authToken string) (articles *entity.Articles, err error)
|
||||||
|
|
@ -71,12 +71,13 @@ type ArticlesService interface {
|
||||||
ArticleMonthlyStats(clientId *uuid.UUID, authToken string, year *int) (articleMonthlyStats []*response.ArticleMonthlyStats, err error)
|
ArticleMonthlyStats(clientId *uuid.UUID, authToken string, year *int) (articleMonthlyStats []*response.ArticleMonthlyStats, err error)
|
||||||
PublishScheduling(clientId *uuid.UUID, id uint, publishSchedule string) error
|
PublishScheduling(clientId *uuid.UUID, id uint, publishSchedule string) error
|
||||||
ExecuteScheduling() error
|
ExecuteScheduling() error
|
||||||
|
|
||||||
// Dynamic approval system methods
|
// Dynamic approval system methods
|
||||||
SubmitForApproval(clientId *uuid.UUID, articleId uint, submittedById uint, workflowId *uint) error
|
SubmitForApproval(clientId *uuid.UUID, articleId uint, submittedById uint, workflowId *uint) error
|
||||||
GetApprovalStatus(clientId *uuid.UUID, articleId uint) (*response.ArticleApprovalStatusResponse, error)
|
GetApprovalStatus(clientId *uuid.UUID, articleId uint) (*response.ArticleApprovalStatusResponse, error)
|
||||||
|
GetArticlesWaitingForApproval(clientId *uuid.UUID, userLevelId uint, page, limit int) ([]*response.ArticleApprovalQueueResponse, paginator.Pagination, error)
|
||||||
GetPendingApprovals(clientId *uuid.UUID, userLevelId uint, page, limit int) ([]*response.ArticleApprovalQueueResponse, paginator.Pagination, error)
|
GetPendingApprovals(clientId *uuid.UUID, userLevelId uint, page, limit int) ([]*response.ArticleApprovalQueueResponse, paginator.Pagination, error)
|
||||||
|
|
||||||
// No-approval system methods
|
// No-approval system methods
|
||||||
CheckApprovalRequired(clientId *uuid.UUID, articleId uint, userId uint, userLevelId uint) (bool, error)
|
CheckApprovalRequired(clientId *uuid.UUID, articleId uint, userId uint, userLevelId uint) (bool, error)
|
||||||
AutoApproveArticle(clientId *uuid.UUID, articleId uint, reason string) error
|
AutoApproveArticle(clientId *uuid.UUID, articleId uint, reason string) error
|
||||||
|
|
@ -115,7 +116,17 @@ func NewArticlesService(
|
||||||
}
|
}
|
||||||
|
|
||||||
// All implement interface of ArticlesService
|
// All implement interface of ArticlesService
|
||||||
func (_i *articlesService) All(clientId *uuid.UUID, req request.ArticlesQueryRequest) (articless []*response.ArticlesResponse, paging paginator.Pagination, err error) {
|
func (_i *articlesService) All(clientId *uuid.UUID, authToken string, req request.ArticlesQueryRequest) (articless []*response.ArticlesResponse, paging paginator.Pagination, err error) {
|
||||||
|
// Extract userLevelId from authToken
|
||||||
|
var userLevelId *uint
|
||||||
|
if authToken != "" {
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user != nil {
|
||||||
|
userLevelId = &user.UserLevelId
|
||||||
|
_i.Log.Info().Interface("userLevelId", userLevelId).Msg("Extracted userLevelId from auth token")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if req.Category != nil {
|
if req.Category != nil {
|
||||||
findCategory, err := _i.ArticleCategoriesRepo.FindOneBySlug(clientId, *req.Category)
|
findCategory, err := _i.ArticleCategoriesRepo.FindOneBySlug(clientId, *req.Category)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -124,7 +135,7 @@ func (_i *articlesService) All(clientId *uuid.UUID, req request.ArticlesQueryReq
|
||||||
req.CategoryId = &findCategory.ID
|
req.CategoryId = &findCategory.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
results, paging, err := _i.Repo.GetAll(clientId, req)
|
results, paging, err := _i.Repo.GetAll(clientId, userLevelId, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -170,7 +181,7 @@ func (_i *articlesService) Save(clientId *uuid.UUID, req request.ArticlesCreateR
|
||||||
newReq := req.ToEntity()
|
newReq := req.ToEntity()
|
||||||
|
|
||||||
var userLevelNumber int
|
var userLevelNumber int
|
||||||
var userParentLevelId int
|
var approvalLevelId int
|
||||||
if req.CreatedById != nil {
|
if req.CreatedById != nil {
|
||||||
createdBy, err := _i.UsersRepo.FindOne(clientId, *req.CreatedById)
|
createdBy, err := _i.UsersRepo.FindOne(clientId, *req.CreatedById)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -178,16 +189,16 @@ func (_i *articlesService) Save(clientId *uuid.UUID, req request.ArticlesCreateR
|
||||||
}
|
}
|
||||||
newReq.CreatedById = &createdBy.ID
|
newReq.CreatedById = &createdBy.ID
|
||||||
userLevelNumber = createdBy.UserLevel.LevelNumber
|
userLevelNumber = createdBy.UserLevel.LevelNumber
|
||||||
if createdBy.UserLevel.ParentLevelId != nil {
|
|
||||||
userParentLevelId = *createdBy.UserLevel.ParentLevelId
|
// Find the next higher level for approval (level_number should be smaller)
|
||||||
}
|
approvalLevelId = _i.findNextApprovalLevel(clientId, userLevelNumber)
|
||||||
} else {
|
} else {
|
||||||
createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
newReq.CreatedById = &createdBy.ID
|
newReq.CreatedById = &createdBy.ID
|
||||||
userLevelNumber = createdBy.UserLevel.LevelNumber
|
userLevelNumber = createdBy.UserLevel.LevelNumber
|
||||||
if createdBy.UserLevel.ParentLevelId != nil {
|
|
||||||
userParentLevelId = *createdBy.UserLevel.ParentLevelId
|
// Find the next higher level for approval (level_number should be smaller)
|
||||||
}
|
approvalLevelId = _i.findNextApprovalLevel(clientId, userLevelNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
isDraft := true
|
isDraft := true
|
||||||
|
|
@ -219,19 +230,29 @@ func (_i *articlesService) Save(clientId *uuid.UUID, req request.ArticlesCreateR
|
||||||
newReq.CreatedAt = parsedTime
|
newReq.CreatedAt = parsedTime
|
||||||
}
|
}
|
||||||
|
|
||||||
// Approval
|
// Dynamic Approval Workflow System
|
||||||
statusIdOne := 1
|
statusIdOne := 1
|
||||||
statusIdTwo := 2
|
statusIdTwo := 2
|
||||||
isPublishFalse := false
|
isPublishFalse := false
|
||||||
|
|
||||||
|
// Get user info for approval logic
|
||||||
createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
|
||||||
|
// Check if user level requires approval
|
||||||
if createdBy != nil && *createdBy.UserLevel.IsApprovalActive == false {
|
if createdBy != nil && *createdBy.UserLevel.IsApprovalActive == false {
|
||||||
|
// User level doesn't require approval - auto publish
|
||||||
newReq.NeedApprovalFrom = nil
|
newReq.NeedApprovalFrom = nil
|
||||||
newReq.StatusId = &statusIdTwo
|
newReq.StatusId = &statusIdTwo
|
||||||
|
newReq.IsPublish = &isPublishFalse
|
||||||
|
newReq.PublishedAt = nil
|
||||||
|
newReq.BypassApproval = &[]bool{true}[0]
|
||||||
} else {
|
} else {
|
||||||
newReq.NeedApprovalFrom = &userParentLevelId
|
// User level requires approval - set to pending
|
||||||
|
newReq.NeedApprovalFrom = &approvalLevelId
|
||||||
newReq.StatusId = &statusIdOne
|
newReq.StatusId = &statusIdOne
|
||||||
newReq.IsPublish = &isPublishFalse
|
newReq.IsPublish = &isPublishFalse
|
||||||
newReq.PublishedAt = nil
|
newReq.PublishedAt = nil
|
||||||
|
newReq.BypassApproval = &[]bool{false}[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
saveArticleRes, err := _i.Repo.Create(clientId, newReq)
|
saveArticleRes, err := _i.Repo.Create(clientId, newReq)
|
||||||
|
|
@ -239,30 +260,63 @@ func (_i *articlesService) Save(clientId *uuid.UUID, req request.ArticlesCreateR
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Approval
|
// Dynamic Approval Workflow Assignment
|
||||||
var articleApproval *entity.ArticleApprovals
|
|
||||||
|
|
||||||
if createdBy != nil && *createdBy.UserLevel.IsApprovalActive == true {
|
if createdBy != nil && *createdBy.UserLevel.IsApprovalActive == true {
|
||||||
articleApproval = &entity.ArticleApprovals{
|
// 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,
|
ArticleId: saveArticleRes.ID,
|
||||||
ApprovalBy: *newReq.CreatedById,
|
ApprovalBy: *newReq.CreatedById,
|
||||||
StatusId: statusIdOne,
|
StatusId: statusIdOne,
|
||||||
Message: "Need Approval",
|
Message: "Need Approval",
|
||||||
ApprovalAtLevel: &userLevelNumber,
|
ApprovalAtLevel: &approvalLevelId,
|
||||||
|
}
|
||||||
|
_, err = _i.ArticleApprovalsRepo.Create(articleApproval)
|
||||||
|
if err != nil {
|
||||||
|
_i.Log.Error().Err(err).Msg("Failed to create legacy approval record")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
articleApproval = &entity.ArticleApprovals{
|
// Auto-publish for users who don't require approval
|
||||||
|
articleApproval := &entity.ArticleApprovals{
|
||||||
ArticleId: saveArticleRes.ID,
|
ArticleId: saveArticleRes.ID,
|
||||||
ApprovalBy: *newReq.CreatedById,
|
ApprovalBy: *newReq.CreatedById,
|
||||||
StatusId: statusIdTwo,
|
StatusId: statusIdTwo,
|
||||||
Message: "Publish Otomatis",
|
Message: "Publish Otomatis",
|
||||||
ApprovalAtLevel: nil,
|
ApprovalAtLevel: nil,
|
||||||
}
|
}
|
||||||
}
|
_, err = _i.ArticleApprovalsRepo.Create(articleApproval)
|
||||||
|
if err != nil {
|
||||||
_, err = _i.ArticleApprovalsRepo.Create(articleApproval)
|
_i.Log.Error().Err(err).Msg("Failed to create auto-approval record")
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var categoryIds []string
|
var categoryIds []string
|
||||||
|
|
@ -395,14 +449,7 @@ func (_i *articlesService) Update(clientId *uuid.UUID, id uint, req request.Arti
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *articlesService) Delete(clientId *uuid.UUID, id uint) error {
|
func (_i *articlesService) Delete(clientId *uuid.UUID, id uint) error {
|
||||||
result, err := _i.Repo.FindOne(clientId, id)
|
return _i.Repo.Delete(clientId, id)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
isActive := false
|
|
||||||
result.IsActive = &isActive
|
|
||||||
return _i.Repo.Update(clientId, id, result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *articlesService) Viewer(clientId *uuid.UUID, c *fiber.Ctx) (err error) {
|
func (_i *articlesService) Viewer(clientId *uuid.UUID, c *fiber.Ctx) (err error) {
|
||||||
|
|
@ -709,11 +756,12 @@ func (_i *articlesService) SubmitForApproval(clientId *uuid.UUID, articleId uint
|
||||||
|
|
||||||
// Create approval flow
|
// Create approval flow
|
||||||
approvalFlow := &entity.ArticleApprovalFlows{
|
approvalFlow := &entity.ArticleApprovalFlows{
|
||||||
ArticleId: articleId,
|
ArticleId: articleId,
|
||||||
WorkflowId: *workflowId,
|
WorkflowId: *workflowId,
|
||||||
CurrentStep: 1,
|
CurrentStep: 1,
|
||||||
StatusId: 1, // 1 = In Progress
|
StatusId: 1, // 1 = In Progress
|
||||||
ClientId: clientId,
|
ClientId: clientId,
|
||||||
|
SubmittedById: submittedById,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = _i.ArticleApprovalFlowsRepo.Create(clientId, approvalFlow)
|
_, err = _i.ArticleApprovalFlowsRepo.Create(clientId, approvalFlow)
|
||||||
|
|
@ -787,19 +835,19 @@ func (_i *articlesService) GetApprovalStatus(clientId *uuid.UUID, articleId uint
|
||||||
status = "revision_requested"
|
status = "revision_requested"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current approver info
|
// Get current approver info
|
||||||
var currentApprover *string
|
var currentApprover *string
|
||||||
var nextStep *string
|
var nextStep *string
|
||||||
if approvalFlow.CurrentStep <= totalSteps && approvalFlow.StatusId == 1 {
|
if approvalFlow.CurrentStep <= totalSteps && approvalFlow.StatusId == 1 {
|
||||||
if approvalFlow.CurrentStep < totalSteps {
|
if approvalFlow.CurrentStep < totalSteps {
|
||||||
// Array indexing starts from 0, so subtract 1 from CurrentStep
|
// Array indexing starts from 0, so subtract 1 from CurrentStep
|
||||||
nextStepIndex := approvalFlow.CurrentStep - 1
|
nextStepIndex := approvalFlow.CurrentStep - 1
|
||||||
if nextStepIndex >= 0 && nextStepIndex < len(workflowSteps) {
|
if nextStepIndex >= 0 && nextStepIndex < len(workflowSteps) {
|
||||||
nextStepInfo := workflowSteps[nextStepIndex]
|
nextStepInfo := workflowSteps[nextStepIndex]
|
||||||
nextStep = &nextStepInfo.RequiredUserLevel.Name
|
nextStep = &nextStepInfo.RequiredUserLevel.Name
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &response.ArticleApprovalStatusResponse{
|
return &response.ArticleApprovalStatusResponse{
|
||||||
ArticleId: articleId,
|
ArticleId: articleId,
|
||||||
|
|
@ -877,20 +925,20 @@ func (_i *articlesService) GetPendingApprovals(clientId *uuid.UUID, userLevelId
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &response.ArticleApprovalQueueResponse{
|
response := &response.ArticleApprovalQueueResponse{
|
||||||
ID: article.ID,
|
ID: article.ID,
|
||||||
Title: article.Title,
|
Title: article.Title,
|
||||||
Slug: article.Slug,
|
Slug: article.Slug,
|
||||||
Description: article.Description,
|
Description: article.Description,
|
||||||
CategoryName: categoryName,
|
CategoryName: categoryName,
|
||||||
AuthorName: authorName,
|
AuthorName: authorName,
|
||||||
SubmittedAt: flow.CreatedAt,
|
SubmittedAt: flow.CreatedAt,
|
||||||
CurrentStep: flow.CurrentStep,
|
CurrentStep: flow.CurrentStep,
|
||||||
TotalSteps: len(workflowSteps),
|
TotalSteps: len(workflowSteps),
|
||||||
Priority: priority,
|
Priority: priority,
|
||||||
DaysInQueue: daysInQueue,
|
DaysInQueue: daysInQueue,
|
||||||
WorkflowName: workflow.Name,
|
WorkflowName: workflow.Name,
|
||||||
CanApprove: true, // TODO: Implement based on user permissions
|
CanApprove: true, // TODO: Implement based on user permissions
|
||||||
EstimatedTime: "2-3 days", // TODO: Calculate based on historical data
|
EstimatedTime: "2-3 days", // TODO: Calculate based on historical data
|
||||||
}
|
}
|
||||||
|
|
||||||
responses = append(responses, response)
|
responses = append(responses, response)
|
||||||
|
|
@ -899,6 +947,40 @@ func (_i *articlesService) GetPendingApprovals(clientId *uuid.UUID, userLevelId
|
||||||
return responses, paging, nil
|
return responses, paging, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetArticlesWaitingForApproval gets articles that are waiting for approval by a specific user level
|
||||||
|
func (_i *articlesService) GetArticlesWaitingForApproval(clientId *uuid.UUID, userLevelId uint, page, limit int) ([]*response.ArticleApprovalQueueResponse, paginator.Pagination, error) {
|
||||||
|
// 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, &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
|
// CheckApprovalRequired checks if an article requires approval based on client settings
|
||||||
func (_i *articlesService) CheckApprovalRequired(clientId *uuid.UUID, articleId uint, userId uint, userLevelId uint) (bool, error) {
|
func (_i *articlesService) CheckApprovalRequired(clientId *uuid.UUID, articleId uint, userId uint, userLevelId uint) (bool, error) {
|
||||||
// Get article to check category and other properties
|
// Get article to check category and other properties
|
||||||
|
|
@ -920,7 +1002,7 @@ func (_i *articlesService) CheckApprovalRequired(clientId *uuid.UUID, articleId
|
||||||
// Check client-level settings (this would require the client approval settings service)
|
// Check client-level settings (this would require the client approval settings service)
|
||||||
// For now, we'll use a simple check
|
// For now, we'll use a simple check
|
||||||
// TODO: Integrate with ClientApprovalSettingsService
|
// TODO: Integrate with ClientApprovalSettingsService
|
||||||
|
|
||||||
// Check if workflow is set to no approval
|
// Check if workflow is set to no approval
|
||||||
if article.WorkflowId != nil {
|
if article.WorkflowId != nil {
|
||||||
workflow, err := _i.ApprovalWorkflowsRepo.FindOne(clientId, *article.WorkflowId)
|
workflow, err := _i.ApprovalWorkflowsRepo.FindOne(clientId, *article.WorkflowId)
|
||||||
|
|
@ -947,9 +1029,9 @@ func (_i *articlesService) AutoApproveArticle(clientId *uuid.UUID, articleId uin
|
||||||
|
|
||||||
// Update article status to approved
|
// Update article status to approved
|
||||||
updates := map[string]interface{}{
|
updates := map[string]interface{}{
|
||||||
"status_id": 2, // Assuming 2 = approved
|
"status_id": 2, // Assuming 2 = approved
|
||||||
"is_publish": true,
|
"is_publish": true,
|
||||||
"published_at": time.Now(),
|
"published_at": time.Now(),
|
||||||
"current_approval_step": 0, // Reset approval step
|
"current_approval_step": 0, // Reset approval step
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -964,7 +1046,7 @@ func (_i *articlesService) AutoApproveArticle(clientId *uuid.UUID, articleId uin
|
||||||
if currentApprovalStep, ok := updates["current_approval_step"].(int); ok {
|
if currentApprovalStep, ok := updates["current_approval_step"].(int); ok {
|
||||||
articleUpdate.CurrentApprovalStep = ¤tApprovalStep
|
articleUpdate.CurrentApprovalStep = ¤tApprovalStep
|
||||||
}
|
}
|
||||||
|
|
||||||
err = _i.Repo.Update(clientId, articleId, articleUpdate)
|
err = _i.Repo.Update(clientId, articleId, articleUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -1033,7 +1115,7 @@ func (_i *articlesService) SetArticleApprovalExempt(clientId *uuid.UUID, article
|
||||||
if currentApprovalStep, ok := updates["current_approval_step"].(int); ok {
|
if currentApprovalStep, ok := updates["current_approval_step"].(int); ok {
|
||||||
articleUpdate.CurrentApprovalStep = ¤tApprovalStep
|
articleUpdate.CurrentApprovalStep = ¤tApprovalStep
|
||||||
}
|
}
|
||||||
|
|
||||||
err := _i.Repo.Update(clientId, articleId, articleUpdate)
|
err := _i.Repo.Update(clientId, articleId, articleUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -1048,3 +1130,21 @@ func (_i *articlesService) SetArticleApprovalExempt(clientId *uuid.UUID, article
|
||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
# Client Approval Settings Module
|
|
||||||
|
|
||||||
Module ini mengatur konfigurasi approval per client, memungkinkan setiap client untuk memiliki setting approval yang berbeda.
|
|
||||||
|
|
||||||
## ✅ Status: SELESAI & SIAP DIGUNAKAN
|
|
||||||
|
|
||||||
### 🎯 Fitur Utama:
|
|
||||||
- **Konfigurasi Approval per Client** - Setiap client bisa mengatur apakah memerlukan approval atau tidak
|
|
||||||
- **Auto Publish Articles** - Artikel bisa di-publish otomatis jika tidak memerlukan approval
|
|
||||||
- **Exemption Rules** - User, role, atau category tertentu bisa di-exempt dari approval
|
|
||||||
- **Default Workflow** - Set workflow default untuk client
|
|
||||||
- **Dynamic Toggle** - Bisa mengaktifkan/menonaktifkan approval secara dinamis
|
|
||||||
|
|
||||||
### 📁 Struktur File:
|
|
||||||
```
|
|
||||||
app/module/client_approval_settings/
|
|
||||||
├── client_approval_settings.module.go # Module & Router
|
|
||||||
├── controller/
|
|
||||||
│ └── client_approval_settings.controller.go
|
|
||||||
├── request/
|
|
||||||
│ └── client_approval_settings.request.go
|
|
||||||
├── response/
|
|
||||||
│ └── client_approval_settings.response.go
|
|
||||||
├── repository/
|
|
||||||
│ ├── client_approval_settings.repository.go
|
|
||||||
│ └── client_approval_settings.repository.impl.go
|
|
||||||
├── service/
|
|
||||||
│ └── client_approval_settings.service.go
|
|
||||||
├── mapper/
|
|
||||||
│ └── client_approval_settings.mapper.go
|
|
||||||
└── README.md
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔗 API Endpoints:
|
|
||||||
- `GET /api/v1/client-approval-settings` - Get settings
|
|
||||||
- `PUT /api/v1/client-approval-settings` - Update settings
|
|
||||||
- `DELETE /api/v1/client-approval-settings` - Delete settings
|
|
||||||
- `POST /api/v1/client-approval-settings/toggle-approval` - Toggle approval
|
|
||||||
- `POST /api/v1/client-approval-settings/enable-approval` - Enable approval
|
|
||||||
- `POST /api/v1/client-approval-settings/disable-approval` - Disable approval
|
|
||||||
- `PUT /api/v1/client-approval-settings/default-workflow` - Set default workflow
|
|
||||||
- `POST /api/v1/client-approval-settings/exempt-users` - Manage exempt users
|
|
||||||
- `POST /api/v1/client-approval-settings/exempt-roles` - Manage exempt roles
|
|
||||||
- `POST /api/v1/client-approval-settings/exempt-categories` - Manage exempt categories
|
|
||||||
|
|
||||||
### 🔧 Integration:
|
|
||||||
- ✅ Terdaftar di Router (`app/router/api.go`)
|
|
||||||
- ✅ Terdaftar di Main (`main.go`)
|
|
||||||
- ✅ Terintegrasi dengan Articles Service
|
|
||||||
- ✅ Menggunakan Entity yang sudah ada
|
|
||||||
- ✅ Dependency Injection lengkap
|
|
||||||
|
|
||||||
### 💡 Cara Penggunaan:
|
|
||||||
|
|
||||||
1. **Get Settings Client**:
|
|
||||||
```bash
|
|
||||||
GET /api/v1/client-approval-settings
|
|
||||||
Headers: X-Client-Key: <client-id>
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Disable Approval untuk Client**:
|
|
||||||
```bash
|
|
||||||
POST /api/v1/client-approval-settings/toggle-approval
|
|
||||||
Body: {"requiresApproval": false}
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Set Auto Publish**:
|
|
||||||
```bash
|
|
||||||
PUT /api/v1/client-approval-settings
|
|
||||||
Body: {"autoPublishArticles": true}
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Add Exempt User**:
|
|
||||||
```bash
|
|
||||||
POST /api/v1/client-approval-settings/exempt-users
|
|
||||||
Body: {"userId": 123, "reason": "Admin user"}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🎉 Module ini sekarang siap digunakan untuk mengatur approval system yang dinamis per client!
|
|
||||||
|
|
@ -12,46 +12,54 @@ import (
|
||||||
// ClientApprovalSettingsRouter struct of ClientApprovalSettingsRouter
|
// ClientApprovalSettingsRouter struct of ClientApprovalSettingsRouter
|
||||||
type ClientApprovalSettingsRouter struct {
|
type ClientApprovalSettingsRouter struct {
|
||||||
App fiber.Router
|
App fiber.Router
|
||||||
Controller controller.ClientApprovalSettingsController
|
Controller *controller.Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClientApprovalSettingsModule register bulky of ClientApprovalSettings module
|
// NewClientApprovalSettingsModule register bulky of ClientApprovalSettings module
|
||||||
var NewClientApprovalSettingsModule = fx.Options(
|
var NewClientApprovalSettingsModule = fx.Options(
|
||||||
|
// register repository of ClientApprovalSettings module
|
||||||
fx.Provide(repository.NewClientApprovalSettingsRepository),
|
fx.Provide(repository.NewClientApprovalSettingsRepository),
|
||||||
|
|
||||||
|
// register service of ClientApprovalSettings module
|
||||||
fx.Provide(service.NewClientApprovalSettingsService),
|
fx.Provide(service.NewClientApprovalSettingsService),
|
||||||
fx.Provide(controller.NewClientApprovalSettingsController),
|
|
||||||
|
// register controller of ClientApprovalSettings module
|
||||||
|
fx.Provide(controller.NewController),
|
||||||
|
|
||||||
|
// register router of ClientApprovalSettings module
|
||||||
fx.Provide(NewClientApprovalSettingsRouter),
|
fx.Provide(NewClientApprovalSettingsRouter),
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewClientApprovalSettingsRouter create new ClientApprovalSettingsRouter
|
// NewClientApprovalSettingsRouter init ClientApprovalSettingsRouter
|
||||||
func NewClientApprovalSettingsRouter(
|
func NewClientApprovalSettingsRouter(fiber *fiber.App, controller *controller.Controller) *ClientApprovalSettingsRouter {
|
||||||
app *fiber.App,
|
|
||||||
controller controller.ClientApprovalSettingsController,
|
|
||||||
) *ClientApprovalSettingsRouter {
|
|
||||||
return &ClientApprovalSettingsRouter{
|
return &ClientApprovalSettingsRouter{
|
||||||
App: app,
|
App: fiber,
|
||||||
Controller: controller,
|
Controller: controller,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterClientApprovalSettingsRoutes register routes of ClientApprovalSettings
|
// RegisterClientApprovalSettingsRoutes register routes of ClientApprovalSettings
|
||||||
func (r *ClientApprovalSettingsRouter) RegisterClientApprovalSettingsRoutes() {
|
func (_i *ClientApprovalSettingsRouter) RegisterClientApprovalSettingsRoutes() {
|
||||||
// Group routes under /api/v1/client-approval-settings
|
// define controllers
|
||||||
api := r.App.Group("/api/v1/client-approval-settings")
|
clientApprovalSettingsController := _i.Controller.ClientApprovalSettings
|
||||||
|
|
||||||
// Basic CRUD routes
|
// define routes
|
||||||
api.Get("/", r.Controller.GetSettings)
|
_i.App.Route("/client-approval-settings", func(router fiber.Router) {
|
||||||
api.Put("/", r.Controller.UpdateSettings)
|
// Basic CRUD routes
|
||||||
api.Delete("/", r.Controller.DeleteSettings)
|
router.Post("/", clientApprovalSettingsController.CreateSettings)
|
||||||
|
router.Get("/", clientApprovalSettingsController.GetSettings)
|
||||||
|
router.Put("/", clientApprovalSettingsController.UpdateSettings)
|
||||||
|
router.Delete("/", clientApprovalSettingsController.DeleteSettings)
|
||||||
|
|
||||||
// Approval management routes
|
// Approval management routes
|
||||||
api.Post("/toggle-approval", r.Controller.ToggleApproval)
|
router.Post("/toggle-approval", clientApprovalSettingsController.ToggleApproval)
|
||||||
api.Post("/enable-approval", r.Controller.EnableApproval)
|
router.Post("/enable-approval", clientApprovalSettingsController.EnableApproval)
|
||||||
api.Post("/disable-approval", r.Controller.DisableApproval)
|
router.Post("/disable-approval", clientApprovalSettingsController.DisableApproval)
|
||||||
api.Put("/default-workflow", r.Controller.SetDefaultWorkflow)
|
router.Put("/default-workflow", clientApprovalSettingsController.SetDefaultWorkflow)
|
||||||
|
|
||||||
// Exemption management routes
|
// Exemption management routes
|
||||||
api.Post("/exempt-users", r.Controller.ManageExemptUsers)
|
router.Post("/exempt-users", clientApprovalSettingsController.ManageExemptUsers)
|
||||||
api.Post("/exempt-roles", r.Controller.ManageExemptRoles)
|
router.Post("/exempt-roles", clientApprovalSettingsController.ManageExemptRoles)
|
||||||
api.Post("/exempt-categories", r.Controller.ManageExemptCategories)
|
router.Post("/exempt-categories", clientApprovalSettingsController.ManageExemptCategories)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ type clientApprovalSettingsController struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientApprovalSettingsController interface {
|
type ClientApprovalSettingsController interface {
|
||||||
|
CreateSettings(c *fiber.Ctx) error
|
||||||
GetSettings(c *fiber.Ctx) error
|
GetSettings(c *fiber.Ctx) error
|
||||||
UpdateSettings(c *fiber.Ctx) error
|
UpdateSettings(c *fiber.Ctx) error
|
||||||
DeleteSettings(c *fiber.Ctx) error
|
DeleteSettings(c *fiber.Ctx) error
|
||||||
|
|
@ -41,6 +42,39 @@ func NewClientApprovalSettingsController(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateSettings ClientApprovalSettings
|
||||||
|
// @Summary Create Client Approval Settings
|
||||||
|
// @Description API for creating client approval settings
|
||||||
|
// @Tags ClientApprovalSettings
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param payload body request.CreateClientApprovalSettingsRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /client-approval-settings [post]
|
||||||
|
func (_i *clientApprovalSettingsController) CreateSettings(c *fiber.Ctx) error {
|
||||||
|
req := new(request.CreateClientApprovalSettingsRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get ClientId from context
|
||||||
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
|
settings, err := _i.clientApprovalSettingsService.Create(clientId, *req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Client approval settings created successfully"},
|
||||||
|
Data: settings,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// GetSettings ClientApprovalSettings
|
// GetSettings ClientApprovalSettings
|
||||||
// @Summary Get Client Approval Settings
|
// @Summary Get Client Approval Settings
|
||||||
// @Description API for getting client approval settings
|
// @Description API for getting client approval settings
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"web-medols-be/app/module/client_approval_settings/service"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
ClientApprovalSettings ClientApprovalSettingsController
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController(ClientApprovalSettingsService service.ClientApprovalSettingsService, log zerolog.Logger) *Controller {
|
||||||
|
return &Controller{
|
||||||
|
ClientApprovalSettings: NewClientApprovalSettingsController(ClientApprovalSettingsService, log),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package controller
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"web-medols-be/app/middleware"
|
||||||
"web-medols-be/app/module/user_levels/request"
|
"web-medols-be/app/module/user_levels/request"
|
||||||
"web-medols-be/app/module/user_levels/service"
|
"web-medols-be/app/module/user_levels/service"
|
||||||
"web-medols-be/utils/paginator"
|
"web-medols-be/utils/paginator"
|
||||||
|
|
@ -38,6 +39,7 @@ func NewUserLevelsController(userLevelsService service.UserLevelsService) UserLe
|
||||||
// @Description API for getting all UserLevels
|
// @Description API for getting all UserLevels
|
||||||
// @Tags UserLevels
|
// @Tags UserLevels
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string true "Client Key"
|
||||||
// @Param req query request.UserLevelsQueryRequest false "query parameters"
|
// @Param req query request.UserLevelsQueryRequest false "query parameters"
|
||||||
// @Param req query paginator.Pagination false "pagination parameters"
|
// @Param req query paginator.Pagination false "pagination parameters"
|
||||||
// @Success 200 {object} response.Response
|
// @Success 200 {object} response.Response
|
||||||
|
|
@ -60,7 +62,10 @@ func (_i *userLevelsController) All(c *fiber.Ctx) error {
|
||||||
req := reqContext.ToParamRequest()
|
req := reqContext.ToParamRequest()
|
||||||
req.Pagination = paginate
|
req.Pagination = paginate
|
||||||
|
|
||||||
userLevelsData, paging, err := _i.userLevelsService.All(req)
|
// Get ClientId from context
|
||||||
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
|
userLevelsData, paging, err := _i.userLevelsService.All(clientId, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -78,6 +83,7 @@ func (_i *userLevelsController) All(c *fiber.Ctx) error {
|
||||||
// @Description API for getting one UserLevels
|
// @Description API for getting one UserLevels
|
||||||
// @Tags UserLevels
|
// @Tags UserLevels
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string true "Client Key"
|
||||||
// @Param id path int true "UserLevels ID"
|
// @Param id path int true "UserLevels ID"
|
||||||
// @Success 200 {object} response.Response
|
// @Success 200 {object} response.Response
|
||||||
// @Failure 400 {object} response.BadRequestError
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
|
@ -90,7 +96,10 @@ func (_i *userLevelsController) Show(c *fiber.Ctx) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
userLevelsData, err := _i.userLevelsService.Show(uint(id))
|
// Get ClientId from context
|
||||||
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
|
userLevelsData, err := _i.userLevelsService.Show(clientId, uint(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -106,6 +115,7 @@ func (_i *userLevelsController) Show(c *fiber.Ctx) error {
|
||||||
// @Description API for getting one UserLevels
|
// @Description API for getting one UserLevels
|
||||||
// @Tags UserLevels
|
// @Tags UserLevels
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string true "Client Key"
|
||||||
// @Param alias path string true "UserLevels Alias"
|
// @Param alias path string true "UserLevels Alias"
|
||||||
// @Success 200 {object} response.Response
|
// @Success 200 {object} response.Response
|
||||||
// @Failure 400 {object} response.BadRequestError
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
|
@ -114,7 +124,11 @@ func (_i *userLevelsController) Show(c *fiber.Ctx) error {
|
||||||
// @Router /user-levels/alias/{alias} [get]
|
// @Router /user-levels/alias/{alias} [get]
|
||||||
func (_i *userLevelsController) ShowByAlias(c *fiber.Ctx) error {
|
func (_i *userLevelsController) ShowByAlias(c *fiber.Ctx) error {
|
||||||
alias := c.Params("alias")
|
alias := c.Params("alias")
|
||||||
userLevelsData, err := _i.userLevelsService.ShowByAlias(alias)
|
|
||||||
|
// Get ClientId from context
|
||||||
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
|
userLevelsData, err := _i.userLevelsService.ShowByAlias(clientId, alias)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -129,7 +143,9 @@ func (_i *userLevelsController) ShowByAlias(c *fiber.Ctx) error {
|
||||||
// @Description API for create UserLevels
|
// @Description API for create UserLevels
|
||||||
// @Tags UserLevels
|
// @Tags UserLevels
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string true "Client Key"
|
||||||
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
// @Param payload body request.UserLevelsCreateRequest true "Required payload"
|
// @Param payload body request.UserLevelsCreateRequest true "Required payload"
|
||||||
// @Success 200 {object} response.Response
|
// @Success 200 {object} response.Response
|
||||||
// @Failure 400 {object} response.BadRequestError
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
|
@ -142,7 +158,10 @@ func (_i *userLevelsController) Save(c *fiber.Ctx) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dataResult, err := _i.userLevelsService.Save(*req)
|
// Get ClientId from context
|
||||||
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
|
dataResult, err := _i.userLevelsService.Save(clientId, *req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -159,7 +178,9 @@ func (_i *userLevelsController) Save(c *fiber.Ctx) error {
|
||||||
// @Description API for update UserLevels
|
// @Description API for update UserLevels
|
||||||
// @Tags UserLevels
|
// @Tags UserLevels
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string true "Client Key"
|
||||||
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
// @Param payload body request.UserLevelsUpdateRequest true "Required payload"
|
// @Param payload body request.UserLevelsUpdateRequest true "Required payload"
|
||||||
// @Param id path int true "UserLevels ID"
|
// @Param id path int true "UserLevels ID"
|
||||||
// @Success 200 {object} response.Response
|
// @Success 200 {object} response.Response
|
||||||
|
|
@ -178,7 +199,10 @@ func (_i *userLevelsController) Update(c *fiber.Ctx) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = _i.userLevelsService.Update(uint(id), *req)
|
// Get ClientId from context
|
||||||
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
|
err = _i.userLevelsService.Update(clientId, uint(id), *req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -194,6 +218,8 @@ func (_i *userLevelsController) Update(c *fiber.Ctx) error {
|
||||||
// @Description API for delete UserLevels
|
// @Description API for delete UserLevels
|
||||||
// @Tags UserLevels
|
// @Tags UserLevels
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string true "Client Key"
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
// @Param id path int true "UserLevels ID"
|
// @Param id path int true "UserLevels ID"
|
||||||
// @Success 200 {object} response.Response
|
// @Success 200 {object} response.Response
|
||||||
|
|
@ -208,7 +234,10 @@ func (_i *userLevelsController) Delete(c *fiber.Ctx) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = _i.userLevelsService.Delete(uint(id))
|
// Get ClientId from context
|
||||||
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
|
err = _i.userLevelsService.Delete(clientId, uint(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -224,6 +253,7 @@ func (_i *userLevelsController) Delete(c *fiber.Ctx) error {
|
||||||
// @Description API for Enable Approval of Article
|
// @Description API for Enable Approval of Article
|
||||||
// @Tags UserLevels
|
// @Tags UserLevels
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string true "Client Key"
|
||||||
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
// @Param payload body request.UserLevelsApprovalRequest true "Required payload"
|
// @Param payload body request.UserLevelsApprovalRequest true "Required payload"
|
||||||
|
|
@ -238,13 +268,16 @@ func (_i *userLevelsController) EnableApproval(c *fiber.Ctx) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get ClientId from context
|
||||||
|
clientId := middleware.GetClientID(c)
|
||||||
|
|
||||||
ids := strings.Split(req.Ids, ",")
|
ids := strings.Split(req.Ids, ",")
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
idUint, err := strconv.ParseUint(id, 10, 64)
|
idUint, err := strconv.ParseUint(id, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = _i.userLevelsService.EnableApproval(uint(idUint), req.IsApprovalActive)
|
err = _i.userLevelsService.EnableApproval(clientId, uint(idUint), req.IsApprovalActive)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import (
|
||||||
"web-medols-be/app/module/user_levels/request"
|
"web-medols-be/app/module/user_levels/request"
|
||||||
"web-medols-be/utils/paginator"
|
"web-medols-be/utils/paginator"
|
||||||
utilSvc "web-medols-be/utils/service"
|
utilSvc "web-medols-be/utils/service"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type userLevelsRepository struct {
|
type userLevelsRepository struct {
|
||||||
|
|
@ -16,12 +18,12 @@ type userLevelsRepository struct {
|
||||||
|
|
||||||
// UserLevelsRepository define interface of IUserLevelsRepository
|
// UserLevelsRepository define interface of IUserLevelsRepository
|
||||||
type UserLevelsRepository interface {
|
type UserLevelsRepository interface {
|
||||||
GetAll(req request.UserLevelsQueryRequest) (userLevelss []*entity.UserLevels, paging paginator.Pagination, err error)
|
GetAll(clientId *uuid.UUID, req request.UserLevelsQueryRequest) (userLevelss []*entity.UserLevels, paging paginator.Pagination, err error)
|
||||||
FindOne(id uint) (userLevels *entity.UserLevels, err error)
|
FindOne(clientId *uuid.UUID, id uint) (userLevels *entity.UserLevels, err error)
|
||||||
FindOneByAlias(alias string) (userLevels *entity.UserLevels, err error)
|
FindOneByAlias(clientId *uuid.UUID, alias string) (userLevels *entity.UserLevels, err error)
|
||||||
Create(userLevels *entity.UserLevels) (userLevelsReturn *entity.UserLevels, err error)
|
Create(clientId *uuid.UUID, userLevels *entity.UserLevels) (userLevelsReturn *entity.UserLevels, err error)
|
||||||
Update(id uint, userLevels *entity.UserLevels) (err error)
|
Update(clientId *uuid.UUID, id uint, userLevels *entity.UserLevels) (err error)
|
||||||
Delete(id uint) (err error)
|
Delete(clientId *uuid.UUID, id uint) (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserLevelsRepository(db *database.Database) UserLevelsRepository {
|
func NewUserLevelsRepository(db *database.Database) UserLevelsRepository {
|
||||||
|
|
@ -31,12 +33,17 @@ func NewUserLevelsRepository(db *database.Database) UserLevelsRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
// implement interface of IUserLevelsRepository
|
// implement interface of IUserLevelsRepository
|
||||||
func (_i *userLevelsRepository) GetAll(req request.UserLevelsQueryRequest) (userLevelss []*entity.UserLevels, paging paginator.Pagination, err error) {
|
func (_i *userLevelsRepository) GetAll(clientId *uuid.UUID, req request.UserLevelsQueryRequest) (userLevelss []*entity.UserLevels, paging paginator.Pagination, err error) {
|
||||||
var count int64
|
var count int64
|
||||||
|
|
||||||
query := _i.DB.DB.Model(&entity.UserLevels{})
|
query := _i.DB.DB.Model(&entity.UserLevels{})
|
||||||
query = query.Where("is_active = ?", true)
|
query = query.Where("is_active = ?", true)
|
||||||
|
|
||||||
|
// Filter by client_id if provided
|
||||||
|
if clientId != nil {
|
||||||
|
query = query.Where("client_id = ?", clientId)
|
||||||
|
}
|
||||||
|
|
||||||
if req.Name != nil && *req.Name != "" {
|
if req.Name != nil && *req.Name != "" {
|
||||||
name := strings.ToLower(*req.Name)
|
name := strings.ToLower(*req.Name)
|
||||||
query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(name)+"%")
|
query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(name)+"%")
|
||||||
|
|
@ -73,28 +80,52 @@ func (_i *userLevelsRepository) GetAll(req request.UserLevelsQueryRequest) (user
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *userLevelsRepository) FindOne(id uint) (userLevels *entity.UserLevels, err error) {
|
func (_i *userLevelsRepository) FindOne(clientId *uuid.UUID, id uint) (userLevels *entity.UserLevels, err error) {
|
||||||
if err := _i.DB.DB.First(&userLevels, id).Error; err != nil {
|
query := _i.DB.DB.Where("id = ?", id)
|
||||||
|
if clientId != nil {
|
||||||
|
query = query.Where("client_id = ?", clientId)
|
||||||
|
}
|
||||||
|
if err := query.First(&userLevels).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return userLevels, nil
|
return userLevels, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *userLevelsRepository) FindOneByAlias(alias string) (userLevels *entity.UserLevels, err error) {
|
func (_i *userLevelsRepository) FindOneByAlias(clientId *uuid.UUID, alias string) (userLevels *entity.UserLevels, err error) {
|
||||||
if err := _i.DB.DB.Where("alias_name = ?", strings.ToLower(alias)).First(&userLevels).Error; err != nil {
|
query := _i.DB.DB.Where("alias_name = ?", strings.ToLower(alias))
|
||||||
|
if clientId != nil {
|
||||||
|
query = query.Where("client_id = ?", clientId)
|
||||||
|
}
|
||||||
|
if err := query.First(&userLevels).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return userLevels, nil
|
return userLevels, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *userLevelsRepository) Create(userLevels *entity.UserLevels) (userLevelsReturn *entity.UserLevels, err error) {
|
func (_i *userLevelsRepository) Create(clientId *uuid.UUID, userLevels *entity.UserLevels) (userLevelsReturn *entity.UserLevels, err error) {
|
||||||
|
// Set client ID
|
||||||
|
if clientId != nil {
|
||||||
|
userLevels.ClientId = clientId
|
||||||
|
}
|
||||||
|
|
||||||
result := _i.DB.DB.Create(userLevels)
|
result := _i.DB.DB.Create(userLevels)
|
||||||
return userLevels, result.Error
|
return userLevels, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *userLevelsRepository) Update(id uint, userLevels *entity.UserLevels) (err error) {
|
func (_i *userLevelsRepository) Update(clientId *uuid.UUID, id uint, userLevels *entity.UserLevels) (err error) {
|
||||||
|
// Validate client access
|
||||||
|
if clientId != nil {
|
||||||
|
var count int64
|
||||||
|
if err := _i.DB.DB.Model(&entity.UserLevels{}).Where("id = ? AND client_id = ?", id, clientId).Count(&count).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
return fmt.Errorf("access denied to this resource")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
userLevelsMap, err := utilSvc.StructToMap(userLevels)
|
userLevelsMap, err := utilSvc.StructToMap(userLevels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -104,6 +135,17 @@ func (_i *userLevelsRepository) Update(id uint, userLevels *entity.UserLevels) (
|
||||||
Updates(userLevelsMap).Error
|
Updates(userLevelsMap).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *userLevelsRepository) Delete(id uint) error {
|
func (_i *userLevelsRepository) Delete(clientId *uuid.UUID, id uint) error {
|
||||||
|
// Validate client access
|
||||||
|
if clientId != nil {
|
||||||
|
var count int64
|
||||||
|
if err := _i.DB.DB.Model(&entity.UserLevels{}).Where("id = ? AND client_id = ?", id, clientId).Count(&count).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
return fmt.Errorf("access denied to this resource")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return _i.DB.DB.Delete(&entity.UserLevels{}, id).Error
|
return _i.DB.DB.Delete(&entity.UserLevels{}, id).Error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"web-medols-be/app/database/entity"
|
"web-medols-be/app/database/entity"
|
||||||
"web-medols-be/app/module/user_levels/mapper"
|
"web-medols-be/app/module/user_levels/mapper"
|
||||||
"web-medols-be/app/module/user_levels/repository"
|
"web-medols-be/app/module/user_levels/repository"
|
||||||
"web-medols-be/app/module/user_levels/request"
|
"web-medols-be/app/module/user_levels/request"
|
||||||
"web-medols-be/app/module/user_levels/response"
|
"web-medols-be/app/module/user_levels/response"
|
||||||
"web-medols-be/utils/paginator"
|
"web-medols-be/utils/paginator"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserLevelsService
|
// UserLevelsService
|
||||||
|
|
@ -18,13 +20,13 @@ type userLevelsService struct {
|
||||||
|
|
||||||
// UserLevelsService define interface of IUserLevelsService
|
// UserLevelsService define interface of IUserLevelsService
|
||||||
type UserLevelsService interface {
|
type UserLevelsService interface {
|
||||||
All(req request.UserLevelsQueryRequest) (userLevels []*response.UserLevelsResponse, paging paginator.Pagination, err error)
|
All(clientId *uuid.UUID, req request.UserLevelsQueryRequest) (userLevels []*response.UserLevelsResponse, paging paginator.Pagination, err error)
|
||||||
Show(id uint) (userLevels *response.UserLevelsResponse, err error)
|
Show(clientId *uuid.UUID, id uint) (userLevels *response.UserLevelsResponse, err error)
|
||||||
ShowByAlias(alias string) (userLevels *response.UserLevelsResponse, err error)
|
ShowByAlias(clientId *uuid.UUID, alias string) (userLevels *response.UserLevelsResponse, err error)
|
||||||
Save(req request.UserLevelsCreateRequest) (userLevels *entity.UserLevels, err error)
|
Save(clientId *uuid.UUID, req request.UserLevelsCreateRequest) (userLevels *entity.UserLevels, err error)
|
||||||
Update(id uint, req request.UserLevelsUpdateRequest) (err error)
|
Update(clientId *uuid.UUID, id uint, req request.UserLevelsUpdateRequest) (err error)
|
||||||
Delete(id uint) error
|
Delete(clientId *uuid.UUID, id uint) error
|
||||||
EnableApproval(id uint, isApprovalActive bool) (err error)
|
EnableApproval(clientId *uuid.UUID, id uint, isApprovalActive bool) (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUserLevelsService init UserLevelsService
|
// NewUserLevelsService init UserLevelsService
|
||||||
|
|
@ -37,8 +39,8 @@ func NewUserLevelsService(repo repository.UserLevelsRepository, log zerolog.Logg
|
||||||
}
|
}
|
||||||
|
|
||||||
// All implement interface of UserLevelsService
|
// All implement interface of UserLevelsService
|
||||||
func (_i *userLevelsService) All(req request.UserLevelsQueryRequest) (userLevelss []*response.UserLevelsResponse, paging paginator.Pagination, err error) {
|
func (_i *userLevelsService) All(clientId *uuid.UUID, req request.UserLevelsQueryRequest) (userLevelss []*response.UserLevelsResponse, paging paginator.Pagination, err error) {
|
||||||
results, paging, err := _i.Repo.GetAll(req)
|
results, paging, err := _i.Repo.GetAll(clientId, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -50,8 +52,8 @@ func (_i *userLevelsService) All(req request.UserLevelsQueryRequest) (userLevels
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *userLevelsService) Show(id uint) (userLevels *response.UserLevelsResponse, err error) {
|
func (_i *userLevelsService) Show(clientId *uuid.UUID, id uint) (userLevels *response.UserLevelsResponse, err error) {
|
||||||
result, err := _i.Repo.FindOne(id)
|
result, err := _i.Repo.FindOne(clientId, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -59,8 +61,8 @@ func (_i *userLevelsService) Show(id uint) (userLevels *response.UserLevelsRespo
|
||||||
return mapper.UserLevelsResponseMapper(result), nil
|
return mapper.UserLevelsResponseMapper(result), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *userLevelsService) ShowByAlias(alias string) (userLevels *response.UserLevelsResponse, err error) {
|
func (_i *userLevelsService) ShowByAlias(clientId *uuid.UUID, alias string) (userLevels *response.UserLevelsResponse, err error) {
|
||||||
result, err := _i.Repo.FindOneByAlias(alias)
|
result, err := _i.Repo.FindOneByAlias(clientId, alias)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -68,10 +70,14 @@ func (_i *userLevelsService) ShowByAlias(alias string) (userLevels *response.Use
|
||||||
return mapper.UserLevelsResponseMapper(result), nil
|
return mapper.UserLevelsResponseMapper(result), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *userLevelsService) Save(req request.UserLevelsCreateRequest) (userLevels *entity.UserLevels, err error) {
|
func (_i *userLevelsService) Save(clientId *uuid.UUID, req request.UserLevelsCreateRequest) (userLevels *entity.UserLevels, err error) {
|
||||||
_i.Log.Info().Interface("data", req).Msg("")
|
_i.Log.Info().Interface("data", req).Msg("")
|
||||||
|
|
||||||
saveUserLevelsRes, err := _i.Repo.Create(req.ToEntity())
|
entity := req.ToEntity()
|
||||||
|
// Set ClientId on entity
|
||||||
|
entity.ClientId = clientId
|
||||||
|
|
||||||
|
saveUserLevelsRes, err := _i.Repo.Create(clientId, entity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -79,32 +85,36 @@ func (_i *userLevelsService) Save(req request.UserLevelsCreateRequest) (userLeve
|
||||||
return saveUserLevelsRes, nil
|
return saveUserLevelsRes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *userLevelsService) Update(id uint, req request.UserLevelsUpdateRequest) (err error) {
|
func (_i *userLevelsService) Update(clientId *uuid.UUID, id uint, req request.UserLevelsUpdateRequest) (err error) {
|
||||||
//_i.Log.Info().Interface("data", req).Msg("")
|
//_i.Log.Info().Interface("data", req).Msg("")
|
||||||
|
|
||||||
_i.Log.Info().Interface("data", req.ToEntity()).Msg("")
|
_i.Log.Info().Interface("data", req.ToEntity()).Msg("")
|
||||||
|
|
||||||
return _i.Repo.Update(id, req.ToEntity())
|
// Set ClientId on entity
|
||||||
|
entity := req.ToEntity()
|
||||||
|
entity.ClientId = clientId
|
||||||
|
|
||||||
|
return _i.Repo.Update(clientId, id, entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *userLevelsService) Delete(id uint) error {
|
func (_i *userLevelsService) Delete(clientId *uuid.UUID, id uint) error {
|
||||||
result, err := _i.Repo.FindOne(id)
|
result, err := _i.Repo.FindOne(clientId, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
isActive := false
|
isActive := false
|
||||||
result.IsActive = &isActive
|
result.IsActive = &isActive
|
||||||
return _i.Repo.Update(id, result)
|
return _i.Repo.Update(clientId, id, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *userLevelsService) EnableApproval(id uint, isApprovalActive bool) (err error) {
|
func (_i *userLevelsService) EnableApproval(clientId *uuid.UUID, id uint, isApprovalActive bool) (err error) {
|
||||||
result, err := _i.Repo.FindOne(id)
|
result, err := _i.Repo.FindOne(clientId, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
*result.IsApprovalActive = isApprovalActive
|
*result.IsApprovalActive = isApprovalActive
|
||||||
|
|
||||||
return _i.Repo.Update(id, result)
|
return _i.Repo.Update(clientId, id, result)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,13 @@ import (
|
||||||
"web-medols-be/app/database/entity/users"
|
"web-medols-be/app/database/entity/users"
|
||||||
userLevelsRepository "web-medols-be/app/module/user_levels/repository"
|
userLevelsRepository "web-medols-be/app/module/user_levels/repository"
|
||||||
res "web-medols-be/app/module/users/response"
|
res "web-medols-be/app/module/users/response"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func UsersResponseMapper(usersReq *users.Users, userLevelsRepo userLevelsRepository.UserLevelsRepository) (usersRes *res.UsersResponse) {
|
func UsersResponseMapper(usersReq *users.Users, userLevelsRepo userLevelsRepository.UserLevelsRepository, clientId *uuid.UUID) (usersRes *res.UsersResponse) {
|
||||||
if usersReq != nil {
|
if usersReq != nil {
|
||||||
findUserLevel, _ := userLevelsRepo.FindOne(usersReq.UserLevelId)
|
findUserLevel, _ := userLevelsRepo.FindOne(clientId, usersReq.UserLevelId)
|
||||||
userLevelGroup := ""
|
userLevelGroup := ""
|
||||||
if findUserLevel != nil {
|
if findUserLevel != nil {
|
||||||
userLevelGroup = findUserLevel.AliasName
|
userLevelGroup = findUserLevel.AliasName
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
paseto "aidanwoods.dev/go-paseto"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Nerzal/gocloak/v13"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"web-medols-be/app/database/entity"
|
"web-medols-be/app/database/entity"
|
||||||
|
|
@ -20,6 +16,11 @@ import (
|
||||||
"web-medols-be/config/config"
|
"web-medols-be/config/config"
|
||||||
"web-medols-be/utils/paginator"
|
"web-medols-be/utils/paginator"
|
||||||
utilSvc "web-medols-be/utils/service"
|
utilSvc "web-medols-be/utils/service"
|
||||||
|
|
||||||
|
paseto "aidanwoods.dev/go-paseto"
|
||||||
|
"github.com/Nerzal/gocloak/v13"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UsersService
|
// UsersService
|
||||||
|
|
@ -73,7 +74,7 @@ func (_i *usersService) All(clientId *uuid.UUID, req request.UsersQueryRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
users = append(users, mapper.UsersResponseMapper(result, _i.UserLevelsRepo))
|
users = append(users, mapper.UsersResponseMapper(result, _i.UserLevelsRepo, clientId))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
@ -85,7 +86,7 @@ func (_i *usersService) Show(clientId *uuid.UUID, id uint) (users *response.User
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return mapper.UsersResponseMapper(result, _i.UserLevelsRepo), nil
|
return mapper.UsersResponseMapper(result, _i.UserLevelsRepo, clientId), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *usersService) ShowByUsername(clientId *uuid.UUID, username string) (users *response.UsersResponse, err error) {
|
func (_i *usersService) ShowByUsername(clientId *uuid.UUID, username string) (users *response.UsersResponse, err error) {
|
||||||
|
|
@ -94,13 +95,13 @@ func (_i *usersService) ShowByUsername(clientId *uuid.UUID, username string) (us
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return mapper.UsersResponseMapper(result, _i.UserLevelsRepo), nil
|
return mapper.UsersResponseMapper(result, _i.UserLevelsRepo, clientId), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *usersService) ShowUserInfo(clientId *uuid.UUID, authToken string) (users *response.UsersResponse, err error) {
|
func (_i *usersService) ShowUserInfo(clientId *uuid.UUID, authToken string) (users *response.UsersResponse, err error) {
|
||||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
|
userInfo := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
|
||||||
|
|
||||||
return mapper.UsersResponseMapper(userInfo, _i.UserLevelsRepo), nil
|
return mapper.UsersResponseMapper(userInfo, _i.UserLevelsRepo, clientId), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *usersService) Save(clientId *uuid.UUID, req request.UsersCreateRequest, authToken string) (userReturn *users.Users, err error) {
|
func (_i *usersService) Save(clientId *uuid.UUID, req request.UsersCreateRequest, authToken string) (userReturn *users.Users, err error) {
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ body-limit = 1048576000 # "100 * 1024 * 1024"
|
||||||
|
|
||||||
[db.postgres]
|
[db.postgres]
|
||||||
dsn = "postgresql://medols_user:MedolsDB@2025@38.47.180.165:5432/medols_db" # <driver>://<username>:<password>@<host>:<port>/<database>
|
dsn = "postgresql://medols_user:MedolsDB@2025@38.47.180.165:5432/medols_db" # <driver>://<username>:<password>@<host>:<port>/<database>
|
||||||
log-mode = "NONE"
|
log-mode = "ERROR"
|
||||||
migrate = true
|
migrate = false
|
||||||
seed = true
|
seed = false
|
||||||
|
|
||||||
[logger]
|
[logger]
|
||||||
log-dir = "debug.log"
|
log-dir = "debug.log"
|
||||||
|
|
|
||||||
|
|
@ -2417,6 +2417,13 @@ const docTemplate = `{
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Period filter (daily, weekly, monthly)",
|
"description": "Period filter (daily, weekly, monthly)",
|
||||||
|
|
@ -2485,10 +2492,11 @@ const docTemplate = `{
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "string",
|
||||||
"description": "User Level ID filter",
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
"name": "userLevelId",
|
"description": "Insert your access token",
|
||||||
"in": "query"
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
|
|
@ -2640,6 +2648,25 @@ const docTemplate = `{
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Include article preview",
|
||||||
|
"name": "includePreview",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Show only urgent articles",
|
||||||
|
"name": "urgentOnly",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"name": "count",
|
"name": "count",
|
||||||
|
|
@ -2730,10 +2757,11 @@ const docTemplate = `{
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "string",
|
||||||
"description": "User Level ID filter",
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
"name": "userLevelId",
|
"description": "Insert your access token",
|
||||||
"in": "query"
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
|
@ -2824,6 +2852,13 @@ const docTemplate = `{
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Submit for approval data",
|
"description": "Submit for approval data",
|
||||||
"name": "req",
|
"name": "req",
|
||||||
|
|
@ -2881,6 +2916,13 @@ const docTemplate = `{
|
||||||
"name": "X-Client-Key",
|
"name": "X-Client-Key",
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
|
|
@ -2987,6 +3029,13 @@ const docTemplate = `{
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "ArticleApprovalFlows ID",
|
"description": "ArticleApprovalFlows ID",
|
||||||
|
|
@ -3052,6 +3101,13 @@ const docTemplate = `{
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "ArticleApprovalFlows ID",
|
"description": "ArticleApprovalFlows ID",
|
||||||
|
|
@ -3117,6 +3173,13 @@ const docTemplate = `{
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "ArticleApprovalFlows ID",
|
"description": "ArticleApprovalFlows ID",
|
||||||
|
|
@ -3182,6 +3245,13 @@ const docTemplate = `{
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "ArticleApprovalFlows ID",
|
"description": "ArticleApprovalFlows ID",
|
||||||
|
|
@ -6747,6 +6817,13 @@ const docTemplate = `{
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"name": "category",
|
"name": "category",
|
||||||
|
|
@ -7488,6 +7565,69 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/articles/waiting-for-approval": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "API for getting articles that are waiting for approval by the current user's level",
|
||||||
|
"tags": [
|
||||||
|
"Articles"
|
||||||
|
],
|
||||||
|
"summary": "Get articles waiting for approval by current user level",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"default": 1,
|
||||||
|
"description": "Page number",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"default": 10,
|
||||||
|
"description": "Items per page",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.BadRequestError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.UnauthorizedError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.InternalServerError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/articles/{id}": {
|
"/articles/{id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
@ -8184,6 +8324,62 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "API for creating client approval settings",
|
||||||
|
"tags": [
|
||||||
|
"ClientApprovalSettings"
|
||||||
|
],
|
||||||
|
"summary": "Create Client Approval Settings",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Insert the X-Client-Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Required payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/request.CreateClientApprovalSettingsRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.BadRequestError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.UnauthorizedError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.InternalServerError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
|
|
@ -12353,6 +12549,13 @@ const docTemplate = `{
|
||||||
],
|
],
|
||||||
"summary": "Get all UserLevels",
|
"summary": "Get all UserLevels",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"name": "levelNumber",
|
"name": "levelNumber",
|
||||||
|
|
@ -12453,12 +12656,26 @@ const docTemplate = `{
|
||||||
],
|
],
|
||||||
"summary": "Create UserLevels",
|
"summary": "Create UserLevels",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Insert the X-Csrf-Token",
|
"description": "Insert the X-Csrf-Token",
|
||||||
"name": "X-Csrf-Token",
|
"name": "X-Csrf-Token",
|
||||||
"in": "header"
|
"in": "header"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Required payload",
|
"description": "Required payload",
|
||||||
"name": "payload",
|
"name": "payload",
|
||||||
|
|
@ -12510,6 +12727,13 @@ const docTemplate = `{
|
||||||
],
|
],
|
||||||
"summary": "Get one UserLevels",
|
"summary": "Get one UserLevels",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "UserLevels Alias",
|
"description": "UserLevels Alias",
|
||||||
|
|
@ -12559,6 +12783,13 @@ const docTemplate = `{
|
||||||
],
|
],
|
||||||
"summary": "EnableApproval Articles",
|
"summary": "EnableApproval Articles",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Insert the X-Csrf-Token",
|
"description": "Insert the X-Csrf-Token",
|
||||||
|
|
@ -12623,6 +12854,13 @@ const docTemplate = `{
|
||||||
],
|
],
|
||||||
"summary": "Get one UserLevels",
|
"summary": "Get one UserLevels",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "UserLevels ID",
|
"description": "UserLevels ID",
|
||||||
|
|
@ -12670,12 +12908,26 @@ const docTemplate = `{
|
||||||
],
|
],
|
||||||
"summary": "update UserLevels",
|
"summary": "update UserLevels",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Insert the X-Csrf-Token",
|
"description": "Insert the X-Csrf-Token",
|
||||||
"name": "X-Csrf-Token",
|
"name": "X-Csrf-Token",
|
||||||
"in": "header"
|
"in": "header"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Required payload",
|
"description": "Required payload",
|
||||||
"name": "payload",
|
"name": "payload",
|
||||||
|
|
@ -12732,6 +12984,20 @@ const docTemplate = `{
|
||||||
],
|
],
|
||||||
"summary": "delete UserLevels",
|
"summary": "delete UserLevels",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Insert the X-Csrf-Token",
|
"description": "Insert the X-Csrf-Token",
|
||||||
|
|
@ -14870,6 +15136,9 @@ const docTemplate = `{
|
||||||
"name"
|
"name"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"autoPublish": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
@ -14881,6 +15150,15 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"requiresApproval": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"steps": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/request.ApprovalWorkflowStepRequest"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -14891,6 +15169,9 @@ const docTemplate = `{
|
||||||
"name"
|
"name"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"autoPublish": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
@ -14902,6 +15183,9 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"requiresApproval": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -14913,6 +15197,9 @@ const docTemplate = `{
|
||||||
"steps"
|
"steps"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"autoPublish": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
@ -14925,6 +15212,9 @@ const docTemplate = `{
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"requiresApproval": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"steps": {
|
"steps": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"minItems": 1,
|
"minItems": 1,
|
||||||
|
|
@ -14942,6 +15232,9 @@ const docTemplate = `{
|
||||||
"steps"
|
"steps"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"autoPublish": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
@ -14954,6 +15247,9 @@ const docTemplate = `{
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"requiresApproval": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"steps": {
|
"steps": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"minItems": 1,
|
"minItems": 1,
|
||||||
|
|
@ -15609,6 +15905,57 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"request.CreateClientApprovalSettingsRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"requires_approval"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"approval_exempt_categories": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"approval_exempt_roles": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"approval_exempt_users": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"auto_publish_articles": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"default_workflow_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"is_active": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"require_approval_for": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requires_approval": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"skip_approval_for": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"request.CustomStaticPagesCreateRequest": {
|
"request.CustomStaticPagesCreateRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
||||||
|
|
@ -2406,6 +2406,13 @@
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Period filter (daily, weekly, monthly)",
|
"description": "Period filter (daily, weekly, monthly)",
|
||||||
|
|
@ -2474,10 +2481,11 @@
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "string",
|
||||||
"description": "User Level ID filter",
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
"name": "userLevelId",
|
"description": "Insert your access token",
|
||||||
"in": "query"
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
|
|
@ -2629,6 +2637,25 @@
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Include article preview",
|
||||||
|
"name": "includePreview",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Show only urgent articles",
|
||||||
|
"name": "urgentOnly",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"name": "count",
|
"name": "count",
|
||||||
|
|
@ -2719,10 +2746,11 @@
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "string",
|
||||||
"description": "User Level ID filter",
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
"name": "userLevelId",
|
"description": "Insert your access token",
|
||||||
"in": "query"
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
|
@ -2813,6 +2841,13 @@
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Submit for approval data",
|
"description": "Submit for approval data",
|
||||||
"name": "req",
|
"name": "req",
|
||||||
|
|
@ -2870,6 +2905,13 @@
|
||||||
"name": "X-Client-Key",
|
"name": "X-Client-Key",
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
|
|
@ -2976,6 +3018,13 @@
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "ArticleApprovalFlows ID",
|
"description": "ArticleApprovalFlows ID",
|
||||||
|
|
@ -3041,6 +3090,13 @@
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "ArticleApprovalFlows ID",
|
"description": "ArticleApprovalFlows ID",
|
||||||
|
|
@ -3106,6 +3162,13 @@
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "ArticleApprovalFlows ID",
|
"description": "ArticleApprovalFlows ID",
|
||||||
|
|
@ -3171,6 +3234,13 @@
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "ArticleApprovalFlows ID",
|
"description": "ArticleApprovalFlows ID",
|
||||||
|
|
@ -6736,6 +6806,13 @@
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"name": "category",
|
"name": "category",
|
||||||
|
|
@ -7477,6 +7554,69 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/articles/waiting-for-approval": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "API for getting articles that are waiting for approval by the current user's level",
|
||||||
|
"tags": [
|
||||||
|
"Articles"
|
||||||
|
],
|
||||||
|
"summary": "Get articles waiting for approval by current user level",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"default": 1,
|
||||||
|
"description": "Page number",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"default": 10,
|
||||||
|
"description": "Items per page",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.BadRequestError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.UnauthorizedError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.InternalServerError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/articles/{id}": {
|
"/articles/{id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
@ -8173,6 +8313,62 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "API for creating client approval settings",
|
||||||
|
"tags": [
|
||||||
|
"ClientApprovalSettings"
|
||||||
|
],
|
||||||
|
"summary": "Create Client Approval Settings",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Insert the X-Client-Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Required payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/request.CreateClientApprovalSettingsRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.BadRequestError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.UnauthorizedError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.InternalServerError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
|
|
@ -12342,6 +12538,13 @@
|
||||||
],
|
],
|
||||||
"summary": "Get all UserLevels",
|
"summary": "Get all UserLevels",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"name": "levelNumber",
|
"name": "levelNumber",
|
||||||
|
|
@ -12442,12 +12645,26 @@
|
||||||
],
|
],
|
||||||
"summary": "Create UserLevels",
|
"summary": "Create UserLevels",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Insert the X-Csrf-Token",
|
"description": "Insert the X-Csrf-Token",
|
||||||
"name": "X-Csrf-Token",
|
"name": "X-Csrf-Token",
|
||||||
"in": "header"
|
"in": "header"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Required payload",
|
"description": "Required payload",
|
||||||
"name": "payload",
|
"name": "payload",
|
||||||
|
|
@ -12499,6 +12716,13 @@
|
||||||
],
|
],
|
||||||
"summary": "Get one UserLevels",
|
"summary": "Get one UserLevels",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "UserLevels Alias",
|
"description": "UserLevels Alias",
|
||||||
|
|
@ -12548,6 +12772,13 @@
|
||||||
],
|
],
|
||||||
"summary": "EnableApproval Articles",
|
"summary": "EnableApproval Articles",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Insert the X-Csrf-Token",
|
"description": "Insert the X-Csrf-Token",
|
||||||
|
|
@ -12612,6 +12843,13 @@
|
||||||
],
|
],
|
||||||
"summary": "Get one UserLevels",
|
"summary": "Get one UserLevels",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "UserLevels ID",
|
"description": "UserLevels ID",
|
||||||
|
|
@ -12659,12 +12897,26 @@
|
||||||
],
|
],
|
||||||
"summary": "update UserLevels",
|
"summary": "update UserLevels",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Insert the X-Csrf-Token",
|
"description": "Insert the X-Csrf-Token",
|
||||||
"name": "X-Csrf-Token",
|
"name": "X-Csrf-Token",
|
||||||
"in": "header"
|
"in": "header"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Required payload",
|
"description": "Required payload",
|
||||||
"name": "payload",
|
"name": "payload",
|
||||||
|
|
@ -12721,6 +12973,20 @@
|
||||||
],
|
],
|
||||||
"summary": "delete UserLevels",
|
"summary": "delete UserLevels",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client Key",
|
||||||
|
"name": "X-Client-Key",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bearer \u003cAdd access token here\u003e",
|
||||||
|
"description": "Insert your access token",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Insert the X-Csrf-Token",
|
"description": "Insert the X-Csrf-Token",
|
||||||
|
|
@ -14859,6 +15125,9 @@
|
||||||
"name"
|
"name"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"autoPublish": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
@ -14870,6 +15139,15 @@
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"requiresApproval": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"steps": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/request.ApprovalWorkflowStepRequest"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -14880,6 +15158,9 @@
|
||||||
"name"
|
"name"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"autoPublish": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
@ -14891,6 +15172,9 @@
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"requiresApproval": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -14902,6 +15186,9 @@
|
||||||
"steps"
|
"steps"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"autoPublish": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
@ -14914,6 +15201,9 @@
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"requiresApproval": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"steps": {
|
"steps": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"minItems": 1,
|
"minItems": 1,
|
||||||
|
|
@ -14931,6 +15221,9 @@
|
||||||
"steps"
|
"steps"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"autoPublish": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
@ -14943,6 +15236,9 @@
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"requiresApproval": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"steps": {
|
"steps": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"minItems": 1,
|
"minItems": 1,
|
||||||
|
|
@ -15598,6 +15894,57 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"request.CreateClientApprovalSettingsRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"requires_approval"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"approval_exempt_categories": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"approval_exempt_roles": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"approval_exempt_users": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"auto_publish_articles": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"default_workflow_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"is_active": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"require_approval_for": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requires_approval": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"skip_approval_for": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"request.CustomStaticPagesCreateRequest": {
|
"request.CustomStaticPagesCreateRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,8 @@ definitions:
|
||||||
type: object
|
type: object
|
||||||
request.ApprovalWorkflowsCreateRequest:
|
request.ApprovalWorkflowsCreateRequest:
|
||||||
properties:
|
properties:
|
||||||
|
autoPublish:
|
||||||
|
type: boolean
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
isActive:
|
isActive:
|
||||||
|
|
@ -120,12 +122,20 @@ definitions:
|
||||||
type: boolean
|
type: boolean
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
requiresApproval:
|
||||||
|
type: boolean
|
||||||
|
steps:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/request.ApprovalWorkflowStepRequest'
|
||||||
|
type: array
|
||||||
required:
|
required:
|
||||||
- description
|
- description
|
||||||
- name
|
- name
|
||||||
type: object
|
type: object
|
||||||
request.ApprovalWorkflowsUpdateRequest:
|
request.ApprovalWorkflowsUpdateRequest:
|
||||||
properties:
|
properties:
|
||||||
|
autoPublish:
|
||||||
|
type: boolean
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
isActive:
|
isActive:
|
||||||
|
|
@ -134,12 +144,16 @@ definitions:
|
||||||
type: boolean
|
type: boolean
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
requiresApproval:
|
||||||
|
type: boolean
|
||||||
required:
|
required:
|
||||||
- description
|
- description
|
||||||
- name
|
- name
|
||||||
type: object
|
type: object
|
||||||
request.ApprovalWorkflowsWithStepsCreateRequest:
|
request.ApprovalWorkflowsWithStepsCreateRequest:
|
||||||
properties:
|
properties:
|
||||||
|
autoPublish:
|
||||||
|
type: boolean
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
isActive:
|
isActive:
|
||||||
|
|
@ -148,6 +162,8 @@ definitions:
|
||||||
type: boolean
|
type: boolean
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
requiresApproval:
|
||||||
|
type: boolean
|
||||||
steps:
|
steps:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/request.ApprovalWorkflowStepRequest'
|
$ref: '#/definitions/request.ApprovalWorkflowStepRequest'
|
||||||
|
|
@ -160,6 +176,8 @@ definitions:
|
||||||
type: object
|
type: object
|
||||||
request.ApprovalWorkflowsWithStepsUpdateRequest:
|
request.ApprovalWorkflowsWithStepsUpdateRequest:
|
||||||
properties:
|
properties:
|
||||||
|
autoPublish:
|
||||||
|
type: boolean
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
isActive:
|
isActive:
|
||||||
|
|
@ -168,6 +186,8 @@ definitions:
|
||||||
type: boolean
|
type: boolean
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
requiresApproval:
|
||||||
|
type: boolean
|
||||||
steps:
|
steps:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/request.ApprovalWorkflowStepRequest'
|
$ref: '#/definitions/request.ApprovalWorkflowStepRequest'
|
||||||
|
|
@ -626,6 +646,40 @@ definitions:
|
||||||
- stepOrder
|
- stepOrder
|
||||||
- workflowId
|
- workflowId
|
||||||
type: object
|
type: object
|
||||||
|
request.CreateClientApprovalSettingsRequest:
|
||||||
|
properties:
|
||||||
|
approval_exempt_categories:
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
approval_exempt_roles:
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
approval_exempt_users:
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
auto_publish_articles:
|
||||||
|
type: boolean
|
||||||
|
default_workflow_id:
|
||||||
|
minimum: 1
|
||||||
|
type: integer
|
||||||
|
is_active:
|
||||||
|
type: boolean
|
||||||
|
require_approval_for:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
requires_approval:
|
||||||
|
type: boolean
|
||||||
|
skip_approval_for:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- requires_approval
|
||||||
|
type: object
|
||||||
request.CustomStaticPagesCreateRequest:
|
request.CustomStaticPagesCreateRequest:
|
||||||
properties:
|
properties:
|
||||||
description:
|
description:
|
||||||
|
|
@ -2906,6 +2960,11 @@ paths:
|
||||||
name: X-Client-Key
|
name: X-Client-Key
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- default: Bearer <Add access token here>
|
||||||
|
description: Insert your access token
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
type: string
|
||||||
- description: ArticleApprovalFlows ID
|
- description: ArticleApprovalFlows ID
|
||||||
in: path
|
in: path
|
||||||
name: id
|
name: id
|
||||||
|
|
@ -2948,6 +3007,11 @@ paths:
|
||||||
name: X-Client-Key
|
name: X-Client-Key
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- default: Bearer <Add access token here>
|
||||||
|
description: Insert your access token
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
type: string
|
||||||
- description: ArticleApprovalFlows ID
|
- description: ArticleApprovalFlows ID
|
||||||
in: path
|
in: path
|
||||||
name: id
|
name: id
|
||||||
|
|
@ -2990,6 +3054,11 @@ paths:
|
||||||
name: X-Client-Key
|
name: X-Client-Key
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- default: Bearer <Add access token here>
|
||||||
|
description: Insert your access token
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
type: string
|
||||||
- description: ArticleApprovalFlows ID
|
- description: ArticleApprovalFlows ID
|
||||||
in: path
|
in: path
|
||||||
name: id
|
name: id
|
||||||
|
|
@ -3032,6 +3101,11 @@ paths:
|
||||||
name: X-Client-Key
|
name: X-Client-Key
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- default: Bearer <Add access token here>
|
||||||
|
description: Insert your access token
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
type: string
|
||||||
- description: ArticleApprovalFlows ID
|
- description: ArticleApprovalFlows ID
|
||||||
in: path
|
in: path
|
||||||
name: id
|
name: id
|
||||||
|
|
@ -3074,6 +3148,11 @@ paths:
|
||||||
name: X-Client-Key
|
name: X-Client-Key
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- default: Bearer <Add access token here>
|
||||||
|
description: Insert your access token
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
type: string
|
||||||
- description: Period filter (daily, weekly, monthly)
|
- description: Period filter (daily, weekly, monthly)
|
||||||
in: query
|
in: query
|
||||||
name: period
|
name: period
|
||||||
|
|
@ -3117,10 +3196,11 @@ paths:
|
||||||
name: X-Client-Key
|
name: X-Client-Key
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- description: User Level ID filter
|
- default: Bearer <Add access token here>
|
||||||
in: query
|
description: Insert your access token
|
||||||
name: userLevelId
|
in: header
|
||||||
type: integer
|
name: Authorization
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
|
|
@ -3215,6 +3295,19 @@ paths:
|
||||||
name: X-Client-Key
|
name: X-Client-Key
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- default: Bearer <Add access token here>
|
||||||
|
description: Insert your access token
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
type: string
|
||||||
|
- description: Include article preview
|
||||||
|
in: query
|
||||||
|
name: includePreview
|
||||||
|
type: boolean
|
||||||
|
- description: Show only urgent articles
|
||||||
|
in: query
|
||||||
|
name: urgentOnly
|
||||||
|
type: boolean
|
||||||
- in: query
|
- in: query
|
||||||
name: count
|
name: count
|
||||||
type: integer
|
type: integer
|
||||||
|
|
@ -3270,10 +3363,11 @@ paths:
|
||||||
name: X-Client-Key
|
name: X-Client-Key
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- description: User Level ID filter
|
- default: Bearer <Add access token here>
|
||||||
in: query
|
description: Insert your access token
|
||||||
name: userLevelId
|
in: header
|
||||||
type: integer
|
name: Authorization
|
||||||
|
type: string
|
||||||
- in: query
|
- in: query
|
||||||
name: count
|
name: count
|
||||||
type: integer
|
type: integer
|
||||||
|
|
@ -3329,6 +3423,11 @@ paths:
|
||||||
name: X-Client-Key
|
name: X-Client-Key
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- default: Bearer <Add access token here>
|
||||||
|
description: Insert your access token
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
type: string
|
||||||
- description: Submit for approval data
|
- description: Submit for approval data
|
||||||
in: body
|
in: body
|
||||||
name: req
|
name: req
|
||||||
|
|
@ -3366,6 +3465,11 @@ paths:
|
||||||
name: X-Client-Key
|
name: X-Client-Key
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- default: Bearer <Add access token here>
|
||||||
|
description: Insert your access token
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
|
|
@ -5623,6 +5727,11 @@ paths:
|
||||||
name: X-Client-Key
|
name: X-Client-Key
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- default: Bearer <Add access token here>
|
||||||
|
description: Insert your access token
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
type: string
|
||||||
- in: query
|
- in: query
|
||||||
name: category
|
name: category
|
||||||
type: string
|
type: string
|
||||||
|
|
@ -6301,6 +6410,48 @@ paths:
|
||||||
summary: Viewer Articles Thumbnail
|
summary: Viewer Articles Thumbnail
|
||||||
tags:
|
tags:
|
||||||
- Articles
|
- Articles
|
||||||
|
/articles/waiting-for-approval:
|
||||||
|
get:
|
||||||
|
description: API for getting articles that are waiting for approval by the current
|
||||||
|
user's level
|
||||||
|
parameters:
|
||||||
|
- description: Client Key
|
||||||
|
in: header
|
||||||
|
name: X-Client-Key
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- default: 1
|
||||||
|
description: Page number
|
||||||
|
in: query
|
||||||
|
name: page
|
||||||
|
type: integer
|
||||||
|
- default: 10
|
||||||
|
description: Items per page
|
||||||
|
in: query
|
||||||
|
name: limit
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.BadRequestError'
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.UnauthorizedError'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.InternalServerError'
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
|
summary: Get articles waiting for approval by current user level
|
||||||
|
tags:
|
||||||
|
- Articles
|
||||||
/cities:
|
/cities:
|
||||||
get:
|
get:
|
||||||
description: API for getting all Cities
|
description: API for getting all Cities
|
||||||
|
|
@ -6539,6 +6690,42 @@ paths:
|
||||||
summary: Get Client Approval Settings
|
summary: Get Client Approval Settings
|
||||||
tags:
|
tags:
|
||||||
- ClientApprovalSettings
|
- ClientApprovalSettings
|
||||||
|
post:
|
||||||
|
description: API for creating client approval settings
|
||||||
|
parameters:
|
||||||
|
- description: Insert the X-Client-Key
|
||||||
|
in: header
|
||||||
|
name: X-Client-Key
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Required payload
|
||||||
|
in: body
|
||||||
|
name: payload
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/request.CreateClientApprovalSettingsRequest'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.BadRequestError'
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.UnauthorizedError'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.InternalServerError'
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
|
summary: Create Client Approval Settings
|
||||||
|
tags:
|
||||||
|
- ClientApprovalSettings
|
||||||
put:
|
put:
|
||||||
description: API for updating client approval settings
|
description: API for updating client approval settings
|
||||||
parameters:
|
parameters:
|
||||||
|
|
@ -9188,6 +9375,11 @@ paths:
|
||||||
get:
|
get:
|
||||||
description: API for getting all UserLevels
|
description: API for getting all UserLevels
|
||||||
parameters:
|
parameters:
|
||||||
|
- description: Client Key
|
||||||
|
in: header
|
||||||
|
name: X-Client-Key
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
- in: query
|
- in: query
|
||||||
name: levelNumber
|
name: levelNumber
|
||||||
type: integer
|
type: integer
|
||||||
|
|
@ -9249,10 +9441,20 @@ paths:
|
||||||
post:
|
post:
|
||||||
description: API for create UserLevels
|
description: API for create UserLevels
|
||||||
parameters:
|
parameters:
|
||||||
|
- description: Client Key
|
||||||
|
in: header
|
||||||
|
name: X-Client-Key
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
- description: Insert the X-Csrf-Token
|
- description: Insert the X-Csrf-Token
|
||||||
in: header
|
in: header
|
||||||
name: X-Csrf-Token
|
name: X-Csrf-Token
|
||||||
type: string
|
type: string
|
||||||
|
- default: Bearer <Add access token here>
|
||||||
|
description: Insert your access token
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
type: string
|
||||||
- description: Required payload
|
- description: Required payload
|
||||||
in: body
|
in: body
|
||||||
name: payload
|
name: payload
|
||||||
|
|
@ -9285,6 +9487,16 @@ paths:
|
||||||
delete:
|
delete:
|
||||||
description: API for delete UserLevels
|
description: API for delete UserLevels
|
||||||
parameters:
|
parameters:
|
||||||
|
- description: Client Key
|
||||||
|
in: header
|
||||||
|
name: X-Client-Key
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- default: Bearer <Add access token here>
|
||||||
|
description: Insert your access token
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
type: string
|
||||||
- description: Insert the X-Csrf-Token
|
- description: Insert the X-Csrf-Token
|
||||||
in: header
|
in: header
|
||||||
name: X-Csrf-Token
|
name: X-Csrf-Token
|
||||||
|
|
@ -9323,6 +9535,11 @@ paths:
|
||||||
get:
|
get:
|
||||||
description: API for getting one UserLevels
|
description: API for getting one UserLevels
|
||||||
parameters:
|
parameters:
|
||||||
|
- description: Client Key
|
||||||
|
in: header
|
||||||
|
name: X-Client-Key
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
- description: UserLevels ID
|
- description: UserLevels ID
|
||||||
in: path
|
in: path
|
||||||
name: id
|
name: id
|
||||||
|
|
@ -9353,10 +9570,20 @@ paths:
|
||||||
put:
|
put:
|
||||||
description: API for update UserLevels
|
description: API for update UserLevels
|
||||||
parameters:
|
parameters:
|
||||||
|
- description: Client Key
|
||||||
|
in: header
|
||||||
|
name: X-Client-Key
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
- description: Insert the X-Csrf-Token
|
- description: Insert the X-Csrf-Token
|
||||||
in: header
|
in: header
|
||||||
name: X-Csrf-Token
|
name: X-Csrf-Token
|
||||||
type: string
|
type: string
|
||||||
|
- default: Bearer <Add access token here>
|
||||||
|
description: Insert your access token
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
type: string
|
||||||
- description: Required payload
|
- description: Required payload
|
||||||
in: body
|
in: body
|
||||||
name: payload
|
name: payload
|
||||||
|
|
@ -9394,6 +9621,11 @@ paths:
|
||||||
get:
|
get:
|
||||||
description: API for getting one UserLevels
|
description: API for getting one UserLevels
|
||||||
parameters:
|
parameters:
|
||||||
|
- description: Client Key
|
||||||
|
in: header
|
||||||
|
name: X-Client-Key
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
- description: UserLevels Alias
|
- description: UserLevels Alias
|
||||||
in: path
|
in: path
|
||||||
name: alias
|
name: alias
|
||||||
|
|
@ -9425,6 +9657,11 @@ paths:
|
||||||
post:
|
post:
|
||||||
description: API for Enable Approval of Article
|
description: API for Enable Approval of Article
|
||||||
parameters:
|
parameters:
|
||||||
|
- description: Client Key
|
||||||
|
in: header
|
||||||
|
name: X-Client-Key
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
- description: Insert the X-Csrf-Token
|
- description: Insert the X-Csrf-Token
|
||||||
in: header
|
in: header
|
||||||
name: X-Csrf-Token
|
name: X-Csrf-Token
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,347 @@
|
||||||
|
# Approval Workflow Architecture & Module Relationships
|
||||||
|
|
||||||
|
## 📋 **Overview**
|
||||||
|
|
||||||
|
Sistem approval workflow di MEDOLS menggunakan arsitektur multi-module yang saling terintegrasi untuk mengelola proses persetujuan artikel secara dinamis. Setiap module memiliki peran spesifik dalam alur approval yang kompleks.
|
||||||
|
|
||||||
|
## 🏗️ **Module Architecture**
|
||||||
|
|
||||||
|
### **1. Core Workflow Modules**
|
||||||
|
|
||||||
|
#### **`approval_workflows`** - Master Workflow Definition
|
||||||
|
- **Purpose**: Mendefinisikan template workflow approval
|
||||||
|
- **Key Fields**:
|
||||||
|
- `id`: Primary key
|
||||||
|
- `name`: Nama workflow (e.g., "Standard Approval", "Fast Track")
|
||||||
|
- `description`: Deskripsi workflow
|
||||||
|
- `is_default`: Apakah workflow default untuk client
|
||||||
|
- `is_active`: Status aktif workflow
|
||||||
|
- `client_id`: Client yang memiliki workflow
|
||||||
|
|
||||||
|
#### **`approval_workflow_steps`** - Workflow Steps Definition
|
||||||
|
- **Purpose**: Mendefinisikan step-step dalam workflow
|
||||||
|
- **Key Fields**:
|
||||||
|
- `id`: Primary key
|
||||||
|
- `workflow_id`: Foreign key ke `approval_workflows`
|
||||||
|
- `step_order`: Urutan step (1, 2, 3, dst.)
|
||||||
|
- `step_name`: Nama step (e.g., "Level 2 Review", "Level 1 Final Approval")
|
||||||
|
- `required_user_level_id`: User level yang harus approve step ini
|
||||||
|
- `is_required`: Apakah step wajib atau optional
|
||||||
|
|
||||||
|
### **2. Execution Modules**
|
||||||
|
|
||||||
|
#### **`article_approval_flows`** - Active Approval Instances
|
||||||
|
- **Purpose**: Instance aktif dari workflow yang sedang berjalan
|
||||||
|
- **Key Fields**:
|
||||||
|
- `id`: Primary key
|
||||||
|
- `article_id`: Foreign key ke `articles`
|
||||||
|
- `workflow_id`: Foreign key ke `approval_workflows`
|
||||||
|
- `current_step`: Step saat ini yang sedang menunggu approval
|
||||||
|
- `status_id`: Status (1=pending, 2=approved, 3=rejected, 4=revision_requested)
|
||||||
|
- `submitted_by_id`: User yang submit artikel
|
||||||
|
- `submitted_at`: Waktu submit
|
||||||
|
- `completed_at`: Waktu selesai (jika approved/rejected)
|
||||||
|
|
||||||
|
#### **`article_approval_step_logs`** - Step Execution History
|
||||||
|
- **Purpose**: Log setiap step yang telah dieksekusi
|
||||||
|
- **Key Fields**:
|
||||||
|
- `id`: Primary key
|
||||||
|
- `approval_flow_id`: Foreign key ke `article_approval_flows`
|
||||||
|
- `step_order`: Urutan step yang dieksekusi
|
||||||
|
- `step_name`: Nama step
|
||||||
|
- `approved_by_id`: User yang approve step ini
|
||||||
|
- `action`: Aksi yang dilakukan (approve, reject, auto_skip)
|
||||||
|
- `message`: Pesan dari approver
|
||||||
|
- `processed_at`: Waktu eksekusi
|
||||||
|
|
||||||
|
### **3. Legacy & Configuration Modules**
|
||||||
|
|
||||||
|
#### **`article_approvals`** - Legacy Approval System
|
||||||
|
- **Purpose**: Sistem approval legacy untuk backward compatibility
|
||||||
|
- **Key Fields**:
|
||||||
|
- `id`: Primary key
|
||||||
|
- `article_id`: Foreign key ke `articles`
|
||||||
|
- `approval_by`: User yang approve
|
||||||
|
- `status_id`: Status approval
|
||||||
|
- `approval_at_level`: Level yang approve
|
||||||
|
- `message`: Pesan approval
|
||||||
|
|
||||||
|
#### **`client_approval_settings`** - Client Configuration
|
||||||
|
- **Purpose**: Konfigurasi approval untuk setiap client
|
||||||
|
- **Key Fields**:
|
||||||
|
- `id`: Primary key
|
||||||
|
- `client_id`: Foreign key ke `clients`
|
||||||
|
- `workflow_id`: Workflow default untuk client
|
||||||
|
- `auto_approve_levels`: Level yang auto-approve
|
||||||
|
- `notification_settings`: Konfigurasi notifikasi
|
||||||
|
|
||||||
|
## 🔄 **Approval Workflow Process Flow**
|
||||||
|
|
||||||
|
### **Phase 1: Setup & Configuration**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Client Setup] --> B[Create Approval Workflow]
|
||||||
|
B --> C[Define Workflow Steps]
|
||||||
|
C --> D[Set Required User Levels]
|
||||||
|
D --> E[Configure Client Settings]
|
||||||
|
E --> F[Activate Workflow]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. **Client Registration**: Client baru dibuat di sistem
|
||||||
|
2. **Workflow Creation**: Admin membuat workflow approval
|
||||||
|
3. **Step Definition**: Mendefinisikan step-step dengan user level requirement
|
||||||
|
4. **Client Configuration**: Set workflow default untuk client
|
||||||
|
|
||||||
|
### **Phase 2: Article Submission**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[User Creates Article] --> B{User Level Requires Approval?}
|
||||||
|
B -->|Yes| C[Create Article Approval Flow]
|
||||||
|
B -->|No| D[Auto Publish]
|
||||||
|
C --> E[Set Current Step = 1]
|
||||||
|
E --> F[Status = Pending]
|
||||||
|
F --> G[Create Legacy Approval Record]
|
||||||
|
G --> H[Notify Approvers]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Database Changes:**
|
||||||
|
- `articles`: `workflow_id`, `current_approval_step = 1`, `status_id = 1`
|
||||||
|
- `article_approval_flows`: New record dengan `current_step = 1`
|
||||||
|
- `article_approvals`: Legacy record untuk backward compatibility
|
||||||
|
|
||||||
|
### **Phase 3: Step-by-Step Approval**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Approver Reviews Article] --> B{Approve or Reject?}
|
||||||
|
B -->|Approve| C[Create Step Log]
|
||||||
|
B -->|Reject| D[Update Status to Rejected]
|
||||||
|
C --> E{More Steps?}
|
||||||
|
E -->|Yes| F[Move to Next Step]
|
||||||
|
E -->|No| G[Complete Approval]
|
||||||
|
F --> H[Update Current Step]
|
||||||
|
H --> I[Notify Next Approver]
|
||||||
|
G --> J[Update Article Status to Approved]
|
||||||
|
D --> K[Notify Submitter]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Database Changes per Step:**
|
||||||
|
- `article_approval_step_logs`: New log record
|
||||||
|
- `article_approval_flows`: Update `current_step` dan `status_id`
|
||||||
|
- `articles`: Update `current_approval_step`
|
||||||
|
|
||||||
|
### **Phase 4: Completion**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[All Steps Approved] --> B[Update Final Status]
|
||||||
|
B --> C[Set Completed At]
|
||||||
|
C --> D[Publish Article]
|
||||||
|
D --> E[Send Notifications]
|
||||||
|
E --> F[Update Analytics]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 **Module Relationships Diagram**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
CLIENTS ||--o{ APPROVAL_WORKFLOWS : "has"
|
||||||
|
CLIENTS ||--o{ CLIENT_APPROVAL_SETTINGS : "configures"
|
||||||
|
|
||||||
|
APPROVAL_WORKFLOWS ||--o{ APPROVAL_WORKFLOW_STEPS : "contains"
|
||||||
|
APPROVAL_WORKFLOWS ||--o{ ARTICLE_APPROVAL_FLOWS : "instantiates"
|
||||||
|
|
||||||
|
ARTICLES ||--o{ ARTICLE_APPROVAL_FLOWS : "has"
|
||||||
|
ARTICLES ||--o{ ARTICLE_APPROVALS : "has_legacy"
|
||||||
|
|
||||||
|
ARTICLE_APPROVAL_FLOWS ||--o{ ARTICLE_APPROVAL_STEP_LOGS : "logs"
|
||||||
|
ARTICLE_APPROVAL_FLOWS }o--|| APPROVAL_WORKFLOWS : "uses"
|
||||||
|
|
||||||
|
USERS ||--o{ ARTICLE_APPROVAL_FLOWS : "submits"
|
||||||
|
USERS ||--o{ ARTICLE_APPROVAL_STEP_LOGS : "approves"
|
||||||
|
|
||||||
|
USER_LEVELS ||--o{ APPROVAL_WORKFLOW_STEPS : "requires"
|
||||||
|
USER_LEVELS ||--o{ USERS : "defines"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 **Technical Implementation Details**
|
||||||
|
|
||||||
|
### **1. Dynamic Workflow Assignment**
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Ketika artikel dibuat
|
||||||
|
if userLevel.RequiresApproval {
|
||||||
|
// 1. Cari workflow default untuk client
|
||||||
|
workflow := getDefaultWorkflow(clientId)
|
||||||
|
|
||||||
|
// 2. Buat approval flow instance
|
||||||
|
approvalFlow := ArticleApprovalFlows{
|
||||||
|
ArticleId: articleId,
|
||||||
|
WorkflowId: workflow.ID,
|
||||||
|
CurrentStep: 1,
|
||||||
|
StatusId: 1, // pending
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Buat legacy record
|
||||||
|
legacyApproval := ArticleApprovals{
|
||||||
|
ArticleId: articleId,
|
||||||
|
ApprovalAtLevel: nextApprovalLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **2. Step Progression Logic**
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Ketika step di-approve
|
||||||
|
func ApproveStep(flowId, approvedById, message) {
|
||||||
|
// 1. Log step yang di-approve
|
||||||
|
stepLog := ArticleApprovalStepLogs{
|
||||||
|
ApprovalFlowId: flowId,
|
||||||
|
StepOrder: currentStep,
|
||||||
|
ApprovedById: approvedById,
|
||||||
|
Action: "approve",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Cek apakah ada next step
|
||||||
|
nextStep := getNextStep(workflowId, currentStep)
|
||||||
|
|
||||||
|
if nextStep != nil {
|
||||||
|
// 3. Pindah ke step berikutnya
|
||||||
|
updateCurrentStep(flowId, nextStep.StepOrder)
|
||||||
|
} else {
|
||||||
|
// 4. Complete approval
|
||||||
|
completeApproval(flowId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **3. User Level Hierarchy**
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Level hierarchy (level_number field)
|
||||||
|
Level 1: Highest Authority (POLDAS)
|
||||||
|
Level 2: Mid Authority (POLDAS)
|
||||||
|
Level 3: Lower Authority (POLRES)
|
||||||
|
|
||||||
|
// Approval flow
|
||||||
|
Level 3 → Level 2 → Level 1 → Approved
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📈 **Data Flow Examples**
|
||||||
|
|
||||||
|
### **Example 1: Standard 3-Step Approval**
|
||||||
|
|
||||||
|
```
|
||||||
|
1. User Level 3 creates article
|
||||||
|
├── article_approval_flows: {current_step: 1, status: pending}
|
||||||
|
├── article_approvals: {approval_at_level: 2}
|
||||||
|
└── articles: {current_approval_step: 1, status: pending}
|
||||||
|
|
||||||
|
2. User Level 2 approves
|
||||||
|
├── article_approval_step_logs: {step_order: 1, action: approve}
|
||||||
|
├── article_approval_flows: {current_step: 2, status: pending}
|
||||||
|
└── articles: {current_approval_step: 2}
|
||||||
|
|
||||||
|
3. User Level 1 approves
|
||||||
|
├── article_approval_step_logs: {step_order: 2, action: approve}
|
||||||
|
├── article_approval_flows: {status: approved, completed_at: now}
|
||||||
|
└── articles: {status: approved, published: true}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Example 2: Auto-Skip Logic**
|
||||||
|
|
||||||
|
```
|
||||||
|
1. User Level 1 creates article (highest authority)
|
||||||
|
├── Check: Can skip all steps?
|
||||||
|
├── article_approval_flows: {status: approved, completed_at: now}
|
||||||
|
└── articles: {status: approved, published: true}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 **API Endpoints Flow**
|
||||||
|
|
||||||
|
### **Article Creation**
|
||||||
|
```
|
||||||
|
POST /api/articles
|
||||||
|
├── Check user level
|
||||||
|
├── Create article
|
||||||
|
├── Assign workflow (if needed)
|
||||||
|
├── Create approval flow
|
||||||
|
└── Return article data
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Approval Process**
|
||||||
|
```
|
||||||
|
PUT /api/article-approval-flows/{id}/approve
|
||||||
|
├── Validate approver permissions
|
||||||
|
├── Create step log
|
||||||
|
├── Check for next step
|
||||||
|
├── Update flow status
|
||||||
|
└── Send notifications
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Status Checking**
|
||||||
|
```
|
||||||
|
GET /api/articles/{id}/approval-status
|
||||||
|
├── Get current flow
|
||||||
|
├── Get step logs
|
||||||
|
├── Get next step info
|
||||||
|
└── Return status data
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 **Debugging & Monitoring**
|
||||||
|
|
||||||
|
### **Key Queries for Monitoring**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Active approval flows
|
||||||
|
SELECT aaf.*, a.title, ul.name as current_approver_level
|
||||||
|
FROM article_approval_flows aaf
|
||||||
|
JOIN articles a ON aaf.article_id = a.id
|
||||||
|
JOIN approval_workflow_steps aws ON aaf.workflow_id = aws.workflow_id
|
||||||
|
AND aaf.current_step = aws.step_order
|
||||||
|
JOIN user_levels ul ON aws.required_user_level_id = ul.id
|
||||||
|
WHERE aaf.status_id = 1;
|
||||||
|
|
||||||
|
-- Approval history
|
||||||
|
SELECT aasl.*, u.name as approver_name, ul.name as level_name
|
||||||
|
FROM article_approval_step_logs aasl
|
||||||
|
JOIN users u ON aasl.approved_by_id = u.id
|
||||||
|
JOIN user_levels ul ON aasl.user_level_id = ul.id
|
||||||
|
WHERE aasl.approval_flow_id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚠️ **Common Issues & Solutions**
|
||||||
|
|
||||||
|
### **Issue 1: Step Not Progressing**
|
||||||
|
- **Cause**: `getUserLevelId` returning wrong level
|
||||||
|
- **Solution**: Fix user level mapping logic
|
||||||
|
|
||||||
|
### **Issue 2: Wrong Approval Level**
|
||||||
|
- **Cause**: `findNextApprovalLevel` logic incorrect
|
||||||
|
- **Solution**: Fix level hierarchy comparison
|
||||||
|
|
||||||
|
### **Issue 3: Missing Step Logs**
|
||||||
|
- **Cause**: Step log not created during approval
|
||||||
|
- **Solution**: Ensure step log creation in `ApproveStep`
|
||||||
|
|
||||||
|
## 📝 **Best Practices**
|
||||||
|
|
||||||
|
1. **Always create step logs** for audit trail
|
||||||
|
2. **Validate user permissions** before approval
|
||||||
|
3. **Use transactions** for multi-table updates
|
||||||
|
4. **Implement proper error handling** for edge cases
|
||||||
|
5. **Log all approval actions** for debugging
|
||||||
|
6. **Test with different user level combinations**
|
||||||
|
|
||||||
|
## 🎯 **Summary**
|
||||||
|
|
||||||
|
Sistem approval workflow MEDOLS menggunakan arsitektur modular yang memisahkan:
|
||||||
|
- **Configuration** (workflows, steps, settings)
|
||||||
|
- **Execution** (flows, logs)
|
||||||
|
- **Legacy Support** (article_approvals)
|
||||||
|
|
||||||
|
Setiap module memiliki tanggung jawab spesifik, dan mereka bekerja sama untuk menciptakan sistem approval yang fleksibel dan dapat di-audit.
|
||||||
|
|
@ -0,0 +1,255 @@
|
||||||
|
# Approval Workflow Flow Diagram
|
||||||
|
|
||||||
|
## 🔄 **Complete Approval Process Flow**
|
||||||
|
|
||||||
|
### **1. System Setup Phase**
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐
|
||||||
|
│ CLIENT │───▶│ APPROVAL_WORKFLOWS │───▶│ APPROVAL_WORKFLOW │
|
||||||
|
│ CREATION │ │ (Master Template) │ │ STEPS │
|
||||||
|
└─────────────────┘ └──────────────────────┘ └─────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────┐
|
||||||
|
│ CLIENT_APPROVAL_ │
|
||||||
|
│ SETTINGS │
|
||||||
|
│ (Configuration) │
|
||||||
|
└──────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### **2. Article Submission Phase**
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐
|
||||||
|
│ USER LEVEL 3 │───▶│ CREATE ARTICLE │───▶│ CHECK USER LEVEL │
|
||||||
|
│ (POLRES) │ │ │ │ REQUIRES APPROVAL? │
|
||||||
|
└─────────────────┘ └──────────────────────┘ └─────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────┐
|
||||||
|
│ YES - CREATE │
|
||||||
|
│ APPROVAL FLOW │
|
||||||
|
└──────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────┐
|
||||||
|
│ ARTICLE_APPROVAL_ │
|
||||||
|
│ FLOWS │
|
||||||
|
│ (current_step: 1) │
|
||||||
|
└──────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### **3. Step-by-Step Approval Process**
|
||||||
|
|
||||||
|
```
|
||||||
|
STEP 1: Level 2 Approval
|
||||||
|
┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐
|
||||||
|
│ USER LEVEL 2 │───▶│ REVIEW ARTICLE │───▶│ APPROVE/REJECT │
|
||||||
|
│ (POLDAS) │ │ (Step 1) │ │ │
|
||||||
|
└─────────────────┘ └──────────────────────┘ └─────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────┐
|
||||||
|
│ CREATE STEP LOG │
|
||||||
|
│ (ARTICLE_APPROVAL_ │
|
||||||
|
│ STEP_LOGS) │
|
||||||
|
└──────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────┐
|
||||||
|
│ MOVE TO STEP 2 │
|
||||||
|
│ (current_step: 2) │
|
||||||
|
└──────────────────────┘
|
||||||
|
|
||||||
|
STEP 2: Level 1 Approval
|
||||||
|
┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐
|
||||||
|
│ USER LEVEL 1 │───▶│ REVIEW ARTICLE │───▶│ APPROVE/REJECT │
|
||||||
|
│ (POLDAS) │ │ (Step 2) │ │ │
|
||||||
|
└─────────────────┘ └──────────────────────┘ └─────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────┐
|
||||||
|
│ CREATE STEP LOG │
|
||||||
|
│ (ARTICLE_APPROVAL_ │
|
||||||
|
│ STEP_LOGS) │
|
||||||
|
└──────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────┐
|
||||||
|
│ COMPLETE APPROVAL │
|
||||||
|
│ (status: approved) │
|
||||||
|
└──────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 **Database State Changes**
|
||||||
|
|
||||||
|
### **Initial State (Article Created)**
|
||||||
|
```
|
||||||
|
ARTICLES:
|
||||||
|
├── id: 101
|
||||||
|
├── title: "Sample Article"
|
||||||
|
├── workflow_id: 1
|
||||||
|
├── current_approval_step: 1
|
||||||
|
├── status_id: 1 (pending)
|
||||||
|
└── created_by_id: 3
|
||||||
|
|
||||||
|
ARTICLE_APPROVAL_FLOWS:
|
||||||
|
├── id: 1
|
||||||
|
├── article_id: 101
|
||||||
|
├── workflow_id: 1
|
||||||
|
├── current_step: 1
|
||||||
|
├── status_id: 1 (pending)
|
||||||
|
├── submitted_by_id: 3
|
||||||
|
└── submitted_at: 2025-01-15 10:00:00
|
||||||
|
|
||||||
|
ARTICLE_APPROVALS (Legacy):
|
||||||
|
├── id: 1
|
||||||
|
├── article_id: 101
|
||||||
|
├── approval_at_level: 2
|
||||||
|
├── status_id: 1 (pending)
|
||||||
|
└── message: "Need Approval"
|
||||||
|
```
|
||||||
|
|
||||||
|
### **After Step 1 Approval (Level 2)**
|
||||||
|
```
|
||||||
|
ARTICLES:
|
||||||
|
├── current_approval_step: 2 ← UPDATED
|
||||||
|
└── status_id: 1 (pending)
|
||||||
|
|
||||||
|
ARTICLE_APPROVAL_FLOWS:
|
||||||
|
├── current_step: 2 ← UPDATED
|
||||||
|
└── status_id: 1 (pending)
|
||||||
|
|
||||||
|
ARTICLE_APPROVAL_STEP_LOGS: ← NEW RECORD
|
||||||
|
├── id: 1
|
||||||
|
├── approval_flow_id: 1
|
||||||
|
├── step_order: 1
|
||||||
|
├── step_name: "Level 2 Review"
|
||||||
|
├── approved_by_id: 2
|
||||||
|
├── action: "approve"
|
||||||
|
├── message: "Approved by Level 2"
|
||||||
|
└── processed_at: 2025-01-15 11:00:00
|
||||||
|
```
|
||||||
|
|
||||||
|
### **After Step 2 Approval (Level 1) - Final**
|
||||||
|
```
|
||||||
|
ARTICLES:
|
||||||
|
├── current_approval_step: null ← UPDATED
|
||||||
|
├── status_id: 2 (approved) ← UPDATED
|
||||||
|
├── is_publish: true ← UPDATED
|
||||||
|
└── published_at: 2025-01-15 12:00:00 ← UPDATED
|
||||||
|
|
||||||
|
ARTICLE_APPROVAL_FLOWS:
|
||||||
|
├── current_step: 2
|
||||||
|
├── status_id: 2 (approved) ← UPDATED
|
||||||
|
└── completed_at: 2025-01-15 12:00:00 ← UPDATED
|
||||||
|
|
||||||
|
ARTICLE_APPROVAL_STEP_LOGS: ← NEW RECORD
|
||||||
|
├── id: 2
|
||||||
|
├── approval_flow_id: 1
|
||||||
|
├── step_order: 2
|
||||||
|
├── step_name: "Level 1 Final Approval"
|
||||||
|
├── approved_by_id: 1
|
||||||
|
├── action: "approve"
|
||||||
|
├── message: "Final approval by Level 1"
|
||||||
|
└── processed_at: 2025-01-15 12:00:00
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 **Module Interaction Matrix**
|
||||||
|
|
||||||
|
| Module | Purpose | Reads From | Writes To | Triggers |
|
||||||
|
|--------|---------|------------|-----------|----------|
|
||||||
|
| `articles` | Article Management | - | `articles` | Creates approval flow |
|
||||||
|
| `approval_workflows` | Workflow Templates | `approval_workflows` | - | Defines steps |
|
||||||
|
| `approval_workflow_steps` | Step Definitions | `approval_workflow_steps` | - | Defines requirements |
|
||||||
|
| `article_approval_flows` | Active Instances | `approval_workflows` | `article_approval_flows` | Manages progression |
|
||||||
|
| `article_approval_step_logs` | Audit Trail | `article_approval_flows` | `article_approval_step_logs` | Logs actions |
|
||||||
|
| `article_approvals` | Legacy Support | - | `article_approvals` | Backward compatibility |
|
||||||
|
| `client_approval_settings` | Configuration | `clients` | `client_approval_settings` | Sets defaults |
|
||||||
|
|
||||||
|
## 🚨 **Error Handling Flow**
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐
|
||||||
|
│ ERROR │───▶│ LOG ERROR │───▶│ ROLLBACK │
|
||||||
|
│ OCCURS │ │ (Zerolog) │ │ TRANSACTION │
|
||||||
|
└─────────────────┘ └──────────────────────┘ └─────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────┐
|
||||||
|
│ NOTIFY USER │
|
||||||
|
│ (Error Response) │
|
||||||
|
└──────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 **Auto-Skip Logic Flow**
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐
|
||||||
|
│ USER LEVEL 1 │───▶│ CHECK CAN SKIP │───▶│ SKIP ALL STEPS │
|
||||||
|
│ (HIGHEST) │ │ ALL STEPS? │ │ │
|
||||||
|
└─────────────────┘ └──────────────────────┘ └─────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────┐
|
||||||
|
│ AUTO APPROVE │
|
||||||
|
│ (status: approved) │
|
||||||
|
└──────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📈 **Performance Considerations**
|
||||||
|
|
||||||
|
### **Database Indexes Needed**
|
||||||
|
```sql
|
||||||
|
-- For fast approval flow lookups
|
||||||
|
CREATE INDEX idx_article_approval_flows_status ON article_approval_flows(status_id);
|
||||||
|
CREATE INDEX idx_article_approval_flows_current_step ON article_approval_flows(current_step);
|
||||||
|
|
||||||
|
-- For step log queries
|
||||||
|
CREATE INDEX idx_article_approval_step_logs_flow_id ON article_approval_step_logs(approval_flow_id);
|
||||||
|
CREATE INDEX idx_article_approval_step_logs_processed_at ON article_approval_step_logs(processed_at);
|
||||||
|
|
||||||
|
-- For user level queries
|
||||||
|
CREATE INDEX idx_users_user_level_id ON users(user_level_id);
|
||||||
|
CREATE INDEX idx_user_levels_level_number ON user_levels(level_number);
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Caching Strategy**
|
||||||
|
```
|
||||||
|
1. Workflow Templates → Cache in Redis
|
||||||
|
2. User Level Mappings → Cache in Memory
|
||||||
|
3. Active Approval Flows → Cache with TTL
|
||||||
|
4. Step Requirements → Cache per Workflow
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 **Key Success Metrics**
|
||||||
|
|
||||||
|
1. **Approval Time**: Average time from submission to approval
|
||||||
|
2. **Step Completion Rate**: Percentage of steps completed successfully
|
||||||
|
3. **Error Rate**: Percentage of failed approval processes
|
||||||
|
4. **User Satisfaction**: Feedback on approval process
|
||||||
|
5. **System Performance**: Response times for approval actions
|
||||||
|
|
||||||
|
## 🔍 **Debugging Checklist**
|
||||||
|
|
||||||
|
- [ ] Check if workflow exists and is active
|
||||||
|
- [ ] Verify user level permissions
|
||||||
|
- [ ] Confirm step order is correct
|
||||||
|
- [ ] Validate foreign key relationships
|
||||||
|
- [ ] Check transaction rollback scenarios
|
||||||
|
- [ ] Monitor step log creation
|
||||||
|
- [ ] Verify notification delivery
|
||||||
|
- [ ] Test edge cases (auto-skip, rejection, etc.)
|
||||||
|
|
||||||
|
## 📝 **Summary**
|
||||||
|
|
||||||
|
Sistem approval workflow MEDOLS menggunakan pendekatan modular yang memungkinkan:
|
||||||
|
|
||||||
|
1. **Fleksibilitas**: Workflow dapat dikonfigurasi per client
|
||||||
|
2. **Auditability**: Setiap action dicatat dalam step logs
|
||||||
|
3. **Scalability**: Dapat menangani multiple workflow types
|
||||||
|
4. **Backward Compatibility**: Legacy system tetap berfungsi
|
||||||
|
5. **User Experience**: Auto-skip untuk user level tinggi
|
||||||
|
|
||||||
|
Dengan arsitektur ini, sistem dapat menangani berbagai skenario approval dengan efisien dan dapat diandalkan.
|
||||||
|
|
@ -0,0 +1,440 @@
|
||||||
|
# Database Relationships & Entity Details
|
||||||
|
|
||||||
|
## 🗄️ **Entity Relationship Overview**
|
||||||
|
|
||||||
|
### **Core Entities**
|
||||||
|
|
||||||
|
#### **1. CLIENTS**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE clients (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
name VARCHAR NOT NULL,
|
||||||
|
is_active BOOLEAN DEFAULT true,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **2. USER_LEVELS**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE user_levels (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR NOT NULL,
|
||||||
|
alias_name VARCHAR NOT NULL,
|
||||||
|
level_number INTEGER NOT NULL, -- 1=Highest, 2=Mid, 3=Lowest
|
||||||
|
parent_level_id INTEGER REFERENCES user_levels(id),
|
||||||
|
province_id INTEGER,
|
||||||
|
group VARCHAR,
|
||||||
|
is_approval_active BOOLEAN DEFAULT false,
|
||||||
|
client_id UUID REFERENCES clients(id),
|
||||||
|
is_active BOOLEAN DEFAULT true,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **3. USERS**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE users (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR NOT NULL,
|
||||||
|
email VARCHAR UNIQUE NOT NULL,
|
||||||
|
user_level_id INTEGER REFERENCES user_levels(id),
|
||||||
|
client_id UUID REFERENCES clients(id),
|
||||||
|
is_active BOOLEAN DEFAULT true,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Workflow Configuration Entities**
|
||||||
|
|
||||||
|
#### **4. APPROVAL_WORKFLOWS**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE approval_workflows (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
is_default BOOLEAN DEFAULT false,
|
||||||
|
is_active BOOLEAN DEFAULT true,
|
||||||
|
client_id UUID REFERENCES clients(id),
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **5. APPROVAL_WORKFLOW_STEPS**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE approval_workflow_steps (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
workflow_id INTEGER REFERENCES approval_workflows(id) ON DELETE CASCADE,
|
||||||
|
step_order INTEGER NOT NULL, -- 1, 2, 3, etc.
|
||||||
|
step_name VARCHAR NOT NULL,
|
||||||
|
required_user_level_id INTEGER REFERENCES user_levels(id),
|
||||||
|
is_required BOOLEAN DEFAULT true,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **6. CLIENT_APPROVAL_SETTINGS**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE client_approval_settings (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
client_id UUID REFERENCES clients(id),
|
||||||
|
workflow_id INTEGER REFERENCES approval_workflows(id),
|
||||||
|
auto_approve_levels JSONB, -- Array of level numbers
|
||||||
|
notification_settings JSONB,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Article & Approval Execution Entities**
|
||||||
|
|
||||||
|
#### **7. ARTICLES**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE articles (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
title VARCHAR NOT NULL,
|
||||||
|
slug VARCHAR UNIQUE,
|
||||||
|
description TEXT,
|
||||||
|
html_description TEXT,
|
||||||
|
workflow_id INTEGER REFERENCES approval_workflows(id),
|
||||||
|
current_approval_step INTEGER,
|
||||||
|
status_id INTEGER, -- 1=pending, 2=approved, 3=rejected
|
||||||
|
is_publish BOOLEAN DEFAULT false,
|
||||||
|
published_at TIMESTAMP,
|
||||||
|
created_by_id INTEGER REFERENCES users(id),
|
||||||
|
client_id UUID REFERENCES clients(id),
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **8. ARTICLE_APPROVAL_FLOWS**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE article_approval_flows (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
article_id INTEGER REFERENCES articles(id) ON DELETE CASCADE,
|
||||||
|
workflow_id INTEGER REFERENCES approval_workflows(id),
|
||||||
|
current_step INTEGER DEFAULT 1,
|
||||||
|
status_id INTEGER DEFAULT 1, -- 1=pending, 2=approved, 3=rejected, 4=revision_requested
|
||||||
|
submitted_by_id INTEGER REFERENCES users(id),
|
||||||
|
submitted_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
completed_at TIMESTAMP,
|
||||||
|
rejection_reason TEXT,
|
||||||
|
revision_requested BOOLEAN DEFAULT false,
|
||||||
|
revision_message TEXT,
|
||||||
|
client_id UUID REFERENCES clients(id),
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **9. ARTICLE_APPROVAL_STEP_LOGS**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE article_approval_step_logs (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
approval_flow_id INTEGER REFERENCES article_approval_flows(id) ON DELETE CASCADE,
|
||||||
|
step_order INTEGER NOT NULL,
|
||||||
|
step_name VARCHAR NOT NULL,
|
||||||
|
approved_by_id INTEGER REFERENCES users(id),
|
||||||
|
action VARCHAR NOT NULL, -- approve, reject, auto_skip, request_revision
|
||||||
|
message TEXT,
|
||||||
|
processed_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
user_level_id INTEGER REFERENCES user_levels(id),
|
||||||
|
created_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **10. ARTICLE_APPROVALS (Legacy)**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE article_approvals (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
article_id INTEGER REFERENCES articles(id) ON DELETE CASCADE,
|
||||||
|
approval_by INTEGER REFERENCES users(id),
|
||||||
|
status_id INTEGER, -- 1=pending, 2=approved, 3=rejected
|
||||||
|
message TEXT,
|
||||||
|
approval_at_level INTEGER REFERENCES user_levels(id),
|
||||||
|
approval_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
created_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔗 **Relationship Details**
|
||||||
|
|
||||||
|
### **One-to-Many Relationships**
|
||||||
|
|
||||||
|
```
|
||||||
|
CLIENTS (1) ──→ (N) USER_LEVELS
|
||||||
|
CLIENTS (1) ──→ (N) USERS
|
||||||
|
CLIENTS (1) ──→ (N) APPROVAL_WORKFLOWS
|
||||||
|
CLIENTS (1) ──→ (N) ARTICLES
|
||||||
|
CLIENTS (1) ──→ (N) CLIENT_APPROVAL_SETTINGS
|
||||||
|
|
||||||
|
USER_LEVELS (1) ──→ (N) USERS
|
||||||
|
USER_LEVELS (1) ──→ (N) APPROVAL_WORKFLOW_STEPS
|
||||||
|
USER_LEVELS (1) ──→ (N) ARTICLE_APPROVAL_STEP_LOGS
|
||||||
|
|
||||||
|
APPROVAL_WORKFLOWS (1) ──→ (N) APPROVAL_WORKFLOW_STEPS
|
||||||
|
APPROVAL_WORKFLOWS (1) ──→ (N) ARTICLE_APPROVAL_FLOWS
|
||||||
|
APPROVAL_WORKFLOWS (1) ──→ (N) ARTICLES
|
||||||
|
|
||||||
|
ARTICLES (1) ──→ (N) ARTICLE_APPROVAL_FLOWS
|
||||||
|
ARTICLES (1) ──→ (N) ARTICLE_APPROVALS
|
||||||
|
|
||||||
|
ARTICLE_APPROVAL_FLOWS (1) ──→ (N) ARTICLE_APPROVAL_STEP_LOGS
|
||||||
|
|
||||||
|
USERS (1) ──→ (N) ARTICLES (as creator)
|
||||||
|
USERS (1) ──→ (N) ARTICLE_APPROVAL_FLOWS (as submitter)
|
||||||
|
USERS (1) ──→ (N) ARTICLE_APPROVAL_STEP_LOGS (as approver)
|
||||||
|
USERS (1) ──→ (N) ARTICLE_APPROVALS (as approver)
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Many-to-Many Relationships (Implicit)**
|
||||||
|
|
||||||
|
```
|
||||||
|
USERS ←→ USER_LEVELS (through user_level_id)
|
||||||
|
ARTICLES ←→ APPROVAL_WORKFLOWS (through workflow_id)
|
||||||
|
ARTICLE_APPROVAL_FLOWS ←→ APPROVAL_WORKFLOW_STEPS (through workflow_id and step_order)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 **Data Flow Examples**
|
||||||
|
|
||||||
|
### **Example 1: Complete Approval Process**
|
||||||
|
|
||||||
|
#### **Step 1: Setup Data**
|
||||||
|
```sql
|
||||||
|
-- Client
|
||||||
|
INSERT INTO clients (id, name) VALUES ('b1ce6602-07ad-46c2-85eb-0cd6decfefa3', 'Test Client');
|
||||||
|
|
||||||
|
-- User Levels
|
||||||
|
INSERT INTO user_levels (name, alias_name, level_number, is_approval_active, client_id) VALUES
|
||||||
|
('POLDAS', 'Poldas', 1, true, 'b1ce6602-07ad-46c2-85eb-0cd6decfefa3'),
|
||||||
|
('POLDAS', 'Poldas', 2, true, 'b1ce6602-07ad-46c2-85eb-0cd6decfefa3'),
|
||||||
|
('POLRES', 'Polres', 3, true, 'b1ce6602-07ad-46c2-85eb-0cd6decfefa3');
|
||||||
|
|
||||||
|
-- Users
|
||||||
|
INSERT INTO users (name, email, user_level_id, client_id) VALUES
|
||||||
|
('Admin Level 1', 'admin1@test.com', 1, 'b1ce6602-07ad-46c2-85eb-0cd6decfefa3'),
|
||||||
|
('Admin Level 2', 'admin2@test.com', 2, 'b1ce6602-07ad-46c2-85eb-0cd6decfefa3'),
|
||||||
|
('User Level 3', 'user3@test.com', 3, 'b1ce6602-07ad-46c2-85eb-0cd6decfefa3');
|
||||||
|
|
||||||
|
-- Workflow
|
||||||
|
INSERT INTO approval_workflows (name, description, is_default, client_id) VALUES
|
||||||
|
('Standard Approval', 'Standard 3-step approval process', true, 'b1ce6602-07ad-46c2-85eb-0cd6decfefa3');
|
||||||
|
|
||||||
|
-- Workflow Steps
|
||||||
|
INSERT INTO approval_workflow_steps (workflow_id, step_order, step_name, required_user_level_id) VALUES
|
||||||
|
(1, 1, 'Level 2 Review', 2),
|
||||||
|
(1, 2, 'Level 1 Final Approval', 1);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **Step 2: Article Creation**
|
||||||
|
```sql
|
||||||
|
-- Article created by User Level 3
|
||||||
|
INSERT INTO articles (title, slug, workflow_id, current_approval_step, status_id, created_by_id, client_id) VALUES
|
||||||
|
('Test Article', 'test-article', 1, 1, 1, 3, 'b1ce6602-07ad-46c2-85eb-0cd6decfefa3');
|
||||||
|
|
||||||
|
-- Approval Flow
|
||||||
|
INSERT INTO article_approval_flows (article_id, workflow_id, current_step, status_id, submitted_by_id, client_id) VALUES
|
||||||
|
(1, 1, 1, 1, 3, 'b1ce6602-07ad-46c2-85eb-0cd6decfefa3');
|
||||||
|
|
||||||
|
-- Legacy Approval
|
||||||
|
INSERT INTO article_approvals (article_id, approval_by, status_id, message, approval_at_level) VALUES
|
||||||
|
(1, 3, 1, 'Need Approval', 2);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **Step 3: Level 2 Approval**
|
||||||
|
```sql
|
||||||
|
-- Step Log
|
||||||
|
INSERT INTO article_approval_step_logs (approval_flow_id, step_order, step_name, approved_by_id, action, message, user_level_id) VALUES
|
||||||
|
(1, 1, 'Level 2 Review', 2, 'approve', 'Approved by Level 2', 2);
|
||||||
|
|
||||||
|
-- Update Flow
|
||||||
|
UPDATE article_approval_flows SET current_step = 2 WHERE id = 1;
|
||||||
|
|
||||||
|
-- Update Article
|
||||||
|
UPDATE articles SET current_approval_step = 2 WHERE id = 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **Step 4: Level 1 Final Approval**
|
||||||
|
```sql
|
||||||
|
-- Step Log
|
||||||
|
INSERT INTO article_approval_step_logs (approval_flow_id, step_order, step_name, approved_by_id, action, message, user_level_id) VALUES
|
||||||
|
(1, 2, 'Level 1 Final Approval', 1, 'approve', 'Final approval by Level 1', 1);
|
||||||
|
|
||||||
|
-- Complete Flow
|
||||||
|
UPDATE article_approval_flows SET
|
||||||
|
status_id = 2,
|
||||||
|
completed_at = NOW()
|
||||||
|
WHERE id = 1;
|
||||||
|
|
||||||
|
-- Publish Article
|
||||||
|
UPDATE articles SET
|
||||||
|
status_id = 2,
|
||||||
|
is_publish = true,
|
||||||
|
published_at = NOW(),
|
||||||
|
current_approval_step = NULL
|
||||||
|
WHERE id = 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 **Key Queries for Monitoring**
|
||||||
|
|
||||||
|
### **Active Approval Flows**
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
aaf.id as flow_id,
|
||||||
|
a.title as article_title,
|
||||||
|
aaf.current_step,
|
||||||
|
aws.step_name as current_step_name,
|
||||||
|
ul.name as required_approver_level,
|
||||||
|
u.name as submitted_by,
|
||||||
|
aaf.submitted_at,
|
||||||
|
aaf.status_id
|
||||||
|
FROM article_approval_flows aaf
|
||||||
|
JOIN articles a ON aaf.article_id = a.id
|
||||||
|
JOIN approval_workflow_steps aws ON aaf.workflow_id = aws.workflow_id
|
||||||
|
AND aaf.current_step = aws.step_order
|
||||||
|
JOIN user_levels ul ON aws.required_user_level_id = ul.id
|
||||||
|
JOIN users u ON aaf.submitted_by_id = u.id
|
||||||
|
WHERE aaf.status_id = 1 -- pending
|
||||||
|
ORDER BY aaf.submitted_at DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Approval History**
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
a.title as article_title,
|
||||||
|
aasl.step_name,
|
||||||
|
aasl.action,
|
||||||
|
aasl.message,
|
||||||
|
u.name as approver_name,
|
||||||
|
ul.name as approver_level,
|
||||||
|
aasl.processed_at
|
||||||
|
FROM article_approval_step_logs aasl
|
||||||
|
JOIN article_approval_flows aaf ON aasl.approval_flow_id = aaf.id
|
||||||
|
JOIN articles a ON aaf.article_id = a.id
|
||||||
|
JOIN users u ON aasl.approved_by_id = u.id
|
||||||
|
JOIN user_levels ul ON aasl.user_level_id = ul.id
|
||||||
|
WHERE aaf.article_id = ? -- specific article
|
||||||
|
ORDER BY aasl.step_order, aasl.processed_at;
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Workflow Performance**
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
aw.name as workflow_name,
|
||||||
|
COUNT(aaf.id) as total_flows,
|
||||||
|
COUNT(CASE WHEN aaf.status_id = 2 THEN 1 END) as approved,
|
||||||
|
COUNT(CASE WHEN aaf.status_id = 3 THEN 1 END) as rejected,
|
||||||
|
AVG(EXTRACT(EPOCH FROM (aaf.completed_at - aaf.submitted_at))/3600) as avg_hours
|
||||||
|
FROM approval_workflows aw
|
||||||
|
LEFT JOIN article_approval_flows aaf ON aw.id = aaf.workflow_id
|
||||||
|
WHERE aw.client_id = ?
|
||||||
|
GROUP BY aw.id, aw.name;
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚠️ **Common Data Issues**
|
||||||
|
|
||||||
|
### **Issue 1: Orphaned Records**
|
||||||
|
```sql
|
||||||
|
-- Find articles without approval flows
|
||||||
|
SELECT a.id, a.title
|
||||||
|
FROM articles a
|
||||||
|
LEFT JOIN article_approval_flows aaf ON a.id = aaf.article_id
|
||||||
|
WHERE aaf.id IS NULL AND a.workflow_id IS NOT NULL;
|
||||||
|
|
||||||
|
-- Find approval flows without articles
|
||||||
|
SELECT aaf.id, aaf.article_id
|
||||||
|
FROM article_approval_flows aaf
|
||||||
|
LEFT JOIN articles a ON aaf.article_id = a.id
|
||||||
|
WHERE a.id IS NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Issue 2: Inconsistent Step Data**
|
||||||
|
```sql
|
||||||
|
-- Find flows with invalid current_step
|
||||||
|
SELECT aaf.id, aaf.current_step, aws.step_order
|
||||||
|
FROM article_approval_flows aaf
|
||||||
|
JOIN approval_workflow_steps aws ON aaf.workflow_id = aws.workflow_id
|
||||||
|
WHERE aaf.current_step != aws.step_order;
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Issue 3: Missing Step Logs**
|
||||||
|
```sql
|
||||||
|
-- Find approved flows without step logs
|
||||||
|
SELECT aaf.id, aaf.article_id, aaf.status_id
|
||||||
|
FROM article_approval_flows aaf
|
||||||
|
LEFT JOIN article_approval_step_logs aasl ON aaf.id = aasl.approval_flow_id
|
||||||
|
WHERE aaf.status_id = 2 AND aasl.id IS NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 **Performance Optimization**
|
||||||
|
|
||||||
|
### **Indexes**
|
||||||
|
```sql
|
||||||
|
-- Primary indexes
|
||||||
|
CREATE INDEX idx_article_approval_flows_status ON article_approval_flows(status_id);
|
||||||
|
CREATE INDEX idx_article_approval_flows_current_step ON article_approval_flows(current_step);
|
||||||
|
CREATE INDEX idx_article_approval_flows_client_id ON article_approval_flows(client_id);
|
||||||
|
CREATE INDEX idx_article_approval_step_logs_flow_id ON article_approval_step_logs(approval_flow_id);
|
||||||
|
CREATE INDEX idx_article_approval_step_logs_processed_at ON article_approval_step_logs(processed_at);
|
||||||
|
CREATE INDEX idx_articles_workflow_id ON articles(workflow_id);
|
||||||
|
CREATE INDEX idx_articles_status_id ON articles(status_id);
|
||||||
|
CREATE INDEX idx_users_user_level_id ON users(user_level_id);
|
||||||
|
CREATE INDEX idx_user_levels_level_number ON user_levels(level_number);
|
||||||
|
|
||||||
|
-- Composite indexes
|
||||||
|
CREATE INDEX idx_article_approval_flows_status_step ON article_approval_flows(status_id, current_step);
|
||||||
|
CREATE INDEX idx_approval_workflow_steps_workflow_order ON approval_workflow_steps(workflow_id, step_order);
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Partitioning (for large datasets)**
|
||||||
|
```sql
|
||||||
|
-- Partition article_approval_step_logs by month
|
||||||
|
CREATE TABLE article_approval_step_logs_2025_01 PARTITION OF article_approval_step_logs
|
||||||
|
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 **Data Validation Rules**
|
||||||
|
|
||||||
|
### **Business Rules**
|
||||||
|
1. **Workflow Steps**: Must be sequential (1, 2, 3, etc.)
|
||||||
|
2. **User Levels**: Level number must be unique per client
|
||||||
|
3. **Approval Flows**: Can only have one active flow per article
|
||||||
|
4. **Step Logs**: Must have corresponding workflow step
|
||||||
|
5. **Status Transitions**: Only valid transitions allowed
|
||||||
|
|
||||||
|
### **Database Constraints**
|
||||||
|
```sql
|
||||||
|
-- Ensure step order is sequential
|
||||||
|
ALTER TABLE approval_workflow_steps
|
||||||
|
ADD CONSTRAINT chk_step_order_positive CHECK (step_order > 0);
|
||||||
|
|
||||||
|
-- Ensure level number is positive
|
||||||
|
ALTER TABLE user_levels
|
||||||
|
ADD CONSTRAINT chk_level_number_positive CHECK (level_number > 0);
|
||||||
|
|
||||||
|
-- Ensure status values are valid
|
||||||
|
ALTER TABLE article_approval_flows
|
||||||
|
ADD CONSTRAINT chk_status_valid CHECK (status_id IN (1, 2, 3, 4));
|
||||||
|
|
||||||
|
-- Ensure current step is valid
|
||||||
|
ALTER TABLE article_approval_flows
|
||||||
|
ADD CONSTRAINT chk_current_step_positive CHECK (current_step > 0);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 **Summary**
|
||||||
|
|
||||||
|
Database design untuk sistem approval workflow MEDOLS menggunakan:
|
||||||
|
|
||||||
|
1. **Normalized Structure**: Memisahkan konfigurasi dari eksekusi
|
||||||
|
2. **Audit Trail**: Step logs untuk tracking lengkap
|
||||||
|
3. **Flexibility**: Workflow dapat dikonfigurasi per client
|
||||||
|
4. **Performance**: Indexes untuk query yang efisien
|
||||||
|
5. **Data Integrity**: Constraints dan validasi
|
||||||
|
6. **Scalability**: Partitioning untuk data besar
|
||||||
|
|
||||||
|
Dengan struktur ini, sistem dapat menangani berbagai skenario approval dengan efisien dan dapat diandalkan.
|
||||||
|
|
@ -0,0 +1,785 @@
|
||||||
|
# End-to-End Testing Scenarios - Approval Workflow System
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Dokumentasi ini berisi skenario testing end-to-end lengkap untuk sistem approval workflow, mulai dari pembuatan client baru hingga pembuatan artikel dengan proses approval yang dinamis.
|
||||||
|
|
||||||
|
## Base Configuration
|
||||||
|
```bash
|
||||||
|
# Base URL
|
||||||
|
BASE_URL="http://localhost:8800/api"
|
||||||
|
|
||||||
|
# Headers
|
||||||
|
AUTH_HEADER="Authorization: Bearer YOUR_JWT_TOKEN"
|
||||||
|
CLIENT_HEADER="X-Client-Key: YOUR_CLIENT_KEY"
|
||||||
|
CONTENT_TYPE="Content-Type: application/json"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏢 Scenario 1: Complete Client Setup to Article Creation
|
||||||
|
|
||||||
|
### Step 1: Create New Client
|
||||||
|
```bash
|
||||||
|
curl -X POST "${BASE_URL}/clients" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"name": "Test Media Company",
|
||||||
|
"is_active": true
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Client created successfully"],
|
||||||
|
"data": {
|
||||||
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"name": "Test Media Company",
|
||||||
|
"is_active": true,
|
||||||
|
"created_at": "2024-01-15T10:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Create User Levels
|
||||||
|
```bash
|
||||||
|
# Create user levels for approval workflow
|
||||||
|
curl -X POST "${BASE_URL}/user-levels" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"name": "Editor",
|
||||||
|
"alias_name": "ED",
|
||||||
|
"level_number": 1,
|
||||||
|
"is_approval_active": true
|
||||||
|
}'
|
||||||
|
|
||||||
|
curl -X POST "${BASE_URL}/user-levels" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"name": "Senior Editor",
|
||||||
|
"alias_name": "SED",
|
||||||
|
"level_number": 2,
|
||||||
|
"is_approval_active": true
|
||||||
|
}'
|
||||||
|
|
||||||
|
curl -X POST "${BASE_URL}/user-levels" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"name": "Editor in Chief",
|
||||||
|
"alias_name": "EIC",
|
||||||
|
"level_number": 3,
|
||||||
|
"is_approval_active": true
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Create Approval Workflow
|
||||||
|
```bash
|
||||||
|
curl -X POST "${BASE_URL}/approval-workflows" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"name": "Standard 3-Level Editorial Review",
|
||||||
|
"description": "Complete editorial workflow with 3 approval levels",
|
||||||
|
"is_default": true,
|
||||||
|
"is_active": true,
|
||||||
|
"requires_approval": true,
|
||||||
|
"auto_publish": false,
|
||||||
|
"client_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Create Workflow Steps
|
||||||
|
```bash
|
||||||
|
# Step 1: Editor Review
|
||||||
|
curl -X POST "${BASE_URL}/approval-workflow-steps" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"workflow_id": 1,
|
||||||
|
"step_order": 1,
|
||||||
|
"step_name": "Editor Review",
|
||||||
|
"required_user_level_id": 1,
|
||||||
|
"can_skip": false,
|
||||||
|
"auto_approve_after_hours": 24,
|
||||||
|
"client_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||||
|
}'
|
||||||
|
|
||||||
|
# Step 2: Senior Editor Review
|
||||||
|
curl -X POST "${BASE_URL}/approval-workflow-steps" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"workflow_id": 1,
|
||||||
|
"step_order": 2,
|
||||||
|
"step_name": "Senior Editor Review",
|
||||||
|
"required_user_level_id": 2,
|
||||||
|
"can_skip": false,
|
||||||
|
"auto_approve_after_hours": 48,
|
||||||
|
"client_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||||
|
}'
|
||||||
|
|
||||||
|
# Step 3: Editor in Chief
|
||||||
|
curl -X POST "${BASE_URL}/approval-workflow-steps" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"workflow_id": 1,
|
||||||
|
"step_order": 3,
|
||||||
|
"step_name": "Editor in Chief Approval",
|
||||||
|
"required_user_level_id": 3,
|
||||||
|
"can_skip": false,
|
||||||
|
"auto_approve_after_hours": 72,
|
||||||
|
"client_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Configure Client Approval Settings
|
||||||
|
```bash
|
||||||
|
curl -X POST "${BASE_URL}/client-approval-settings" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"client_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"requires_approval": true,
|
||||||
|
"default_workflow_id": 1,
|
||||||
|
"auto_publish_articles": false,
|
||||||
|
"approval_exempt_users": [],
|
||||||
|
"approval_exempt_roles": [],
|
||||||
|
"approval_exempt_categories": [],
|
||||||
|
"require_approval_for": ["article", "news", "review"],
|
||||||
|
"skip_approval_for": ["announcement", "update"],
|
||||||
|
"is_active": true
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: Create Article Category
|
||||||
|
```bash
|
||||||
|
curl -X POST "${BASE_URL}/article-categories" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"title": "Technology News",
|
||||||
|
"description": "Latest technology news and updates",
|
||||||
|
"slug": "technology-news",
|
||||||
|
"status_id": 1,
|
||||||
|
"is_publish": true,
|
||||||
|
"client_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 7: Create Article
|
||||||
|
```bash
|
||||||
|
curl -X POST "${BASE_URL}/articles" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"title": "Revolutionary AI Technology Breakthrough",
|
||||||
|
"slug": "revolutionary-ai-technology-breakthrough",
|
||||||
|
"description": "A comprehensive look at the latest AI breakthrough that could change everything",
|
||||||
|
"html_description": "<p>A comprehensive look at the latest AI breakthrough that could change everything</p>",
|
||||||
|
"category_id": 1,
|
||||||
|
"type_id": 1,
|
||||||
|
"tags": "AI, Technology, Innovation, Breakthrough",
|
||||||
|
"created_by_id": 1,
|
||||||
|
"status_id": 1,
|
||||||
|
"is_draft": false,
|
||||||
|
"client_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Article created successfully"],
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"title": "Revolutionary AI Technology Breakthrough",
|
||||||
|
"status_id": 1,
|
||||||
|
"is_draft": false,
|
||||||
|
"approval_required": true,
|
||||||
|
"workflow_id": 1,
|
||||||
|
"created_at": "2024-01-15T10:30:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Scenario 2: Complete Approval Process
|
||||||
|
|
||||||
|
### Step 1: Submit Article for Approval
|
||||||
|
```bash
|
||||||
|
curl -X POST "${BASE_URL}/articles/1/submit-approval" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"workflow_id": 1,
|
||||||
|
"message": "Article ready for editorial review process"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Article submitted for approval successfully"],
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"article_id": 1,
|
||||||
|
"workflow_id": 1,
|
||||||
|
"current_step": 1,
|
||||||
|
"status_id": 1,
|
||||||
|
"submitted_at": "2024-01-15T10:35:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Check Approval Status
|
||||||
|
```bash
|
||||||
|
curl -X GET "${BASE_URL}/articles/1/approval-status" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Approval status retrieved successfully"],
|
||||||
|
"data": {
|
||||||
|
"article_id": 1,
|
||||||
|
"current_status": "pending_approval",
|
||||||
|
"current_step": 1,
|
||||||
|
"total_steps": 3,
|
||||||
|
"workflow_name": "Standard 3-Level Editorial Review",
|
||||||
|
"current_step_name": "Editor Review",
|
||||||
|
"next_step_name": "Senior Editor Review",
|
||||||
|
"waiting_since": "2024-01-15T10:35:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Editor Approves (Step 1)
|
||||||
|
```bash
|
||||||
|
curl -X POST "${BASE_URL}/articles/1/approve" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"message": "Content quality meets editorial standards, approved for next level"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Article approved successfully"],
|
||||||
|
"data": {
|
||||||
|
"current_step": 2,
|
||||||
|
"status": "moved_to_next_level",
|
||||||
|
"next_approver_level": 2,
|
||||||
|
"approved_at": "2024-01-15T11:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Senior Editor Approves (Step 2)
|
||||||
|
```bash
|
||||||
|
curl -X POST "${BASE_URL}/articles/1/approve" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"message": "Excellent content quality and structure, ready for final approval"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Article approved successfully"],
|
||||||
|
"data": {
|
||||||
|
"current_step": 3,
|
||||||
|
"status": "moved_to_next_level",
|
||||||
|
"next_approver_level": 3,
|
||||||
|
"approved_at": "2024-01-15T12:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Editor in Chief Approves (Step 3 - Final)
|
||||||
|
```bash
|
||||||
|
curl -X POST "${BASE_URL}/articles/1/approve" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"message": "Final approval granted, content ready for publication"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Article approved and published successfully"],
|
||||||
|
"data": {
|
||||||
|
"status": "approved",
|
||||||
|
"article_status": "published",
|
||||||
|
"is_publish": true,
|
||||||
|
"published_at": "2024-01-15T13:00:00Z",
|
||||||
|
"completion_date": "2024-01-15T13:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ❌ Scenario 3: Article Rejection and Revision
|
||||||
|
|
||||||
|
### Step 1: Submit Another Article
|
||||||
|
```bash
|
||||||
|
curl -X POST "${BASE_URL}/articles" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"title": "Product Review: New Smartphone",
|
||||||
|
"slug": "product-review-new-smartphone",
|
||||||
|
"description": "Comprehensive review of the latest smartphone",
|
||||||
|
"html_description": "<p>Comprehensive review of the latest smartphone</p>",
|
||||||
|
"category_id": 1,
|
||||||
|
"type_id": 1,
|
||||||
|
"tags": "Review, Smartphone, Technology",
|
||||||
|
"created_by_id": 1,
|
||||||
|
"status_id": 1,
|
||||||
|
"is_draft": false,
|
||||||
|
"client_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Submit for Approval
|
||||||
|
```bash
|
||||||
|
curl -X POST "${BASE_URL}/articles/2/submit-approval" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"workflow_id": 1,
|
||||||
|
"message": "Product review ready for approval"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Editor Approves (Step 1)
|
||||||
|
```bash
|
||||||
|
curl -X POST "${BASE_URL}/articles/2/approve" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"message": "Initial review passed, good structure"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Senior Editor Rejects (Step 2)
|
||||||
|
```bash
|
||||||
|
curl -X POST "${BASE_URL}/articles/2/reject" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"message": "Insufficient technical details and benchmark comparisons needed"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Article rejected successfully"],
|
||||||
|
"data": {
|
||||||
|
"status": "rejected",
|
||||||
|
"article_status": "draft",
|
||||||
|
"rejection_reason": "Insufficient technical details and benchmark comparisons needed",
|
||||||
|
"rejected_at": "2024-01-15T14:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Request Revision
|
||||||
|
```bash
|
||||||
|
curl -X POST "${BASE_URL}/articles/2/request-revision" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"message": "Please add detailed technical specifications, benchmark comparisons, and more comprehensive testing results"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Revision requested successfully"],
|
||||||
|
"data": {
|
||||||
|
"status": "revision_requested",
|
||||||
|
"revision_message": "Please add detailed technical specifications, benchmark comparisons, and more comprehensive testing results",
|
||||||
|
"requested_at": "2024-01-15T14:15:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: Resubmit After Revision
|
||||||
|
```bash
|
||||||
|
curl -X POST "${BASE_URL}/articles/2/resubmit" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"message": "Article revised with additional technical details and benchmark comparisons"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Article resubmitted successfully"],
|
||||||
|
"data": {
|
||||||
|
"status": "pending_approval",
|
||||||
|
"current_step": 1,
|
||||||
|
"resubmitted_at": "2024-01-15T15:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚡ Scenario 4: Dynamic Approval Toggle
|
||||||
|
|
||||||
|
### Step 1: Check Current Settings
|
||||||
|
```bash
|
||||||
|
curl -X GET "${BASE_URL}/client-approval-settings" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Disable Approval System
|
||||||
|
```bash
|
||||||
|
curl -X PUT "${BASE_URL}/client-approval-settings/1" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"requires_approval": false,
|
||||||
|
"auto_publish_articles": true,
|
||||||
|
"reason": "Breaking news mode - immediate publishing required"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Approval settings updated successfully"],
|
||||||
|
"data": {
|
||||||
|
"requires_approval": false,
|
||||||
|
"auto_publish_articles": true,
|
||||||
|
"updated_at": "2024-01-15T16:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Create Article (Should Auto-Publish)
|
||||||
|
```bash
|
||||||
|
curl -X POST "${BASE_URL}/articles" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"title": "BREAKING: Major Tech Acquisition",
|
||||||
|
"slug": "breaking-major-tech-acquisition",
|
||||||
|
"description": "Breaking news about major technology acquisition",
|
||||||
|
"html_description": "<p>Breaking news about major technology acquisition</p>",
|
||||||
|
"category_id": 1,
|
||||||
|
"type_id": 1,
|
||||||
|
"tags": "Breaking, News, Acquisition, Technology",
|
||||||
|
"created_by_id": 1,
|
||||||
|
"status_id": 1,
|
||||||
|
"is_draft": false,
|
||||||
|
"client_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Article created and published successfully"],
|
||||||
|
"data": {
|
||||||
|
"id": 3,
|
||||||
|
"title": "BREAKING: Major Tech Acquisition",
|
||||||
|
"status": "published",
|
||||||
|
"is_publish": true,
|
||||||
|
"published_at": "2024-01-15T16:05:00Z",
|
||||||
|
"approval_bypassed": true,
|
||||||
|
"bypass_reason": "approval_disabled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Re-enable Approval System
|
||||||
|
```bash
|
||||||
|
curl -X PUT "${BASE_URL}/client-approval-settings/1" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"requires_approval": true,
|
||||||
|
"auto_publish_articles": false,
|
||||||
|
"default_workflow_id": 1,
|
||||||
|
"reason": "Returning to normal approval process"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Scenario 5: Approval Dashboard and Monitoring
|
||||||
|
|
||||||
|
### Step 1: Get Pending Approvals
|
||||||
|
```bash
|
||||||
|
curl -X GET "${BASE_URL}/approvals/pending?page=1&limit=10" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Get My Approval Queue
|
||||||
|
```bash
|
||||||
|
curl -X GET "${BASE_URL}/approvals/my-queue?page=1&limit=10" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Get Approval History for Article
|
||||||
|
```bash
|
||||||
|
curl -X GET "${BASE_URL}/articles/1/approval-history" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Get My Approval Statistics
|
||||||
|
```bash
|
||||||
|
curl -X GET "${BASE_URL}/approvals/my-stats" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Scenario 6: Workflow Management
|
||||||
|
|
||||||
|
### Step 1: Get All Workflows
|
||||||
|
```bash
|
||||||
|
curl -X GET "${BASE_URL}/approval-workflows?page=1&limit=10" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Get Workflow by ID
|
||||||
|
```bash
|
||||||
|
curl -X GET "${BASE_URL}/approval-workflows/1" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Update Workflow
|
||||||
|
```bash
|
||||||
|
curl -X PUT "${BASE_URL}/approval-workflows/1" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"name": "Updated 3-Level Editorial Review",
|
||||||
|
"description": "Updated workflow with improved efficiency",
|
||||||
|
"is_active": true
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Add New Workflow Step
|
||||||
|
```bash
|
||||||
|
curl -X POST "${BASE_URL}/approval-workflow-steps" \
|
||||||
|
-H "${AUTH_HEADER}" \
|
||||||
|
-H "${CLIENT_HEADER}" \
|
||||||
|
-H "${CONTENT_TYPE}" \
|
||||||
|
-d '{
|
||||||
|
"workflow_id": 1,
|
||||||
|
"step_order": 2,
|
||||||
|
"step_name": "Legal Review",
|
||||||
|
"required_user_level_id": 4,
|
||||||
|
"can_skip": true,
|
||||||
|
"auto_approve_after_hours": 24,
|
||||||
|
"client_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Test Data Setup Script
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
BASE_URL="http://localhost:8800/api"
|
||||||
|
AUTH_HEADER="Authorization: Bearer YOUR_JWT_TOKEN"
|
||||||
|
CLIENT_HEADER="X-Client-Key: YOUR_CLIENT_KEY"
|
||||||
|
CONTENT_TYPE="Content-Type: application/json"
|
||||||
|
|
||||||
|
# Function to make API calls
|
||||||
|
make_request() {
|
||||||
|
local method=$1
|
||||||
|
local endpoint=$2
|
||||||
|
local data=$3
|
||||||
|
|
||||||
|
if [ -n "$data" ]; then
|
||||||
|
curl -X "$method" "${BASE_URL}${endpoint}" \
|
||||||
|
-H "$AUTH_HEADER" \
|
||||||
|
-H "$CLIENT_HEADER" \
|
||||||
|
-H "$CONTENT_TYPE" \
|
||||||
|
-d "$data"
|
||||||
|
else
|
||||||
|
curl -X "$method" "${BASE_URL}${endpoint}" \
|
||||||
|
-H "$AUTH_HEADER" \
|
||||||
|
-H "$CLIENT_HEADER"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Setting up test data..."
|
||||||
|
|
||||||
|
# 1. Create client
|
||||||
|
echo "Creating client..."
|
||||||
|
make_request "POST" "/clients" '{
|
||||||
|
"name": "Test Media Company",
|
||||||
|
"is_active": true
|
||||||
|
}'
|
||||||
|
|
||||||
|
# 2. Create user levels
|
||||||
|
echo "Creating user levels..."
|
||||||
|
make_request "POST" "/user-levels" '{
|
||||||
|
"name": "Editor",
|
||||||
|
"alias_name": "ED",
|
||||||
|
"level_number": 1,
|
||||||
|
"is_approval_active": true
|
||||||
|
}'
|
||||||
|
|
||||||
|
make_request "POST" "/user-levels" '{
|
||||||
|
"name": "Senior Editor",
|
||||||
|
"alias_name": "SED",
|
||||||
|
"level_number": 2,
|
||||||
|
"is_approval_active": true
|
||||||
|
}'
|
||||||
|
|
||||||
|
make_request "POST" "/user-levels" '{
|
||||||
|
"name": "Editor in Chief",
|
||||||
|
"alias_name": "EIC",
|
||||||
|
"level_number": 3,
|
||||||
|
"is_approval_active": true
|
||||||
|
}'
|
||||||
|
|
||||||
|
# 3. Create approval workflow
|
||||||
|
echo "Creating approval workflow..."
|
||||||
|
make_request "POST" "/approval-workflows" '{
|
||||||
|
"name": "Standard 3-Level Editorial Review",
|
||||||
|
"description": "Complete editorial workflow with 3 approval levels",
|
||||||
|
"is_default": true,
|
||||||
|
"is_active": true,
|
||||||
|
"requires_approval": true,
|
||||||
|
"auto_publish": false
|
||||||
|
}'
|
||||||
|
|
||||||
|
echo "Test data setup completed!"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Test Validation Checklist
|
||||||
|
|
||||||
|
### ✅ Functional Testing
|
||||||
|
- [ ] Client creation and configuration
|
||||||
|
- [ ] User level management
|
||||||
|
- [ ] Approval workflow creation and modification
|
||||||
|
- [ ] Article creation and submission
|
||||||
|
- [ ] Complete approval process flow
|
||||||
|
- [ ] Article rejection and revision process
|
||||||
|
- [ ] Dynamic approval toggle functionality
|
||||||
|
- [ ] Approval dashboard and monitoring
|
||||||
|
- [ ] Multi-step workflow progression
|
||||||
|
- [ ] Auto-publish functionality
|
||||||
|
|
||||||
|
### ✅ Error Handling
|
||||||
|
- [ ] Invalid client key handling
|
||||||
|
- [ ] Invalid JWT token handling
|
||||||
|
- [ ] Missing required fields validation
|
||||||
|
- [ ] Workflow step validation
|
||||||
|
- [ ] User permission validation
|
||||||
|
- [ ] Article status validation
|
||||||
|
|
||||||
|
### ✅ Performance Testing
|
||||||
|
- [ ] Response time < 500ms for all endpoints
|
||||||
|
- [ ] Concurrent approval processing
|
||||||
|
- [ ] Large dataset pagination
|
||||||
|
- [ ] Database query optimization
|
||||||
|
|
||||||
|
### ✅ Security Testing
|
||||||
|
- [ ] Client isolation
|
||||||
|
- [ ] User authorization
|
||||||
|
- [ ] Data validation and sanitization
|
||||||
|
- [ ] SQL injection prevention
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Running the Tests
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
1. Ensure the backend server is running on `http://localhost:8800`
|
||||||
|
2. Obtain valid JWT token for authentication
|
||||||
|
3. Set up client key for multi-tenant support
|
||||||
|
4. Database should be clean and ready for testing
|
||||||
|
|
||||||
|
### Execution Steps
|
||||||
|
1. Run the test data setup script
|
||||||
|
2. Execute each scenario sequentially
|
||||||
|
3. Validate responses against expected outputs
|
||||||
|
4. Check database state after each scenario
|
||||||
|
5. Clean up test data after completion
|
||||||
|
|
||||||
|
### Monitoring
|
||||||
|
- Monitor server logs during testing
|
||||||
|
- Check database performance metrics
|
||||||
|
- Validate all audit trails are created
|
||||||
|
- Ensure proper error handling and logging
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This documentation provides comprehensive end-to-end testing scenarios for the approval workflow system. Each scenario includes detailed curl commands and expected responses for complete testing coverage.*
|
||||||
Loading…
Reference in New Issue