commit 10efa72cedc1a8e34a60a78ddf0c7bc5b66fa454 Author: Anang Yusman Date: Tue Feb 24 17:37:19 2026 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff92df --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/vendor +debug.log +/.ideadebug.log diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..53307d4 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,28 @@ +stages: + - build-image + - deploy + +build-2: + stage: build-image + image: docker:24 + services: + - name: docker:24-dind + command: ["--insecure-registry=38.47.185.86:8900"] + + variables: + DOCKER_HOST: tcp://docker:2375 + DOCKER_TLS_CERTDIR: "" + + script: + - docker version + - docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 38.47.185.86:8900 + - docker build -t registry.gitlab.com/hanifsalafi/web-qudo-be:dev . + - docker tag registry.gitlab.com/hanifsalafi/web-qudo-be:dev 38.47.185.86:8900/medols/web-qudo-be:dev + - docker push 38.47.185.86:8900/medols/web-qudo-be:dev + +deploy: + stage: deploy + when: on_success + image: curlimages/curl:latest + script: + - curl --user $JENKINS_USER:$JENKINS_PWD "http://38.47.185.86:8080/job/autodeploy-medols-be/build?token=autodeploymedols" diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..dfae721 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/web-qudo-be.iml b/.idea/web-qudo-be.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/web-qudo-be.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..06753a7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM golang:alpine + +WORKDIR /app +COPY . . + +RUN go mod download +RUN go get -v ./... +RUN go mod vendor +RUN go build -o main . + +EXPOSE 8800 + +CMD ["sh", "-c", "go run main.go"] \ No newline at end of file diff --git a/app/database/entity/activity_log_types.entity.go b/app/database/entity/activity_log_types.entity.go new file mode 100644 index 0000000..d7d3ae0 --- /dev/null +++ b/app/database/entity/activity_log_types.entity.go @@ -0,0 +1,7 @@ +package entity + +type ActivityLogTypes struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Name string `json:"name" gorm:"type:varchar"` + IsActive bool `json:"is_active" gorm:"type:bool"` +} diff --git a/app/database/entity/activity_logs.entity.go b/app/database/entity/activity_logs.entity.go new file mode 100644 index 0000000..c84e5d0 --- /dev/null +++ b/app/database/entity/activity_logs.entity.go @@ -0,0 +1,18 @@ +package entity + +import ( + "time" + + "github.com/google/uuid" +) + +type ActivityLogs struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + ActivityTypeId int `json:"activity_type_id" gorm:"type:int4"` + Url string `json:"url" gorm:"type:varchar"` + VisitorIp *string `json:"visitor_ip" gorm:"type:varchar"` + ArticleId *uint `json:"article_id" gorm:"type:int4"` + UserId *uint `json:"user_id" gorm:"type:int4"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` +} diff --git a/app/database/entity/advertisement.entity.go b/app/database/entity/advertisement.entity.go new file mode 100644 index 0000000..f997d18 --- /dev/null +++ b/app/database/entity/advertisement.entity.go @@ -0,0 +1,22 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type Advertisement struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Title string `json:"title" gorm:"type:varchar"` + Description string `json:"description" gorm:"type:varchar"` + RedirectLink string `json:"redirect_link" gorm:"type:varchar"` + ContentFilePath *string `json:"content_file_path" gorm:"type:varchar"` + ContentFileName *string `json:"content_file_name" gorm:"type:varchar"` + Placement string `json:"placement" gorm:"type:varchar"` + StatusId int `json:"status_id" gorm:"type:int4"` + IsPublish bool `json:"is_publish" gorm:"type:bool"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/approval_workflow_steps.entity.go b/app/database/entity/approval_workflow_steps.entity.go new file mode 100644 index 0000000..c59676c --- /dev/null +++ b/app/database/entity/approval_workflow_steps.entity.go @@ -0,0 +1,24 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type ApprovalWorkflowSteps struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + WorkflowId uint `json:"workflow_id" gorm:"type:int4;not null"` + StepOrder int `json:"step_order" gorm:"type:int4;not null"` + StepName string `json:"step_name" gorm:"type:varchar;not null"` + RequiredUserLevelId uint `json:"required_user_level_id" gorm:"type:int4;not null"` + CanSkip *bool `json:"can_skip" gorm:"type:bool;default:false"` + AutoApproveAfterHours *int `json:"auto_approve_after_hours" gorm:"type:int4"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + + // Relations + Workflow ApprovalWorkflows `json:"workflow" gorm:"foreignKey:WorkflowId;constraint:OnDelete:CASCADE"` + RequiredUserLevel UserLevels `json:"required_user_level" gorm:"foreignKey:RequiredUserLevelId"` +} \ No newline at end of file diff --git a/app/database/entity/approval_workflows.entity.go b/app/database/entity/approval_workflows.entity.go new file mode 100644 index 0000000..94747c6 --- /dev/null +++ b/app/database/entity/approval_workflows.entity.go @@ -0,0 +1,23 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type ApprovalWorkflows struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Name string `json:"name" gorm:"type:varchar;not null"` + Description *string `json:"description" gorm:"type:text"` + IsDefault *bool `json:"is_default" gorm:"type:bool;default:false"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + // New fields for no-approval support + RequiresApproval *bool `json:"requires_approval" gorm:"type:bool;default:true"` // false = no approval needed + AutoPublish *bool `json:"auto_publish" gorm:"type:bool;default:false"` // true = auto publish after creation + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + + // Relations + Steps []ApprovalWorkflowSteps `json:"steps" gorm:"foreignKey:WorkflowId;constraint:OnDelete:CASCADE"` +} \ No newline at end of file diff --git a/app/database/entity/article_approval_flows.entity.go b/app/database/entity/article_approval_flows.entity.go new file mode 100644 index 0000000..6e57c5d --- /dev/null +++ b/app/database/entity/article_approval_flows.entity.go @@ -0,0 +1,29 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type ArticleApprovalFlows struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + ArticleId uint `json:"article_id" gorm:"type:int4;not null"` + WorkflowId uint `json:"workflow_id" gorm:"type:int4;not null"` + CurrentStep int `json:"current_step" gorm:"type:int4;default:1"` + StatusId int `json:"status_id" gorm:"type:int4;default:1"` // 1=pending, 2=approved, 3=rejected, 4=revision_requested + SubmittedById uint `json:"submitted_by_id" gorm:"type:int4;not null"` + SubmittedAt time.Time `json:"submitted_at" gorm:"default:now()"` + CompletedAt *time.Time `json:"completed_at" gorm:"type:timestamp"` + RejectionReason *string `json:"rejection_reason" gorm:"type:text"` + RevisionRequested *bool `json:"revision_requested" gorm:"type:bool;default:false"` + RevisionMessage *string `json:"revision_message" gorm:"type:text"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + + // Relations + Article Articles `json:"article" gorm:"foreignKey:ArticleId;constraint:OnDelete:CASCADE"` + Workflow ApprovalWorkflows `json:"workflow" gorm:"foreignKey:WorkflowId"` + SubmittedBy *Users `json:"submitted_by" gorm:"foreignKey:SubmittedById"` + StepLogs []ArticleApprovalStepLogs `json:"step_logs" gorm:"foreignKey:ApprovalFlowId;constraint:OnDelete:CASCADE"` +} \ No newline at end of file diff --git a/app/database/entity/article_approval_step_logs.entity.go b/app/database/entity/article_approval_step_logs.entity.go new file mode 100644 index 0000000..6790462 --- /dev/null +++ b/app/database/entity/article_approval_step_logs.entity.go @@ -0,0 +1,25 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type ArticleApprovalStepLogs struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + ApprovalFlowId uint `json:"approval_flow_id" gorm:"type:int4;not null"` + StepOrder int `json:"step_order" gorm:"type:int4;not null"` + StepName string `json:"step_name" gorm:"type:varchar;not null"` + ApprovedById *uint `json:"approved_by_id" gorm:"type:int4"` + Action string `json:"action" gorm:"type:varchar;not null"` // approve, reject, request_revision + Message *string `json:"message" gorm:"type:text"` + ProcessedAt time.Time `json:"processed_at" gorm:"default:now()"` + UserLevelId uint `json:"user_level_id" gorm:"type:int4;not null"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + + // Relations + ApprovalFlow ArticleApprovalFlows `json:"approval_flow" gorm:"foreignKey:ApprovalFlowId;constraint:OnDelete:CASCADE"` + ApprovedBy Users `json:"approved_by" gorm:"foreignKey:ApprovedById"` + UserLevel UserLevels `json:"user_level" gorm:"foreignKey:UserLevelId"` +} \ No newline at end of file diff --git a/app/database/entity/article_approvals.entity.go b/app/database/entity/article_approvals.entity.go new file mode 100644 index 0000000..a5a8fc3 --- /dev/null +++ b/app/database/entity/article_approvals.entity.go @@ -0,0 +1,18 @@ +package entity + +import ( + "time" + + "github.com/google/uuid" +) + +type ArticleApprovals struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + ArticleId uint `json:"article_id" gorm:"type:int4"` + ApprovalBy uint `json:"approval_by" gorm:"type:int4"` + StatusId int `json:"status_id" gorm:"type:int4"` + Message string `json:"message" gorm:"type:varchar"` + ApprovalAtLevel *int `json:"approval_at_level" gorm:"type:int4"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` +} diff --git a/app/database/entity/article_categories.entity.go b/app/database/entity/article_categories.entity.go new file mode 100644 index 0000000..0c1ebdd --- /dev/null +++ b/app/database/entity/article_categories.entity.go @@ -0,0 +1,26 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type ArticleCategories struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Title string `json:"title" gorm:"type:varchar"` + Description string `json:"description" gorm:"type:varchar"` + ThumbnailPath *string `json:"thumbnail_path" gorm:"type:varchar"` + Slug *string `json:"slug" gorm:"type:varchar"` + ParentId *int `json:"parent_id" gorm:"type:int4"` + Tags *string `json:"tags" gorm:"type:varchar"` + Position *int `json:"position" gorm:"type:int4"` + CreatedById *uint `json:"created_by_id" gorm:"type:int4"` + OldCategoryId *uint `json:"old_category_id" gorm:"type:int4"` + StatusId int `json:"status_id" gorm:"type:int4;default:1"` + IsPublish *bool `json:"is_publish" gorm:"type:bool;default:false"` + PublishedAt *time.Time `json:"published_at" gorm:"type:timestamp"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/article_category_details/article_category_details.entity.go b/app/database/entity/article_category_details/article_category_details.entity.go new file mode 100644 index 0000000..45fdc52 --- /dev/null +++ b/app/database/entity/article_category_details/article_category_details.entity.go @@ -0,0 +1,22 @@ +package article_category_details + +import ( + "time" + entity "web-qudo-be/app/database/entity" + + "github.com/google/uuid" +) + +type ArticleCategoryDetails struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + ArticleId uint `json:"article_id" gorm:"type:int4"` + CategoryId int `json:"category_id" gorm:"type:int4"` + Category *entity.ArticleCategories `json:"category" gorm:"foreignKey:CategoryId;references:ID"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive bool `json:"is_active" gorm:"type:bool"` + CreatedAt time.Time `json:"created_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"` +} diff --git a/app/database/entity/article_comments.entity.go b/app/database/entity/article_comments.entity.go new file mode 100644 index 0000000..a73626f --- /dev/null +++ b/app/database/entity/article_comments.entity.go @@ -0,0 +1,27 @@ +package entity + +import ( + "time" + + "github.com/google/uuid" +) + +type ArticleComments struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Message string `json:"message" gorm:"type:varchar"` + ArticleId uint `json:"article_id" gorm:"type:int4"` + CommentFrom *uint `json:"comment_from" gorm:"type:int4"` + ParentId *int `json:"parent_id" gorm:"type:int4"` + IsPublic bool `json:"is_public" gorm:"type:bool;default:false"` + StatusId int `json:"status_id" gorm:"type:int4;default:0"` + ApprovedAt *time.Time `json:"approved_at" gorm:"type:timestamp"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive bool `json:"is_active" gorm:"type:bool"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + + // Relations + Article Articles `json:"article" gorm:"foreignKey:ArticleId"` +} + +// statusId => 0: waiting, 1: accepted, 2: replied, 3: rejected diff --git a/app/database/entity/article_files.entity.go b/app/database/entity/article_files.entity.go new file mode 100644 index 0000000..9c2a010 --- /dev/null +++ b/app/database/entity/article_files.entity.go @@ -0,0 +1,33 @@ +package entity + +import ( + "time" + + "github.com/google/uuid" +) + +type ArticleFiles struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + ArticleId uint `json:"article_id" gorm:"type:int4"` + UploadID *string `json:"upload_id" gorm:"type:varchar"` + FilePath *string `json:"file_path" gorm:"type:varchar"` + FileUrl *string `json:"file_url" gorm:"type:varchar"` + FileName *string `json:"file_name" gorm:"type:varchar"` + FileThumbnail *string `json:"file_thumbnail" gorm:"type:varchar"` + FileAlt *string `json:"file_alt" gorm:"type:varchar"` + WidthPixel *string `json:"width_pixel" gorm:"type:varchar"` + HeightPixel *string `json:"height_pixel" gorm:"type:varchar"` + Size *string `json:"size" gorm:"type:varchar"` + DownloadCount *int `json:"download_count" gorm:"type:int4;default:0"` + CreatedById int `json:"created_by_id" gorm:"type:int4"` + StatusId int `json:"status_id" gorm:"type:int4"` + IsPublish *bool `json:"is_publish" gorm:"type:bool;default:false"` + PublishedAt *time.Time `json:"published_at" gorm:"type:timestamp"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + + // Relations + Article Articles `json:"article" gorm:"foreignKey:ArticleId"` +} diff --git a/app/database/entity/article_nulis_ai.entity.go b/app/database/entity/article_nulis_ai.entity.go new file mode 100644 index 0000000..fb3d9f8 --- /dev/null +++ b/app/database/entity/article_nulis_ai.entity.go @@ -0,0 +1,21 @@ +package entity + +import "time" + +type ArticleNulisAI struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + NulisAiId int `json:"nulis_ai_id" gorm:"type:int4"` + ArticleId int `json:"article_id" gorm:"type:int4"` + Title string `json:"title" gorm:"type:varchar"` + Description string `json:"description" gorm:"type:varchar"` + HtmlDescription string `json:"html_description" gorm:"type:varchar"` + CategoryId int `json:"category_id" gorm:"type:int4"` + CreatorId uint `json:"creator_id" gorm:"type:int4"` + Tags string `json:"tags" gorm:"type:varchar"` + ThumbnailPath string `json:"thumbnail_path" gorm:"type:varchar"` + IsPublish bool `json:"is_publish" gorm:"type:bool"` + PublishedAt time.Time `json:"published_at" gorm:"type:timestamp"` + IsActive bool `json:"is_active" gorm:"type:bool"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/articles.entity.go b/app/database/entity/articles.entity.go new file mode 100644 index 0000000..e4f7806 --- /dev/null +++ b/app/database/entity/articles.entity.go @@ -0,0 +1,47 @@ +package entity + +import ( + "time" + + "github.com/google/uuid" +) + +type Articles struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Title string `json:"title" gorm:"type:varchar"` + Slug string `json:"slug" gorm:"type:varchar"` + Description string `json:"description" gorm:"type:varchar"` + CategoryId int `json:"category_id" gorm:"type:int4"` + HtmlDescription string `json:"html_description" gorm:"type:varchar"` + TypeId int `json:"type_id" gorm:"type:int4"` + Tags string `json:"tags" gorm:"type:varchar"` + ThumbnailName *string `json:"thumbnail_name" gorm:"type:varchar"` + ThumbnailPath *string `json:"thumbnail_path" gorm:"type:varchar"` + PageUrl *string `json:"page_url" gorm:"type:varchar"` + CreatedById *uint `json:"created_by_id" gorm:"type:int4"` + AiArticleId *int `json:"ai_article_id" gorm:"type:int4"` + CommentCount *int `json:"comment_count" gorm:"type:int4;default:0"` + ShareCount *int `json:"share_count" gorm:"type:int4;default:0"` + ViewCount *int `json:"view_count" gorm:"type:int4;default:0"` + StatusId *int `json:"status_id" gorm:"type:int4"` + OldId *uint `json:"old_id" gorm:"type:int4"` + NeedApprovalFrom *int `json:"need_approval_from" gorm:"type:int4"` + HasApprovedBy *string `json:"has_approved_by" gorm:"type:varchar"` + WorkflowId *uint `json:"workflow_id" gorm:"type:int4"` + CurrentApprovalStep *int `json:"current_approval_step" gorm:"type:int4;default:0"` // 0=not submitted, 1+=approval step + // New fields for no-approval support + BypassApproval *bool `json:"bypass_approval" gorm:"type:bool;default:false"` // true = skip approval process + ApprovalExempt *bool `json:"approval_exempt" gorm:"type:bool;default:false"` // true = permanently exempt from approval + IsPublish *bool `json:"is_publish" gorm:"type:bool;default:false"` + IsBanner *bool `json:"is_banner" gorm:"type:bool;default:false"` + PublishedAt *time.Time `json:"published_at" gorm:"type:timestamp"` + IsDraft *bool `json:"is_draft" gorm:"type:bool;default:false"` + DraftedAt *time.Time `json:"drafted_at" gorm:"type:timestamp"` + PublishSchedule *string `json:"publish_schedule" gorm:"type:varchar"` + Source *string `json:"source" gorm:"type:varchar"` + CustomCreatorName *string `json:"custom_creator_name" gorm:"type:varchar"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/audit_trails.entity.go b/app/database/entity/audit_trails.entity.go new file mode 100644 index 0000000..399c8f2 --- /dev/null +++ b/app/database/entity/audit_trails.entity.go @@ -0,0 +1,21 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type AuditTrails struct { + ID uint `gorm:"primaryKey"` + Method string + Path string + IP string + Status int + UserID *string + RequestHeaders string + RequestBody string + ResponseBody string + DurationMs int64 + ClientId *uuid.UUID + CreatedAt time.Time +} diff --git a/app/database/entity/bookmarks.entity.go b/app/database/entity/bookmarks.entity.go new file mode 100644 index 0000000..562c5f7 --- /dev/null +++ b/app/database/entity/bookmarks.entity.go @@ -0,0 +1,20 @@ +package entity + +import ( + "time" + + "github.com/google/uuid" +) + +type Bookmarks struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + UserId uint `json:"user_id" gorm:"type:int4"` + ArticleId uint `json:"article_id" gorm:"type:int4"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + + // Relations + Article Articles `json:"article" gorm:"foreignKey:ArticleId"` +} diff --git a/app/database/entity/cities.entity.go b/app/database/entity/cities.entity.go new file mode 100644 index 0000000..bdb0a80 --- /dev/null +++ b/app/database/entity/cities.entity.go @@ -0,0 +1,7 @@ +package entity + +type Cities struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + CityName string `json:"city_name" gorm:"type:varchar"` + ProvId int `json:"prov_id" gorm:"type:int4"` +} diff --git a/app/database/entity/client_approval_settings.entity.go b/app/database/entity/client_approval_settings.entity.go new file mode 100644 index 0000000..6caa3bd --- /dev/null +++ b/app/database/entity/client_approval_settings.entity.go @@ -0,0 +1,57 @@ +package entity + +import ( + "database/sql/driver" + "encoding/json" + "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 { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + 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 + 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 + 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 + ApprovalExemptCategories []uint `json:"approval_exempt_categories" gorm:"type:int4[]"` // category IDs exempt from approval + RequireApprovalFor []string `json:"require_approval_for" gorm:"type:jsonb"` // specific content types that need 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"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + + // Relations + Client Clients `json:"client" gorm:"foreignKey:ClientId;constraint:OnDelete:CASCADE"` + Workflow *ApprovalWorkflows `json:"workflow" gorm:"foreignKey:DefaultWorkflowId"` +} diff --git a/app/database/entity/clients.go b/app/database/entity/clients.go new file mode 100644 index 0000000..b4ba2f1 --- /dev/null +++ b/app/database/entity/clients.go @@ -0,0 +1,15 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type Clients struct { + ID uuid.UUID `json:"id" gorm:"primaryKey;type:UUID"` + Name string `json:"name" gorm:"type:varchar"` + CreatedById *uint `json:"created_by_id" gorm:"type:int4"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/csrf_token_records.entity.go b/app/database/entity/csrf_token_records.entity.go new file mode 100644 index 0000000..101dd8c --- /dev/null +++ b/app/database/entity/csrf_token_records.entity.go @@ -0,0 +1,15 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type CsrfTokenRecords struct { + ID uint `gorm:"primaryKey"` + Token string `gorm:"uniqueIndex;size:255"` + Value []byte `gorm:"value"` + ExpireAt time.Time `gorm:"index"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + CreatedAt time.Time +} diff --git a/app/database/entity/custom_static_pages.entity.go b/app/database/entity/custom_static_pages.entity.go new file mode 100644 index 0000000..68c8f5f --- /dev/null +++ b/app/database/entity/custom_static_pages.entity.go @@ -0,0 +1,18 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type CustomStaticPages struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Title string `json:"title" gorm:"type:varchar"` + Description string `json:"description" gorm:"type:varchar"` + Slug string `json:"slug" gorm:"type:varchar"` + HtmlBody string `json:"html_body" gorm:"type:text"` + IsActive bool `json:"is_active" gorm:"type:bool"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/districts.entity.go b/app/database/entity/districts.entity.go new file mode 100644 index 0000000..480de99 --- /dev/null +++ b/app/database/entity/districts.entity.go @@ -0,0 +1,7 @@ +package entity + +type Districts struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + DisName string `json:"dis_name" gorm:"type:varchar"` + CityId int `json:"city_id" gorm:"type:int4"` +} diff --git a/app/database/entity/feedbacks.entity.go b/app/database/entity/feedbacks.entity.go new file mode 100644 index 0000000..499dfd2 --- /dev/null +++ b/app/database/entity/feedbacks.entity.go @@ -0,0 +1,22 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type Feedbacks struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Message string `json:"message" gorm:"type:varchar"` + CommentFromName string `json:"comment_from_name" gorm:"type:varchar"` + CommentFromEmail string `json:"comment_from_email" gorm:"type:varchar"` + StatusId int `json:"status_id" gorm:"type:int4;default:0"` + ApprovedAt *time.Time `json:"approved_at" gorm:"type:timestamp"` + ReplyMessage *string `json:"reply_message" gorm:"type:varchar"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} + +// statusId => 0: waiting, 1: accepted, 2: replied, 3: nothing diff --git a/app/database/entity/forgot_passwords.entity.go b/app/database/entity/forgot_passwords.entity.go new file mode 100644 index 0000000..2fd5543 --- /dev/null +++ b/app/database/entity/forgot_passwords.entity.go @@ -0,0 +1,16 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type ForgotPasswords struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + KeycloakID string `json:"keycloak_id" gorm:"type:varchar"` + CodeRequest string `json:"code_request" gorm:"type:varchar"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive bool `json:"is_active" gorm:"type:bool"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/magazine_files.entity.go b/app/database/entity/magazine_files.entity.go new file mode 100644 index 0000000..1ebc3f4 --- /dev/null +++ b/app/database/entity/magazine_files.entity.go @@ -0,0 +1,28 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type MagazineFiles struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Title string `json:"title" gorm:"type:varchar"` + Description string `json:"description" gorm:"type:varchar"` + MagazineId uint `json:"magazine_id" gorm:"type:int4"` + DownloadCount *int `json:"download_count" gorm:"type:int4"` + StatusId int `json:"status_id" gorm:"type:int4"` + IsPublish *bool `json:"is_publish" gorm:"type:bool"` + FilePath *string `json:"file_path" gorm:"type:varchar"` + FileUrl *string `json:"file_url" gorm:"type:varchar"` + FileName *string `json:"file_name" gorm:"type:varchar"` + FileAlt *string `json:"file_alt" gorm:"type:varchar"` + WidthPixel *string `json:"width_pixel" gorm:"type:varchar"` + HeightPixel *string `json:"height_pixel" gorm:"type:varchar"` + Size *string `json:"size" gorm:"type:varchar"` + PublishedAt *time.Time `json:"published_at" gorm:"type:timestamp"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive bool `json:"is_active" gorm:"type:bool"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/magazines.entity.go b/app/database/entity/magazines.entity.go new file mode 100644 index 0000000..d2ed199 --- /dev/null +++ b/app/database/entity/magazines.entity.go @@ -0,0 +1,24 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type Magazines struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Title string `json:"title" gorm:"type:varchar"` + Description string `json:"description" gorm:"type:varchar"` + ThumbnailName *string `json:"thumbnail_name" gorm:"type:varchar"` + ThumbnailPath *string `json:"thumbnail_path" gorm:"type:varchar"` + ThumbnailUrl *string `json:"thumbnail_url" gorm:"type:varchar"` + PageUrl *string `json:"page_url" gorm:"type:varchar"` + CreatedById *uint `json:"created_by_id" gorm:"type:int4"` + StatusId int `json:"status_id" gorm:"type:int4"` + IsPublish *bool `json:"is_publish" gorm:"type:bool"` + PublishedAt *time.Time `json:"published_at" gorm:"type:timestamp"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive bool `json:"is_active" gorm:"type:bool"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/master_approval_statuses.entity.go b/app/database/entity/master_approval_statuses.entity.go new file mode 100644 index 0000000..7a24565 --- /dev/null +++ b/app/database/entity/master_approval_statuses.entity.go @@ -0,0 +1,7 @@ +package entity + +type MasterApprovalStatuses struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Name string `json:"name" gorm:"type:varchar"` + IsActive bool `json:"is_active" gorm:"type:bool"` +} diff --git a/app/database/entity/master_menus.entity.go b/app/database/entity/master_menus.entity.go new file mode 100644 index 0000000..b0e1290 --- /dev/null +++ b/app/database/entity/master_menus.entity.go @@ -0,0 +1,22 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type MasterMenus struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Name string `json:"name" gorm:"type:varchar"` + Description string `json:"description" gorm:"type:varchar"` + ModuleId int `json:"module_id" gorm:"type:int4"` + ParentMenuId *int `json:"parent_menu_id" gorm:"type:int4"` + Icon *string `json:"icon" gorm:"type:varchar"` + Group string `json:"group" gorm:"type:varchar"` + Position *int `json:"position" gorm:"type:int4"` + StatusId int `json:"status_id" gorm:"type:int4"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/master_modules.entity.go b/app/database/entity/master_modules.entity.go new file mode 100644 index 0000000..4eedc12 --- /dev/null +++ b/app/database/entity/master_modules.entity.go @@ -0,0 +1,18 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type MasterModules struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Name string `json:"name" gorm:"type:varchar"` + Description string `json:"description" gorm:"type:varchar"` + PathUrl string `json:"path_url" gorm:"type:varchar"` + StatusId int `json:"status_id" gorm:"type:int4"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/master_statuses.entity.go b/app/database/entity/master_statuses.entity.go new file mode 100644 index 0000000..75d20a0 --- /dev/null +++ b/app/database/entity/master_statuses.entity.go @@ -0,0 +1,7 @@ +package entity + +type MasterStatuses struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Name string `json:"name" gorm:"type:varchar"` + IsActive bool `json:"is_active" gorm:"type:bool"` +} diff --git a/app/database/entity/one_time_passwords.entity.go b/app/database/entity/one_time_passwords.entity.go new file mode 100644 index 0000000..1b7b6a6 --- /dev/null +++ b/app/database/entity/one_time_passwords.entity.go @@ -0,0 +1,18 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type OneTimePasswords struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Email string `json:"email" gorm:"type:varchar"` + Name *string `json:"name" gorm:"type:varchar"` + Identity *string `json:"identity" gorm:"type:varchar"` + OtpCode string `json:"otp_code" gorm:"type:varchar"` + ValidUntil time.Time `json:"valid_until" gorm:"default:(NOW() + INTERVAL '10 minutes')"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive bool `json:"is_active" gorm:"type:bool"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` +} diff --git a/app/database/entity/provinces.entity.go b/app/database/entity/provinces.entity.go new file mode 100644 index 0000000..f9f3902 --- /dev/null +++ b/app/database/entity/provinces.entity.go @@ -0,0 +1,9 @@ +package entity + +type Provinces struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + ProvName string `json:"prov_name" gorm:"type:varchar"` + LocationId int `json:"location_id" gorm:"type:int4"` + Status int `json:"status" gorm:"type:int4"` + Timezone string `json:"timezone" gorm:"type:varchar"` +} diff --git a/app/database/entity/schedules.entity.go b/app/database/entity/schedules.entity.go new file mode 100644 index 0000000..57838d3 --- /dev/null +++ b/app/database/entity/schedules.entity.go @@ -0,0 +1,29 @@ +package entity + +import ( + "time" + + "github.com/google/uuid" +) + +type Schedules struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Title string `json:"title" gorm:"type:varchar"` + Description string `json:"description" gorm:"type:varchar"` + Location string `json:"location" gorm:"type:varchar"` + IsLiveStreaming *bool `json:"is_live_streaming" gorm:"type:bool;default:false"` + LiveStreamingUrl *string `json:"live_streaming_url" gorm:"type:varchar"` + TypeId int `json:"type_id" gorm:"type:int4"` + StartDate *time.Time `json:"start_date" gorm:"type:date"` + EndDate *time.Time `json:"end_date" gorm:"type:date"` + StartTime *string `json:"start_time" gorm:"type:varchar"` + EndTime *string `json:"end_time" gorm:"type:varchar"` + Speakers string `json:"speakers" gorm:"type:varchar"` + PosterImagePath *string `json:"poster_image_path" gorm:"type:varchar"` + CreatedById *uint `json:"created_by_id" gorm:"type:int4"` + StatusId *int `json:"status_id" gorm:"type:int4"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/subscription.entity.go b/app/database/entity/subscription.entity.go new file mode 100644 index 0000000..3d11947 --- /dev/null +++ b/app/database/entity/subscription.entity.go @@ -0,0 +1,15 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type Subscription struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Email string `json:"email" gorm:"type:varchar"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/user_levels.entity.go b/app/database/entity/user_levels.entity.go new file mode 100644 index 0000000..ee7a906 --- /dev/null +++ b/app/database/entity/user_levels.entity.go @@ -0,0 +1,21 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type UserLevels struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Name string `json:"name" gorm:"type:varchar"` + AliasName string `json:"alias_name" gorm:"type:varchar"` + LevelNumber int `json:"level_number" gorm:"type:int4"` + ParentLevelId *int `json:"parent_level_id" gorm:"type:int4"` + ProvinceId *int `json:"province_id" gorm:"type:int4"` + Group *string `json:"group" gorm:"type:varchar"` + IsApprovalActive *bool `json:"is_approval_active" gorm:"type:bool;default:false"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive *bool `json:"is_active" gorm:"type:bool"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/user_role_accesses.entity.go b/app/database/entity/user_role_accesses.entity.go new file mode 100644 index 0000000..2ebf920 --- /dev/null +++ b/app/database/entity/user_role_accesses.entity.go @@ -0,0 +1,22 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type UserRoleAccesses struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + UserRoleId uint `json:"user_role_id" gorm:"type:int4"` + MenuId int `json:"menu_id" gorm:"type:int4"` + IsViewEnabled bool `json:"is_view_enabled" gorm:"type:bool"` + IsInsertEnabled bool `json:"is_insert_enabled" gorm:"type:bool"` + IsUpdateEnabled bool `json:"is_update_enabled" gorm:"type:bool"` + IsDeleteEnabled bool `json:"is_delete_enabled" gorm:"type:bool"` + IsApprovalEnabled bool `json:"is_approval_enabled" gorm:"type:bool"` + IsAdminEnabled bool `json:"is_admin_enabled" gorm:"type:bool"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/user_role_level_details.entity.go b/app/database/entity/user_role_level_details.entity.go new file mode 100644 index 0000000..2bc793f --- /dev/null +++ b/app/database/entity/user_role_level_details.entity.go @@ -0,0 +1,16 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type UserRoleLevelDetails struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + UserRoleId uint `json:"user_role_id" gorm:"type:int4"` + UserLevelId uint `json:"user_level_id" gorm:"type:int4"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/user_roles.entity.go b/app/database/entity/user_roles.entity.go new file mode 100644 index 0000000..f04223a --- /dev/null +++ b/app/database/entity/user_roles.entity.go @@ -0,0 +1,20 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type UserRoles struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Name string `json:"name" gorm:"type:varchar"` + Description string `json:"description" gorm:"type:varchar"` + Code string `json:"code" gorm:"type:varchar"` + StatusId int `json:"status_id" gorm:"type:int4;default:1"` + CreatedById *uint `json:"created_by_id" gorm:"type:int4"` + UserLevelId uint `json:"user_level_id" gorm:"type:int4"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/users.entity.go b/app/database/entity/users.entity.go new file mode 100644 index 0000000..0b0adf0 --- /dev/null +++ b/app/database/entity/users.entity.go @@ -0,0 +1,36 @@ +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type Users struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Username string `json:"username" gorm:"type:varchar"` + Email string `json:"email" gorm:"type:varchar"` + Fullname string `json:"fullname" gorm:"type:varchar"` + Address *string `json:"address" gorm:"type:varchar"` + PhoneNumber *string `json:"phone_number" gorm:"type:varchar"` + WorkType *string `json:"work_type" gorm:"type:varchar"` + GenderType *string `json:"gender_type" gorm:"type:varchar"` + IdentityType *string `json:"identity_type" gorm:"type:varchar"` + IdentityGroup *string `json:"identity_group" gorm:"type:varchar"` + IdentityGroupNumber *string `json:"identity_group_number" gorm:"type:varchar"` + IdentityNumber *string `json:"identity_number" gorm:"type:varchar"` + DateOfBirth *string `json:"date_of_birth" gorm:"type:varchar"` + LastEducation *string `json:"last_education" gorm:"type:varchar"` + UserRoleId uint `json:"user_role_id" gorm:"type:int4"` + UserLevelId uint `json:"user_level_id" gorm:"type:int4"` + UserLevel *UserLevels `json:"user_levels" gorm:"foreignKey:UserLevelId;references:ID"` + KeycloakId *string `json:"keycloak_id" gorm:"type:varchar"` + StatusId *int `json:"status_id" gorm:"type:int4;default:1"` + CreatedById *uint `json:"created_by_id" gorm:"type:int4"` + ProfilePicturePath *string `json:"profile_picture_path" gorm:"type:varchar"` + TempPassword *string `json:"temp_password" gorm:"type:varchar"` + IsEmailUpdated *bool `json:"is_email_updated" gorm:"type:bool;default:false"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} \ No newline at end of file diff --git a/app/database/entity/users/users.entity.go b/app/database/entity/users/users.entity.go new file mode 100644 index 0000000..4528f59 --- /dev/null +++ b/app/database/entity/users/users.entity.go @@ -0,0 +1,37 @@ +package users + +import ( + "github.com/google/uuid" + "time" + "web-qudo-be/app/database/entity" +) + +type Users struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Username string `json:"username" gorm:"type:varchar"` + Email string `json:"email" gorm:"type:varchar"` + Fullname string `json:"fullname" gorm:"type:varchar"` + Address *string `json:"address" gorm:"type:varchar"` + PhoneNumber *string `json:"phone_number" gorm:"type:varchar"` + WorkType *string `json:"work_type" gorm:"type:varchar"` + GenderType *string `json:"gender_type" gorm:"type:varchar"` + IdentityType *string `json:"identity_type" gorm:"type:varchar"` + IdentityGroup *string `json:"identity_group" gorm:"type:varchar"` + IdentityGroupNumber *string `json:"identity_group_number" gorm:"type:varchar"` + IdentityNumber *string `json:"identity_number" gorm:"type:varchar"` + DateOfBirth *string `json:"date_of_birth" gorm:"type:varchar"` + LastEducation *string `json:"last_education" gorm:"type:varchar"` + UserRoleId uint `json:"user_role_id" gorm:"type:int4"` + UserLevelId uint `json:"user_level_id" gorm:"type:int4"` + UserLevel *entity.UserLevels `json:"user_levels" gorm:"foreignKey:UserLevelId;references:ID"` + KeycloakId *string `json:"keycloak_id" gorm:"type:varchar"` + StatusId *int `json:"status_id" gorm:"type:int4;default:1"` + CreatedById *uint `json:"created_by_id" gorm:"type:int4"` + ProfilePicturePath *string `json:"profile_picture_path" gorm:"type:varchar"` + TempPassword *string `json:"temp_password" gorm:"type:varchar"` + IsEmailUpdated *bool `json:"is_email_updated" gorm:"type:bool;default:false"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/index.database.go b/app/database/index.database.go new file mode 100644 index 0000000..69425fc --- /dev/null +++ b/app/database/index.database.go @@ -0,0 +1,148 @@ +package database + +import ( + "web-qudo-be/app/database/entity" + "web-qudo-be/app/database/entity/article_category_details" + "web-qudo-be/config/config" + + "github.com/rs/zerolog" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +// Database setup database with gorm +type Database struct { + DB *gorm.DB + Log zerolog.Logger + Cfg *config.Config +} + +type Seeder interface { + Seed(*gorm.DB) error + Count(*gorm.DB) (int, error) +} + +func NewDatabase(cfg *config.Config, log zerolog.Logger) *Database { + db := &Database{ + Cfg: cfg, + Log: log, + } + + return db +} + +// ConnectDatabase connect database +func (_db *Database) ConnectDatabase() { + logMode := _db.Cfg.DB.Postgres.LogMode + var logLevel logger.LogLevel + if logMode == "INFO" { + logLevel = logger.Info + } else if logMode == "WARN" { + logLevel = logger.Warn + } else if logMode == "ERROR" { + logLevel = logger.Error + } else if logMode == "NONE" { + logLevel = logger.Silent + } + conn, err := gorm.Open(postgres.Open(_db.Cfg.DB.Postgres.DSN), &gorm.Config{ + Logger: logger.Default.LogMode(logLevel), + }) + if err != nil { + _db.Log.Error().Err(err).Msg("An unknown error occurred when to connect the database!") + } else { + _db.Log.Info().Msg("Connected the database succesfully!") + } + + _db.DB = conn +} + +// ShutdownDatabase shutdown database +func (_db *Database) ShutdownDatabase() { + sqlDB, err := _db.DB.DB() + if err != nil { + _db.Log.Error().Err(err).Msg("An unknown error occurred when to shutdown the database!") + } else { + _db.Log.Info().Msg("Shutdown the database succesfully!") + } + sqlDB.Close() +} + +// MigrateModels migrate models +func (_db *Database) MigrateModels() { + err := _db.DB.AutoMigrate( + Models()..., + ) + if err != nil { + _db.Log.Error().Err(err).Msg("An unknown error occurred when to migrate the database!") + } else { + _db.Log.Info().Msg("Migrate the database entity succesfully!") + } +} + +// Models list of models for migration +func Models() []interface{} { + return []interface{}{ + entity.ActivityLogs{}, + entity.ActivityLogTypes{}, + entity.Advertisement{}, + entity.ApprovalWorkflows{}, + entity.ApprovalWorkflowSteps{}, + entity.Articles{}, + entity.ArticleApprovalFlows{}, + entity.ArticleApprovalStepLogs{}, + entity.ArticleCategories{}, + entity.ArticleApprovals{}, + article_category_details.ArticleCategoryDetails{}, + entity.ArticleFiles{}, + entity.ArticleComments{}, + entity.ArticleNulisAI{}, + entity.AuditTrails{}, + entity.Bookmarks{}, + entity.Cities{}, + entity.Clients{}, + entity.ClientApprovalSettings{}, + entity.CsrfTokenRecords{}, + entity.CustomStaticPages{}, + entity.Districts{}, + entity.Feedbacks{}, + entity.ForgotPasswords{}, + entity.Magazines{}, + entity.MagazineFiles{}, + entity.MasterMenus{}, + entity.MasterModules{}, + entity.MasterStatuses{}, + entity.MasterApprovalStatuses{}, + entity.Provinces{}, + entity.OneTimePasswords{}, + entity.Subscription{}, + entity.Schedules{}, + entity.UserLevels{}, + entity.UserRoles{}, + entity.UserRoleAccesses{}, + entity.Users{}, + entity.UserRoleLevelDetails{}, + } +} + +// SeedModels seed data +func (_db *Database) SeedModels(seeder []Seeder) { + for _, seed := range seeder { + count, err := seed.Count(_db.DB) + if err != nil { + _db.Log.Error().Err(err).Msg("An unknown error occurred when to seed the database!") + } + + if count == 0 { + if err := seed.Seed(_db.DB); err != nil { + _db.Log.Error().Err(err).Msg("An unknown error occurred when to seed the database!") + } + + _db.Log.Info().Msg("Seeded the database successfully!") + } else { + _db.Log.Info().Msg("Database is already seeded!") + } + } + + _db.Log.Info().Msg("Seeded the database succesfully!") +} diff --git a/app/database/seeds/activity_log_types.seeds.go b/app/database/seeds/activity_log_types.seeds.go new file mode 100644 index 0000000..50bdc81 --- /dev/null +++ b/app/database/seeds/activity_log_types.seeds.go @@ -0,0 +1,50 @@ +package seeds + +import ( + "gorm.io/gorm" + "web-qudo-be/app/database/entity" +) + +type ActivityLogsSeeder struct{} + +var activityLogTypes = []entity.ActivityLogTypes{ + { + ID: 1, + Name: "Login", + IsActive: true, + }, + { + ID: 2, + Name: "View", + IsActive: true, + }, + { + ID: 3, + Name: "Share", + IsActive: true, + }, + { + ID: 4, + Name: "Comment", + IsActive: true, + }, +} + +func (ActivityLogsSeeder) Seed(conn *gorm.DB) error { + for _, row := range activityLogTypes { + if err := conn.Create(&row).Error; err != nil { + return err + } + } + + return nil +} + +func (ActivityLogsSeeder) Count(conn *gorm.DB) (int, error) { + var count int64 + if err := conn.Model(&entity.ActivityLogTypes{}).Count(&count).Error; err != nil { + return 0, err + } + + return int(count), nil +} diff --git a/app/database/seeds/approval_workflows.seeds.go b/app/database/seeds/approval_workflows.seeds.go new file mode 100644 index 0000000..3c74b8c --- /dev/null +++ b/app/database/seeds/approval_workflows.seeds.go @@ -0,0 +1,112 @@ +package seeds + +import ( + "gorm.io/gorm" + "web-qudo-be/app/database/entity" +) + +type ApprovalWorkflowsSeeder struct{} + +// Sample 3-level approval workflow +var approvalWorkflows = []entity.ApprovalWorkflows{ + { + ID: 1, + Name: "3-Level Approval Workflow", + Description: &[]string{"Standard 3-level approval workflow for articles: Editor -> Senior Editor -> Chief Editor"}[0], + IsActive: &[]bool{true}[0], + }, + { + ID: 2, + Name: "2-Level Approval Workflow", + Description: &[]string{"Simple 2-level approval workflow: Editor -> Chief Editor"}[0], + IsActive: &[]bool{true}[0], + }, +} + +// Sample approval workflow steps for 3-level workflow +var approvalWorkflowSteps = []entity.ApprovalWorkflowSteps{ + // 3-Level Workflow Steps + { + ID: 1, + WorkflowId: 1, + StepOrder: 1, + StepName: "Editor Review", + RequiredUserLevelId: 3, // Assuming Editor user level ID is 3 + CanSkip: &[]bool{false}[0], + AutoApproveAfterHours: &[]int{24}[0], + IsActive: &[]bool{true}[0], + }, + { + ID: 2, + WorkflowId: 1, + StepOrder: 2, + StepName: "Senior Editor Review", + RequiredUserLevelId: 4, // Assuming Senior Editor user level ID is 4 + CanSkip: &[]bool{false}[0], + AutoApproveAfterHours: &[]int{48}[0], + IsActive: &[]bool{true}[0], + }, + { + ID: 3, + WorkflowId: 1, + StepOrder: 3, + StepName: "Chief Editor Final Approval", + RequiredUserLevelId: 5, // Assuming Chief Editor user level ID is 5 + CanSkip: &[]bool{false}[0], + AutoApproveAfterHours: &[]int{72}[0], + IsActive: &[]bool{true}[0], + }, + // 2-Level Workflow Steps + { + ID: 4, + WorkflowId: 2, + StepOrder: 1, + StepName: "Editor Review", + RequiredUserLevelId: 3, // Editor user level + CanSkip: &[]bool{false}[0], + AutoApproveAfterHours: &[]int{24}[0], + IsActive: &[]bool{true}[0], + }, + { + ID: 5, + WorkflowId: 2, + StepOrder: 2, + StepName: "Chief Editor Approval", + RequiredUserLevelId: 5, // Chief Editor user level + CanSkip: &[]bool{false}[0], + AutoApproveAfterHours: &[]int{48}[0], + IsActive: &[]bool{true}[0], + }, +} + +func (ApprovalWorkflowsSeeder) Seed(conn *gorm.DB) error { + // Seed approval workflows + for _, workflow := range approvalWorkflows { + if err := conn.Create(&workflow).Error; err != nil { + return err + } + } + + // Seed approval workflow steps + for _, step := range approvalWorkflowSteps { + if err := conn.Create(&step).Error; err != nil { + return err + } + } + + return nil +} + +func (ApprovalWorkflowsSeeder) Count(conn *gorm.DB) (int, error) { + var workflowCount int64 + if err := conn.Model(&entity.ApprovalWorkflows{}).Count(&workflowCount).Error; err != nil { + return 0, err + } + + var stepCount int64 + if err := conn.Model(&entity.ApprovalWorkflowSteps{}).Count(&stepCount).Error; err != nil { + return 0, err + } + + return int(workflowCount + stepCount), nil +} \ No newline at end of file diff --git a/app/database/seeds/master_approval_statuses.seeds.go b/app/database/seeds/master_approval_statuses.seeds.go new file mode 100644 index 0000000..790c4fa --- /dev/null +++ b/app/database/seeds/master_approval_statuses.seeds.go @@ -0,0 +1,45 @@ +package seeds + +import ( + "gorm.io/gorm" + "web-qudo-be/app/database/entity" +) + +type MasterApprovalStatusesSeeder struct{} + +var masterApprovalStatuses = []entity.MasterApprovalStatuses{ + { + ID: 1, + Name: "Accepted", + IsActive: true, + }, + { + ID: 2, + Name: "Need Update", + IsActive: true, + }, + { + ID: 3, + Name: "Rejected", + IsActive: true, + }, +} + +func (MasterApprovalStatusesSeeder) Seed(conn *gorm.DB) error { + for _, row := range masterApprovalStatuses { + if err := conn.Create(&row).Error; err != nil { + return err + } + } + + return nil +} + +func (MasterApprovalStatusesSeeder) Count(conn *gorm.DB) (int, error) { + var count int64 + if err := conn.Model(&entity.MasterApprovalStatuses{}).Count(&count).Error; err != nil { + return 0, err + } + + return int(count), nil +} diff --git a/app/database/seeds/master_statuses.seeds.go b/app/database/seeds/master_statuses.seeds.go new file mode 100644 index 0000000..a2286aa --- /dev/null +++ b/app/database/seeds/master_statuses.seeds.go @@ -0,0 +1,45 @@ +package seeds + +import ( + "gorm.io/gorm" + "web-qudo-be/app/database/entity" +) + +type MasterStatusesSeeder struct{} + +var masterStatuses = []entity.MasterStatuses{ + { + ID: 1, + Name: "Waiting", + IsActive: true, + }, + { + ID: 2, + Name: "Active", + IsActive: true, + }, + { + ID: 3, + Name: "Inactive", + IsActive: true, + }, +} + +func (MasterStatusesSeeder) Seed(conn *gorm.DB) error { + for _, row := range masterStatuses { + if err := conn.Create(&row).Error; err != nil { + return err + } + } + + return nil +} + +func (MasterStatusesSeeder) Count(conn *gorm.DB) (int, error) { + var count int64 + if err := conn.Model(&entity.MasterStatuses{}).Count(&count).Error; err != nil { + return 0, err + } + + return int(count), nil +} diff --git a/app/database/seeds/no_approval_workflow.seeds.go b/app/database/seeds/no_approval_workflow.seeds.go new file mode 100644 index 0000000..4e2d54d --- /dev/null +++ b/app/database/seeds/no_approval_workflow.seeds.go @@ -0,0 +1,36 @@ +package seeds + +import ( + "web-qudo-be/app/database/entity" + + "github.com/google/uuid" +) + +// CreateNoApprovalWorkflow creates a special workflow that bypasses approval +func CreateNoApprovalWorkflow() *entity.ApprovalWorkflows { + return &entity.ApprovalWorkflows{ + Name: "No Approval Required", + Description: &[]string{"Workflow for clients that don't require approval process"}[0], + IsDefault: &[]bool{false}[0], + IsActive: &[]bool{true}[0], + RequiresApproval: &[]bool{false}[0], // This is the key field + AutoPublish: &[]bool{true}[0], // Auto publish articles + Steps: []entity.ApprovalWorkflowSteps{}, // No steps needed + } +} + +// CreateClientApprovalSettings creates default settings for a client +func CreateClientApprovalSettings(clientId string, requiresApproval bool) *entity.ClientApprovalSettings { + clientUUID, _ := uuid.Parse(clientId) + return &entity.ClientApprovalSettings{ + ClientId: clientUUID, + RequiresApproval: &[]bool{requiresApproval}[0], + AutoPublishArticles: &[]bool{!requiresApproval}[0], // Auto publish if no approval needed + IsActive: &[]bool{true}[0], + ApprovalExemptUsers: []uint{}, + ApprovalExemptRoles: []uint{}, + ApprovalExemptCategories: []uint{}, + RequireApprovalFor: []string{}, + SkipApprovalFor: []string{}, + } +} diff --git a/app/go-humas-be.exe b/app/go-humas-be.exe new file mode 100644 index 0000000..1fc8f85 Binary files /dev/null and b/app/go-humas-be.exe differ diff --git a/app/middleware/audit_trails.middleware.go b/app/middleware/audit_trails.middleware.go new file mode 100644 index 0000000..32aec3a --- /dev/null +++ b/app/middleware/audit_trails.middleware.go @@ -0,0 +1,68 @@ +package middleware + +import ( + "encoding/json" + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" + "log" + "strings" + "time" + "web-qudo-be/app/database/entity" + utilSvc "web-qudo-be/utils/service" +) + +func AuditTrailsMiddleware(db *gorm.DB) fiber.Handler { + return func(c *fiber.Ctx) error { + start := time.Now() + requestBody := c.Body() + + headersMap := c.GetReqHeaders() + headersJSON, _ := json.Marshal(headersMap) + + authHeader := c.Get("Authorization") + userId := utilSvc.GetUserId(authHeader) + + err := c.Next() + + audit := entity.AuditTrails{ + Method: c.Method(), + Path: c.OriginalURL(), + IP: getIP(c), + Status: c.Response().StatusCode(), + UserID: userId, + RequestHeaders: string(headersJSON), + RequestBody: string(requestBody), + ResponseBody: string(c.Response().Body()), + DurationMs: time.Since(start).Milliseconds(), + CreatedAt: time.Now(), + } + + go db.Create(&audit) + + return err + } +} + +func StartAuditTrailCleanup(db *gorm.DB, retention int) { + go func() { + for { + time.Sleep(24 * time.Hour) + + cutoff := time.Now().AddDate(0, 0, retention) + db.Where("created_at < ?", cutoff).Delete(&entity.AuditTrails{}) + + log.Printf(" at: %s", cutoff) + } + }() +} + +func getIP(c *fiber.Ctx) string { + ip := c.Get("X-Forwarded-For") + if ip == "" { + ip = c.IP() + } + if strings.Contains(ip, ":") { + ip = strings.Split(ip, ":")[0] + } + return ip +} diff --git a/app/middleware/client.middleware.go b/app/middleware/client.middleware.go new file mode 100644 index 0000000..f10e81c --- /dev/null +++ b/app/middleware/client.middleware.go @@ -0,0 +1,128 @@ +package middleware + +import ( + "strings" + "web-qudo-be/app/database/entity" + + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" + "gorm.io/gorm" +) + +const ( + ClientKeyHeader = "X-Client-Key" + ClientContextKey = "client_id" +) + +// excludedPaths contains paths that don't require client key validation +var excludedPaths = []string{ + "/swagger/*", + "/docs/*", + "/users/login", + "/health/*", + "/clients", + "/clients/*", + "*/viewer/*", + "/bookmarks/test-table", +} + +// isPathExcluded checks if the given path should be excluded from client key validation +func isPathExcluded(path string) bool { + for _, excludedPath := range excludedPaths { + if strings.HasPrefix(excludedPath, "*") && strings.HasSuffix(excludedPath, "*") { + // Handle wildcard at both beginning and end (e.g., "*/viewer/*") + pattern := excludedPath[1 : len(excludedPath)-1] // Remove * from both ends + if strings.Contains(path, pattern) { + return true + } + } else if strings.HasPrefix(excludedPath, "*") { + // Handle wildcard at the beginning + if strings.HasSuffix(path, excludedPath[1:]) { + return true + } + } else if strings.HasSuffix(excludedPath, "*") { + // Handle wildcard at the end + prefix := excludedPath[:len(excludedPath)-1] + if strings.HasPrefix(path, prefix) { + return true + } + } else { + // Exact match + if path == excludedPath { + return true + } + } + } + return false +} + +// ClientMiddleware extracts and validates the Client Key from request headers +func ClientMiddleware(db *gorm.DB) fiber.Handler { + return func(c *fiber.Ctx) error { + // Check if path should be excluded from client key validation + if isPathExcluded(c.Path()) { + return c.Next() + } + + // Extract Client Key from header + clientKey := c.Get(ClientKeyHeader) + + if clientKey == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "success": false, + "code": 400, + "messages": []string{"Client Key is required in header: " + ClientKeyHeader}, + }) + } + + // Parse UUID + clientUUID, err := uuid.Parse(clientKey) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "success": false, + "code": 400, + "messages": []string{"Invalid Client Key format"}, + }) + } + + // Validate client exists and is active + var client entity.Clients + if err := db.Where("id = ? AND is_active = ?", clientUUID, true).First(&client).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "success": false, + "code": 401, + "messages": []string{"Invalid or inactive Client Key"}, + }) + } + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "success": false, + "code": 500, + "messages": []string{"Error validating Client Key"}, + }) + } + + // Store client ID in context for use in handlers + c.Locals(ClientContextKey, clientUUID) + + return c.Next() + } +} + +// GetClientID retrieves the client ID from the context +func GetClientID(c *fiber.Ctx) *uuid.UUID { + if clientID, ok := c.Locals(ClientContextKey).(uuid.UUID); ok { + return &clientID + } + return nil +} + +// AddExcludedPath adds a new path to the excluded paths list +func AddExcludedPath(path string) { + excludedPaths = append(excludedPaths, path) +} + +// GetExcludedPaths returns the current list of excluded paths +func GetExcludedPaths() []string { + return excludedPaths +} diff --git a/app/middleware/csrf.middleware.go b/app/middleware/csrf.middleware.go new file mode 100644 index 0000000..21057d9 --- /dev/null +++ b/app/middleware/csrf.middleware.go @@ -0,0 +1,78 @@ +package middleware + +import ( + "fmt" + "gorm.io/gorm" + "time" + "web-qudo-be/app/database/entity" +) + +type PostgresStorage struct { + DB *gorm.DB +} + +func (s *PostgresStorage) Get(key string) ([]byte, error) { + //log.Printf("CSRF Storage: Get token %s", key) + + var record entity.CsrfTokenRecords + result := s.DB.Where("token = ?", key).First(&record) + + if result.Error != nil { + //log.Printf("CSRF Storage Get error: %v for token: %s", result.Error, key) + return nil, result.Error + } + + if record.ExpireAt.Before(time.Now()) { + //log.Printf("CSRF token %s is expired", key) + return nil, fmt.Errorf("CSRF token is expired") + } + + return record.Value, nil +} + +func (s *PostgresStorage) Set(key string, value []byte, exp time.Duration) error { + //log.Printf("CSRF Storage: Setting token %s with expiration %v", key, exp) + + // Calculate expiration time + expireAt := time.Now().Add(exp) + + // Try to update existing record first + result := s.DB.Model(&entity.CsrfTokenRecords{}). + Where("token = ?", key). + Updates(map[string]interface{}{ + "expire_at": expireAt, + }) + + // If no rows were affected (not found), create a new record + if result.RowsAffected == 0 { + record := entity.CsrfTokenRecords{ + Token: key, + Value: value, + ExpireAt: expireAt, + CreatedAt: time.Now(), + } + + if err := s.DB.Create(&record).Error; err != nil { + //log.Printf("CSRF Storage: Error saving token: %v", err) + return err + } + } else if result.Error != nil { + //log.Printf("CSRF Storage: Error updating token: %v", result.Error) + return result.Error + } + + //log.Printf("CSRF Storage: Successfully saved/updated token") + return nil +} + +func (s *PostgresStorage) Delete(key string) error { + return s.DB.Where("token = ?", key).Delete(&entity.CsrfTokenRecords{}).Error +} + +func (s *PostgresStorage) Reset() error { + return s.DB.Where("expire_at < ?", time.Now()).Delete(&entity.CsrfTokenRecords{}).Error +} + +func (s *PostgresStorage) Close() error { + return nil +} diff --git a/app/middleware/register.middleware.go b/app/middleware/register.middleware.go new file mode 100644 index 0000000..d33978b --- /dev/null +++ b/app/middleware/register.middleware.go @@ -0,0 +1,170 @@ +package middleware + +import ( + "log" + "time" + "web-qudo-be/app/database" + "web-qudo-be/config/config" + utilsSvc "web-qudo-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/middleware/compress" + "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v2/middleware/limiter" + "github.com/gofiber/fiber/v2/middleware/monitor" + "github.com/gofiber/fiber/v2/middleware/pprof" + "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v2/utils" +) + +// Middleware is a struct that contains all the middleware functions +type Middleware struct { + App *fiber.App + Cfg *config.Config +} + +func NewMiddleware(app *fiber.App, cfg *config.Config) *Middleware { + return &Middleware{ + App: app, + Cfg: cfg, + } +} + +// Register registers all the middleware functions +func (m *Middleware) Register(db *database.Database) { + // Add Extra Middlewares + + m.App.Use(limiter.New(limiter.Config{ + Next: utilsSvc.IsEnabled(m.Cfg.Middleware.Limiter.Enable), + Max: m.Cfg.Middleware.Limiter.Max, + Expiration: m.Cfg.Middleware.Limiter.Expiration * time.Second, + })) + + m.App.Use(compress.New(compress.Config{ + Next: utilsSvc.IsEnabled(m.Cfg.Middleware.Compress.Enable), + Level: m.Cfg.Middleware.Compress.Level, + })) + + m.App.Use(recover.New(recover.Config{ + Next: utilsSvc.IsEnabled(m.Cfg.Middleware.Recover.Enable), + })) + + m.App.Use(pprof.New(pprof.Config{ + Next: utilsSvc.IsEnabled(m.Cfg.Middleware.Pprof.Enable), + })) + + m.App.Use(cors.New(cors.Config{ + Next: utilsSvc.IsEnabled(m.Cfg.Middleware.Cors.Enable), + AllowOrigins: "http://localhost:3000, http://localhost:4000, https://dev.mikulnews.com, https://n8n.qudoco.com, https://narasiahli.com, https://dev.asuransiaman.com, https://dev.beritabumn.com, https://dev.kabarharapan.com, https://dev.kebaikanindonesia.com, https://dev.isukini.com, https://dev.bhayangkarakita.com, https://dev.infokreasi.com, https://dev.milenialbersuara.com, https://dev2.fokusaja.com, https://dev.arahnegeri.com, https://dev.wargabicara.com", + AllowMethods: "HEAD, GET, POST, PUT, DELETE, OPTIONS, PATCH", + AllowHeaders: "Origin, Content-Type, Accept, Accept-Language, Authorization, X-Requested-With, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Allow-Origin, Access-Control-Allow-Credentials, X-Csrf-Token, Cookie, Set-Cookie, X-Client-Key", + ExposeHeaders: "Content-Length, Content-Type", + AllowCredentials: true, + MaxAge: 12, + })) + + //=============================== + // CSRF CONFIG + //=============================== + + // Custom storage for CSRF + csrfSessionStorage := &PostgresStorage{ + DB: db.DB, + } + + // Store initialization for session + store := session.New(session.Config{ + CookieSameSite: m.Cfg.Middleware.Csrf.CookieSameSite, + CookieSecure: m.Cfg.Middleware.Csrf.CookieSecure, + CookieSessionOnly: m.Cfg.Middleware.Csrf.CookieSessionOnly, + CookieHTTPOnly: m.Cfg.Middleware.Csrf.CookieHttpOnly, + Storage: csrfSessionStorage, + }) + + m.App.Use(func(c *fiber.Ctx) error { + sess, err := store.Get(c) + if err != nil { + return err + } + c.Locals("session", sess) + return c.Next() + }) + + // Cleanup the expired token + go func() { + ticker := time.NewTicker(1 * time.Hour) + defer ticker.Stop() + + for range ticker.C { + if err := csrfSessionStorage.Reset(); err != nil { + log.Printf("Error cleaning up expired CSRF tokens: %v", err) + } + } + }() + + m.App.Use(csrf.New(csrf.Config{ + Next: utilsSvc.IsEnabled(m.Cfg.Middleware.Csrf.Enable), + KeyLookup: "header:" + csrf.HeaderName, + CookieName: m.Cfg.Middleware.Csrf.CookieName, + CookieSameSite: m.Cfg.Middleware.Csrf.CookieSameSite, + CookieSecure: m.Cfg.Middleware.Csrf.CookieSecure, + CookieSessionOnly: m.Cfg.Middleware.Csrf.CookieSessionOnly, + CookieHTTPOnly: m.Cfg.Middleware.Csrf.CookieHttpOnly, + Expiration: 1 * time.Hour, + KeyGenerator: utils.UUIDv4, + ContextKey: "csrf", + ErrorHandler: func(c *fiber.Ctx, err error) error { + return utilsSvc.CsrfErrorHandler(c, err) + }, + Extractor: csrf.CsrfFromHeader(csrf.HeaderName), + Session: store, + SessionKey: "fiber.csrf.token", + })) + + //=============================== + + // Client middleware - must be applied before other business logic + m.App.Use(ClientMiddleware(db.DB)) + + m.App.Use(AuditTrailsMiddleware(db.DB)) + // StartAuditTrailCleanup(db.DB, m.Cfg.Middleware.AuditTrails.Retention) + + //m.App.Use(filesystem.New(filesystem.Config{ + // Next: utils.IsEnabled(m.Cfg.Middleware.FileSystem.Enable), + // Root: http.Dir(m.Cfg.Middleware.FileSystem.Root), + // Browse: m.Cfg.Middleware.FileSystem.Browse, + // MaxAge: m.Cfg.Middleware.FileSystem.MaxAge, + //})) + + // ================================================== + + m.App.Get(m.Cfg.Middleware.Monitor.Path, monitor.New(monitor.Config{ + Next: utilsSvc.IsEnabled(m.Cfg.Middleware.Monitor.Enable), + })) + + // Route for generate CSRF token + m.App.Get("/csrf-token", func(c *fiber.Ctx) error { + // Retrieve CSRF token from Fiber's middleware context + token, ok := c.Locals("csrf").(string) + + //c.Context().VisitUserValues(func(key []byte, value interface{}) { + // log.Printf("Local Key: %s, Value: %v", key, value) + //}) + + if !ok || token == "" { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "success": false, + "code": 500, + "messages": []string{"Failed to retrieve CSRF token"}, + }) + } + + return c.JSON(fiber.Map{ + "success": true, + "csrf_token": token, + }) + }) +} diff --git a/app/middleware/user.middleware.go b/app/middleware/user.middleware.go new file mode 100644 index 0000000..89b25d4 --- /dev/null +++ b/app/middleware/user.middleware.go @@ -0,0 +1,57 @@ +package middleware + +import ( + "web-qudo-be/app/database/entity/users" + "web-qudo-be/app/module/users/repository" + utilSvc "web-qudo-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 +} diff --git a/app/module/activity_logs/activity_logs.module.go b/app/module/activity_logs/activity_logs.module.go new file mode 100644 index 0000000..a5e908c --- /dev/null +++ b/app/module/activity_logs/activity_logs.module.go @@ -0,0 +1,55 @@ +package activity_logs + +import ( + "web-qudo-be/app/module/activity_logs/controller" + "web-qudo-be/app/module/activity_logs/repository" + "web-qudo-be/app/module/activity_logs/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of ActivityLogsRouter +type ActivityLogsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of ActivityLogs module +var NewActivityLogsModule = fx.Options( + // register repository of ActivityLogs module + fx.Provide(repository.NewActivityLogsRepository), + + // register service of ActivityLogs module + fx.Provide(service.NewActivityLogsService), + + // register controller of ActivityLogs module + fx.Provide(controller.NewController), + + // register router of ActivityLogs module + fx.Provide(NewActivityLogsRouter), +) + +// init ActivityLogsRouter +func NewActivityLogsRouter(fiber *fiber.App, controller *controller.Controller) *ActivityLogsRouter { + return &ActivityLogsRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of ActivityLogs module +func (_i *ActivityLogsRouter) RegisterActivityLogsRoutes() { + // define controllers + activityLogsController := _i.Controller.ActivityLogs + + // define routes + _i.App.Route("/activity-logs", func(router fiber.Router) { + router.Get("/", activityLogsController.All) + router.Get("/statistics", activityLogsController.GetActivityStats) + router.Get("/detail/:id", activityLogsController.Show) + router.Post("/", activityLogsController.Save) + router.Put("/:id", activityLogsController.Update) + router.Delete("/:id", activityLogsController.Delete) + }) +} diff --git a/app/module/activity_logs/controller/activity_logs.controller.go b/app/module/activity_logs/controller/activity_logs.controller.go new file mode 100644 index 0000000..f5a4cf0 --- /dev/null +++ b/app/module/activity_logs/controller/activity_logs.controller.go @@ -0,0 +1,263 @@ +package controller + +import ( + "strconv" + "strings" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/activity_logs/request" + "web-qudo-be/app/module/activity_logs/service" + "web-qudo-be/utils/paginator" + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" +) + +type activityLogsController struct { + activityLogsService service.ActivityLogsService + Log zerolog.Logger +} + +type ActivityLogsController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + GetActivityStats(c *fiber.Ctx) error +} + +func NewActivityLogsController(activityLogsService service.ActivityLogsService, log zerolog.Logger) ActivityLogsController { + return &activityLogsController{ + activityLogsService: activityLogsService, + Log: log, + } +} + +// All get all ActivityLogs +// @Summary Get all ActivityLogs +// @Description API for getting all ActivityLogs +// @Tags ActivityLogs +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param req query request.ActivityLogsQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /activity-logs [get] +func (_i *activityLogsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + clientId := middleware.GetClientID(c) + + reqContext := request.ActivityLogsQueryRequestContext{ + ActivityTypeId: c.Query("activityTypeId"), + Url: c.Query("url"), + ArticleId: c.Query("articleId"), + UserId: c.Query("userId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + activityLogsData, paging, err := _i.activityLogsService.All(clientId, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ActivityLogs list successfully retrieved"}, + Data: activityLogsData, + Meta: paging, + }) +} + +// Show get one ActivityLogs +// @Summary Get one ActivityLogs +// @Description API for getting one ActivityLogs +// @Tags ActivityLogs +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param id path int true "ActivityLogs ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /activity-logs/detail/{id} [get] +func (_i *activityLogsController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + clientId := middleware.GetClientID(c) + + activityLogsData, err := _i.activityLogsService.Show(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ActivityLogs successfully retrieved"}, + Data: activityLogsData, + }) +} + +// Save create ActivityLogs +// @Summary Create ActivityLogs +// @Description API for create ActivityLogs +// @Tags ActivityLogs +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.ActivityLogsCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /activity-logs [post] +func (_i *activityLogsController) Save(c *fiber.Ctx) error { + req := new(request.ActivityLogsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + clientId := middleware.GetClientID(c) + + var authToken *string + getTokenFromHeader := c.Get("Authorization") + if getTokenFromHeader == "" { + authToken = nil + } else { + authToken = &getTokenFromHeader + } + visitorIp := GetVisitorIP(c) + req.VisitorIp = &visitorIp + dataResult, err := _i.activityLogsService.Save(clientId, *req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ActivityLogs successfully created"}, + Data: dataResult, + }) +} + +// Update update ActivityLogs +// @Summary update ActivityLogs +// @Description API for update ActivityLogs +// @Tags ActivityLogs +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.ActivityLogsUpdateRequest true "Required payload" +// @Param id path int true "ActivityLogs ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /activity-logs/{id} [put] +func (_i *activityLogsController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ActivityLogsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + clientId := middleware.GetClientID(c) + + err = _i.activityLogsService.Update(clientId, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ActivityLogs successfully updated"}, + }) +} + +// Delete delete ActivityLogs +// @Summary delete ActivityLogs +// @Description API for delete ActivityLogs +// @Tags ActivityLogs +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "ActivityLogs ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /activity-logs/{id} [delete] +func (_i *activityLogsController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + clientId := middleware.GetClientID(c) + + err = _i.activityLogsService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ActivityLogs successfully deleted"}, + }) +} + +// GetActivityStats get activity stats ActivityLogs +// @Summary Get activity stats ActivityLogs +// @Description API for get activity stats ActivityLogs +// @Tags ActivityLogs +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /activity-logs/statistics [get] +func (_i *activityLogsController) GetActivityStats(c *fiber.Ctx) error { + _i.Log.Info().Interface("GetActivityStats", "checker controller").Msg("") + + clientId := middleware.GetClientID(c) + + activityStatsData, err := _i.activityLogsService.GetActivityStats(clientId) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ActivityLogs Stats successfully retrieved"}, + Data: activityStatsData, + }) +} + +func GetVisitorIP(c *fiber.Ctx) string { + ip := c.Get("X-Forwarded-For") + if ip == "" { + ip = c.IP() + } + if strings.Contains(ip, ":") { + ip = strings.Split(ip, ":")[0] + } + return ip +} diff --git a/app/module/activity_logs/controller/controller.go b/app/module/activity_logs/controller/controller.go new file mode 100644 index 0000000..ab83d19 --- /dev/null +++ b/app/module/activity_logs/controller/controller.go @@ -0,0 +1,16 @@ +package controller + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/activity_logs/service" +) + +type Controller struct { + ActivityLogs ActivityLogsController +} + +func NewController(ActivityLogsService service.ActivityLogsService, log zerolog.Logger) *Controller { + return &Controller{ + ActivityLogs: NewActivityLogsController(ActivityLogsService, log), + } +} diff --git a/app/module/activity_logs/mapper/activity_logs.mapper.go b/app/module/activity_logs/mapper/activity_logs.mapper.go new file mode 100644 index 0000000..08d5ae9 --- /dev/null +++ b/app/module/activity_logs/mapper/activity_logs.mapper.go @@ -0,0 +1,20 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/activity_logs/response" +) + +func ActivityLogsResponseMapper(activityLogsReq *entity.ActivityLogs) (activityLogsRes *res.ActivityLogsResponse) { + if activityLogsReq != nil { + activityLogsRes = &res.ActivityLogsResponse{ + ID: activityLogsReq.ID, + ActivityTypeId: activityLogsReq.ActivityTypeId, + Url: activityLogsReq.Url, + ArticleId: activityLogsReq.ArticleId, + UserId: activityLogsReq.UserId, + CreatedAt: activityLogsReq.CreatedAt, + } + } + return activityLogsRes +} diff --git a/app/module/activity_logs/repository/activity_logs.repository.go b/app/module/activity_logs/repository/activity_logs.repository.go new file mode 100644 index 0000000..8dd2b74 --- /dev/null +++ b/app/module/activity_logs/repository/activity_logs.repository.go @@ -0,0 +1,149 @@ +package repository + +import ( + "fmt" + "github.com/google/uuid" + "github.com/rs/zerolog" + "strings" + "time" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/activity_logs/request" + "web-qudo-be/utils/paginator" +) + +type activityLogsRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ActivityLogsRepository define interface of IActivityLogsRepository +type ActivityLogsRepository interface { + GetAll(clientId *uuid.UUID, req request.ActivityLogsQueryRequest) (activityLogss []*entity.ActivityLogs, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (activityLogs *entity.ActivityLogs, err error) + Create(activityLogs *entity.ActivityLogs) (activityLogsReturn *entity.ActivityLogs, err error) + Update(clientId *uuid.UUID, id uint, activityLogs *entity.ActivityLogs) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) + CountUniqueVisitorAllTime(clientId *uuid.UUID) (count int64, err error) + CountUniqueVisitorToday(clientId *uuid.UUID) (count int64, err error) + CountTotalViewAllTime(clientId *uuid.UUID) (count int64, err error) +} + +func NewActivityLogsRepository(db *database.Database, logger zerolog.Logger) ActivityLogsRepository { + return &activityLogsRepository{ + DB: db, + Log: logger, + } +} + +// implement interface of IActivityLogsRepository +func (_i *activityLogsRepository) GetAll(clientId *uuid.UUID, req request.ActivityLogsQueryRequest) (activityLogss []*entity.ActivityLogs, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ActivityLogs{}) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if req.ActivityTypeId != nil { + query = query.Where("activity_type_id = ?", req.ActivityTypeId) + } + if req.Url != nil && *req.Url != "" { + url := strings.ToLower(*req.Url) + query = query.Where("LOWER(url) LIKE ?", "%"+strings.ToLower(url)+"%") + } + if req.ArticleId != nil { + query = query.Where("article_id = ?", req.ArticleId) + } + if req.UserId != nil { + query = query.Where("user_id = ?", req.UserId) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&activityLogss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *activityLogsRepository) FindOne(clientId *uuid.UUID, id uint) (activityLogs *entity.ActivityLogs, err error) { + query := _i.DB.DB.Where("id = ?", id) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err := query.First(&activityLogs).Error; err != nil { + return nil, err + } + + return activityLogs, nil +} + +func (_i *activityLogsRepository) Create(activityLogs *entity.ActivityLogs) (activityLogsReturn *entity.ActivityLogs, err error) { + result := _i.DB.DB.Create(activityLogs) + return activityLogs, result.Error +} + +func (_i *activityLogsRepository) Update(clientId *uuid.UUID, id uint, activityLogs *entity.ActivityLogs) (err error) { + query := _i.DB.DB.Model(&entity.ActivityLogs{}).Where(&entity.ActivityLogs{ID: id}) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + return query.Updates(activityLogs).Error +} + +func (_i *activityLogsRepository) Delete(clientId *uuid.UUID, id uint) error { + query := _i.DB.DB.Model(&entity.ActivityLogs{}).Where("id = ?", id) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + return query.Delete(&entity.ActivityLogs{}).Error +} + +func (_i *activityLogsRepository) CountUniqueVisitorAllTime(clientId *uuid.UUID) (count int64, err error) { + query := _i.DB.DB.Model(&entity.ActivityLogs{}) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + err = query.Distinct("visitor_ip").Count(&count).Error + return +} + +func (_i *activityLogsRepository) CountTotalViewAllTime(clientId *uuid.UUID) (count int64, err error) { + query := _i.DB.DB.Model(&entity.ActivityLogs{}).Where("activity_type_id = ?", 2) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + err = query.Count(&count).Error + return +} + +func (_i *activityLogsRepository) CountUniqueVisitorToday(clientId *uuid.UUID) (count int64, err error) { + tenMinutesAgo := time.Now().Add(-10 * time.Minute) + + query := _i.DB.DB.Model(&entity.AuditTrails{}).Where("created_at >= ?", tenMinutesAgo) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + err = query.Select("ip").Group("ip").Count(&count).Error + return +} diff --git a/app/module/activity_logs/request/activity_logs.request.go b/app/module/activity_logs/request/activity_logs.request.go new file mode 100644 index 0000000..adf98b4 --- /dev/null +++ b/app/module/activity_logs/request/activity_logs.request.go @@ -0,0 +1,92 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type ActivityLogsGeneric interface { + ToEntity() +} + +type ActivityLogsQueryRequest struct { + ActivityTypeId *int `json:"activityTypeId"` + Url *string `json:"url"` + ArticleId *int `json:"articleId"` + UserId *int `json:"userId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ActivityLogsCreateRequest struct { + ActivityTypeId int `json:"activityTypeId" validate:"required"` + Url string `json:"url" validate:"required"` + ArticleId *uint `json:"articleId"` + UserId *uint `json:"userId"` + VisitorIp *string `json:"visitorIp"` +} + +func (req ActivityLogsCreateRequest) ToEntity() *entity.ActivityLogs { + return &entity.ActivityLogs{ + ActivityTypeId: req.ActivityTypeId, + Url: req.Url, + ArticleId: req.ArticleId, + UserId: req.UserId, + VisitorIp: req.VisitorIp, + CreatedAt: time.Now(), + } +} + +type ActivityLogsUpdateRequest struct { + ID uint `json:"id" validate:"required"` + ActivityTypeId int `json:"activityTypeId" validate:"required"` + Url string `json:"url" validate:"required"` + ArticleId *uint `json:"articleId"` + UserId *uint `json:"userId"` +} + +func (req ActivityLogsUpdateRequest) ToEntity() *entity.ActivityLogs { + return &entity.ActivityLogs{ + ID: req.ID, + ActivityTypeId: req.ActivityTypeId, + Url: req.Url, + ArticleId: req.ArticleId, + UserId: req.UserId, + } +} + +type ActivityLogsQueryRequestContext struct { + ActivityTypeId string `json:"activityTypeId"` + Url string `json:"url"` + ArticleId string `json:"articleId"` + UserId string `json:"userId"` +} + +func (req ActivityLogsQueryRequestContext) ToParamRequest() ActivityLogsQueryRequest { + var request ActivityLogsQueryRequest + + if activityTypeIdStr := req.ActivityTypeId; activityTypeIdStr != "" { + activityTypeId, err := strconv.Atoi(activityTypeIdStr) + if err == nil { + request.ActivityTypeId = &activityTypeId + } + } + if url := req.Url; url != "" { + request.Url = &url + } + if articleIdStr := req.ArticleId; articleIdStr != "" { + articleId, err := strconv.Atoi(articleIdStr) + if err == nil { + request.ArticleId = &articleId + } + } + if userIdStr := req.UserId; userIdStr != "" { + userId, err := strconv.Atoi(userIdStr) + if err == nil { + request.UserId = &userId + } + } + + return request +} diff --git a/app/module/activity_logs/response/activity_logs.response.go b/app/module/activity_logs/response/activity_logs.response.go new file mode 100644 index 0000000..6c2040e --- /dev/null +++ b/app/module/activity_logs/response/activity_logs.response.go @@ -0,0 +1,18 @@ +package response + +import "time" + +type ActivityLogsResponse struct { + ID uint `json:"id"` + ActivityTypeId int `json:"activityTypeId"` + Url string `json:"url"` + ArticleId *uint `json:"articleId"` + UserId *uint `json:"userId"` + CreatedAt time.Time `json:"createdAt"` +} + +type ActivityStatsResponse struct { + TotalVisitorAllTime int64 `json:"totalVisitorAllTime"` + TotalVisitorToday int64 `json:"totalVisitorToday"` + TotalViewAllTime int64 `json:"totalViewAllTime"` +} diff --git a/app/module/activity_logs/service/activity_logs.service.go b/app/module/activity_logs/service/activity_logs.service.go new file mode 100644 index 0000000..c2fc848 --- /dev/null +++ b/app/module/activity_logs/service/activity_logs.service.go @@ -0,0 +1,131 @@ +package service + +import ( + "github.com/google/uuid" + "github.com/rs/zerolog" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/activity_logs/mapper" + "web-qudo-be/app/module/activity_logs/repository" + "web-qudo-be/app/module/activity_logs/request" + "web-qudo-be/app/module/activity_logs/response" + "web-qudo-be/app/module/articles/service" + usersRepository "web-qudo-be/app/module/users/repository" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" +) + +// ActivityLogsService +type activityLogsService struct { + Repo repository.ActivityLogsRepository + UsersRepo usersRepository.UsersRepository + ArticleService service.ArticlesService + Log zerolog.Logger +} + +// ActivityLogsService define interface of IActivityLogsService +type ActivityLogsService interface { + All(clientId *uuid.UUID, req request.ActivityLogsQueryRequest) (activityLogs []*response.ActivityLogsResponse, paging paginator.Pagination, err error) + Show(clientId *uuid.UUID, id uint) (activityLogs *response.ActivityLogsResponse, err error) + Save(clientId *uuid.UUID, req request.ActivityLogsCreateRequest, authToken *string) (activityLogs *entity.ActivityLogs, err error) + Update(clientId *uuid.UUID, id uint, req request.ActivityLogsUpdateRequest) (err error) + Delete(clientId *uuid.UUID, id uint) error + GetActivityStats(clientId *uuid.UUID) (activityStats *response.ActivityStatsResponse, err error) +} + +// NewActivityLogsService init ActivityLogsService +func NewActivityLogsService(repo repository.ActivityLogsRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository, articleService service.ArticlesService) ActivityLogsService { + + return &activityLogsService{ + Repo: repo, + Log: log, + UsersRepo: usersRepo, + ArticleService: articleService, + } +} + +// All implement interface of ActivityLogsService +func (_i *activityLogsService) All(clientId *uuid.UUID, req request.ActivityLogsQueryRequest) (activityLogss []*response.ActivityLogsResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(clientId, req) + if err != nil { + return + } + + for _, result := range results { + activityLogss = append(activityLogss, mapper.ActivityLogsResponseMapper(result)) + } + + return +} + +func (_i *activityLogsService) Show(clientId *uuid.UUID, id uint) (activityLogs *response.ActivityLogsResponse, err error) { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return nil, err + } + + return mapper.ActivityLogsResponseMapper(result), nil +} + +func (_i *activityLogsService) Save(clientId *uuid.UUID, req request.ActivityLogsCreateRequest, authToken *string) (activityLogs *entity.ActivityLogs, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + newReq := req.ToEntity() + + if clientId != nil { + newReq.ClientId = clientId + } + + if authToken != nil { + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, *authToken) + newReq.UserId = &createdBy.ID + } + + result, err := _i.Repo.Create(newReq) + if err != nil { + return nil, err + } + + // update article + err = _i.ArticleService.UpdateActivityCount(clientId, *req.ArticleId, req.ActivityTypeId) + if err != nil { + return nil, err + } + + return result, nil +} + +func (_i *activityLogsService) Update(clientId *uuid.UUID, id uint, req request.ActivityLogsUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + if clientId != nil { + newReq.ClientId = clientId + } + return _i.Repo.Update(clientId, id, newReq) +} + +func (_i *activityLogsService) Delete(clientId *uuid.UUID, id uint) error { + return _i.Repo.Delete(clientId, id) +} + +func (_i *activityLogsService) GetActivityStats(clientId *uuid.UUID) (activityStats *response.ActivityStatsResponse, err error) { + _i.Log.Info().Interface("GetActivityStats", "checker").Msg("") + countUniqueVisitorAllTime, err := _i.Repo.CountUniqueVisitorAllTime(clientId) + if err != nil { + return nil, err + } + countUniqueVisitorToday, err := _i.Repo.CountUniqueVisitorToday(clientId) + if err != nil { + return nil, err + } + countTotalViewAllTime, err := _i.Repo.CountTotalViewAllTime(clientId) + if err != nil { + return nil, err + } + + getActivityStats := &response.ActivityStatsResponse{ + TotalVisitorAllTime: countUniqueVisitorAllTime, + TotalVisitorToday: countUniqueVisitorToday, + TotalViewAllTime: countTotalViewAllTime, + } + return getActivityStats, nil +} diff --git a/app/module/advertisement/advertisement.module.go b/app/module/advertisement/advertisement.module.go new file mode 100644 index 0000000..f660299 --- /dev/null +++ b/app/module/advertisement/advertisement.module.go @@ -0,0 +1,56 @@ +package advertisement + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/advertisement/controller" + "web-qudo-be/app/module/advertisement/repository" + "web-qudo-be/app/module/advertisement/service" +) + +// struct of AdvertisementRouter +type AdvertisementRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of Advertisement module +var NewAdvertisementModule = fx.Options( + // register repository of Advertisement module + fx.Provide(repository.NewAdvertisementRepository), + + // register service of Advertisement module + fx.Provide(service.NewAdvertisementService), + + // register controller of Advertisement module + fx.Provide(controller.NewController), + + // register router of Advertisement module + fx.Provide(NewAdvertisementRouter), +) + +// init AdvertisementRouter +func NewAdvertisementRouter(fiber *fiber.App, controller *controller.Controller) *AdvertisementRouter { + return &AdvertisementRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of Advertisement module +func (_i *AdvertisementRouter) RegisterAdvertisementRoutes() { + // define controllers + advertisementController := _i.Controller.Advertisement + + // define routes + _i.App.Route("/advertisement", func(router fiber.Router) { + router.Get("/", advertisementController.All) + router.Get("/:id", advertisementController.Show) + router.Post("/", advertisementController.Save) + router.Post("/upload/:id", advertisementController.Upload) + router.Get("/viewer/:filename", advertisementController.Viewer) + router.Put("/:id", advertisementController.Update) + router.Put("/publish/:id", advertisementController.UpdatePublish) + router.Delete("/:id", advertisementController.Delete) + }) +} diff --git a/app/module/advertisement/controller/advertisement.controller.go b/app/module/advertisement/controller/advertisement.controller.go new file mode 100644 index 0000000..1026ea6 --- /dev/null +++ b/app/module/advertisement/controller/advertisement.controller.go @@ -0,0 +1,308 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/advertisement/request" + "web-qudo-be/app/module/advertisement/service" + "web-qudo-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type advertisementController struct { + advertisementService service.AdvertisementService + Log zerolog.Logger +} + +type AdvertisementController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Upload(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + UpdatePublish(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + Viewer(c *fiber.Ctx) error +} + +func NewAdvertisementController(advertisementService service.AdvertisementService, log zerolog.Logger) AdvertisementController { + return &advertisementController{ + advertisementService: advertisementService, + Log: log, + } +} + +// All get all Advertisement +// @Summary Get all Advertisement +// @Description API for getting all Advertisement +// @Tags Advertisement +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param req query request.AdvertisementQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /advertisement [get] +func (_i *advertisementController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + clientId := middleware.GetClientID(c) + + reqContext := request.AdvertisementQueryRequestContext{ + Title: c.Query("title"), + Description: c.Query("description"), + RedirectLink: c.Query("redirectLink"), + Placement: c.Query("placement"), + StatusId: c.Query("statusId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + advertisementData, paging, err := _i.advertisementService.All(clientId, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Advertisement list successfully retrieved"}, + Data: advertisementData, + Meta: paging, + }) +} + +// Show get one Advertisement +// @Summary Get one Advertisement +// @Description API for getting one Advertisement +// @Tags Advertisement +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param id path int true "Advertisement ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /advertisement/{id} [get] +func (_i *advertisementController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + clientId := middleware.GetClientID(c) + + advertisementData, err := _i.advertisementService.Show(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Advertisement successfully retrieved"}, + Data: advertisementData, + }) +} + +// Save create Advertisement +// @Summary Create Advertisement +// @Description API for create Advertisement +// @Tags Advertisement +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.AdvertisementCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /advertisement [post] +func (_i *advertisementController) Save(c *fiber.Ctx) error { + req := new(request.AdvertisementCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + clientId := middleware.GetClientID(c) + + dataResult, err := _i.advertisementService.Save(clientId, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Advertisement successfully created"}, + Data: dataResult, + }) +} + +// Upload Advertisement +// @Summary Upload Advertisement +// @Description API for Upload File Advertisement +// @Tags Advertisement +// @Security Bearer +// @Produce json +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param file formData file true "Upload file" multiple false +// @Param id path int true "Advertisement ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /advertisement/upload/{id} [post] +func (_i *advertisementController) Upload(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + clientId := middleware.GetClientID(c) + + err = _i.advertisementService.Upload(clientId, c, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Advertisement successfully upload"}, + }) +} + +// Update update Advertisement +// @Summary update Advertisement +// @Description API for update Advertisement +// @Tags Advertisement +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.AdvertisementUpdateRequest true "Required payload" +// @Param id path int true "Advertisement ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /advertisement/{id} [put] +func (_i *advertisementController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.AdvertisementUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + clientId := middleware.GetClientID(c) + + err = _i.advertisementService.Update(clientId, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Advertisement successfully updated"}, + }) +} + +// UpdatePublish Advertisement +// @Summary Update Publish Advertisement +// @Description API for Update Publish Advertisement +// @Tags Advertisement +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Advertisement ID" +// @Param isPublish query bool true "Advertisement Publish Status" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /advertisement/publish/{id} [put] +func (_i *advertisementController) UpdatePublish(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + isPublish, err := strconv.ParseBool(c.Query("isPublish")) + if err != nil { + return err + } + + clientId := middleware.GetClientID(c) + + err = _i.advertisementService.UpdatePublish(clientId, uint(id), isPublish) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Advertisement successfully publish updated"}, + }) +} + +// Delete delete Advertisement +// @Summary delete Advertisement +// @Description API for delete Advertisement +// @Tags Advertisement +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "Advertisement ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /advertisement/{id} [delete] +func (_i *advertisementController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + clientId := middleware.GetClientID(c) + + err = _i.advertisementService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Advertisement successfully deleted"}, + }) +} + +// Viewer Advertisement +// @Summary Viewer Advertisement +// @Description API for Viewer Advertisement +// @Tags Advertisement +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param filename path string true "Content File Name" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /advertisement/viewer/{filename} [get] +func (_i *advertisementController) Viewer(c *fiber.Ctx) error { + clientId := middleware.GetClientID(c) + return _i.advertisementService.Viewer(clientId, c) +} diff --git a/app/module/advertisement/controller/controller.go b/app/module/advertisement/controller/controller.go new file mode 100644 index 0000000..2105be4 --- /dev/null +++ b/app/module/advertisement/controller/controller.go @@ -0,0 +1,16 @@ +package controller + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/advertisement/service" +) + +type Controller struct { + Advertisement AdvertisementController +} + +func NewController(AdvertisementService service.AdvertisementService, log zerolog.Logger) *Controller { + return &Controller{ + Advertisement: NewAdvertisementController(AdvertisementService, log), + } +} diff --git a/app/module/advertisement/mapper/advertisement.mapper.go b/app/module/advertisement/mapper/advertisement.mapper.go new file mode 100644 index 0000000..e27230d --- /dev/null +++ b/app/module/advertisement/mapper/advertisement.mapper.go @@ -0,0 +1,28 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/advertisement/response" +) + +func AdvertisementResponseMapper(advertisementReq *entity.Advertisement, host string) (advertisementRes *res.AdvertisementResponse) { + if advertisementReq != nil { + advertisementRes = &res.AdvertisementResponse{ + ID: advertisementReq.ID, + Title: advertisementReq.Title, + Description: advertisementReq.Description, + RedirectLink: advertisementReq.RedirectLink, + Placement: advertisementReq.Placement, + StatusId: advertisementReq.StatusId, + IsActive: advertisementReq.IsActive, + IsPublish: advertisementReq.IsPublish, + CreatedAt: advertisementReq.CreatedAt, + UpdatedAt: advertisementReq.UpdatedAt, + } + + if advertisementReq.ContentFilePath != nil { + advertisementRes.ContentFileUrl = host + "/advertisement/viewer/" + *advertisementReq.ContentFileName + } + } + return advertisementRes +} diff --git a/app/module/advertisement/repository/advertisement.repository.go b/app/module/advertisement/repository/advertisement.repository.go new file mode 100644 index 0000000..64ce428 --- /dev/null +++ b/app/module/advertisement/repository/advertisement.repository.go @@ -0,0 +1,145 @@ +package repository + +import ( + "fmt" + "github.com/google/uuid" + "github.com/rs/zerolog" + "strings" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/advertisement/request" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" +) + +type advertisementRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// AdvertisementRepository define interface of IAdvertisementRepository +type AdvertisementRepository interface { + GetAll(clientId *uuid.UUID, req request.AdvertisementQueryRequest) (advertisements []*entity.Advertisement, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (advertisement *entity.Advertisement, err error) + FindByFilename(clientId *uuid.UUID, contentFilename string) (advertisement *entity.Advertisement, err error) + Create(advertisement *entity.Advertisement) (advertisementReturn *entity.Advertisement, err error) + Update(clientId *uuid.UUID, id uint, advertisement *entity.Advertisement) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) +} + +func NewAdvertisementRepository(db *database.Database, logger zerolog.Logger) AdvertisementRepository { + return &advertisementRepository{ + DB: db, + Log: logger, + } +} + +// implement interface of IAdvertisementRepository +func (_i *advertisementRepository) GetAll(clientId *uuid.UUID, req request.AdvertisementQueryRequest) (advertisements []*entity.Advertisement, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Advertisement{}) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("is_active = ?", true) + + if req.Title != nil && *req.Title != "" { + title := strings.ToLower(*req.Title) + query = query.Where("LOWER(title) LIKE ?", "%"+strings.ToLower(title)+"%") + } + if req.Description != nil && *req.Description != "" { + description := strings.ToLower(*req.Description) + query = query.Where("LOWER(description) LIKE ?", "%"+strings.ToLower(description)+"%") + } + if req.RedirectLink != nil && *req.RedirectLink != "" { + redirectLink := strings.ToLower(*req.RedirectLink) + query = query.Where("LOWER(redirect_link) LIKE ?", "%"+strings.ToLower(redirectLink)+"%") + } + if req.Placement != nil && *req.Placement != "" { + placement := strings.ToLower(*req.Placement) + query = query.Where("LOWER(placement) LIKE ?", "%"+strings.ToLower(placement)+"%") + } + if req.StatusId != nil { + query = query.Where("status_id = ?", req.StatusId) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&advertisements).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *advertisementRepository) FindOne(clientId *uuid.UUID, id uint) (advertisement *entity.Advertisement, err error) { + query := _i.DB.DB.Where("id = ?", id) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err := query.First(&advertisement).Error; err != nil { + return nil, err + } + + return advertisement, nil +} + +func (_i *advertisementRepository) FindByFilename(clientId *uuid.UUID, contentFilename string) (advertisement *entity.Advertisement, err error) { + query := _i.DB.DB.Where("content_file_name = ?", contentFilename) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err := query.First(&advertisement).Error; err != nil { + return nil, err + } + + return advertisement, nil +} + +func (_i *advertisementRepository) Create(advertisement *entity.Advertisement) (advertisementReturn *entity.Advertisement, err error) { + result := _i.DB.DB.Create(advertisement) + return advertisement, result.Error +} + +func (_i *advertisementRepository) Update(clientId *uuid.UUID, id uint, advertisement *entity.Advertisement) (err error) { + advertisementMap, err := utilSvc.StructToMap(advertisement) + if err != nil { + return err + } + query := _i.DB.DB.Model(&entity.Advertisement{}).Where(&entity.Advertisement{ID: id}) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + return query.Updates(advertisementMap).Error +} + +func (_i *advertisementRepository) Delete(clientId *uuid.UUID, id uint) error { + query := _i.DB.DB.Model(&entity.Advertisement{}).Where("id = ?", id) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + return query.Delete(&entity.Advertisement{}).Error +} diff --git a/app/module/advertisement/request/advertisement.request.go b/app/module/advertisement/request/advertisement.request.go new file mode 100644 index 0000000..0ac5aac --- /dev/null +++ b/app/module/advertisement/request/advertisement.request.go @@ -0,0 +1,100 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type AdvertisementGeneric interface { + ToEntity() +} + +type AdvertisementQueryRequest struct { + Title *string `json:"title"` + Description *string `json:"description"` + RedirectLink *string `json:"redirectLink"` + Placement *string `json:"placement"` + IsPublish *bool `json:"isPublish"` + StatusId *int `json:"statusId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type AdvertisementCreateRequest struct { + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + RedirectLink string `json:"redirectLink" validate:"required"` + Placement string `json:"placement" validate:"required"` +} + +func (req AdvertisementCreateRequest) ToEntity() *entity.Advertisement { + return &entity.Advertisement{ + Title: req.Title, + Description: req.Description, + RedirectLink: req.RedirectLink, + Placement: req.Placement, + StatusId: 1, + IsPublish: true, + IsActive: true, + } +} + +type AdvertisementUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + RedirectLink string `json:"redirectLink" validate:"required"` + Placement string `json:"placement" validate:"required"` +} + +func (req AdvertisementUpdateRequest) ToEntity() *entity.Advertisement { + return &entity.Advertisement{ + ID: req.ID, + Title: req.Title, + Description: req.Description, + RedirectLink: req.RedirectLink, + Placement: req.Placement, + UpdatedAt: time.Now(), + } +} + +type AdvertisementQueryRequestContext struct { + Title string `json:"title"` + Description string `json:"description"` + RedirectLink string `json:"redirectLink"` + Placement string `json:"placement"` + StatusId string `json:"statusId"` + IsPublish string `json:"isPublish"` +} + +func (req AdvertisementQueryRequestContext) ToParamRequest() AdvertisementQueryRequest { + var request AdvertisementQueryRequest + + if title := req.Title; title != "" { + request.Title = &title + } + if description := req.Description; description != "" { + request.Description = &description + } + if redirectLink := req.RedirectLink; redirectLink != "" { + request.RedirectLink = &redirectLink + } + if placement := req.Placement; placement != "" { + request.Placement = &placement + } + if isPublishStr := req.IsPublish; isPublishStr != "" { + isPublish, err := strconv.ParseBool(isPublishStr) + if err == nil { + request.IsPublish = &isPublish + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + + return request +} diff --git a/app/module/advertisement/response/advertisement.response.go b/app/module/advertisement/response/advertisement.response.go new file mode 100644 index 0000000..55db4aa --- /dev/null +++ b/app/module/advertisement/response/advertisement.response.go @@ -0,0 +1,17 @@ +package response + +import "time" + +type AdvertisementResponse struct { + ID uint `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + RedirectLink string `json:"redirectLink"` + ContentFileUrl string `json:"contentFileUrl"` + Placement string `json:"placement"` + StatusId int `json:"statusId"` + IsPublish bool `json:"isPublish"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/advertisement/service/advertisement.service.go b/app/module/advertisement/service/advertisement.service.go new file mode 100644 index 0000000..1dcd3ec --- /dev/null +++ b/app/module/advertisement/service/advertisement.service.go @@ -0,0 +1,275 @@ +package service + +import ( + "context" + "fmt" + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" + "io" + "log" + "math/rand" + "mime" + "path/filepath" + "strconv" + "strings" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/advertisement/mapper" + "web-qudo-be/app/module/advertisement/repository" + "web-qudo-be/app/module/advertisement/request" + "web-qudo-be/app/module/advertisement/response" + usersRepository "web-qudo-be/app/module/users/repository" + config "web-qudo-be/config/config" + minioStorage "web-qudo-be/config/config" + "web-qudo-be/utils/paginator" +) + +// AdvertisementService +type advertisementService struct { + Repo repository.AdvertisementRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger + Cfg *config.Config + MinioStorage *minioStorage.MinioStorage +} + +// AdvertisementService define interface of IAdvertisementService +type AdvertisementService interface { + All(clientId *uuid.UUID, req request.AdvertisementQueryRequest) (advertisement []*response.AdvertisementResponse, paging paginator.Pagination, err error) + Show(clientId *uuid.UUID, id uint) (advertisement *response.AdvertisementResponse, err error) + Save(clientId *uuid.UUID, req request.AdvertisementCreateRequest) (advertisement *entity.Advertisement, err error) + Upload(clientId *uuid.UUID, c *fiber.Ctx, id uint) (err error) + Update(clientId *uuid.UUID, id uint, req request.AdvertisementUpdateRequest) (err error) + UpdatePublish(clientId *uuid.UUID, id uint, isPublish bool) (err error) + Delete(clientId *uuid.UUID, id uint) error + Viewer(clientId *uuid.UUID, c *fiber.Ctx) (err error) +} + +// NewAdvertisementService init AdvertisementService +func NewAdvertisementService(repo repository.AdvertisementRepository, minioStorage *minioStorage.MinioStorage, usersRepo usersRepository.UsersRepository, log zerolog.Logger, cfg *config.Config) AdvertisementService { + + return &advertisementService{ + Repo: repo, + UsersRepo: usersRepo, + MinioStorage: minioStorage, + Log: log, + Cfg: cfg, + } +} + +// All implement interface of AdvertisementService +func (_i *advertisementService) All(clientId *uuid.UUID, req request.AdvertisementQueryRequest) (advertisements []*response.AdvertisementResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(clientId, req) + if err != nil { + return + } + + host := _i.Cfg.App.Domain + for _, result := range results { + advertisements = append(advertisements, mapper.AdvertisementResponseMapper(result, host)) + } + + return +} + +func (_i *advertisementService) Show(clientId *uuid.UUID, id uint) (advertisement *response.AdvertisementResponse, err error) { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return nil, err + } + + host := _i.Cfg.App.Domain + return mapper.AdvertisementResponseMapper(result, host), nil +} + +func (_i *advertisementService) Save(clientId *uuid.UUID, req request.AdvertisementCreateRequest) (advertisement *entity.Advertisement, err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + if clientId != nil { + newReq.ClientId = clientId + } + + return _i.Repo.Create(newReq) +} + +func (_i *advertisementService) Upload(clientId *uuid.UUID, c *fiber.Ctx, id uint) (err error) { + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + + form, err := c.MultipartForm() + + if err != nil { + return err + } + //filess := form.File["files"] + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + + result, err := _i.Repo.FindOne(clientId, id) + + if result == nil { + // Return status 400. Id not found. + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + for _, files := range form.File { + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: top"). + Interface("files", files).Msg("") + + for _, fileHeader := range files { + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop"). + Interface("data", fileHeader).Msg("") + + src, err := fileHeader.Open() + if err != nil { + return err + } + defer src.Close() + + filename := filepath.Base(fileHeader.Filename) + filename = strings.ReplaceAll(filename, " ", "") + filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + extension := filepath.Ext(fileHeader.Filename)[1:] + + now := time.Now() + rand.New(rand.NewSource(now.UnixNano())) + randUniqueId := rand.Intn(1000000) + + newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId) + newFilename := newFilenameWithoutExt + "." + extension + + objectName := fmt.Sprintf("advertisement/upload/%d/%d/%s", now.Year(), now.Month(), newFilename) + + result.ContentFileName = &newFilename + result.ContentFilePath = &objectName + + err = _i.Repo.Update(clientId, id, result) + if err != nil { + return err + } + + // Upload file ke MinIO + _, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{}) + if err != nil { + return err + } + } + } + + return +} + +func (_i *advertisementService) Update(clientId *uuid.UUID, id uint, req request.AdvertisementUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + if clientId != nil { + newReq.ClientId = clientId + } + return _i.Repo.Update(clientId, id, newReq) +} + +func (_i *advertisementService) UpdatePublish(clientId *uuid.UUID, id uint, isPublish bool) (err error) { + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "UpdatePublish"). + Interface("ids", id).Msg("") + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "UpdatePublish"). + Interface("isPublish", isPublish).Msg("") + + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return err + } + + result.IsPublish = isPublish + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "UpdatePublish"). + Interface("result", result).Msg("") + + return _i.Repo.Update(clientId, id, result) +} + +func (_i *advertisementService) Delete(clientId *uuid.UUID, id uint) error { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return err + } + result.IsActive = false + if clientId != nil { + result.ClientId = clientId + } + return _i.Repo.Update(clientId, id, result) +} + +func (_i *advertisementService) Viewer(clientId *uuid.UUID, c *fiber.Ctx) (err error) { + filename := c.Params("filename") + result, err := _i.Repo.FindByFilename(clientId, filename) + if err != nil { + return err + } + + ctx := context.Background() + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + objectName := *result.ContentFilePath + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{}) + if err != nil { + log.Fatalln(err) + } + defer fileContent.Close() + + // Tentukan Content-Type berdasarkan ekstensi file + contentType := mime.TypeByExtension("." + getFileExtension(objectName)) + if contentType == "" { + contentType = "application/octet-stream" // fallback jika tidak ada tipe MIME yang cocok + } + + c.Set("Content-Type", contentType) + + if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil { + return err + } + + return +} + +func getFileExtension(filename string) string { + // split file name + parts := strings.Split(filename, ".") + + // jika tidak ada ekstensi, kembalikan string kosong + if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") { + return "" + } + + // ambil ekstensi terakhir + return parts[len(parts)-1] +} diff --git a/app/module/approval_workflow_steps/approval_workflow_steps.module.go b/app/module/approval_workflow_steps/approval_workflow_steps.module.go new file mode 100644 index 0000000..bb47286 --- /dev/null +++ b/app/module/approval_workflow_steps/approval_workflow_steps.module.go @@ -0,0 +1,57 @@ +package approval_workflow_steps + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/approval_workflow_steps/controller" + "web-qudo-be/app/module/approval_workflow_steps/repository" + "web-qudo-be/app/module/approval_workflow_steps/service" +) + +// ApprovalWorkflowStepsRouter struct of ApprovalWorkflowStepsRouter +type ApprovalWorkflowStepsRouter struct { + App fiber.Router + Controller controller.ApprovalWorkflowStepsController +} + +// NewApprovalWorkflowStepsModule register bulky of ApprovalWorkflowSteps module +var NewApprovalWorkflowStepsModule = fx.Options( + // register repository of ApprovalWorkflowSteps module + fx.Provide(repository.NewApprovalWorkflowStepsRepository), + + // register service of ApprovalWorkflowSteps module + fx.Provide(service.NewApprovalWorkflowStepsService), + + // register controller of ApprovalWorkflowSteps module + fx.Provide(controller.NewApprovalWorkflowStepsController), + + // register router of ApprovalWorkflowSteps module + fx.Provide(NewApprovalWorkflowStepsRouter), +) + +// NewApprovalWorkflowStepsRouter init ApprovalWorkflowStepsRouter +func NewApprovalWorkflowStepsRouter(fiber *fiber.App, controller controller.ApprovalWorkflowStepsController) *ApprovalWorkflowStepsRouter { + return &ApprovalWorkflowStepsRouter{ + App: fiber, + Controller: controller, + } +} + +// RegisterApprovalWorkflowStepsRoutes register routes of ApprovalWorkflowSteps module +func (_i *ApprovalWorkflowStepsRouter) RegisterApprovalWorkflowStepsRoutes() { + // define controllers + approvalWorkflowStepsController := _i.Controller + + // define routes + _i.App.Route("/approval-workflow-steps", func(router fiber.Router) { + router.Get("/", approvalWorkflowStepsController.All) + router.Get("/:id", approvalWorkflowStepsController.Show) + router.Post("/", approvalWorkflowStepsController.Save) + router.Put("/:id", approvalWorkflowStepsController.Update) + router.Delete("/:id", approvalWorkflowStepsController.Delete) + router.Get("/workflow/:workflowId", approvalWorkflowStepsController.GetByWorkflow) + router.Get("/role/:roleId", approvalWorkflowStepsController.GetByRole) + router.Post("/bulk", approvalWorkflowStepsController.BulkSave) + router.Put("/workflow/:workflowId/reorder", approvalWorkflowStepsController.Reorder) + }) +} \ No newline at end of file diff --git a/app/module/approval_workflow_steps/controller/approval_workflow_steps.controller.go b/app/module/approval_workflow_steps/controller/approval_workflow_steps.controller.go new file mode 100644 index 0000000..39e436f --- /dev/null +++ b/app/module/approval_workflow_steps/controller/approval_workflow_steps.controller.go @@ -0,0 +1,443 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/approval_workflow_steps/request" + "web-qudo-be/app/module/approval_workflow_steps/service" + "web-qudo-be/utils/paginator" + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" +) + +// Helper functions for parsing query parameters +func parseUintPointer(s string) *uint { + if s == "" { + return nil + } + if val, err := strconv.ParseUint(s, 10, 32); err == nil { + uval := uint(val) + return &uval + } + return nil +} + +func parseIntPointer(s string) *int { + if s == "" { + return nil + } + if val, err := strconv.Atoi(s); err == nil { + return &val + } + return nil +} + +func parseStringPointer(s string) *string { + if s == "" { + return nil + } + return &s +} + +func parseBoolPointer(s string) *bool { + if s == "" { + return nil + } + if val, err := strconv.ParseBool(s); err == nil { + return &val + } + return nil +} + +type approvalWorkflowStepsController struct { + approvalWorkflowStepsService service.ApprovalWorkflowStepsService + Log zerolog.Logger +} + +type ApprovalWorkflowStepsController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + GetByWorkflow(c *fiber.Ctx) error + GetByRole(c *fiber.Ctx) error + BulkSave(c *fiber.Ctx) error + Reorder(c *fiber.Ctx) error +} + +func NewApprovalWorkflowStepsController(approvalWorkflowStepsService service.ApprovalWorkflowStepsService, log zerolog.Logger) ApprovalWorkflowStepsController { + return &approvalWorkflowStepsController{ + approvalWorkflowStepsService: approvalWorkflowStepsService, + Log: log, + } +} + +// All ApprovalWorkflowSteps +// @Summary Get all ApprovalWorkflowSteps +// @Description API for getting all ApprovalWorkflowSteps +// @Tags ApprovalWorkflowSteps +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param workflowId query int false "Workflow ID filter" +// @Param stepOrder query int false "Step order filter" +// @Param stepName query string false "Step name filter" +// @Param userLevelId query int false "User level ID filter" +// @Param isOptional query bool false "Is optional filter" +// @Param isActive query bool false "Is active filter" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflow-steps [get] +func (_i *approvalWorkflowStepsController) All(c *fiber.Ctx) error { + _, err := paginator.Paginate(c) + if err != nil { + return err + } + + req := request.GetApprovalWorkflowStepsRequest{ + WorkflowID: parseUintPointer(c.Query("workflowId")), + StepOrder: parseIntPointer(c.Query("stepOrder")), + StepName: parseStringPointer(c.Query("stepName")), + UserLevelID: parseUintPointer(c.Query("userLevelId")), + IsOptional: parseBoolPointer(c.Query("isOptional")), + IsActive: parseBoolPointer(c.Query("isActive")), + Page: 1, + Limit: 10, + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + _i.Log.Info().Interface("clientId", clientId).Msg("") + + approvalWorkflowStepsData, paging, err := _i.approvalWorkflowStepsService.GetAll(clientId, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflowSteps list successfully retrieved"}, + Data: approvalWorkflowStepsData, + Meta: paging, + }) +} + +// Show ApprovalWorkflowSteps +// @Summary Get one ApprovalWorkflowSteps +// @Description API for getting one ApprovalWorkflowSteps +// @Tags ApprovalWorkflowSteps +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "ApprovalWorkflowSteps ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflow-steps/{id} [get] +func (_i *approvalWorkflowStepsController) Show(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + approvalWorkflowStepsData, err := _i.approvalWorkflowStepsService.FindOne(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflowSteps successfully retrieved"}, + Data: approvalWorkflowStepsData, + }) +} + +// Save ApprovalWorkflowSteps +// @Summary Save ApprovalWorkflowSteps +// @Description API for saving ApprovalWorkflowSteps +// @Tags ApprovalWorkflowSteps +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param payload body request.CreateApprovalWorkflowStepsRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflow-steps [post] +func (_i *approvalWorkflowStepsController) Save(c *fiber.Ctx) error { + req := new(request.CreateApprovalWorkflowStepsRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Convert request to entity + step := &entity.ApprovalWorkflowSteps{ + WorkflowId: req.WorkflowID, + StepOrder: req.StepOrder, + StepName: req.StepName, + RequiredUserLevelId: req.ApproverRoleID, + CanSkip: &req.IsOptional, + } + + approvalWorkflowStepsData, err := _i.approvalWorkflowStepsService.Create(clientId, step) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflowSteps successfully created"}, + Data: approvalWorkflowStepsData, + }) +} + +// Update ApprovalWorkflowSteps +// @Summary Update ApprovalWorkflowSteps +// @Description API for updating ApprovalWorkflowSteps +// @Tags ApprovalWorkflowSteps +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "ApprovalWorkflowSteps ID" +// @Param payload body request.UpdateApprovalWorkflowStepsRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflow-steps/{id} [put] +func (_i *approvalWorkflowStepsController) Update(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + req := new(request.UpdateApprovalWorkflowStepsRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Convert request to entity + step := &entity.ApprovalWorkflowSteps{} + if req.StepOrder != nil { + step.StepOrder = *req.StepOrder + } + if req.StepName != nil { + step.StepName = *req.StepName + } + if req.ApproverRoleID != nil { + step.RequiredUserLevelId = *req.ApproverRoleID + } + if req.IsOptional != nil { + step.CanSkip = req.IsOptional + } + + err = _i.approvalWorkflowStepsService.Update(clientId, uint(id), step) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflowSteps successfully updated"}, + }) +} + +// Delete ApprovalWorkflowSteps +// @Summary Delete ApprovalWorkflowSteps +// @Description API for deleting ApprovalWorkflowSteps +// @Tags ApprovalWorkflowSteps +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "ApprovalWorkflowSteps ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflow-steps/{id} [delete] +func (_i *approvalWorkflowStepsController) Delete(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.approvalWorkflowStepsService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflowSteps successfully deleted"}, + }) +} + +// GetByWorkflow ApprovalWorkflowSteps +// @Summary Get ApprovalWorkflowSteps by Workflow ID +// @Description API for getting ApprovalWorkflowSteps by Workflow ID +// @Tags ApprovalWorkflowSteps +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param workflowId path int true "Workflow ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflow-steps/workflow/{workflowId} [get] +func (_i *approvalWorkflowStepsController) GetByWorkflow(c *fiber.Ctx) error { + workflowId, err := strconv.Atoi(c.Params("workflowId")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid workflow ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + approvalWorkflowStepsData, err := _i.approvalWorkflowStepsService.GetByWorkflowID(clientId, uint(workflowId)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflowSteps by workflow successfully retrieved"}, + Data: approvalWorkflowStepsData, + }) +} + +// GetByRole ApprovalWorkflowSteps +// @Summary Get ApprovalWorkflowSteps by Role ID +// @Description API for getting ApprovalWorkflowSteps by Role ID +// @Tags ApprovalWorkflowSteps +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param roleId path int true "Role ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflow-steps/role/{roleId} [get] +func (_i *approvalWorkflowStepsController) GetByRole(c *fiber.Ctx) error { + roleId, err := strconv.Atoi(c.Params("roleId")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid role ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + approvalWorkflowStepsData, err := _i.approvalWorkflowStepsService.GetByWorkflowID(clientId, uint(roleId)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflowSteps by role successfully retrieved"}, + Data: approvalWorkflowStepsData, + }) +} + +// BulkSave ApprovalWorkflowSteps +// @Summary Bulk create ApprovalWorkflowSteps +// @Description API for bulk creating ApprovalWorkflowSteps +// @Tags ApprovalWorkflowSteps +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param payload body request.BulkCreateApprovalWorkflowStepsRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflow-steps/bulk [post] +func (_i *approvalWorkflowStepsController) BulkSave(c *fiber.Ctx) error { + req := new(request.BulkCreateApprovalWorkflowStepsRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Convert request to entities + var steps []*entity.ApprovalWorkflowSteps + for _, stepReq := range req.Steps { + step := &entity.ApprovalWorkflowSteps{ + WorkflowId: stepReq.WorkflowID, + StepOrder: stepReq.StepOrder, + StepName: stepReq.StepName, + RequiredUserLevelId: stepReq.ApproverRoleID, + CanSkip: &stepReq.IsOptional, + } + steps = append(steps, step) + } + + approvalWorkflowStepsData, err := _i.approvalWorkflowStepsService.BulkCreate(clientId, req.WorkflowID, steps) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflowSteps successfully bulk created"}, + Data: approvalWorkflowStepsData, + }) +} + +// Reorder ApprovalWorkflowSteps +// @Summary Reorder ApprovalWorkflowSteps +// @Description API for reordering ApprovalWorkflowSteps +// @Tags ApprovalWorkflowSteps +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param workflowId path int true "Workflow ID" +// @Param payload body request.ReorderApprovalWorkflowStepsRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflow-steps/workflow/{workflowId}/reorder [put] +func (_i *approvalWorkflowStepsController) Reorder(c *fiber.Ctx) error { + workflowId, err := strconv.Atoi(c.Params("workflowId")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid workflow ID format") + } + + req := new(request.ReorderApprovalWorkflowStepsRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Convert request to expected format + stepOrders := req.ToStepOrders() + + err = _i.approvalWorkflowStepsService.ReorderSteps(clientId, uint(workflowId), stepOrders) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflowSteps successfully reordered"}, + }) +} diff --git a/app/module/approval_workflow_steps/mapper/approval_workflow_steps.mapper.go b/app/module/approval_workflow_steps/mapper/approval_workflow_steps.mapper.go new file mode 100644 index 0000000..74a3fd5 --- /dev/null +++ b/app/module/approval_workflow_steps/mapper/approval_workflow_steps.mapper.go @@ -0,0 +1,75 @@ +package mapper + +import ( + "github.com/google/uuid" + "github.com/rs/zerolog" + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/approval_workflow_steps/response" + usersRepository "web-qudo-be/app/module/users/repository" +) + +func ApprovalWorkflowStepsResponseMapper( + log zerolog.Logger, + clientId *uuid.UUID, + approvalWorkflowStepsReq *entity.ApprovalWorkflowSteps, + usersRepo usersRepository.UsersRepository, +) (approvalWorkflowStepsRes *res.ApprovalWorkflowStepsResponse) { + + if approvalWorkflowStepsReq != nil { + // Convert boolean pointers to boolean values + isOptional := false + if approvalWorkflowStepsReq.CanSkip != nil { + isOptional = *approvalWorkflowStepsReq.CanSkip + } + + autoApprove := false + if approvalWorkflowStepsReq.AutoApproveAfterHours != nil { + autoApprove = *approvalWorkflowStepsReq.AutoApproveAfterHours > 0 + } + + approvalWorkflowStepsRes = &res.ApprovalWorkflowStepsResponse{ + ID: approvalWorkflowStepsReq.ID, + WorkflowID: approvalWorkflowStepsReq.WorkflowId, + StepName: approvalWorkflowStepsReq.StepName, + StepOrder: approvalWorkflowStepsReq.StepOrder, + ApproverRoleID: approvalWorkflowStepsReq.RequiredUserLevelId, + IsOptional: isOptional, + RequiresComment: false, // Default value + AutoApprove: autoApprove, + TimeoutHours: approvalWorkflowStepsReq.AutoApproveAfterHours, + CreatedAt: approvalWorkflowStepsReq.CreatedAt, + UpdatedAt: approvalWorkflowStepsReq.UpdatedAt, + } + } + + return approvalWorkflowStepsRes +} + +func ApprovalWorkflowStepsSummaryResponseMapper( + log zerolog.Logger, + clientId *uuid.UUID, + approvalWorkflowStepsReq *entity.ApprovalWorkflowSteps, + usersRepo usersRepository.UsersRepository, +) (approvalWorkflowStepsRes *res.ApprovalWorkflowStepsSummaryResponse) { + + if approvalWorkflowStepsReq != nil { + // Convert boolean pointers to boolean values + isOptional := false + if approvalWorkflowStepsReq.CanSkip != nil { + isOptional = *approvalWorkflowStepsReq.CanSkip + } + + approvalWorkflowStepsRes = &res.ApprovalWorkflowStepsSummaryResponse{ + ID: approvalWorkflowStepsReq.ID, + WorkflowID: approvalWorkflowStepsReq.WorkflowId, + StepName: approvalWorkflowStepsReq.StepName, + StepOrder: approvalWorkflowStepsReq.StepOrder, + ApproverRoleID: approvalWorkflowStepsReq.RequiredUserLevelId, + IsOptional: isOptional, + RequiresComment: false, // Default value + TimeoutHours: approvalWorkflowStepsReq.AutoApproveAfterHours, + } + } + + return approvalWorkflowStepsRes +} \ No newline at end of file diff --git a/app/module/approval_workflow_steps/repository/approval_workflow_steps.repository.go b/app/module/approval_workflow_steps/repository/approval_workflow_steps.repository.go new file mode 100644 index 0000000..e0cc962 --- /dev/null +++ b/app/module/approval_workflow_steps/repository/approval_workflow_steps.repository.go @@ -0,0 +1,373 @@ +package repository + +import ( + "fmt" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/approval_workflow_steps/request" + "web-qudo-be/utils/paginator" + + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +type approvalWorkflowStepsRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ApprovalWorkflowStepsRepository define interface of IApprovalWorkflowStepsRepository +type ApprovalWorkflowStepsRepository interface { + // Basic CRUD + GetAll(clientId *uuid.UUID, req request.GetApprovalWorkflowStepsRequest) (steps []*entity.ApprovalWorkflowSteps, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (step *entity.ApprovalWorkflowSteps, err error) + Create(clientId *uuid.UUID, step *entity.ApprovalWorkflowSteps) (stepReturn *entity.ApprovalWorkflowSteps, err error) + Update(id uint, step *entity.ApprovalWorkflowSteps) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) + + // Workflow-specific methods + GetByWorkflowId(clientId *uuid.UUID, workflowId uint) (steps []*entity.ApprovalWorkflowSteps, err error) + GetActiveByWorkflowId(clientId *uuid.UUID, workflowId uint) (steps []*entity.ApprovalWorkflowSteps, err error) + FindByWorkflowAndStep(clientId *uuid.UUID, workflowId uint, stepOrder int) (step *entity.ApprovalWorkflowSteps, err error) + GetNextStep(clientId *uuid.UUID, workflowId uint, currentStep int) (step *entity.ApprovalWorkflowSteps, err error) + GetPreviousStep(clientId *uuid.UUID, workflowId uint, currentStep int) (step *entity.ApprovalWorkflowSteps, err error) + + // Step management methods + ReorderSteps(clientId *uuid.UUID, workflowId uint, stepOrders []map[string]interface{}) (err error) + GetMaxStepOrder(clientId *uuid.UUID, workflowId uint) (maxOrder int, err error) + GetStepsByUserLevel(clientId *uuid.UUID, userLevelId uint) (steps []*entity.ApprovalWorkflowSteps, err error) + + // Validation methods + ValidateStepSequence(clientId *uuid.UUID, workflowId uint) (isValid bool, errors []string, err error) + CheckStepDependencies(clientId *uuid.UUID, stepId uint) (canDelete bool, dependencies []string, err error) +} + +func NewApprovalWorkflowStepsRepository(db *database.Database, log zerolog.Logger) ApprovalWorkflowStepsRepository { + return &approvalWorkflowStepsRepository{ + DB: db, + Log: log, + } +} + +// Basic CRUD implementations +func (_i *approvalWorkflowStepsRepository) GetAll(clientId *uuid.UUID, req request.GetApprovalWorkflowStepsRequest) (steps []*entity.ApprovalWorkflowSteps, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ApprovalWorkflowSteps{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + // Apply filters from request + if req.WorkflowID != nil && *req.WorkflowID > 0 { + query = query.Where("workflow_id = ?", *req.WorkflowID) + } + + if req.IsActive != nil { + query = query.Where("is_active = ?", *req.IsActive) + } + + if req.UserLevelID != nil && *req.UserLevelID > 0 { + query = query.Where("required_user_level_id = ?", *req.UserLevelID) + } + + if req.StepOrder != nil && *req.StepOrder > 0 { + query = query.Where("step_order = ?", *req.StepOrder) + } + + if req.StepName != nil && *req.StepName != "" { + query = query.Where("step_name ILIKE ?", "%"+*req.StepName+"%") + } + + query = query.Preload("Workflow").Preload("RequiredUserLevel") + query = query.Order("workflow_id ASC, step_order ASC") + + err = query.Count(&count).Error + if err != nil { + return nil, paginator.Pagination{}, err + } + + // Apply pagination + page := req.Page + limit := req.Limit + if page <= 0 { + page = 1 + } + if limit <= 0 { + limit = 10 + } + + offset := (page - 1) * limit + err = query.Offset(offset).Limit(limit).Find(&steps).Error + if err != nil { + return nil, paginator.Pagination{}, err + } + + paging = paginator.Pagination{ + Page: page, + Limit: limit, + Count: count, + TotalPage: int((count + int64(limit) - 1) / int64(limit)), + } + + return steps, paging, nil +} + +func (_i *approvalWorkflowStepsRepository) FindOne(clientId *uuid.UUID, id uint) (step *entity.ApprovalWorkflowSteps, err error) { + query := _i.DB.DB.Model(&entity.ApprovalWorkflowSteps{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Preload("Workflow").Preload("RequiredUserLevel") + + err = query.First(&step, id).Error + return step, err +} + +func (_i *approvalWorkflowStepsRepository) Create(clientId *uuid.UUID, step *entity.ApprovalWorkflowSteps) (stepReturn *entity.ApprovalWorkflowSteps, err error) { + step.ClientId = clientId + err = _i.DB.DB.Create(&step).Error + return step, err +} + +func (_i *approvalWorkflowStepsRepository) Update(id uint, step *entity.ApprovalWorkflowSteps) (err error) { + err = _i.DB.DB.Model(&entity.ApprovalWorkflowSteps{}).Where("id = ?", id).Updates(step).Error + return err +} + +func (_i *approvalWorkflowStepsRepository) Delete(clientId *uuid.UUID, id uint) (err error) { + query := _i.DB.DB.Model(&entity.ApprovalWorkflowSteps{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + err = query.Delete(&entity.ApprovalWorkflowSteps{}, id).Error + return err +} + +// Workflow-specific methods +func (_i *approvalWorkflowStepsRepository) GetByWorkflowId(clientId *uuid.UUID, workflowId uint) (steps []*entity.ApprovalWorkflowSteps, err error) { + query := _i.DB.DB.Model(&entity.ApprovalWorkflowSteps{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("workflow_id = ?", workflowId) + query = query.Preload("Workflow").Preload("RequiredUserLevel") + query = query.Order("step_order ASC") + + err = query.Find(&steps).Error + return steps, err +} + +func (_i *approvalWorkflowStepsRepository) GetActiveByWorkflowId(clientId *uuid.UUID, workflowId uint) (steps []*entity.ApprovalWorkflowSteps, err error) { + query := _i.DB.DB.Model(&entity.ApprovalWorkflowSteps{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("workflow_id = ? AND is_active = ?", workflowId, true) + query = query.Preload("Workflow").Preload("RequiredUserLevel") + query = query.Order("step_order ASC") + + err = query.Find(&steps).Error + return steps, err +} + +func (_i *approvalWorkflowStepsRepository) FindByWorkflowAndStep(clientId *uuid.UUID, workflowId uint, stepOrder int) (step *entity.ApprovalWorkflowSteps, err error) { + query := _i.DB.DB.Model(&entity.ApprovalWorkflowSteps{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("workflow_id = ? AND step_order = ?", workflowId, stepOrder) + query = query.Preload("Workflow").Preload("RequiredUserLevel") + + err = query.First(&step).Error + return step, err +} + +func (_i *approvalWorkflowStepsRepository) GetNextStep(clientId *uuid.UUID, workflowId uint, currentStep int) (step *entity.ApprovalWorkflowSteps, err error) { + query := _i.DB.DB.Model(&entity.ApprovalWorkflowSteps{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("workflow_id = ? AND step_order > ? AND is_active = ?", workflowId, currentStep, true) + query = query.Preload("Workflow").Preload("RequiredUserLevel") + query = query.Order("step_order ASC") + + err = query.First(&step).Error + return step, err +} + +func (_i *approvalWorkflowStepsRepository) GetPreviousStep(clientId *uuid.UUID, workflowId uint, currentStep int) (step *entity.ApprovalWorkflowSteps, err error) { + query := _i.DB.DB.Model(&entity.ApprovalWorkflowSteps{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("workflow_id = ? AND step_order < ? AND is_active = ?", workflowId, currentStep, true) + query = query.Preload("Workflow").Preload("RequiredUserLevel") + query = query.Order("step_order DESC") + + err = query.First(&step).Error + return step, err +} + +// Step management methods +func (_i *approvalWorkflowStepsRepository) ReorderSteps(clientId *uuid.UUID, workflowId uint, stepOrders []map[string]interface{}) (err error) { + // Start transaction + tx := _i.DB.DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + if err := tx.Error; err != nil { + return err + } + + // Update each step order + for _, stepOrder := range stepOrders { + stepId := stepOrder["id"] + newOrder := stepOrder["step_order"] + + query := tx.Model(&entity.ApprovalWorkflowSteps{}) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + err = query.Where("id = ? AND workflow_id = ?", stepId, workflowId).Update("step_order", newOrder).Error + if err != nil { + tx.Rollback() + return err + } + } + + return tx.Commit().Error +} + +func (_i *approvalWorkflowStepsRepository) GetMaxStepOrder(clientId *uuid.UUID, workflowId uint) (maxOrder int, err error) { + query := _i.DB.DB.Model(&entity.ApprovalWorkflowSteps{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("workflow_id = ?") + query = query.Select("COALESCE(MAX(step_order), 0) as max_order") + + err = query.Scan(&maxOrder).Error + return maxOrder, err +} + +func (_i *approvalWorkflowStepsRepository) GetStepsByUserLevel(clientId *uuid.UUID, userLevelId uint) (steps []*entity.ApprovalWorkflowSteps, err error) { + query := _i.DB.DB.Model(&entity.ApprovalWorkflowSteps{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("required_user_level_id = ? AND is_active = ?", userLevelId, true) + query = query.Preload("Workflow").Preload("RequiredUserLevel") + query = query.Order("workflow_id ASC, step_order ASC") + + err = query.Find(&steps).Error + return steps, err +} + +// Validation methods +func (_i *approvalWorkflowStepsRepository) ValidateStepSequence(clientId *uuid.UUID, workflowId uint) (isValid bool, errors []string, err error) { + errors = make([]string, 0) + + // Get all steps for the workflow + steps, err := _i.GetActiveByWorkflowId(clientId, workflowId) + if err != nil { + return false, errors, err + } + + if len(steps) == 0 { + errors = append(errors, "Workflow must have at least one step") + return false, errors, nil + } + + // Check for sequential step orders starting from 1 + expectedOrder := 1 + for _, step := range steps { + if step.StepOrder != expectedOrder { + errors = append(errors, fmt.Sprintf("Step order %d is missing or out of sequence", expectedOrder)) + } + expectedOrder++ + } + + // Check for duplicate step orders + stepOrderMap := make(map[int]bool) + for _, step := range steps { + if stepOrderMap[step.StepOrder] { + errors = append(errors, fmt.Sprintf("Duplicate step order found: %d", step.StepOrder)) + } + stepOrderMap[step.StepOrder] = true + } + + isValid = len(errors) == 0 + return isValid, errors, nil +} + +func (_i *approvalWorkflowStepsRepository) CheckStepDependencies(clientId *uuid.UUID, stepId uint) (canDelete bool, dependencies []string, err error) { + dependencies = make([]string, 0) + + // Check if step is referenced in any active approval flows + var activeFlowCount int64 + query := _i.DB.DB.Model(&entity.ArticleApprovalFlows{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + // Join with workflow steps to check current step + query = query.Joins("JOIN approval_workflow_steps ON article_approval_flows.workflow_id = approval_workflow_steps.workflow_id AND article_approval_flows.current_step = approval_workflow_steps.step_order") + query = query.Where("approval_workflow_steps.id = ? AND article_approval_flows.status_id IN (1, 4)", stepId) // pending or revision_requested + + err = query.Count(&activeFlowCount).Error + if err != nil { + return false, dependencies, err + } + + if activeFlowCount > 0 { + dependencies = append(dependencies, fmt.Sprintf("%d active approval flows are currently at this step", activeFlowCount)) + } + + // Check if step is referenced in approval step logs + var logCount int64 + logQuery := _i.DB.DB.Model(&entity.ArticleApprovalStepLogs{}) + + if clientId != nil { + logQuery = logQuery.Where("client_id = ?", clientId) + } + + // This is a simplified check - in reality, you might want to join with the step to check step_order + logQuery = logQuery.Where("step_order IN (SELECT step_order FROM approval_workflow_steps WHERE id = ?)", stepId) + + err = logQuery.Count(&logCount).Error + if err != nil { + return false, dependencies, err + } + + if logCount > 0 { + dependencies = append(dependencies, fmt.Sprintf("%d approval step logs reference this step", logCount)) + } + + canDelete = len(dependencies) == 0 + return canDelete, dependencies, nil +} diff --git a/app/module/approval_workflow_steps/request/approval_workflow_steps.request.go b/app/module/approval_workflow_steps/request/approval_workflow_steps.request.go new file mode 100644 index 0000000..cc94ed2 --- /dev/null +++ b/app/module/approval_workflow_steps/request/approval_workflow_steps.request.go @@ -0,0 +1,72 @@ +package request + +type CreateApprovalWorkflowStepsRequest struct { + WorkflowID uint `json:"workflowId" validate:"required"` + StepOrder int `json:"stepOrder" validate:"required,min=1"` + StepName string `json:"stepName" validate:"required,min=3,max=100"` + Description *string `json:"description" validate:"omitempty,max=500"` + ApproverRoleID uint `json:"approverRoleId" validate:"required"` + IsOptional bool `json:"isOptional"` + RequiresComment bool `json:"requiresComment"` + AutoApprove bool `json:"autoApprove"` + TimeoutHours *int `json:"timeoutHours" validate:"omitempty,min=1,max=720"` +} + +type UpdateApprovalWorkflowStepsRequest struct { + StepOrder *int `json:"stepOrder" validate:"omitempty,min=1"` + StepName *string `json:"stepName" validate:"omitempty,min=3,max=100"` + Description *string `json:"description" validate:"omitempty,max=500"` + ApproverRoleID *uint `json:"approverRoleId" validate:"omitempty"` + IsOptional *bool `json:"isOptional"` + RequiresComment *bool `json:"requiresComment"` + AutoApprove *bool `json:"autoApprove"` + TimeoutHours *int `json:"timeoutHours" validate:"omitempty,min=1,max=720"` +} + +type GetApprovalWorkflowStepsRequest struct { + WorkflowID *uint `json:"workflowId" form:"workflowId"` + RoleID *uint `json:"roleId" form:"roleId"` + UserLevelID *uint `json:"userLevelId" form:"userLevelId"` + StepOrder *int `json:"stepOrder" form:"stepOrder"` + StepName *string `json:"stepName" form:"stepName"` + IsOptional *bool `json:"isOptional" form:"isOptional"` + IsActive *bool `json:"isActive" form:"isActive"` + Page int `json:"page" form:"page" validate:"min=1"` + Limit int `json:"limit" form:"limit" validate:"min=1,max=100"` + SortBy *string `json:"sortBy" form:"sortBy"` + SortOrder *string `json:"sortOrder" form:"sortOrder" validate:"omitempty,oneof=asc desc"` +} + +type BulkCreateApprovalWorkflowStepsRequest struct { + WorkflowID uint `json:"workflowId" validate:"required"` + Steps []CreateApprovalWorkflowStepsRequest `json:"steps" validate:"required,min=1,max=20,dive"` +} + +type ReorderApprovalWorkflowStepsRequest struct { + StepOrders []struct { + ID uint `json:"id" validate:"required"` + StepOrder int `json:"stepOrder" validate:"required,min=1"` + } `json:"stepOrders" validate:"required,min=1,dive"` +} + +func (r *ReorderApprovalWorkflowStepsRequest) ToStepOrders() []struct { + ID uint + StepOrder int +} { + result := make([]struct { + ID uint + StepOrder int + }, len(r.StepOrders)) + + for i, step := range r.StepOrders { + result[i] = struct { + ID uint + StepOrder int + }{ + ID: step.ID, + StepOrder: step.StepOrder, + } + } + + return result +} diff --git a/app/module/approval_workflow_steps/response/approval_workflow_steps.response.go b/app/module/approval_workflow_steps/response/approval_workflow_steps.response.go new file mode 100644 index 0000000..0651c0f --- /dev/null +++ b/app/module/approval_workflow_steps/response/approval_workflow_steps.response.go @@ -0,0 +1,40 @@ +package response + +import ( + "time" +) + +type ApprovalWorkflowStepsResponse struct { + ID uint `json:"id"` + WorkflowID uint `json:"workflowId"` + StepOrder int `json:"stepOrder"` + StepName string `json:"stepName"` + Description *string `json:"description"` + ApproverRoleID uint `json:"approverRoleId"` + ApproverRoleName *string `json:"approverRoleName,omitempty"` + IsOptional bool `json:"isOptional"` + RequiresComment bool `json:"requiresComment"` + AutoApprove bool `json:"autoApprove"` + TimeoutHours *int `json:"timeoutHours"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type ApprovalWorkflowStepsSummaryResponse struct { + ID uint `json:"id"` + WorkflowID uint `json:"workflowId"` + StepOrder int `json:"stepOrder"` + StepName string `json:"stepName"` + ApproverRoleID uint `json:"approverRoleId"` + IsOptional bool `json:"isOptional"` + RequiresComment bool `json:"requiresComment"` + TimeoutHours *int `json:"timeoutHours"` +} + +type ApprovalWorkflowStepsStatsResponse struct { + TotalSteps int `json:"totalSteps"` + OptionalSteps int `json:"optionalSteps"` + MandatorySteps int `json:"mandatorySteps"` + StepsWithTimeout int `json:"stepsWithTimeout"` + AverageTimeoutHours float64 `json:"averageTimeoutHours"` +} \ No newline at end of file diff --git a/app/module/approval_workflow_steps/service/approval_workflow_steps.service.go b/app/module/approval_workflow_steps/service/approval_workflow_steps.service.go new file mode 100644 index 0000000..9aabffe --- /dev/null +++ b/app/module/approval_workflow_steps/service/approval_workflow_steps.service.go @@ -0,0 +1,319 @@ +package service + +import ( + "errors" + "fmt" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/approval_workflow_steps/repository" + "web-qudo-be/app/module/approval_workflow_steps/request" + workflowRepo "web-qudo-be/app/module/approval_workflows/repository" + "web-qudo-be/utils/paginator" + + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +type approvalWorkflowStepsService struct { + ApprovalWorkflowStepsRepository repository.ApprovalWorkflowStepsRepository + ApprovalWorkflowsRepository workflowRepo.ApprovalWorkflowsRepository + Log zerolog.Logger +} + +// ApprovalWorkflowStepsService define interface of IApprovalWorkflowStepsService +type ApprovalWorkflowStepsService interface { + // Basic CRUD + GetAll(clientId *uuid.UUID, req request.GetApprovalWorkflowStepsRequest) (steps []*entity.ApprovalWorkflowSteps, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (step *entity.ApprovalWorkflowSteps, err error) + Create(clientId *uuid.UUID, step *entity.ApprovalWorkflowSteps) (stepReturn *entity.ApprovalWorkflowSteps, err error) + Update(clientId *uuid.UUID, id uint, step *entity.ApprovalWorkflowSteps) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) + + // Workflow steps management + GetByWorkflowID(clientId *uuid.UUID, workflowID uint) (steps []*entity.ApprovalWorkflowSteps, err error) + // GetByRoleID(clientId *uuid.UUID, roleID uint) (steps []*entity.ApprovalWorkflowSteps, err error) // Not implemented yet + BulkCreate(clientId *uuid.UUID, workflowID uint, steps []*entity.ApprovalWorkflowSteps) (stepsReturn []*entity.ApprovalWorkflowSteps, err error) + ReorderSteps(clientId *uuid.UUID, workflowID uint, stepOrders []struct { + ID uint + StepOrder int + }) (err error) + + // Validation + ValidateStep(clientId *uuid.UUID, step *entity.ApprovalWorkflowSteps) (isValid bool, errors []string, err error) + CanDeleteStep(clientId *uuid.UUID, id uint) (canDelete bool, reason string, err error) + ValidateStepOrder(clientId *uuid.UUID, workflowID uint, stepOrder int, excludeID *uint) (isValid bool, err error) +} + +func NewApprovalWorkflowStepsService( + approvalWorkflowStepsRepository repository.ApprovalWorkflowStepsRepository, + approvalWorkflowsRepository workflowRepo.ApprovalWorkflowsRepository, + log zerolog.Logger, +) ApprovalWorkflowStepsService { + return &approvalWorkflowStepsService{ + ApprovalWorkflowStepsRepository: approvalWorkflowStepsRepository, + ApprovalWorkflowsRepository: approvalWorkflowsRepository, + Log: log, + } +} + +func (_i *approvalWorkflowStepsService) GetAll(clientId *uuid.UUID, req request.GetApprovalWorkflowStepsRequest) (steps []*entity.ApprovalWorkflowSteps, paging paginator.Pagination, err error) { + return _i.ApprovalWorkflowStepsRepository.GetAll(clientId, req) +} + +func (_i *approvalWorkflowStepsService) FindOne(clientId *uuid.UUID, id uint) (step *entity.ApprovalWorkflowSteps, err error) { + return _i.ApprovalWorkflowStepsRepository.FindOne(clientId, id) +} + +func (_i *approvalWorkflowStepsService) Create(clientId *uuid.UUID, step *entity.ApprovalWorkflowSteps) (stepReturn *entity.ApprovalWorkflowSteps, err error) { + // Validate workflow exists + workflow, err := _i.ApprovalWorkflowsRepository.FindOne(clientId, step.WorkflowId) + if err != nil { + return nil, fmt.Errorf("workflow not found: %w", err) + } + if workflow == nil { + return nil, errors.New("workflow not found") + } + + // Validate step order is unique within workflow + isValid, err := _i.ValidateStepOrder(clientId, step.WorkflowId, step.StepOrder, nil) + if err != nil { + return nil, err + } + if !isValid { + return nil, errors.New("step order already exists in this workflow") + } + + // Validate step data + isValid, validationErrors, err := _i.ValidateStep(clientId, step) + if err != nil { + return nil, err + } + if !isValid { + return nil, fmt.Errorf("validation failed: %v", validationErrors) + } + + return _i.ApprovalWorkflowStepsRepository.Create(clientId, step) +} + +func (_i *approvalWorkflowStepsService) Update(clientId *uuid.UUID, id uint, step *entity.ApprovalWorkflowSteps) (err error) { + // Check if step exists + existingStep, err := _i.ApprovalWorkflowStepsRepository.FindOne(clientId, id) + if err != nil { + return err + } + if existingStep == nil { + return errors.New("step not found") + } + + // If step order is being changed, validate it's unique + if step.StepOrder != 0 && step.StepOrder != existingStep.StepOrder { + isValid, err := _i.ValidateStepOrder(clientId, existingStep.WorkflowId, step.StepOrder, &id) + if err != nil { + return err + } + if !isValid { + return errors.New("step order already exists in this workflow") + } + } + + return _i.ApprovalWorkflowStepsRepository.Update(id, step) +} + +func (_i *approvalWorkflowStepsService) Delete(clientId *uuid.UUID, id uint) (err error) { + // Check if step can be deleted + canDelete, reason, err := _i.CanDeleteStep(clientId, id) + if err != nil { + return err + } + if !canDelete { + return fmt.Errorf("cannot delete step: %s", reason) + } + + return _i.ApprovalWorkflowStepsRepository.Delete(clientId, id) +} + +func (_i *approvalWorkflowStepsService) GetByWorkflowID(clientId *uuid.UUID, workflowID uint) (steps []*entity.ApprovalWorkflowSteps, err error) { + return _i.ApprovalWorkflowStepsRepository.GetByWorkflowId(clientId, workflowID) +} + +// GetByRoleID method is not implemented in repository yet +// func (_i *approvalWorkflowStepsService) GetByRoleID(clientId *uuid.UUID, roleID uint) (steps []*entity.ApprovalWorkflowSteps, err error) { +// return _i.ApprovalWorkflowStepsRepository.GetByRoleID(clientId, roleID) +// } + +func (_i *approvalWorkflowStepsService) BulkCreate(clientId *uuid.UUID, workflowID uint, steps []*entity.ApprovalWorkflowSteps) (stepsReturn []*entity.ApprovalWorkflowSteps, err error) { + // Validate workflow exists + workflow, err := _i.ApprovalWorkflowsRepository.FindOne(clientId, workflowID) + if err != nil { + return nil, fmt.Errorf("workflow not found: %w", err) + } + if workflow == nil { + return nil, errors.New("workflow not found") + } + + // Validate all steps + stepOrders := make(map[int]bool) + for i, step := range steps { + step.WorkflowId = workflowID + + // Check for duplicate step orders within the batch + if stepOrders[step.StepOrder] { + return nil, fmt.Errorf("duplicate step order %d in batch", step.StepOrder) + } + stepOrders[step.StepOrder] = true + + // Validate step order is unique in database + isValid, err := _i.ValidateStepOrder(clientId, workflowID, step.StepOrder, nil) + if err != nil { + return nil, err + } + if !isValid { + return nil, fmt.Errorf("step order %d already exists in workflow", step.StepOrder) + } + + // Validate step data + var errors []string + if step.RequiredUserLevelId == 0 { + errors = append(errors, fmt.Sprintf("Step %d: RequiredUserLevelId is required", i+1)) + } + + if step.AutoApproveAfterHours != nil && *step.AutoApproveAfterHours <= 0 { + errors = append(errors, fmt.Sprintf("Step %d: AutoApproveAfterHours must be positive", i+1)) + } + + if step.AutoApproveAfterHours != nil && *step.AutoApproveAfterHours > 720 { + errors = append(errors, fmt.Sprintf("Step %d: AutoApproveAfterHours cannot exceed 720 hours (30 days)", i+1)) + } + + if len(step.StepName) > 500 { + errors = append(errors, fmt.Sprintf("Step %d: StepName cannot exceed 500 characters", i+1)) + } + + if len(errors) > 0 { + return nil, fmt.Errorf("validation failed for step %d: %v", step.StepOrder, errors) + } + } + + // BulkCreate method is not implemented in repository yet + // return _i.ApprovalWorkflowStepsRepository.BulkCreate(clientId, steps) + return nil, fmt.Errorf("BulkCreate method not implemented yet") +} + +func (_i *approvalWorkflowStepsService) ReorderSteps(clientId *uuid.UUID, workflowID uint, stepOrders []struct { + ID uint + StepOrder int +}) (err error) { + // Validate workflow exists + workflow, err := _i.ApprovalWorkflowsRepository.FindOne(clientId, workflowID) + if err != nil { + return fmt.Errorf("workflow not found: %w", err) + } + if workflow == nil { + return errors.New("workflow not found") + } + + // Validate all steps belong to the workflow and orders are unique + orders := make(map[int]bool) + for _, stepOrder := range stepOrders { + // Check step exists and belongs to workflow + step, err := _i.ApprovalWorkflowStepsRepository.FindOne(clientId, stepOrder.ID) + if err != nil { + return err + } + if step == nil { + return fmt.Errorf("step with ID %d not found", stepOrder.ID) + } + if step.WorkflowId != workflowID { + return fmt.Errorf("step with ID %d does not belong to workflow %d", stepOrder.ID, workflowID) + } + + // Check for duplicate orders + if orders[stepOrder.StepOrder] { + return fmt.Errorf("duplicate step order %d", stepOrder.StepOrder) + } + orders[stepOrder.StepOrder] = true + } + + // Convert stepOrders to the format expected by repository + stepOrderMaps := make([]map[string]interface{}, len(stepOrders)) + for i, stepOrder := range stepOrders { + stepOrderMaps[i] = map[string]interface{}{ + "id": stepOrder.ID, + "step_order": stepOrder.StepOrder, + } + } + + return _i.ApprovalWorkflowStepsRepository.ReorderSteps(clientId, workflowID, stepOrderMaps) +} + +func (_i *approvalWorkflowStepsService) ValidateStep(clientId *uuid.UUID, step *entity.ApprovalWorkflowSteps) (isValid bool, errors []string, err error) { + var validationErrors []string + + // Validate step name + if step.StepName == "" { + validationErrors = append(validationErrors, "step name is required") + } + if len(step.StepName) < 3 { + validationErrors = append(validationErrors, "step name must be at least 3 characters") + } + if len(step.StepName) > 500 { + validationErrors = append(validationErrors, "step name must not exceed 500 characters") + } + + // Validate step order + if step.StepOrder < 1 { + validationErrors = append(validationErrors, "step order must be at least 1") + } + + // Validate required user level ID + if step.RequiredUserLevelId == 0 { + validationErrors = append(validationErrors, "required user level ID is required") + } + + // Validate auto approve after hours if provided + if step.AutoApproveAfterHours != nil && *step.AutoApproveAfterHours < 1 { + validationErrors = append(validationErrors, "auto approve after hours must be at least 1") + } + if step.AutoApproveAfterHours != nil && *step.AutoApproveAfterHours > 720 { + validationErrors = append(validationErrors, "auto approve after hours must not exceed 720 (30 days)") + } + + return len(validationErrors) == 0, validationErrors, nil +} + +func (_i *approvalWorkflowStepsService) CanDeleteStep(clientId *uuid.UUID, id uint) (canDelete bool, reason string, err error) { + // Check if step exists + step, err := _i.ApprovalWorkflowStepsRepository.FindOne(clientId, id) + if err != nil { + return false, "", err + } + if step == nil { + return false, "step not found", nil + } + + // Check if there are any active approval flows using this step + // This would require checking article_approval_step_logs table + // For now, we'll allow deletion but this should be implemented + // based on business requirements + + return true, "", nil +} + +func (_i *approvalWorkflowStepsService) ValidateStepOrder(clientId *uuid.UUID, workflowID uint, stepOrder int, excludeID *uint) (isValid bool, err error) { + existingStep, err := _i.ApprovalWorkflowStepsRepository.FindByWorkflowAndStep(clientId, workflowID, stepOrder) + if err != nil { + return false, err + } + + // If no existing step found, order is valid + if existingStep == nil { + return true, nil + } + + // If excludeID is provided and matches existing step, order is valid (updating same step) + if excludeID != nil && existingStep.ID == *excludeID { + return true, nil + } + + // Order already exists for different step + return false, nil +} diff --git a/app/module/approval_workflows/approval_workflows.module.go b/app/module/approval_workflows/approval_workflows.module.go new file mode 100644 index 0000000..1614b03 --- /dev/null +++ b/app/module/approval_workflows/approval_workflows.module.go @@ -0,0 +1,60 @@ +package approval_workflows + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/approval_workflows/controller" + "web-qudo-be/app/module/approval_workflows/repository" + "web-qudo-be/app/module/approval_workflows/service" +) + +// ApprovalWorkflowsRouter struct of ApprovalWorkflowsRouter +type ApprovalWorkflowsRouter struct { + App fiber.Router + Controller controller.ApprovalWorkflowsController +} + +// NewApprovalWorkflowsModule register bulky of ApprovalWorkflows module +var NewApprovalWorkflowsModule = fx.Options( + // register repository of ApprovalWorkflows module + fx.Provide(repository.NewApprovalWorkflowsRepository), + + // register service of ApprovalWorkflows module + fx.Provide(service.NewApprovalWorkflowsService), + + // register controller of ApprovalWorkflows module + fx.Provide(controller.NewApprovalWorkflowsController), + + // register router of ApprovalWorkflows module + fx.Provide(NewApprovalWorkflowsRouter), +) + +// NewApprovalWorkflowsRouter init ApprovalWorkflowsRouter +func NewApprovalWorkflowsRouter(fiber *fiber.App, controller controller.ApprovalWorkflowsController) *ApprovalWorkflowsRouter { + return &ApprovalWorkflowsRouter{ + App: fiber, + Controller: controller, + } +} + +// RegisterApprovalWorkflowsRoutes register routes of ApprovalWorkflows module +func (_i *ApprovalWorkflowsRouter) RegisterApprovalWorkflowsRoutes() { + // define controllers + approvalWorkflowsController := _i.Controller + + // define routes + _i.App.Route("/approval-workflows", func(router fiber.Router) { + router.Get("/", approvalWorkflowsController.All) + router.Get("/default", approvalWorkflowsController.GetDefault) + router.Get("/:id", approvalWorkflowsController.Show) + router.Get("/:id/with-steps", approvalWorkflowsController.GetWithSteps) + router.Post("/", approvalWorkflowsController.Save) + router.Post("/with-steps", approvalWorkflowsController.SaveWithSteps) + router.Put("/:id", approvalWorkflowsController.Update) + router.Put("/:id/with-steps", approvalWorkflowsController.UpdateWithSteps) + router.Put("/:id/set-default", approvalWorkflowsController.SetDefault) + router.Put("/:id/activate", approvalWorkflowsController.Activate) + router.Put("/:id/deactivate", approvalWorkflowsController.Deactivate) + router.Delete("/:id", approvalWorkflowsController.Delete) + }) +} \ No newline at end of file diff --git a/app/module/approval_workflows/controller/approval_workflows.controller.go b/app/module/approval_workflows/controller/approval_workflows.controller.go new file mode 100644 index 0000000..a3b1fb8 --- /dev/null +++ b/app/module/approval_workflows/controller/approval_workflows.controller.go @@ -0,0 +1,481 @@ +package controller + +import ( + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + "strconv" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/approval_workflows/request" + "web-qudo-be/app/module/approval_workflows/service" + "web-qudo-be/utils/paginator" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type approvalWorkflowsController struct { + approvalWorkflowsService service.ApprovalWorkflowsService + Log zerolog.Logger +} + +type ApprovalWorkflowsController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + GetDefault(c *fiber.Ctx) error + SetDefault(c *fiber.Ctx) error + Activate(c *fiber.Ctx) error + Deactivate(c *fiber.Ctx) error + GetWithSteps(c *fiber.Ctx) error + SaveWithSteps(c *fiber.Ctx) error + UpdateWithSteps(c *fiber.Ctx) error +} + +func NewApprovalWorkflowsController(approvalWorkflowsService service.ApprovalWorkflowsService, log zerolog.Logger) ApprovalWorkflowsController { + return &approvalWorkflowsController{ + approvalWorkflowsService: approvalWorkflowsService, + Log: log, + } +} + +// All ApprovalWorkflows +// @Summary Get all ApprovalWorkflows +// @Description API for getting all ApprovalWorkflows +// @Tags ApprovalWorkflows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param req query request.ApprovalWorkflowsQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflows [get] +func (_i *approvalWorkflowsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.ApprovalWorkflowsQueryRequestContext{ + Name: c.Query("name"), + Description: c.Query("description"), + IsActive: c.Query("isActive"), + IsDefault: c.Query("isDefault"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + _i.Log.Info().Interface("clientId", clientId).Msg("") + + approvalWorkflowsData, paging, err := _i.approvalWorkflowsService.GetAll(clientId, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflows list successfully retrieved"}, + Data: approvalWorkflowsData, + Meta: paging, + }) +} + +// Show ApprovalWorkflows +// @Summary Get one ApprovalWorkflows +// @Description API for getting one ApprovalWorkflows +// @Tags ApprovalWorkflows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "ApprovalWorkflows ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflows/{id} [get] +func (_i *approvalWorkflowsController) Show(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + approvalWorkflowsData, err := _i.approvalWorkflowsService.FindOne(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflows successfully retrieved"}, + Data: approvalWorkflowsData, + }) +} + +// Save ApprovalWorkflows +// @Summary Save ApprovalWorkflows +// @Description API for saving ApprovalWorkflows +// @Tags ApprovalWorkflows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param payload body request.ApprovalWorkflowsCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflows [post] +func (_i *approvalWorkflowsController) Save(c *fiber.Ctx) error { + req := new(request.ApprovalWorkflowsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Convert request to entity + workflow := req.ToEntity() + steps := req.ToStepsEntity() + + approvalWorkflowsData, err := _i.approvalWorkflowsService.Create(clientId, workflow, steps) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflows successfully created"}, + Data: approvalWorkflowsData, + }) +} + +// Update ApprovalWorkflows +// @Summary Update ApprovalWorkflows +// @Description API for updating ApprovalWorkflows +// @Tags ApprovalWorkflows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "ApprovalWorkflows ID" +// @Param payload body request.ApprovalWorkflowsUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflows/{id} [put] +func (_i *approvalWorkflowsController) Update(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + req := new(request.ApprovalWorkflowsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Convert request to entity + workflow := req.ToEntity() + + err = _i.approvalWorkflowsService.Update(clientId, uint(id), workflow) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflows successfully updated"}, + }) +} + +// Delete ApprovalWorkflows +// @Summary Delete ApprovalWorkflows +// @Description API for deleting ApprovalWorkflows +// @Tags ApprovalWorkflows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "ApprovalWorkflows ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflows/{id} [delete] +func (_i *approvalWorkflowsController) Delete(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.approvalWorkflowsService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflows successfully deleted"}, + }) +} + +// GetDefault ApprovalWorkflows +// @Summary Get default ApprovalWorkflows +// @Description API for getting default ApprovalWorkflows +// @Tags ApprovalWorkflows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflows/default [get] +func (_i *approvalWorkflowsController) GetDefault(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + approvalWorkflowsData, err := _i.approvalWorkflowsService.GetDefault(clientId) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Default ApprovalWorkflows successfully retrieved"}, + Data: approvalWorkflowsData, + }) +} + +// SetDefault ApprovalWorkflows +// @Summary Set default ApprovalWorkflows +// @Description API for setting default ApprovalWorkflows +// @Tags ApprovalWorkflows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "ApprovalWorkflows ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflows/{id}/set-default [put] +func (_i *approvalWorkflowsController) SetDefault(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.approvalWorkflowsService.SetDefault(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflows successfully set as default"}, + }) +} + +// Activate ApprovalWorkflows +// @Summary Activate ApprovalWorkflows +// @Description API for activating ApprovalWorkflows +// @Tags ApprovalWorkflows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "ApprovalWorkflows ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflows/{id}/activate [put] +func (_i *approvalWorkflowsController) Activate(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.approvalWorkflowsService.ActivateWorkflow(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflows successfully activated"}, + }) +} + +// Deactivate ApprovalWorkflows +// @Summary Deactivate ApprovalWorkflows +// @Description API for deactivating ApprovalWorkflows +// @Tags ApprovalWorkflows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "ApprovalWorkflows ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflows/{id}/deactivate [put] +func (_i *approvalWorkflowsController) Deactivate(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.approvalWorkflowsService.DeactivateWorkflow(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflows successfully deactivated"}, + }) +} + +// GetWithSteps ApprovalWorkflows +// @Summary Get ApprovalWorkflows with steps +// @Description API for getting ApprovalWorkflows with steps +// @Tags ApprovalWorkflows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "ApprovalWorkflows ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflows/{id}/with-steps [get] +func (_i *approvalWorkflowsController) GetWithSteps(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + workflowData, stepsData, err := _i.approvalWorkflowsService.GetWorkflowWithSteps(clientId, uint(id)) + if err != nil { + return err + } + + // Combine workflow and steps data + responseData := map[string]interface{}{ + "workflow": workflowData, + "steps": stepsData, + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflows with steps successfully retrieved"}, + Data: responseData, + }) +} + +// SaveWithSteps ApprovalWorkflows +// @Summary Create ApprovalWorkflows with steps +// @Description API for creating ApprovalWorkflows with steps +// @Tags ApprovalWorkflows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param req body request.ApprovalWorkflowsWithStepsCreateRequest true "ApprovalWorkflows with steps data" +// @Success 201 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflows/with-steps [post] +func (_i *approvalWorkflowsController) SaveWithSteps(c *fiber.Ctx) error { + req := new(request.ApprovalWorkflowsWithStepsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Convert request to entities + workflow := req.ToEntity() + steps := req.ToStepsEntity() + + approvalWorkflowsData, err := _i.approvalWorkflowsService.CreateWorkflowWithSteps(clientId, workflow, steps) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflows with steps successfully created"}, + Data: approvalWorkflowsData, + }) +} + +// UpdateWithSteps ApprovalWorkflows +// @Summary Update ApprovalWorkflows with steps +// @Description API for updating ApprovalWorkflows with steps +// @Tags ApprovalWorkflows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "ApprovalWorkflows ID" +// @Param req body request.ApprovalWorkflowsWithStepsUpdateRequest true "ApprovalWorkflows with steps data" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflows/{id}/with-steps [put] +func (_i *approvalWorkflowsController) UpdateWithSteps(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + req := new(request.ApprovalWorkflowsWithStepsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Convert request to entities + workflow := &entity.ApprovalWorkflows{ + Name: req.Name, + Description: &req.Description, + IsActive: req.IsActive, + IsDefault: req.IsDefault, + } + + steps := make([]*entity.ApprovalWorkflowSteps, len(req.Steps)) + for i, stepReq := range req.Steps { + steps[i] = stepReq.ToEntity(uint(id)) + } + + err = _i.approvalWorkflowsService.UpdateWorkflowWithSteps(clientId, uint(id), workflow, steps) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ApprovalWorkflows with steps successfully updated"}, + }) +} \ No newline at end of file diff --git a/app/module/approval_workflows/mapper/approval_workflows.mapper.go b/app/module/approval_workflows/mapper/approval_workflows.mapper.go new file mode 100644 index 0000000..731d758 --- /dev/null +++ b/app/module/approval_workflows/mapper/approval_workflows.mapper.go @@ -0,0 +1,126 @@ +package mapper + +import ( + "github.com/google/uuid" + "github.com/rs/zerolog" + "web-qudo-be/app/database/entity" + approvalWorkflowStepsMapper "web-qudo-be/app/module/approval_workflow_steps/mapper" + approvalWorkflowStepsRepository "web-qudo-be/app/module/approval_workflow_steps/repository" + approvalWorkflowStepsResponse "web-qudo-be/app/module/approval_workflow_steps/response" + res "web-qudo-be/app/module/approval_workflows/response" + usersRepository "web-qudo-be/app/module/users/repository" +) + +func ApprovalWorkflowsResponseMapper( + log zerolog.Logger, + clientId *uuid.UUID, + approvalWorkflowsReq *entity.ApprovalWorkflows, + approvalWorkflowStepsRepo approvalWorkflowStepsRepository.ApprovalWorkflowStepsRepository, + usersRepo usersRepository.UsersRepository, +) (approvalWorkflowsRes *res.ApprovalWorkflowsResponse) { + + // Get workflow steps using GetAll with filter + var workflowStepsArr []*approvalWorkflowStepsResponse.ApprovalWorkflowStepsResponse + if len(approvalWorkflowsReq.Steps) > 0 { + for _, step := range approvalWorkflowsReq.Steps { + workflowStepsArr = append(workflowStepsArr, approvalWorkflowStepsMapper.ApprovalWorkflowStepsResponseMapper( + log, + clientId, + &step, + usersRepo, + )) + } + } + + if approvalWorkflowsReq != nil { + // Convert boolean pointer to boolean value + isActive := false + if approvalWorkflowsReq.IsActive != nil { + isActive = *approvalWorkflowsReq.IsActive + } + + + + approvalWorkflowsRes = &res.ApprovalWorkflowsResponse{ + ID: approvalWorkflowsReq.ID, + Name: approvalWorkflowsReq.Name, + Description: approvalWorkflowsReq.Description, + IsActive: isActive, + CreatedBy: 0, // Default value since entity doesn't have CreatedBy field + CreatedAt: approvalWorkflowsReq.CreatedAt, + UpdatedAt: approvalWorkflowsReq.UpdatedAt, + Steps: workflowStepsArr, + } + } + + return approvalWorkflowsRes +} + +func ApprovalWorkflowsWithStepsResponseMapper( + log zerolog.Logger, + clientId *uuid.UUID, + approvalWorkflowsReq *entity.ApprovalWorkflows, + approvalWorkflowStepsRepo approvalWorkflowStepsRepository.ApprovalWorkflowStepsRepository, + usersRepo usersRepository.UsersRepository, +) (approvalWorkflowsRes *res.ApprovalWorkflowsWithStepsResponse) { + + // Get workflow steps with detailed information + var workflowStepsArr []*approvalWorkflowStepsResponse.ApprovalWorkflowStepsResponse + if len(approvalWorkflowsReq.Steps) > 0 { + for _, step := range approvalWorkflowsReq.Steps { + workflowStepsArr = append(workflowStepsArr, approvalWorkflowStepsMapper.ApprovalWorkflowStepsResponseMapper( + log, + clientId, + &step, + usersRepo, + )) + } + } + + if approvalWorkflowsReq != nil { + // Convert boolean pointer to boolean value + isActive := false + if approvalWorkflowsReq.IsActive != nil { + isActive = *approvalWorkflowsReq.IsActive + } + + approvalWorkflowsRes = &res.ApprovalWorkflowsWithStepsResponse{ + ID: approvalWorkflowsReq.ID, + Name: approvalWorkflowsReq.Name, + Description: approvalWorkflowsReq.Description, + IsActive: isActive, + CreatedBy: 0, // Default value since entity doesn't have CreatedBy field + CreatedAt: approvalWorkflowsReq.CreatedAt, + UpdatedAt: approvalWorkflowsReq.UpdatedAt, + Steps: workflowStepsArr, + } + } + + return approvalWorkflowsRes +} + +func ApprovalWorkflowsSummaryResponseMapper( + log zerolog.Logger, + clientId *uuid.UUID, + approvalWorkflowsReq *entity.ApprovalWorkflows, + usersRepo usersRepository.UsersRepository, +) (approvalWorkflowsRes *res.ApprovalWorkflowsSummaryResponse) { + + if approvalWorkflowsReq != nil { + // Convert boolean pointer to boolean value + isActive := false + if approvalWorkflowsReq.IsActive != nil { + isActive = *approvalWorkflowsReq.IsActive + } + + approvalWorkflowsRes = &res.ApprovalWorkflowsSummaryResponse{ + ID: approvalWorkflowsReq.ID, + Name: approvalWorkflowsReq.Name, + Description: approvalWorkflowsReq.Description, + IsActive: isActive, + StepCount: 0, // Default value, should be calculated if needed + } + } + + return approvalWorkflowsRes +} \ No newline at end of file diff --git a/app/module/approval_workflows/repository/approval_workflows.repository.go b/app/module/approval_workflows/repository/approval_workflows.repository.go new file mode 100644 index 0000000..3187af4 --- /dev/null +++ b/app/module/approval_workflows/repository/approval_workflows.repository.go @@ -0,0 +1,196 @@ +package repository + +import ( + "fmt" + "strings" + "github.com/google/uuid" + "github.com/rs/zerolog" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + approvalWorkflowStepsEntity "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/approval_workflows/request" + "web-qudo-be/utils/paginator" +) + +type approvalWorkflowsRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ApprovalWorkflowsRepository define interface of IApprovalWorkflowsRepository +type ApprovalWorkflowsRepository interface { + GetAll(clientId *uuid.UUID, req request.ApprovalWorkflowsQueryRequest) (workflows []*entity.ApprovalWorkflows, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (workflow *entity.ApprovalWorkflows, err error) + FindDefault(clientId *uuid.UUID) (workflow *entity.ApprovalWorkflows, err error) + GetDefault(clientId *uuid.UUID) (workflow *entity.ApprovalWorkflows, err error) + GetWorkflowSteps(clientId *uuid.UUID, workflowId uint) (steps []*entity.ApprovalWorkflowSteps, err error) + Create(clientId *uuid.UUID, workflow *entity.ApprovalWorkflows) (workflowReturn *entity.ApprovalWorkflows, err error) + Update(clientId *uuid.UUID, id uint, workflow *entity.ApprovalWorkflows) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) + SetDefault(clientId *uuid.UUID, id uint) (err error) +} + +func NewApprovalWorkflowsRepository(db *database.Database, log zerolog.Logger) ApprovalWorkflowsRepository { + return &approvalWorkflowsRepository{ + DB: db, + Log: log, + } +} + +// implement interface of IApprovalWorkflowsRepository +func (_i *approvalWorkflowsRepository) GetAll(clientId *uuid.UUID, req request.ApprovalWorkflowsQueryRequest) (workflows []*entity.ApprovalWorkflows, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ApprovalWorkflows{}) + + // Add client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("approval_workflows.is_active = ?", true) + + if req.Name != nil && *req.Name != "" { + name := strings.ToLower(*req.Name) + query = query.Where("LOWER(approval_workflows.name) LIKE ?", "%"+strings.ToLower(name)+"%") + } + if req.Description != nil && *req.Description != "" { + description := strings.ToLower(*req.Description) + query = query.Where("LOWER(approval_workflows.description) LIKE ?", "%"+strings.ToLower(description)+"%") + } + if req.IsActive != nil { + query = query.Where("approval_workflows.is_active = ?", req.IsActive) + } + if req.IsDefault != nil { + query = query.Where("approval_workflows.is_default = ?", req.IsDefault) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } else { + direction := "DESC" + sortBy := "approval_workflows.is_default DESC, approval_workflows.created_at" + query.Order(fmt.Sprintf("%s %s", sortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Preload("Steps").Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&workflows).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *approvalWorkflowsRepository) FindOne(clientId *uuid.UUID, id uint) (workflow *entity.ApprovalWorkflows, err error) { + query := _i.DB.DB.Model(&entity.ApprovalWorkflows{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("is_active = ?", true) + query = query.Preload("Steps") + + err = query.First(&workflow, id).Error + return workflow, err +} + +func (_i *approvalWorkflowsRepository) FindDefault(clientId *uuid.UUID) (workflow *entity.ApprovalWorkflows, err error) { + query := _i.DB.DB.Model(&entity.ApprovalWorkflows{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("is_active = ? AND is_default = ?", true, true) + query = query.Preload("Steps") + + err = query.First(&workflow).Error + return workflow, err +} + +func (_i *approvalWorkflowsRepository) Create(clientId *uuid.UUID, workflow *entity.ApprovalWorkflows) (workflowReturn *entity.ApprovalWorkflows, err error) { + workflow.ClientId = clientId + err = _i.DB.DB.Create(&workflow).Error + return workflow, err +} + +func (_i *approvalWorkflowsRepository) Update(clientId *uuid.UUID, id uint, workflow *entity.ApprovalWorkflows) (err error) { + query := _i.DB.DB.Model(&entity.ApprovalWorkflows{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + err = query.Where("id = ?", id).Updates(workflow).Error + return err +} + +func (_i *approvalWorkflowsRepository) Delete(clientId *uuid.UUID, id uint) (err error) { + query := _i.DB.DB.Model(&entity.ApprovalWorkflows{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + isActive := false + err = query.Where("id = ?", id).Update("is_active", isActive).Error + return err +} + +func (_i *approvalWorkflowsRepository) SetDefault(clientId *uuid.UUID, id uint) (err error) { + // First, unset all default workflows + query := _i.DB.DB.Model(&entity.ApprovalWorkflows{}) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + err = query.Update("is_default", false).Error + if err != nil { + return err + } + + // Then set the specified workflow as default + query = _i.DB.DB.Model(&entity.ApprovalWorkflows{}) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + err = query.Where("id = ?", id).Update("is_default", true).Error + return err +} + +// GetDefault gets the default workflow for a client +func (_i *approvalWorkflowsRepository) GetDefault(clientId *uuid.UUID) (workflow *entity.ApprovalWorkflows, err error) { + return _i.FindDefault(clientId) +} + +// GetWorkflowSteps gets all steps for a specific workflow +func (_i *approvalWorkflowsRepository) GetWorkflowSteps(clientId *uuid.UUID, workflowId uint) (steps []*entity.ApprovalWorkflowSteps, err error) { + query := _i.DB.DB.Model(&approvalWorkflowStepsEntity.ApprovalWorkflowSteps{}) + + // Join with approval_workflows to check client_id + query = query.Joins("JOIN approval_workflows ON approval_workflow_steps.workflow_id = approval_workflows.id") + + if clientId != nil { + query = query.Where("approval_workflows.client_id = ?", clientId) + } + + query = query.Where("approval_workflow_steps.workflow_id = ?", workflowId) + query = query.Where("approval_workflows.is_active = ?", true) + query = query.Order("approval_workflow_steps.step_order ASC") + + // Preload the RequiredUserLevel relation + query = query.Preload("RequiredUserLevel") + + err = query.Find(&steps).Error + return steps, err +} \ No newline at end of file diff --git a/app/module/approval_workflows/request/approval_workflows.request.go b/app/module/approval_workflows/request/approval_workflows.request.go new file mode 100644 index 0000000..c36b3e6 --- /dev/null +++ b/app/module/approval_workflows/request/approval_workflows.request.go @@ -0,0 +1,195 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type ApprovalWorkflowsGeneric interface { + ToEntity() +} + +type ApprovalWorkflowsQueryRequest struct { + Name *string `json:"name"` + Description *string `json:"description"` + IsActive *bool `json:"isActive"` + IsDefault *bool `json:"isDefault"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ApprovalWorkflowsCreateRequest struct { + Name string `json:"name" validate:"required"` + Description string `json:"description" validate:"required"` + IsActive *bool `json:"isActive"` + IsDefault *bool `json:"isDefault"` + RequiresApproval *bool `json:"requiresApproval"` + AutoPublish *bool `json:"autoPublish"` + Steps []ApprovalWorkflowStepRequest `json:"steps"` +} + +func (req ApprovalWorkflowsCreateRequest) ToEntity() *entity.ApprovalWorkflows { + return &entity.ApprovalWorkflows{ + Name: req.Name, + Description: &req.Description, + IsActive: req.IsActive, + IsDefault: req.IsDefault, + RequiresApproval: req.RequiresApproval, + AutoPublish: req.AutoPublish, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } +} + +func (req ApprovalWorkflowsCreateRequest) ToStepsEntity() []*entity.ApprovalWorkflowSteps { + steps := make([]*entity.ApprovalWorkflowSteps, len(req.Steps)) + 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 { + Name string `json:"name" validate:"required"` + Description string `json:"description" validate:"required"` + IsActive *bool `json:"isActive"` + IsDefault *bool `json:"isDefault"` + RequiresApproval *bool `json:"requiresApproval"` + AutoPublish *bool `json:"autoPublish"` +} + +func (req ApprovalWorkflowsUpdateRequest) ToEntity() *entity.ApprovalWorkflows { + return &entity.ApprovalWorkflows{ + Name: req.Name, + Description: &req.Description, + IsActive: req.IsActive, + IsDefault: req.IsDefault, + RequiresApproval: req.RequiresApproval, + AutoPublish: req.AutoPublish, + UpdatedAt: time.Now(), + } +} + +type ApprovalWorkflowStepRequest struct { + StepOrder int `json:"stepOrder" validate:"required"` + StepName string `json:"stepName" validate:"required"` + RequiredUserLevelId uint `json:"requiredUserLevelId" validate:"required"` + CanSkip *bool `json:"canSkip"` + AutoApproveAfterHours *int `json:"autoApproveAfterHours"` + IsActive *bool `json:"isActive"` +} + +func (req ApprovalWorkflowStepRequest) ToEntity(workflowId uint) *entity.ApprovalWorkflowSteps { + return &entity.ApprovalWorkflowSteps{ + WorkflowId: workflowId, + StepOrder: req.StepOrder, + StepName: req.StepName, + RequiredUserLevelId: req.RequiredUserLevelId, + CanSkip: req.CanSkip, + AutoApproveAfterHours: req.AutoApproveAfterHours, + IsActive: req.IsActive, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } +} + +type ApprovalWorkflowsWithStepsCreateRequest struct { + Name string `json:"name" validate:"required"` + Description string `json:"description" validate:"required"` + IsActive *bool `json:"isActive"` + IsDefault *bool `json:"isDefault"` + RequiresApproval *bool `json:"requiresApproval"` + AutoPublish *bool `json:"autoPublish"` + Steps []ApprovalWorkflowStepRequest `json:"steps" validate:"required,min=1"` +} + +func (req ApprovalWorkflowsWithStepsCreateRequest) ToEntity() *entity.ApprovalWorkflows { + return &entity.ApprovalWorkflows{ + Name: req.Name, + Description: &req.Description, + IsActive: req.IsActive, + IsDefault: req.IsDefault, + RequiresApproval: req.RequiresApproval, + AutoPublish: req.AutoPublish, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } +} + +func (req ApprovalWorkflowsWithStepsCreateRequest) ToStepsEntity() []*entity.ApprovalWorkflowSteps { + steps := make([]*entity.ApprovalWorkflowSteps, len(req.Steps)) + 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 ApprovalWorkflowsWithStepsUpdateRequest struct { + Name string `json:"name" validate:"required"` + Description string `json:"description" validate:"required"` + IsActive *bool `json:"isActive"` + IsDefault *bool `json:"isDefault"` + RequiresApproval *bool `json:"requiresApproval"` + AutoPublish *bool `json:"autoPublish"` + Steps []ApprovalWorkflowStepRequest `json:"steps" validate:"required,min=1"` +} + +type ApprovalWorkflowsQueryRequestContext struct { + Name string `json:"name"` + Description string `json:"description"` + IsActive string `json:"isActive"` + IsDefault string `json:"isDefault"` +} + +func (req ApprovalWorkflowsQueryRequestContext) ToParamRequest() ApprovalWorkflowsQueryRequest { + var name *string + var description *string + var isActive *bool + var isDefault *bool + + if req.Name != "" { + name = &req.Name + } + + if req.Description != "" { + description = &req.Description + } + + if req.IsActive != "" { + if parsedIsActive, err := strconv.ParseBool(req.IsActive); err == nil { + isActive = &parsedIsActive + } + } + + if req.IsDefault != "" { + if parsedIsDefault, err := strconv.ParseBool(req.IsDefault); err == nil { + isDefault = &parsedIsDefault + } + } + + return ApprovalWorkflowsQueryRequest{ + Name: name, + Description: description, + IsActive: isActive, + IsDefault: isDefault, + } +} diff --git a/app/module/approval_workflows/response/approval_workflows.response.go b/app/module/approval_workflows/response/approval_workflows.response.go new file mode 100644 index 0000000..1bf7149 --- /dev/null +++ b/app/module/approval_workflows/response/approval_workflows.response.go @@ -0,0 +1,48 @@ +package response + +import ( + "time" + approvalWorkflowStepsResponse "web-qudo-be/app/module/approval_workflow_steps/response" +) + +type ApprovalWorkflowsResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + Description *string `json:"description"` + IsActive bool `json:"isActive"` + CreatedBy uint `json:"createdBy"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + // Relations + Steps []*approvalWorkflowStepsResponse.ApprovalWorkflowStepsResponse `json:"steps,omitempty"` +} + +type ApprovalWorkflowsWithStepsResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + Description *string `json:"description"` + IsActive bool `json:"isActive"` + CreatedBy uint `json:"createdBy"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + // Relations + Steps []*approvalWorkflowStepsResponse.ApprovalWorkflowStepsResponse `json:"steps"` +} + +type ApprovalWorkflowsSummaryResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + Description *string `json:"description"` + IsActive bool `json:"isActive"` + StepCount int `json:"stepCount"` +} + +type ApprovalWorkflowsStatsResponse struct { + TotalWorkflows int `json:"totalWorkflows"` + ActiveWorkflows int `json:"activeWorkflows"` + InactiveWorkflows int `json:"inactiveWorkflows"` + TotalSteps int `json:"totalSteps"` + AverageStepsPerFlow int `json:"averageStepsPerFlow"` +} \ No newline at end of file diff --git a/app/module/approval_workflows/service/approval_workflows.service.go b/app/module/approval_workflows/service/approval_workflows.service.go new file mode 100644 index 0000000..9807047 --- /dev/null +++ b/app/module/approval_workflows/service/approval_workflows.service.go @@ -0,0 +1,319 @@ +package service + +import ( + "errors" + "fmt" + "github.com/google/uuid" + "github.com/rs/zerolog" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/approval_workflows/repository" + "web-qudo-be/app/module/approval_workflows/request" + stepRepo "web-qudo-be/app/module/approval_workflow_steps/repository" + "web-qudo-be/utils/paginator" +) + +type approvalWorkflowsService struct { + ApprovalWorkflowsRepository repository.ApprovalWorkflowsRepository + ApprovalWorkflowStepsRepository stepRepo.ApprovalWorkflowStepsRepository + Log zerolog.Logger +} + +// ApprovalWorkflowsService define interface of IApprovalWorkflowsService +type ApprovalWorkflowsService interface { + // Basic CRUD + GetAll(clientId *uuid.UUID, req request.ApprovalWorkflowsQueryRequest) (workflows []*entity.ApprovalWorkflows, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (workflow *entity.ApprovalWorkflows, err error) + Create(clientId *uuid.UUID, workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps) (workflowReturn *entity.ApprovalWorkflows, err error) + Update(clientId *uuid.UUID, id uint, workflow *entity.ApprovalWorkflows) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) + + // Workflow management + GetDefault(clientId *uuid.UUID) (workflow *entity.ApprovalWorkflows, err error) + SetDefault(clientId *uuid.UUID, id uint) (err error) + ActivateWorkflow(clientId *uuid.UUID, id uint) (err error) + DeactivateWorkflow(clientId *uuid.UUID, id uint) (err error) + + // Workflow with steps + GetWorkflowWithSteps(clientId *uuid.UUID, id uint) (workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps, err error) + CreateWorkflowWithSteps(clientId *uuid.UUID, workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps) (workflowReturn *entity.ApprovalWorkflows, err error) + UpdateWorkflowWithSteps(clientId *uuid.UUID, id uint, workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps) (err error) + + // Validation + ValidateWorkflow(clientId *uuid.UUID, workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps) (isValid bool, errors []string, err error) + CanDeleteWorkflow(clientId *uuid.UUID, id uint) (canDelete bool, reason string, err error) +} + +func NewApprovalWorkflowsService( + approvalWorkflowsRepository repository.ApprovalWorkflowsRepository, + approvalWorkflowStepsRepository stepRepo.ApprovalWorkflowStepsRepository, + log zerolog.Logger, +) ApprovalWorkflowsService { + return &approvalWorkflowsService{ + ApprovalWorkflowsRepository: approvalWorkflowsRepository, + ApprovalWorkflowStepsRepository: approvalWorkflowStepsRepository, + Log: log, + } +} + +// Basic CRUD implementations +func (_i *approvalWorkflowsService) GetAll(clientId *uuid.UUID, req request.ApprovalWorkflowsQueryRequest) (workflows []*entity.ApprovalWorkflows, paging paginator.Pagination, err error) { + return _i.ApprovalWorkflowsRepository.GetAll(clientId, req) +} + +func (_i *approvalWorkflowsService) FindOne(clientId *uuid.UUID, id uint) (workflow *entity.ApprovalWorkflows, err error) { + return _i.ApprovalWorkflowsRepository.FindOne(clientId, id) +} + +func (_i *approvalWorkflowsService) Create(clientId *uuid.UUID, workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps) (workflowReturn *entity.ApprovalWorkflows, err error) { + // Validate workflow and steps + isValid, validationErrors, err := _i.ValidateWorkflow(clientId, workflow, steps) + if err != nil { + return nil, err + } + + if !isValid { + return nil, errors.New(fmt.Sprintf("Validation failed: %v", validationErrors)) + } + + // Create workflow + workflowReturn, err = _i.ApprovalWorkflowsRepository.Create(clientId, workflow) + if err != nil { + return nil, err + } + + // Create steps + for i, step := range steps { + step.WorkflowId = workflowReturn.ID + step.StepOrder = i + 1 + _, err = _i.ApprovalWorkflowStepsRepository.Create(clientId, step) + if err != nil { + // Rollback workflow creation if step creation fails + _i.ApprovalWorkflowsRepository.Delete(clientId, workflowReturn.ID) + return nil, err + } + } + + return workflowReturn, nil +} + +func (_i *approvalWorkflowsService) Update(clientId *uuid.UUID, id uint, workflow *entity.ApprovalWorkflows) (err error) { + // Check if workflow exists + existingWorkflow, err := _i.ApprovalWorkflowsRepository.FindOne(clientId, id) + if err != nil { + return err + } + + if existingWorkflow == nil { + return errors.New("workflow not found") + } + + return _i.ApprovalWorkflowsRepository.Update(clientId, id, workflow) +} + +func (_i *approvalWorkflowsService) Delete(clientId *uuid.UUID, id uint) (err error) { + // Check if workflow can be deleted + canDelete, reason, err := _i.CanDeleteWorkflow(clientId, id) + if err != nil { + return err + } + + if !canDelete { + return errors.New(reason) + } + + return _i.ApprovalWorkflowsRepository.Delete(clientId, id) +} + +// Workflow management +func (_i *approvalWorkflowsService) GetDefault(clientId *uuid.UUID) (workflow *entity.ApprovalWorkflows, err error) { + return _i.ApprovalWorkflowsRepository.FindDefault(clientId) +} + +func (_i *approvalWorkflowsService) SetDefault(clientId *uuid.UUID, id uint) (err error) { + // Check if workflow exists and is active + workflow, err := _i.ApprovalWorkflowsRepository.FindOne(clientId, id) + if err != nil { + return err + } + + if workflow == nil { + return errors.New("workflow not found") + } + + if workflow.IsActive == nil || !*workflow.IsActive { + return errors.New("cannot set inactive workflow as default") + } + + return _i.ApprovalWorkflowsRepository.SetDefault(clientId, id) +} + +func (_i *approvalWorkflowsService) ActivateWorkflow(clientId *uuid.UUID, id uint) (err error) { + // Validate workflow before activation + workflow, err := _i.ApprovalWorkflowsRepository.FindOne(clientId, id) + if err != nil { + return err + } + + if workflow == nil { + return errors.New("workflow not found") + } + + // Get workflow steps and validate + steps, err := _i.ApprovalWorkflowStepsRepository.GetByWorkflowId(clientId, id) + if err != nil { + return err + } + + isValid, validationErrors, err := _i.ValidateWorkflow(clientId, workflow, steps) + if err != nil { + return err + } + + if !isValid { + return errors.New(fmt.Sprintf("Cannot activate invalid workflow: %v", validationErrors)) + } + + // Activate workflow + isActive := true + updateData := &entity.ApprovalWorkflows{IsActive: &isActive} + return _i.ApprovalWorkflowsRepository.Update(clientId, id, updateData) +} + +func (_i *approvalWorkflowsService) DeactivateWorkflow(clientId *uuid.UUID, id uint) (err error) { + // Check if this is the default workflow + defaultWorkflow, err := _i.ApprovalWorkflowsRepository.FindDefault(clientId) + if err != nil { + return err + } + + if defaultWorkflow != nil && defaultWorkflow.ID == id { + return errors.New("cannot deactivate default workflow") + } + + // Check if workflow is being used in active approval flows + canDelete, reason, err := _i.CanDeleteWorkflow(clientId, id) + if err != nil { + return err + } + + if !canDelete { + return errors.New(fmt.Sprintf("Cannot deactivate workflow: %s", reason)) + } + + // Deactivate workflow + isActive := false + updateData := &entity.ApprovalWorkflows{IsActive: &isActive} + return _i.ApprovalWorkflowsRepository.Update(clientId, id, updateData) +} + +// Workflow with steps +func (_i *approvalWorkflowsService) GetWorkflowWithSteps(clientId *uuid.UUID, id uint) (workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps, err error) { + workflow, err = _i.ApprovalWorkflowsRepository.FindOne(clientId, id) + if err != nil { + return nil, nil, err + } + + steps, err = _i.ApprovalWorkflowStepsRepository.GetByWorkflowId(clientId, id) + if err != nil { + return nil, nil, err + } + + return workflow, steps, nil +} + +func (_i *approvalWorkflowsService) CreateWorkflowWithSteps(clientId *uuid.UUID, workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps) (workflowReturn *entity.ApprovalWorkflows, err error) { + return _i.Create(clientId, workflow, steps) +} + +func (_i *approvalWorkflowsService) UpdateWorkflowWithSteps(clientId *uuid.UUID, id uint, workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps) (err error) { + // Update workflow + err = _i.Update(clientId, id, workflow) + if err != nil { + return err + } + + // Get existing steps + existingSteps, err := _i.ApprovalWorkflowStepsRepository.GetByWorkflowId(clientId, id) + if err != nil { + return err + } + + // Delete existing steps (simplified approach - in production, you might want to update/merge) + for _, existingStep := range existingSteps { + err = _i.ApprovalWorkflowStepsRepository.Delete(clientId, existingStep.ID) + if err != nil { + return err + } + } + + // Create new steps + for i, step := range steps { + step.WorkflowId = id + step.StepOrder = i + 1 + _, err = _i.ApprovalWorkflowStepsRepository.Create(clientId, step) + if err != nil { + return err + } + } + + return nil +} + +// Validation +func (_i *approvalWorkflowsService) ValidateWorkflow(clientId *uuid.UUID, workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps) (isValid bool, errors []string, err error) { + errors = make([]string, 0) + + // Validate workflow + if workflow.Name == "" { + errors = append(errors, "Workflow name is required") + } + + // Validate steps + if len(steps) == 0 { + errors = append(errors, "Workflow must have at least one step") + } else { + // Check for duplicate step orders + stepOrderMap := make(map[int]bool) + for i, step := range steps { + expectedOrder := i + 1 + if step.StepOrder != 0 && step.StepOrder != expectedOrder { + errors = append(errors, fmt.Sprintf("Step %d has incorrect order %d, expected %d", i+1, step.StepOrder, expectedOrder)) + } + + if stepOrderMap[step.StepOrder] { + errors = append(errors, fmt.Sprintf("Duplicate step order: %d", step.StepOrder)) + } + stepOrderMap[step.StepOrder] = true + + if step.StepName == "" { + errors = append(errors, fmt.Sprintf("Step %d name is required", i+1)) + } + + if step.RequiredUserLevelId == 0 { + errors = append(errors, fmt.Sprintf("Step %d must have a required user level", i+1)) + } + } + } + + isValid = len(errors) == 0 + return isValid, errors, nil +} + +func (_i *approvalWorkflowsService) CanDeleteWorkflow(clientId *uuid.UUID, id uint) (canDelete bool, reason string, err error) { + // Check if workflow is default + defaultWorkflow, err := _i.ApprovalWorkflowsRepository.FindDefault(clientId) + if err != nil { + return false, "", err + } + + if defaultWorkflow != nil && defaultWorkflow.ID == id { + return false, "Cannot delete default workflow", nil + } + + // Check if workflow is being used in active approval flows + // This would require a method in ArticleApprovalFlowsRepository + // For now, we'll assume it can be deleted + // TODO: Implement check for active approval flows + + return true, "", nil +} \ No newline at end of file diff --git a/app/module/article_approval_flows/article_approval_flows.module.go b/app/module/article_approval_flows/article_approval_flows.module.go new file mode 100644 index 0000000..b9521ab --- /dev/null +++ b/app/module/article_approval_flows/article_approval_flows.module.go @@ -0,0 +1,61 @@ +package article_approval_flows + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/article_approval_flows/controller" + "web-qudo-be/app/module/article_approval_flows/repository" + "web-qudo-be/app/module/article_approval_flows/service" +) + +// ArticleApprovalFlowsRouter struct of ArticleApprovalFlowsRouter +type ArticleApprovalFlowsRouter struct { + App fiber.Router + Controller controller.ArticleApprovalFlowsController +} + +// NewArticleApprovalFlowsModule register bulky of ArticleApprovalFlows module +var NewArticleApprovalFlowsModule = fx.Options( + // register repository of ArticleApprovalFlows module + fx.Provide(repository.NewArticleApprovalFlowsRepository), + + // register service of ArticleApprovalFlows module + fx.Provide(service.NewArticleApprovalFlowsService), + + // register controller of ArticleApprovalFlows module + fx.Provide(controller.NewArticleApprovalFlowsController), + + // register router of ArticleApprovalFlows module + fx.Provide(NewArticleApprovalFlowsRouter), +) + +// NewArticleApprovalFlowsRouter init ArticleApprovalFlowsRouter +func NewArticleApprovalFlowsRouter(fiber *fiber.App, controller controller.ArticleApprovalFlowsController) *ArticleApprovalFlowsRouter { + return &ArticleApprovalFlowsRouter{ + App: fiber, + Controller: controller, + } +} + +// RegisterArticleApprovalFlowsRoutes register routes of ArticleApprovalFlows module +func (_i *ArticleApprovalFlowsRouter) RegisterArticleApprovalFlowsRoutes() { + // define controllers + articleApprovalFlowsController := _i.Controller + + // define routes + _i.App.Route("/article-approval-flows", func(router fiber.Router) { + router.Get("/", articleApprovalFlowsController.All) + router.Get("/my-queue", articleApprovalFlowsController.GetMyApprovalQueue) + router.Get("/pending", articleApprovalFlowsController.GetPendingApprovals) + router.Get("/history", articleApprovalFlowsController.GetApprovalHistory) + router.Get("/dashboard-stats", articleApprovalFlowsController.GetDashboardStats) + router.Get("/workload-stats", articleApprovalFlowsController.GetWorkloadStats) + router.Get("/analytics", articleApprovalFlowsController.GetApprovalAnalytics) + router.Get("/:id", articleApprovalFlowsController.Show) + router.Post("/submit", articleApprovalFlowsController.SubmitForApproval) + router.Put("/:id/approve", articleApprovalFlowsController.Approve) + router.Put("/:id/reject", articleApprovalFlowsController.Reject) + router.Put("/:id/request-revision", articleApprovalFlowsController.RequestRevision) + router.Put("/:id/resubmit", articleApprovalFlowsController.Resubmit) + }) +} \ No newline at end of file diff --git a/app/module/article_approval_flows/controller/article_approval_flows.controller.go b/app/module/article_approval_flows/controller/article_approval_flows.controller.go new file mode 100644 index 0000000..58f3ee3 --- /dev/null +++ b/app/module/article_approval_flows/controller/article_approval_flows.controller.go @@ -0,0 +1,664 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/article_approval_flows/request" + "web-qudo-be/app/module/article_approval_flows/service" + usersRepository "web-qudo-be/app/module/users/repository" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type articleApprovalFlowsController struct { + articleApprovalFlowsService service.ArticleApprovalFlowsService + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +type ArticleApprovalFlowsController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + SubmitForApproval(c *fiber.Ctx) error + Approve(c *fiber.Ctx) error + Reject(c *fiber.Ctx) error + RequestRevision(c *fiber.Ctx) error + Resubmit(c *fiber.Ctx) error + GetMyApprovalQueue(c *fiber.Ctx) error + GetPendingApprovals(c *fiber.Ctx) error + GetApprovalHistory(c *fiber.Ctx) error + GetDashboardStats(c *fiber.Ctx) error + GetWorkloadStats(c *fiber.Ctx) error + GetApprovalAnalytics(c *fiber.Ctx) error +} + +func NewArticleApprovalFlowsController(articleApprovalFlowsService service.ArticleApprovalFlowsService, usersRepo usersRepository.UsersRepository, log zerolog.Logger) ArticleApprovalFlowsController { + return &articleApprovalFlowsController{ + articleApprovalFlowsService: articleApprovalFlowsService, + UsersRepo: usersRepo, + Log: log, + } +} + +// All ArticleApprovalFlows +// @Summary Get all ArticleApprovalFlows +// @Description API for getting all ArticleApprovalFlows +// @Tags ArticleApprovalFlows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param req query request.ArticleApprovalFlowsQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-flows [get] +func (_i *articleApprovalFlowsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.ArticleApprovalFlowsQueryRequestContext{ + ArticleId: c.Query("articleId"), + WorkflowId: c.Query("workflowId"), + StatusId: c.Query("statusId"), + SubmittedBy: c.Query("submittedBy"), + CurrentStep: c.Query("currentStep"), + DateFrom: c.Query("dateFrom"), + DateTo: c.Query("dateTo"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + _i.Log.Info().Interface("clientId", clientId).Msg("") + + articleApprovalFlowsData, paging, err := _i.articleApprovalFlowsService.GetAll(clientId, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleApprovalFlows list successfully retrieved"}, + Data: articleApprovalFlowsData, + Meta: paging, + }) +} + +// Show ArticleApprovalFlows +// @Summary Get one ArticleApprovalFlows +// @Description API for getting one ArticleApprovalFlows +// @Tags ArticleApprovalFlows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "ArticleApprovalFlows ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-flows/{id} [get] +func (_i *articleApprovalFlowsController) Show(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + articleApprovalFlowsData, err := _i.articleApprovalFlowsService.FindOne(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleApprovalFlows successfully retrieved"}, + Data: articleApprovalFlowsData, + }) +} + +// SubmitForApproval ArticleApprovalFlows +// @Summary Submit article for approval +// @Description API for submitting article for approval +// @Tags ArticleApprovalFlows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param req body request.SubmitForApprovalRequest true "Submit for approval data" +// @Success 201 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-flows/submit [post] +func (_i *articleApprovalFlowsController) SubmitForApproval(c *fiber.Ctx) error { + req := new(request.SubmitForApprovalRequest) + if err := c.BodyParser(req); err != nil { + return utilRes.ErrorBadRequest(c, "Invalid request body") + } + + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + 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 + if req.WorkflowId != nil { + workflowIdVal := uint(*req.WorkflowId) + workflowId = &workflowIdVal + } + articleApprovalFlowsData, err := _i.articleApprovalFlowsService.SubmitArticleForApproval(clientId, uint(req.ArticleId), user.ID, workflowId) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Article successfully submitted for approval"}, + Data: articleApprovalFlowsData, + }) +} + +// Approve ArticleApprovalFlows +// @Summary Approve article +// @Description API for approving article +// @Tags ArticleApprovalFlows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "ArticleApprovalFlows ID" +// @Param req body request.ApprovalActionRequest true "Approval action data" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-flows/{id}/approve [put] +func (_i *articleApprovalFlowsController) Approve(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + req := new(request.ApprovalActionRequest) + if err := c.BodyParser(req); err != nil { + return utilRes.ErrorBadRequest(c, "Invalid request body") + } + + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + 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") + } + + err = _i.articleApprovalFlowsService.ApproveStep(clientId, uint(id), user.ID, req.Message) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Article successfully approved"}, + Data: nil, + }) +} + +// Reject ArticleApprovalFlows +// @Summary Reject article +// @Description API for rejecting article +// @Tags ArticleApprovalFlows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "ArticleApprovalFlows ID" +// @Param req body request.RejectionRequest true "Rejection data" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-flows/{id}/reject [put] +func (_i *articleApprovalFlowsController) Reject(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + req := new(request.RejectionRequest) + if err := c.BodyParser(req); err != nil { + return utilRes.ErrorBadRequest(c, "Invalid request body") + } + + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + 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") + } + + err = _i.articleApprovalFlowsService.RejectArticle(clientId, uint(id), user.ID, req.Reason) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Article successfully rejected"}, + Data: nil, + }) +} + +// RequestRevision ArticleApprovalFlows +// @Summary Request revision for article +// @Description API for requesting revision for article +// @Tags ArticleApprovalFlows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "ArticleApprovalFlows ID" +// @Param req body request.RevisionRequest true "Revision request data" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-flows/{id}/request-revision [put] +func (_i *articleApprovalFlowsController) RequestRevision(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + req := new(request.RevisionRequest) + if err := c.BodyParser(req); err != nil { + return utilRes.ErrorBadRequest(c, "Invalid request body") + } + + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + 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") + } + + err = _i.articleApprovalFlowsService.RequestRevision(clientId, uint(id), user.ID, req.Message) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Revision successfully requested"}, + Data: nil, + }) +} + +// Resubmit ArticleApprovalFlows +// @Summary Resubmit article after revision +// @Description API for resubmitting article after revision +// @Tags ArticleApprovalFlows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "ArticleApprovalFlows ID" +// @Param req body request.ResubmitRequest true "Resubmit data" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-flows/{id}/resubmit [put] +func (_i *articleApprovalFlowsController) Resubmit(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + req := new(request.ResubmitRequest) + if err := c.BodyParser(req); err != nil { + return utilRes.ErrorBadRequest(c, "Invalid request body") + } + + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + 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") + } + + err = _i.articleApprovalFlowsService.ResubmitAfterRevision(clientId, uint(id), user.ID) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Article successfully resubmitted"}, + Data: nil, + }) +} + +// GetMyApprovalQueue ArticleApprovalFlows +// @Summary Get my approval queue +// @Description API for getting my approval queue +// @Tags ArticleApprovalFlows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @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" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-flows/my-queue [get] +func (_i *articleApprovalFlowsController) GetMyApprovalQueue(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + // Get ClientId from context + 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") + } + + // 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 { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"My approval queue successfully retrieved"}, + Data: approvalQueueData, + Meta: paging, + }) +} + +// GetPendingApprovals ArticleApprovalFlows +// @Summary Get pending approvals +// @Description API for getting pending approvals +// @Tags ArticleApprovalFlows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-flows/pending [get] +func (_i *articleApprovalFlowsController) GetPendingApprovals(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + // Get ClientId from context + 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{}) + pendingApprovalsData, paging, err := _i.articleApprovalFlowsService.GetPendingApprovals(clientId, user.UserLevelId, paginate.Page, paginate.Limit, filters) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Pending approvals successfully retrieved"}, + Data: pendingApprovalsData, + Meta: paging, + }) +} + +// GetApprovalHistory ArticleApprovalFlows +// @Summary Get approval history +// @Description API for getting approval history +// @Tags ArticleApprovalFlows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param articleId query int false "Article ID filter" +// @Param userId query int false "User ID filter" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-flows/history [get] +func (_i *articleApprovalFlowsController) GetApprovalHistory(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + articleId := 0 + if articleIdStr := c.Query("articleId"); articleIdStr != "" { + articleId, err = strconv.Atoi(articleIdStr) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid articleId format") + } + } + + // userId parameter is not used in the current implementation + // userId := 0 + // if userIdStr := c.Query("userId"); userIdStr != "" { + // userId, err := strconv.Atoi(userIdStr) + // if err != nil { + // return utilRes.ErrorBadRequest(c, "Invalid userId format") + // } + // } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + approvalHistoryData, paging, err := _i.articleApprovalFlowsService.GetApprovalHistory(clientId, uint(articleId), paginate.Page, paginate.Limit) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Approval history successfully retrieved"}, + Data: approvalHistoryData, + Meta: paging, + }) +} + +// GetDashboardStats ArticleApprovalFlows +// @Summary Get dashboard statistics +// @Description API for getting dashboard statistics +// @Tags ArticleApprovalFlows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-flows/dashboard-stats [get] +func (_i *articleApprovalFlowsController) GetDashboardStats(c *fiber.Ctx) error { + // Get ClientId from context + 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") + } + + // TODO: Implement GetDashboardStats method in service + _ = clientId // suppress unused variable warning + _ = user.UserLevelId // suppress unused variable warning + // dashboardStatsData, err := _i.articleApprovalFlowsService.GetDashboardStats(clientId, user.UserLevelId) + // if err != nil { + // return err + // } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Dashboard statistics successfully retrieved"}, + Data: nil, + }) +} + +// GetWorkloadStats ArticleApprovalFlows +// @Summary Get workload statistics +// @Description API for getting workload statistics +// @Tags ArticleApprovalFlows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-flows/workload-stats [get] +func (_i *articleApprovalFlowsController) GetWorkloadStats(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // TODO: Implement GetWorkloadStats method in service + _ = clientId // suppress unused variable warning + // workloadStatsData, err := _i.articleApprovalFlowsService.GetWorkloadStats(clientId) + // if err != nil { + // return err + // } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Workload statistics successfully retrieved"}, + Data: nil, + }) +} + +// GetApprovalAnalytics ArticleApprovalFlows +// @Summary Get approval analytics +// @Description API for getting approval analytics +// @Tags ArticleApprovalFlows +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param period query string false "Period filter (daily, weekly, monthly)" +// @Param startDate query string false "Start date filter (YYYY-MM-DD)" +// @Param endDate query string false "End date filter (YYYY-MM-DD)" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-flows/analytics [get] +func (_i *articleApprovalFlowsController) GetApprovalAnalytics(c *fiber.Ctx) error { + // period := c.Query("period", "monthly") + // startDate := c.Query("startDate") + // endDate := c.Query("endDate") + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // TODO: Implement GetApprovalAnalytics method in service + _ = clientId // suppress unused variable warning + // analyticsData, err := _i.articleApprovalFlowsService.GetApprovalAnalytics(clientId, period, startDate, endDate) + // if err != nil { + // return err + // } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Approval analytics successfully retrieved"}, + Data: nil, + }) +} diff --git a/app/module/article_approval_flows/mapper/article_approval_flows.mapper.go b/app/module/article_approval_flows/mapper/article_approval_flows.mapper.go new file mode 100644 index 0000000..f31c61f --- /dev/null +++ b/app/module/article_approval_flows/mapper/article_approval_flows.mapper.go @@ -0,0 +1,257 @@ +package mapper + +import ( + "time" + "github.com/google/uuid" + "github.com/rs/zerolog" + "web-qudo-be/app/database/entity" + articleApprovalStepLogsResponse "web-qudo-be/app/module/article_approval_step_logs/response" + articleApprovalStepLogsRepository "web-qudo-be/app/module/article_approval_step_logs/repository" + approvalWorkflowsRepository "web-qudo-be/app/module/approval_workflows/repository" + approvalWorkflowsResponse "web-qudo-be/app/module/approval_workflows/response" + articlesRepository "web-qudo-be/app/module/articles/repository" + articlesResponse "web-qudo-be/app/module/articles/response" + usersRepository "web-qudo-be/app/module/users/repository" + res "web-qudo-be/app/module/article_approval_flows/response" +) + +func ArticleApprovalFlowsResponseMapper( + log zerolog.Logger, + host string, + clientId *uuid.UUID, + articleApprovalFlowsReq *entity.ArticleApprovalFlows, + articlesRepo articlesRepository.ArticlesRepository, + approvalWorkflowsRepo approvalWorkflowsRepository.ApprovalWorkflowsRepository, + articleApprovalStepLogsRepo articleApprovalStepLogsRepository.ArticleApprovalStepLogsRepository, + usersRepo usersRepository.UsersRepository, +) (articleApprovalFlowsRes *res.ArticleApprovalFlowsResponse) { + + + + // Get article information + var articleRes *articlesResponse.ArticlesResponse + if articleApprovalFlowsReq.ArticleId != 0 { + article, _ := articlesRepo.FindOne(clientId, articleApprovalFlowsReq.ArticleId) + if article != nil { + // Note: This would need additional repositories for full mapping + // For now, we'll create a basic response + articleRes = &articlesResponse.ArticlesResponse{ + ID: article.ID, + Title: article.Title, + Slug: article.Slug, + Description: article.Description, + StatusId: article.StatusId, + IsActive: article.IsActive, + CreatedAt: article.CreatedAt, + UpdatedAt: article.UpdatedAt, + } + } + } + + // Get approval workflow information + var approvalWorkflowRes *approvalWorkflowsResponse.ApprovalWorkflowsResponse + if articleApprovalFlowsReq.WorkflowId != 0 { + approvalWorkflow, _ := approvalWorkflowsRepo.FindOne(clientId, articleApprovalFlowsReq.WorkflowId) + if approvalWorkflow != nil { + // Note: This would need additional repositories for full mapping + // For now, we'll create a basic response + approvalWorkflowRes = &approvalWorkflowsResponse.ApprovalWorkflowsResponse{ + ID: approvalWorkflow.ID, + Name: approvalWorkflow.Name, + Description: approvalWorkflow.Description, + IsActive: *approvalWorkflow.IsActive, + CreatedAt: approvalWorkflow.CreatedAt, + UpdatedAt: approvalWorkflow.UpdatedAt, + } + } + } + + // Get step logs + var stepLogsArr []*articleApprovalStepLogsResponse.ArticleApprovalStepLogsResponse + + if articleApprovalFlowsReq != nil { + // Map status ID to status string + statusStr := "pending" + switch articleApprovalFlowsReq.StatusId { + case 1: + statusStr = "pending" + case 2: + statusStr = "approved" + case 3: + statusStr = "rejected" + case 4: + statusStr = "revision_requested" + } + + articleApprovalFlowsRes = &res.ArticleApprovalFlowsResponse{ + ID: articleApprovalFlowsReq.ID, + ArticleID: articleApprovalFlowsReq.ArticleId, + WorkflowID: articleApprovalFlowsReq.WorkflowId, + CurrentStep: articleApprovalFlowsReq.CurrentStep, + Status: statusStr, + SubmittedBy: articleApprovalFlowsReq.SubmittedById, + SubmittedAt: articleApprovalFlowsReq.SubmittedAt, + CompletedAt: articleApprovalFlowsReq.CompletedAt, + RejectionReason: articleApprovalFlowsReq.RejectionReason, + RevisionReason: articleApprovalFlowsReq.RevisionMessage, + CreatedAt: articleApprovalFlowsReq.CreatedAt, + UpdatedAt: articleApprovalFlowsReq.UpdatedAt, + Article: articleRes, + Workflow: approvalWorkflowRes, + StepLogs: stepLogsArr, + } + } + + return articleApprovalFlowsRes +} + +func ArticleApprovalFlowsDetailResponseMapper( + log zerolog.Logger, + host string, + clientId *uuid.UUID, + articleApprovalFlowsReq *entity.ArticleApprovalFlows, + articlesRepo articlesRepository.ArticlesRepository, + approvalWorkflowsRepo approvalWorkflowsRepository.ApprovalWorkflowsRepository, + articleApprovalStepLogsRepo articleApprovalStepLogsRepository.ArticleApprovalStepLogsRepository, + usersRepo usersRepository.UsersRepository, +) (articleApprovalFlowsRes *res.ArticleApprovalFlowsDetailResponse) { + + submittedByName := "" + if articleApprovalFlowsReq.SubmittedById != 0 { + findUser, _ := usersRepo.FindOne(clientId, articleApprovalFlowsReq.SubmittedById) + if findUser != nil { + submittedByName = findUser.Fullname + } + } + if articleApprovalFlowsReq.SubmittedById != 0 { + findUser, _ := usersRepo.FindOne(clientId, articleApprovalFlowsReq.SubmittedById) + if findUser != nil { + submittedByName = findUser.Fullname + } + } + + // Get article information with full details + var articleRes *articlesResponse.ArticlesResponse + if articleApprovalFlowsReq.ArticleId != 0 { + article, _ := articlesRepo.FindOne(clientId, articleApprovalFlowsReq.ArticleId) + if article != nil { + // Note: This would need additional repositories for full mapping + // For now, we'll create a basic response + articleRes = &articlesResponse.ArticlesResponse{ + ID: article.ID, + Title: article.Title, + Slug: article.Slug, + Description: article.Description, + StatusId: article.StatusId, + IsActive: article.IsActive, + CreatedAt: article.CreatedAt, + UpdatedAt: article.UpdatedAt, + } + } + } + + // Get approval workflow information with full details + var approvalWorkflowRes *approvalWorkflowsResponse.ApprovalWorkflowsWithStepsResponse + if articleApprovalFlowsReq.WorkflowId != 0 { + approvalWorkflow, _ := approvalWorkflowsRepo.FindOne(clientId, articleApprovalFlowsReq.WorkflowId) + if approvalWorkflow != nil { + // Note: This would need additional repositories for full mapping + // For now, we'll create a basic response + approvalWorkflowRes = &approvalWorkflowsResponse.ApprovalWorkflowsWithStepsResponse{ + ID: approvalWorkflow.ID, + Name: approvalWorkflow.Name, + Description: approvalWorkflow.Description, + IsActive: *approvalWorkflow.IsActive, + CreatedAt: approvalWorkflow.CreatedAt, + UpdatedAt: approvalWorkflow.UpdatedAt, + + } + } + } + + // Get step logs with detailed information + var stepLogsArr []*articleApprovalStepLogsResponse.ArticleApprovalStepLogsResponse + + if articleApprovalFlowsReq != nil { + // Map status ID to status string + statusStr := "pending" + switch articleApprovalFlowsReq.StatusId { + case 1: + statusStr = "pending" + case 2: + statusStr = "approved" + case 3: + statusStr = "rejected" + case 4: + statusStr = "revision_requested" + } + + articleApprovalFlowsRes = &res.ArticleApprovalFlowsDetailResponse{ + ID: articleApprovalFlowsReq.ID, + ArticleID: articleApprovalFlowsReq.ArticleId, + WorkflowID: articleApprovalFlowsReq.WorkflowId, + CurrentStep: articleApprovalFlowsReq.CurrentStep, + Status: statusStr, + SubmittedBy: articleApprovalFlowsReq.SubmittedById, + SubmittedByName: submittedByName, + SubmittedAt: articleApprovalFlowsReq.SubmittedAt, + CompletedAt: articleApprovalFlowsReq.CompletedAt, + RejectionReason: articleApprovalFlowsReq.RejectionReason, + RevisionReason: articleApprovalFlowsReq.RevisionMessage, + CreatedAt: articleApprovalFlowsReq.CreatedAt, + UpdatedAt: articleApprovalFlowsReq.UpdatedAt, + Article: articleRes, + Workflow: approvalWorkflowRes, + StepLogs: stepLogsArr, + } + } + + return articleApprovalFlowsRes +} + +func ArticleApprovalFlowsSummaryResponseMapper( + log zerolog.Logger, + clientId *uuid.UUID, + articleApprovalFlowsReq *entity.ArticleApprovalFlows, + usersRepo usersRepository.UsersRepository, +) (articleApprovalFlowsRes *res.ArticleApprovalFlowsSummaryResponse) { + + submittedByName := "" + if articleApprovalFlowsReq.SubmittedById != 0 { + findUser, _ := usersRepo.FindOne(clientId, articleApprovalFlowsReq.SubmittedById) + if findUser != nil { + submittedByName = findUser.Fullname + } + } + + if articleApprovalFlowsReq != nil { + // Map status ID to status string + statusStr := "pending" + switch articleApprovalFlowsReq.StatusId { + case 1: + statusStr = "pending" + case 2: + statusStr = "approved" + case 3: + statusStr = "rejected" + case 4: + statusStr = "revision_requested" + } + + // Calculate days in approval + daysInApproval := int(time.Since(articleApprovalFlowsReq.SubmittedAt).Hours() / 24) + + articleApprovalFlowsRes = &res.ArticleApprovalFlowsSummaryResponse{ + ID: articleApprovalFlowsReq.ID, + ArticleID: articleApprovalFlowsReq.ArticleId, + CurrentStep: articleApprovalFlowsReq.CurrentStep, + Status: statusStr, + SubmittedBy: articleApprovalFlowsReq.SubmittedById, + SubmittedByName: submittedByName, + SubmittedAt: articleApprovalFlowsReq.SubmittedAt, + DaysInApproval: daysInApproval, + } + } + + return articleApprovalFlowsRes +} \ No newline at end of file diff --git a/app/module/article_approval_flows/repository/article_approval_flows.repository.go b/app/module/article_approval_flows/repository/article_approval_flows.repository.go new file mode 100644 index 0000000..ddb81d6 --- /dev/null +++ b/app/module/article_approval_flows/repository/article_approval_flows.repository.go @@ -0,0 +1,420 @@ +package repository + +import ( + "fmt" + "time" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/article_approval_flows/request" + "web-qudo-be/utils/paginator" + + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +type articleApprovalFlowsRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ArticleApprovalFlowsRepository define interface of IArticleApprovalFlowsRepository +type ArticleApprovalFlowsRepository interface { + // Basic CRUD + GetAll(clientId *uuid.UUID, req request.ArticleApprovalFlowsQueryRequest) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (flow *entity.ArticleApprovalFlows, err error) + FindActiveByArticleId(articleId uint) (flow *entity.ArticleApprovalFlows, err error) + FindByArticleId(clientId *uuid.UUID, articleId uint) (flow *entity.ArticleApprovalFlows, err error) + Create(clientId *uuid.UUID, flow *entity.ArticleApprovalFlows) (flowReturn *entity.ArticleApprovalFlows, err error) + Update(id uint, flow *entity.ArticleApprovalFlows) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) + + // Approval Queue Methods + GetPendingApprovals(clientId *uuid.UUID, userLevelId uint, page, limit int, filters map[string]interface{}) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error) + GetPendingApprovalsByUserLevel(clientId *uuid.UUID, userLevelId uint, page, limit int) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error) + GetMyApprovalQueue(clientId *uuid.UUID, userLevelId uint, page, limit int, includePreview bool, urgentOnly bool) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error) + + // Statistics Methods + GetPendingCountByLevel(clientId *uuid.UUID, userLevelId uint) (count int64, err error) + GetOverdueCountByLevel(clientId *uuid.UUID, userLevelId uint) (count int64, err error) + GetApprovedCountByPeriod(clientId *uuid.UUID, userLevelId uint, startDate, endDate time.Time) (count int64, err error) + GetRejectedCountByPeriod(clientId *uuid.UUID, userLevelId uint, startDate, endDate time.Time) (count int64, err error) + GetRevisionRequestCountByPeriod(clientId *uuid.UUID, userLevelId uint, startDate, endDate time.Time) (count int64, err error) + + // Workload Methods + GetLevelWorkload(clientId *uuid.UUID, userLevelId uint) (workload map[string]interface{}, err error) + GetTeamWorkloadComparison(clientId *uuid.UUID, userLevelId uint) (comparison []map[string]interface{}, err error) + GetWorkflowBottlenecks(clientId *uuid.UUID) (bottlenecks []map[string]interface{}, err error) +} + +func NewArticleApprovalFlowsRepository(db *database.Database, log zerolog.Logger) ArticleApprovalFlowsRepository { + return &articleApprovalFlowsRepository{ + DB: db, + Log: log, + } +} + +// Basic CRUD implementations +func (_i *articleApprovalFlowsRepository) GetAll(clientId *uuid.UUID, req request.ArticleApprovalFlowsQueryRequest) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ArticleApprovalFlows{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + // Apply filters from request + if req.ArticleId != nil && *req.ArticleId > 0 { + query = query.Where("article_id = ?", *req.ArticleId) + } + + if req.StatusId != nil && *req.StatusId > 0 { + query = query.Where("status_id = ?", *req.StatusId) + } + + if req.SubmittedBy != nil && *req.SubmittedBy > 0 { + query = query.Where("submitted_by_id = ?", *req.SubmittedBy) + } + + if req.CurrentStep != nil && *req.CurrentStep > 0 { + query = query.Where("current_step = ?", *req.CurrentStep) + } + + if req.DateFrom != nil { + query = query.Where("submitted_at >= ?", *req.DateFrom) + } + + if req.WorkflowId != nil { + query = query.Where("workflow_id = ?", *req.WorkflowId) + } + + if req.DateTo != nil { + query = query.Where("submitted_at <= ?", *req.DateTo) + } + + // Preload related data + query = query.Preload("Article").Preload("Workflow").Preload("SubmittedBy").Preload("StepLogs") + query = query.Order("submitted_at DESC") + + err = query.Count(&count).Error + if err != nil { + return nil, paginator.Pagination{}, err + } + + // Apply pagination + page := req.Pagination.Page + limit := req.Pagination.Limit + if page <= 0 { + page = 1 + } + if limit <= 0 { + limit = 10 + } + + offset := (page - 1) * limit + err = query.Offset(offset).Limit(limit).Find(&flows).Error + if err != nil { + return nil, paginator.Pagination{}, err + } + + paging = paginator.Pagination{ + Page: page, + Limit: limit, + Count: int64(count), + TotalPage: int((count + int64(limit) - 1) / int64(limit)), + } + + return flows, paging, nil +} +func (_i *articleApprovalFlowsRepository) FindOne(clientId *uuid.UUID, id uint) (flow *entity.ArticleApprovalFlows, err error) { + query := _i.DB.DB.Model(&entity.ArticleApprovalFlows{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Preload("Article").Preload("Workflow").Preload("SubmittedBy").Preload("StepLogs") + + err = query.First(&flow, id).Error + return flow, err +} + +func (_i *articleApprovalFlowsRepository) FindActiveByArticleId(articleId uint) (flow *entity.ArticleApprovalFlows, err error) { + query := _i.DB.DB.Model(&entity.ArticleApprovalFlows{}) + query = query.Where("article_id = ? AND status_id IN (1, 4)", articleId) // pending or revision_requested + query = query.Preload("Article").Preload("Workflow").Preload("SubmittedBy").Preload("StepLogs") + + err = query.First(&flow).Error + return flow, err +} + +func (_i *articleApprovalFlowsRepository) Create(clientId *uuid.UUID, flow *entity.ArticleApprovalFlows) (flowReturn *entity.ArticleApprovalFlows, err error) { + flow.ClientId = clientId + err = _i.DB.DB.Create(&flow).Error + return flow, err +} + +func (_i *articleApprovalFlowsRepository) Update(id uint, flow *entity.ArticleApprovalFlows) (err error) { + err = _i.DB.DB.Model(&entity.ArticleApprovalFlows{}).Where("id = ?", id).Updates(flow).Error + return err +} + +func (_i *articleApprovalFlowsRepository) Delete(clientId *uuid.UUID, id uint) (err error) { + query := _i.DB.DB.Model(&entity.ArticleApprovalFlows{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + err = query.Delete(&entity.ArticleApprovalFlows{}, id).Error + return err +} + +// Approval Queue Methods +func (_i *articleApprovalFlowsRepository) GetPendingApprovals(clientId *uuid.UUID, userLevelId uint, page, limit int, filters map[string]interface{}) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ArticleApprovalFlows{}) + + if clientId != nil { + query = query.Where("article_approval_flows.client_id = ?", clientId) + } + + // Join with workflow steps to find current step requirements + query = query.Joins("JOIN approval_workflows ON article_approval_flows.workflow_id = approval_workflows.id") + query = query.Joins("JOIN approval_workflow_steps ON approval_workflows.id = approval_workflow_steps.workflow_id AND article_approval_flows.current_step = approval_workflow_steps.step_order") + query = query.Where("approval_workflow_steps.required_user_level_id = ?", userLevelId) + query = query.Where("article_approval_flows.status_id IN (1, 4)") // pending or revision_requested + query = query.Where("article_approval_flows.current_step > 0") // exclude completed flows (current_step = 0) + + // Debug logging + _i.Log.Info(). + Interface("clientId", clientId). + Uint("userLevelId", userLevelId). + Msg("Getting pending approvals for user level") + + // Apply filters + if categoryId, ok := filters["category_id"]; ok { + query = query.Joins("JOIN articles ON article_approval_flows.article_id = articles.id") + query = query.Where("articles.category_id = ?", categoryId) + } + + if typeId, ok := filters["type_id"]; ok { + query = query.Joins("JOIN articles ON article_approval_flows.article_id = articles.id") + query = query.Where("articles.type_id = ?", typeId) + } + + if search, ok := filters["search"]; ok { + query = query.Joins("JOIN articles ON article_approval_flows.article_id = articles.id") + query = query.Where("articles.title ILIKE ? OR articles.description ILIKE ?", fmt.Sprintf("%%%s%%", search), fmt.Sprintf("%%%s%%", search)) + } + + query = query.Preload("Article").Preload("Workflow").Preload("SubmittedBy").Preload("StepLogs") + query = query.Order("article_approval_flows.submitted_at ASC") + + err = query.Count(&count).Error + if err != nil { + return nil, paginator.Pagination{}, err + } + + // Debug logging + _i.Log.Info(). + Int64("count", count). + Msg("Found pending approvals count") + + offset := (page - 1) * limit + err = query.Offset(offset).Limit(limit).Find(&flows).Error + if err != nil { + return nil, paginator.Pagination{}, err + } + + // Debug logging + _i.Log.Info(). + Int("flowsCount", len(flows)). + Msg("Retrieved flows from database") + + paging = paginator.Pagination{ + Page: page, + Limit: limit, + Count: int64(count), + TotalPage: int((count + int64(limit) - 1) / int64(limit)), + } + + return flows, paging, nil +} + +func (_i *articleApprovalFlowsRepository) GetMyApprovalQueue(clientId *uuid.UUID, userLevelId uint, page, limit int, includePreview bool, urgentOnly bool) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error) { + filters := make(map[string]interface{}) + if urgentOnly { + // Add logic for urgent articles (e.g., submitted more than 24 hours ago) + filters["urgent"] = true + } + + return _i.GetPendingApprovals(clientId, userLevelId, page, limit, filters) +} + +// Statistics Methods +func (_i *articleApprovalFlowsRepository) GetPendingCountByLevel(clientId *uuid.UUID, userLevelId uint) (count int64, err error) { + query := _i.DB.DB.Model(&entity.ArticleApprovalFlows{}) + + if clientId != nil { + query = query.Where("article_approval_flows.client_id = ?", clientId) + } + + query = query.Joins("JOIN approval_workflows ON article_approval_flows.workflow_id = approval_workflows.id") + query = query.Joins("JOIN approval_workflow_steps ON approval_workflows.id = approval_workflow_steps.workflow_id AND article_approval_flows.current_step = approval_workflow_steps.step_order") + query = query.Where("approval_workflow_steps.required_user_level_id = ?", userLevelId) + query = query.Where("article_approval_flows.status_id IN (1, 4)") // pending or revision_requested + + err = query.Count(&count).Error + return count, err +} + +func (_i *articleApprovalFlowsRepository) GetOverdueCountByLevel(clientId *uuid.UUID, userLevelId uint) (count int64, err error) { + query := _i.DB.DB.Model(&entity.ArticleApprovalFlows{}) + + if clientId != nil { + query = query.Where("article_approval_flows.client_id = ?", clientId) + } + + query = query.Joins("JOIN approval_workflows ON article_approval_flows.workflow_id = approval_workflows.id") + query = query.Joins("JOIN approval_workflow_steps ON approval_workflows.id = approval_workflow_steps.workflow_id AND article_approval_flows.current_step = approval_workflow_steps.step_order") + query = query.Where("approval_workflow_steps.required_user_level_id = ?", userLevelId) + query = query.Where("article_approval_flows.status_id IN (1, 4)") // pending or revision_requested + query = query.Where("article_approval_flows.submitted_at < ?", time.Now().Add(-24*time.Hour)) // overdue after 24 hours + + err = query.Count(&count).Error + return count, err +} + +func (_i *articleApprovalFlowsRepository) GetApprovedCountByPeriod(clientId *uuid.UUID, userLevelId uint, startDate, endDate time.Time) (count int64, err error) { + query := _i.DB.DB.Model(&entity.ArticleApprovalStepLogs{}) + + if clientId != nil { + query = query.Where("article_approval_step_logs.client_id = ?", clientId) + } + + query = query.Where("user_level_id = ? AND action = 'approve' AND processed_at BETWEEN ? AND ?", userLevelId, startDate, endDate) + + err = query.Count(&count).Error + return count, err +} + +func (_i *articleApprovalFlowsRepository) GetRejectedCountByPeriod(clientId *uuid.UUID, userLevelId uint, startDate, endDate time.Time) (count int64, err error) { + query := _i.DB.DB.Model(&entity.ArticleApprovalStepLogs{}) + + if clientId != nil { + query = query.Where("article_approval_step_logs.client_id = ?", clientId) + } + + query = query.Where("user_level_id = ? AND action = 'reject' AND processed_at BETWEEN ? AND ?", userLevelId, startDate, endDate) + + err = query.Count(&count).Error + return count, err +} + +func (_i *articleApprovalFlowsRepository) GetRevisionRequestCountByPeriod(clientId *uuid.UUID, userLevelId uint, startDate, endDate time.Time) (count int64, err error) { + query := _i.DB.DB.Model(&entity.ArticleApprovalStepLogs{}) + + if clientId != nil { + query = query.Where("article_approval_step_logs.client_id = ?", clientId) + } + + query = query.Where("user_level_id = ? AND action = 'request_revision' AND processed_at BETWEEN ? AND ?", userLevelId, startDate, endDate) + + err = query.Count(&count).Error + return count, err +} + +// Workload Methods (simplified implementations) +func (_i *articleApprovalFlowsRepository) GetLevelWorkload(clientId *uuid.UUID, userLevelId uint) (workload map[string]interface{}, err error) { + workload = make(map[string]interface{}) + + // Get pending count + pendingCount, err := _i.GetPendingCountByLevel(clientId, userLevelId) + if err != nil { + return nil, err + } + + workload["pending_count"] = pendingCount + workload["level_id"] = userLevelId + + return workload, nil +} + +func (_i *articleApprovalFlowsRepository) GetTeamWorkloadComparison(clientId *uuid.UUID, userLevelId uint) (comparison []map[string]interface{}, err error) { + // This would require more complex queries to get team members at the same level + // For now, return empty slice + comparison = make([]map[string]interface{}, 0) + return comparison, nil +} + +func (_i *articleApprovalFlowsRepository) GetWorkflowBottlenecks(clientId *uuid.UUID) (bottlenecks []map[string]interface{}, err error) { + // This would require complex analysis of workflow steps + // For now, return empty slice + bottlenecks = make([]map[string]interface{}, 0) + return bottlenecks, nil +} + +// FindByArticleId finds an approval flow by article ID for a specific client +func (_i *articleApprovalFlowsRepository) FindByArticleId(clientId *uuid.UUID, articleId uint) (flow *entity.ArticleApprovalFlows, err error) { + query := _i.DB.DB.Model(&entity.ArticleApprovalFlows{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("article_id = ?", articleId) + query = query.Where("status_id IN (1, 2, 3, 4)") // active flows (pending, approved, rejected, revision_requested) + query = query.Order("created_at DESC") + + err = query.First(&flow).Error + return flow, err +} + +// GetPendingApprovalsByUserLevel gets pending approvals for a specific user level with pagination +func (_i *articleApprovalFlowsRepository) GetPendingApprovalsByUserLevel(clientId *uuid.UUID, userLevelId uint, page, limit int) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ArticleApprovalFlows{}) + + if clientId != nil { + query = query.Where("article_approval_flows.client_id = ?", clientId) + } + + // Join with workflow steps to get approvals for this user level + query = query.Joins("JOIN approval_workflows ON article_approval_flows.workflow_id = approval_workflows.id") + query = query.Joins("JOIN approval_workflow_steps ON approval_workflows.id = approval_workflow_steps.workflow_id AND article_approval_flows.current_step = approval_workflow_steps.step_order") + query = query.Where("approval_workflow_steps.required_user_level_id = ?", userLevelId) + query = query.Where("article_approval_flows.status_id IN (1, 4)") // pending or revision_requested + query = query.Where("article_approval_flows.current_step > 0") // exclude completed flows (current_step = 0) + + // Preload related data + query = query.Preload("Article").Preload("Workflow") + + // Count total + err = query.Count(&count).Error + if err != nil { + return nil, paginator.Pagination{}, err + } + + // Apply pagination + offset := (page - 1) * limit + query = query.Offset(offset).Limit(limit) + query = query.Order("article_approval_flows.created_at ASC") // oldest first + + err = query.Find(&flows).Error + if err != nil { + return nil, paginator.Pagination{}, err + } + + // Create pagination response + paging = paginator.Pagination{ + Page: page, + Limit: limit, + Count: count, + Offset: offset, + } + + return flows, paging, nil +} diff --git a/app/module/article_approval_flows/request/article_approval_flows.request.go b/app/module/article_approval_flows/request/article_approval_flows.request.go new file mode 100644 index 0000000..8df240e --- /dev/null +++ b/app/module/article_approval_flows/request/article_approval_flows.request.go @@ -0,0 +1,115 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/utils/paginator" +) + +type ArticleApprovalFlowsGeneric interface { + ToEntity() +} + +type ArticleApprovalFlowsQueryRequest struct { + ArticleId *int `json:"articleId"` + WorkflowId *int `json:"workflowId"` + StatusId *int `json:"statusId"` + SubmittedBy *int `json:"submittedBy"` + CurrentStep *int `json:"currentStep"` + DateFrom *time.Time `json:"dateFrom"` + DateTo *time.Time `json:"dateTo"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type SubmitForApprovalRequest struct { + ArticleId int `json:"articleId" validate:"required"` + WorkflowId *int `json:"workflowId"` +} + +type ApprovalActionRequest struct { + Message string `json:"message"` +} + +type RejectionRequest struct { + Reason string `json:"reason" validate:"required"` +} + +type RevisionRequest struct { + Message string `json:"message" validate:"required"` +} + +type ResubmitRequest struct { + Message string `json:"message"` +} + +type ArticleApprovalFlowsQueryRequestContext struct { + ArticleId string `json:"articleId"` + WorkflowId string `json:"workflowId"` + StatusId string `json:"statusId"` + SubmittedBy string `json:"submittedBy"` + CurrentStep string `json:"currentStep"` + DateFrom string `json:"dateFrom"` + DateTo string `json:"dateTo"` +} + +func (req ArticleApprovalFlowsQueryRequestContext) ToParamRequest() ArticleApprovalFlowsQueryRequest { + var articleId *int + var workflowId *int + var statusId *int + var submittedBy *int + var currentStep *int + var dateFrom *time.Time + var dateTo *time.Time + + if req.ArticleId != "" { + if parsedArticleId, err := strconv.Atoi(req.ArticleId); err == nil { + articleId = &parsedArticleId + } + } + + if req.WorkflowId != "" { + if parsedWorkflowId, err := strconv.Atoi(req.WorkflowId); err == nil { + workflowId = &parsedWorkflowId + } + } + + if req.StatusId != "" { + if parsedStatusId, err := strconv.Atoi(req.StatusId); err == nil { + statusId = &parsedStatusId + } + } + + if req.SubmittedBy != "" { + if parsedSubmittedBy, err := strconv.Atoi(req.SubmittedBy); err == nil { + submittedBy = &parsedSubmittedBy + } + } + + if req.CurrentStep != "" { + if parsedCurrentStep, err := strconv.Atoi(req.CurrentStep); err == nil { + currentStep = &parsedCurrentStep + } + } + + if req.DateFrom != "" { + if parsedDateFrom, err := time.Parse("2006-01-02", req.DateFrom); err == nil { + dateFrom = &parsedDateFrom + } + } + + if req.DateTo != "" { + if parsedDateTo, err := time.Parse("2006-01-02", req.DateTo); err == nil { + dateTo = &parsedDateTo + } + } + + return ArticleApprovalFlowsQueryRequest{ + ArticleId: articleId, + WorkflowId: workflowId, + StatusId: statusId, + SubmittedBy: submittedBy, + CurrentStep: currentStep, + DateFrom: dateFrom, + DateTo: dateTo, + } +} \ No newline at end of file diff --git a/app/module/article_approval_flows/response/article_approval_flows.response.go b/app/module/article_approval_flows/response/article_approval_flows.response.go new file mode 100644 index 0000000..e0e620d --- /dev/null +++ b/app/module/article_approval_flows/response/article_approval_flows.response.go @@ -0,0 +1,111 @@ +package response + +import ( + "time" + articlesResponse "web-qudo-be/app/module/articles/response" + approvalWorkflowsResponse "web-qudo-be/app/module/approval_workflows/response" + articleApprovalStepLogsResponse "web-qudo-be/app/module/article_approval_step_logs/response" +) + +type ArticleApprovalFlowsResponse struct { + ID uint `json:"id"` + ArticleID uint `json:"articleId"` + WorkflowID uint `json:"workflowId"` + CurrentStep int `json:"currentStep"` + Status string `json:"status"` + SubmittedBy uint `json:"submittedBy"` + SubmittedAt time.Time `json:"submittedAt"` + CompletedAt *time.Time `json:"completedAt"` + RejectedAt *time.Time `json:"rejectedAt"` + RejectionReason *string `json:"rejectionReason"` + RevisionRequestedAt *time.Time `json:"revisionRequestedAt"` + RevisionReason *string `json:"revisionReason"` + ResubmittedAt *time.Time `json:"resubmittedAt"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + // Relations + Article *articlesResponse.ArticlesResponse `json:"article,omitempty"` + Workflow *approvalWorkflowsResponse.ApprovalWorkflowsResponse `json:"workflow,omitempty"` + StepLogs []*articleApprovalStepLogsResponse.ArticleApprovalStepLogsResponse `json:"stepLogs,omitempty"` +} + +type ArticleApprovalFlowsDetailResponse struct { + ID uint `json:"id"` + ArticleID uint `json:"articleId"` + WorkflowID uint `json:"workflowId"` + CurrentStep int `json:"currentStep"` + Status string `json:"status"` + SubmittedBy uint `json:"submittedBy"` + SubmittedByName string `json:"submittedByName"` + SubmittedAt time.Time `json:"submittedAt"` + CompletedAt *time.Time `json:"completedAt"` + RejectedAt *time.Time `json:"rejectedAt"` + RejectionReason *string `json:"rejectionReason"` + RevisionRequestedAt *time.Time `json:"revisionRequestedAt"` + RevisionReason *string `json:"revisionReason"` + ResubmittedAt *time.Time `json:"resubmittedAt"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + // Relations with full details + Article *articlesResponse.ArticlesResponse `json:"article"` + Workflow *approvalWorkflowsResponse.ApprovalWorkflowsWithStepsResponse `json:"workflow"` + StepLogs []*articleApprovalStepLogsResponse.ArticleApprovalStepLogsResponse `json:"stepLogs"` +} + +type ArticleApprovalFlowsSummaryResponse struct { + ID uint `json:"id"` + ArticleID uint `json:"articleId"` + ArticleTitle string `json:"articleTitle"` + WorkflowName string `json:"workflowName"` + CurrentStep int `json:"currentStep"` + TotalSteps int `json:"totalSteps"` + Status string `json:"status"` + SubmittedBy uint `json:"submittedBy"` + SubmittedByName string `json:"submittedByName"` + SubmittedAt time.Time `json:"submittedAt"` + DaysInApproval int `json:"daysInApproval"` +} + +type ApprovalQueueResponse struct { + ID uint `json:"id"` + ArticleID uint `json:"articleId"` + ArticleTitle string `json:"articleTitle"` + ArticleAuthor string `json:"articleAuthor"` + WorkflowName string `json:"workflowName"` + CurrentStepName string `json:"currentStepName"` + SubmittedAt time.Time `json:"submittedAt"` + DaysWaiting int `json:"daysWaiting"` + Priority string `json:"priority"` + RequiresComment bool `json:"requiresComment"` +} + +type ApprovalDashboardStatsResponse struct { + PendingApprovals int `json:"pendingApprovals"` + MyPendingApprovals int `json:"myPendingApprovals"` + ApprovedToday int `json:"approvedToday"` + RejectedToday int `json:"rejectedToday"` + RevisionRequested int `json:"revisionRequested"` + OverdueApprovals int `json:"overdueApprovals"` + AverageApprovalTime int `json:"averageApprovalTime"` // in hours +} + +type ApprovalWorkloadStatsResponse struct { + ApproverID uint `json:"approverId"` + ApproverName string `json:"approverName"` + PendingCount int `json:"pendingCount"` + ApprovedThisWeek int `json:"approvedThisWeek"` + RejectedThisWeek int `json:"rejectedThisWeek"` + AverageResponseTime int `json:"averageResponseTime"` // in hours +} + +type ApprovalAnalyticsResponse struct { + Period string `json:"period"` + TotalSubmissions int `json:"totalSubmissions"` + TotalApproved int `json:"totalApproved"` + TotalRejected int `json:"totalRejected"` + TotalRevisions int `json:"totalRevisions"` + ApprovalRate float64 `json:"approvalRate"` + AverageApprovalTime float64 `json:"averageApprovalTime"` // in hours +} \ No newline at end of file diff --git a/app/module/article_approval_flows/service/article_approval_flows.service.go b/app/module/article_approval_flows/service/article_approval_flows.service.go new file mode 100644 index 0000000..45403e6 --- /dev/null +++ b/app/module/article_approval_flows/service/article_approval_flows.service.go @@ -0,0 +1,788 @@ +package service + +import ( + "errors" + "time" + "web-qudo-be/app/database/entity" + approvalWorkflowStepsRepo "web-qudo-be/app/module/approval_workflow_steps/repository" + approvalWorkflowsRepo "web-qudo-be/app/module/approval_workflows/repository" + "web-qudo-be/app/module/article_approval_flows/repository" + "web-qudo-be/app/module/article_approval_flows/request" + approvalStepLogsRepo "web-qudo-be/app/module/article_approval_step_logs/repository" + articlesRepo "web-qudo-be/app/module/articles/repository" + usersRepo "web-qudo-be/app/module/users/repository" + "web-qudo-be/utils/paginator" + + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +type articleApprovalFlowsService struct { + ArticleApprovalFlowsRepository repository.ArticleApprovalFlowsRepository + ApprovalWorkflowsRepository approvalWorkflowsRepo.ApprovalWorkflowsRepository + ApprovalWorkflowStepsRepository approvalWorkflowStepsRepo.ApprovalWorkflowStepsRepository + ArticleApprovalStepLogsRepository approvalStepLogsRepo.ArticleApprovalStepLogsRepository + ArticlesRepository articlesRepo.ArticlesRepository + UsersRepository usersRepo.UsersRepository + Log zerolog.Logger +} + +// ArticleApprovalFlowsService define interface of IArticleApprovalFlowsService +type ArticleApprovalFlowsService interface { + // Basic CRUD + GetAll(clientId *uuid.UUID, req request.ArticleApprovalFlowsQueryRequest) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (flow *entity.ArticleApprovalFlows, err error) + Create(clientId *uuid.UUID, flow *entity.ArticleApprovalFlows) (flowReturn *entity.ArticleApprovalFlows, err error) + Update(id uint, flow *entity.ArticleApprovalFlows) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) + + // Article submission and approval workflow + SubmitArticleForApproval(clientId *uuid.UUID, articleId uint, submittedById uint, workflowId *uint) (flow *entity.ArticleApprovalFlows, err error) + ApproveStep(clientId *uuid.UUID, flowId uint, approvedById uint, message string) (err error) + RejectArticle(clientId *uuid.UUID, flowId uint, rejectedById uint, reason string) (err error) + RequestRevision(clientId *uuid.UUID, flowId uint, requestedById uint, revisionMessage string) (err error) + ResubmitAfterRevision(clientId *uuid.UUID, flowId uint, resubmittedById uint) (err error) + + // Dashboard and queue methods + GetPendingApprovals(clientId *uuid.UUID, userLevelId uint, page, limit int, filters map[string]interface{}) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error) + GetMyApprovalQueue(clientId *uuid.UUID, userLevelId uint, page, limit int, includePreview bool, urgentOnly bool) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error) + GetApprovalHistory(clientId *uuid.UUID, articleId uint, page, limit int) (logs []*entity.ArticleApprovalStepLogs, paging paginator.Pagination, err error) + + // Statistics and analytics + GetPendingCountByLevel(clientId *uuid.UUID, userLevelId uint) (count int64, err error) + GetOverdueCountByLevel(clientId *uuid.UUID, userLevelId uint) (count int64, err error) + GetApprovalStatistics(clientId *uuid.UUID, userLevelId uint, startDate, endDate time.Time) (stats map[string]interface{}, err error) + GetWorkloadAnalytics(clientId *uuid.UUID, userLevelId uint) (analytics map[string]interface{}, err error) + + // Workflow management + CanUserApproveStep(clientId *uuid.UUID, flowId uint, userId uint, userLevelId uint) (canApprove bool, reason string, err error) + GetCurrentStepInfo(clientId *uuid.UUID, flowId uint) (stepInfo map[string]interface{}, err error) + GetNextStepPreview(clientId *uuid.UUID, flowId uint) (nextStep *entity.ApprovalWorkflowSteps, err error) +} + +func NewArticleApprovalFlowsService( + articleApprovalFlowsRepository repository.ArticleApprovalFlowsRepository, + approvalWorkflowsRepository approvalWorkflowsRepo.ApprovalWorkflowsRepository, + approvalWorkflowStepsRepository approvalWorkflowStepsRepo.ApprovalWorkflowStepsRepository, + articleApprovalStepLogsRepository approvalStepLogsRepo.ArticleApprovalStepLogsRepository, + articlesRepository articlesRepo.ArticlesRepository, + usersRepository usersRepo.UsersRepository, + log zerolog.Logger, +) ArticleApprovalFlowsService { + return &articleApprovalFlowsService{ + ArticleApprovalFlowsRepository: articleApprovalFlowsRepository, + ApprovalWorkflowsRepository: approvalWorkflowsRepository, + ApprovalWorkflowStepsRepository: approvalWorkflowStepsRepository, + ArticleApprovalStepLogsRepository: articleApprovalStepLogsRepository, + ArticlesRepository: articlesRepository, + UsersRepository: usersRepository, + Log: log, + } +} + +// Basic CRUD implementations +func (_i *articleApprovalFlowsService) GetAll(clientId *uuid.UUID, req request.ArticleApprovalFlowsQueryRequest) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error) { + return _i.ArticleApprovalFlowsRepository.GetAll(clientId, req) +} +func (_i *articleApprovalFlowsService) FindOne(clientId *uuid.UUID, id uint) (flow *entity.ArticleApprovalFlows, err error) { + return _i.ArticleApprovalFlowsRepository.FindOne(clientId, id) +} + +func (_i *articleApprovalFlowsService) Create(clientId *uuid.UUID, flow *entity.ArticleApprovalFlows) (flowReturn *entity.ArticleApprovalFlows, err error) { + return _i.ArticleApprovalFlowsRepository.Create(clientId, flow) +} + +func (_i *articleApprovalFlowsService) Update(id uint, flow *entity.ArticleApprovalFlows) (err error) { + return _i.ArticleApprovalFlowsRepository.Update(id, flow) +} + +func (_i *articleApprovalFlowsService) Delete(clientId *uuid.UUID, id uint) (err error) { + return _i.ArticleApprovalFlowsRepository.Delete(clientId, id) +} + +// Article submission and approval workflow +func (_i *articleApprovalFlowsService) SubmitArticleForApproval(clientId *uuid.UUID, articleId uint, submittedById uint, workflowId *uint) (flow *entity.ArticleApprovalFlows, err error) { + // Check if article already has an active approval flow + existingFlow, err := _i.ArticleApprovalFlowsRepository.FindActiveByArticleId(articleId) + if err == nil && existingFlow != nil { + return nil, errors.New("article already has an active approval flow") + } + + // Get workflow (use default if not specified) + var workflow *entity.ApprovalWorkflows + if workflowId != nil { + workflow, err = _i.ApprovalWorkflowsRepository.FindOne(clientId, *workflowId) + } else { + workflow, err = _i.ApprovalWorkflowsRepository.FindDefault(clientId) + } + + if err != nil { + return nil, err + } + + if workflow == nil { + return nil, errors.New("no workflow found") + } + + if workflow.IsActive != nil && !*workflow.IsActive { + return nil, errors.New("workflow is not active") + } + + // Get first step of workflow + firstStep, err := _i.ApprovalWorkflowStepsRepository.FindByWorkflowAndStep(clientId, workflow.ID, 1) + if err != nil { + return nil, err + } + + if firstStep == nil { + return nil, errors.New("workflow has no steps") + } + + // Create approval flow + flow = &entity.ArticleApprovalFlows{ + ArticleId: articleId, + WorkflowId: workflow.ID, + CurrentStep: 1, + StatusId: 1, // pending + SubmittedById: submittedById, + SubmittedAt: time.Now(), + } + + flow, err = _i.ArticleApprovalFlowsRepository.Create(clientId, flow) + if err != nil { + return nil, err + } + + // Get current article data first + currentArticle, err := _i.ArticlesRepository.FindOne(clientId, articleId) + if err != nil { + return nil, err + } + + // Update only the necessary fields + currentArticle.WorkflowId = &workflow.ID + currentArticle.CurrentApprovalStep = &flow.CurrentStep + currentArticle.StatusId = &[]int{1}[0] // pending approval + + err = _i.ArticlesRepository.UpdateSkipNull(clientId, articleId, currentArticle) + if err != nil { + return nil, err + } + + // Process auto-skip logic based on user level + err = _i.processAutoSkipSteps(clientId, flow, submittedById) + if err != nil { + return nil, err + } + + 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.CurrentStep = 0 // Set to 0 to indicate completion + 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 = &[]int{0}[0] // Set to 0 to indicate completion + + 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) { + // Get approval flow + flow, err := _i.ArticleApprovalFlowsRepository.FindOne(clientId, flowId) + if err != nil { + return err + } + + if flow == nil { + return errors.New("approval flow not found") + } + + if flow.StatusId != 1 && flow.StatusId != 4 { // not pending or revision_requested + return errors.New("approval flow is not in pending state") + } + + // Get current step + currentStep, err := _i.ApprovalWorkflowStepsRepository.FindByWorkflowAndStep(clientId, flow.WorkflowId, flow.CurrentStep) + if err != nil { + return err + } + + if currentStep == nil { + return errors.New("current step not found") + } + + // Create step log + stepLog := &entity.ArticleApprovalStepLogs{ + ApprovalFlowId: flow.ID, + StepOrder: flow.CurrentStep, + StepName: currentStep.StepName, + ApprovedById: &approvedById, + Action: "approve", + Message: &message, + ProcessedAt: time.Now(), + UserLevelId: currentStep.RequiredUserLevelId, + } + + _, err = _i.ArticleApprovalStepLogsRepository.Create(clientId, stepLog) + if err != nil { + return err + } + + // Check if there's a next step + nextStep, err := _i.ApprovalWorkflowStepsRepository.GetNextStep(clientId, flow.WorkflowId, flow.CurrentStep) + if err != nil && err.Error() != "record not found" { + return err + } + + if nextStep == nil || nextStep.ID == 0 { + // No next step - approval complete + flowUpdate := &entity.ArticleApprovalFlows{ + StatusId: 2, // approved + CurrentStep: 0, // Set to 0 to indicate completion + CompletedAt: &[]time.Time{time.Now()}[0], + } + + // Debug logging + _i.Log.Info(). + Interface("flowUpdate :: ", flowUpdate). + Msg("Retrieved next step from database") + + err = _i.ArticleApprovalFlowsRepository.Update(flowId, flowUpdate) + 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 = &[]int{0}[0] // Set to 0 to indicate completion + currentArticle.IsPublish = &[]bool{true}[0] // Set to true to indicate publication + currentArticle.IsDraft = &[]bool{false}[0] // Set to false to indicate publication + + err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle) + if err != nil { + return err + } + } else { + // Move to next step + flowUpdate := &entity.ArticleApprovalFlows{ + CurrentStep: nextStep.StepOrder, + StatusId: 1, // pending + } + + err = _i.ArticleApprovalFlowsRepository.Update(flowId, flowUpdate) + 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 = &nextStep.StepOrder + + err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle) + if err != nil { + return err + } + } + + return nil +} + +func (_i *articleApprovalFlowsService) RejectArticle(clientId *uuid.UUID, flowId uint, rejectedById uint, reason string) (err error) { + // Get approval flow + flow, err := _i.ArticleApprovalFlowsRepository.FindOne(clientId, flowId) + if err != nil { + return err + } + + if flow == nil { + return errors.New("approval flow not found") + } + + if flow.StatusId != 1 && flow.StatusId != 4 { // not pending or revision_requested + return errors.New("approval flow is not in pending state") + } + + // Get current step + currentStep, err := _i.ApprovalWorkflowStepsRepository.FindByWorkflowAndStep(clientId, flow.WorkflowId, flow.CurrentStep) + if err != nil { + return err + } + + // Create step log + stepLog := &entity.ArticleApprovalStepLogs{ + ApprovalFlowId: flow.ID, + StepOrder: flow.CurrentStep, + StepName: currentStep.StepName, + ApprovedById: &rejectedById, + Action: "reject", + Message: &reason, + ProcessedAt: time.Now(), + UserLevelId: currentStep.RequiredUserLevelId, + } + + _, err = _i.ArticleApprovalStepLogsRepository.Create(clientId, stepLog) + if err != nil { + return err + } + + // Update approval flow status + flowUpdate := &entity.ArticleApprovalFlows{ + StatusId: 3, // rejected + RejectionReason: &reason, + CompletedAt: &[]time.Time{time.Now()}[0], + } + + err = _i.ArticleApprovalFlowsRepository.Update(flowId, flowUpdate) + 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{3}[0] // rejected + currentArticle.CurrentApprovalStep = nil + + err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle) + if err != nil { + return err + } + + return nil +} + +func (_i *articleApprovalFlowsService) RequestRevision(clientId *uuid.UUID, flowId uint, requestedById uint, revisionMessage string) (err error) { + // Get approval flow + flow, err := _i.ArticleApprovalFlowsRepository.FindOne(clientId, flowId) + if err != nil { + return err + } + + if flow == nil { + return errors.New("approval flow not found") + } + + if flow.StatusId != 1 { // not pending + return errors.New("approval flow is not in pending state") + } + + // Get current step + currentStep, err := _i.ApprovalWorkflowStepsRepository.FindByWorkflowAndStep(clientId, flow.WorkflowId, flow.CurrentStep) + if err != nil { + return err + } + + // Create step log + stepLog := &entity.ArticleApprovalStepLogs{ + ApprovalFlowId: flow.ID, + StepOrder: flow.CurrentStep, + StepName: currentStep.StepName, + ApprovedById: &requestedById, + Action: "request_revision", + Message: &revisionMessage, + ProcessedAt: time.Now(), + UserLevelId: currentStep.RequiredUserLevelId, + } + + _, err = _i.ArticleApprovalStepLogsRepository.Create(clientId, stepLog) + if err != nil { + return err + } + + // Update approval flow status + flowUpdate := &entity.ArticleApprovalFlows{ + StatusId: 4, // revision_requested + RevisionRequested: &[]bool{true}[0], + RevisionMessage: &revisionMessage, + } + + err = _i.ArticleApprovalFlowsRepository.Update(flowId, flowUpdate) + 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{4}[0] // revision_requested + + err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle) + if err != nil { + return err + } + + return nil +} + +func (_i *articleApprovalFlowsService) ResubmitAfterRevision(clientId *uuid.UUID, flowId uint, resubmittedById uint) (err error) { + // Get approval flow + flow, err := _i.ArticleApprovalFlowsRepository.FindOne(clientId, flowId) + if err != nil { + return err + } + + if flow == nil { + return errors.New("approval flow not found") + } + + if flow.StatusId != 4 { // not revision_requested + return errors.New("approval flow is not in revision requested state") + } + + // Reset approval flow to pending + flowUpdate := &entity.ArticleApprovalFlows{ + StatusId: 1, // pending + RevisionRequested: &[]bool{false}[0], + RevisionMessage: nil, + CurrentStep: 1, // restart from first step + } + + err = _i.ArticleApprovalFlowsRepository.Update(flowId, flowUpdate) + 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{1}[0] // pending approval + currentArticle.CurrentApprovalStep = &[]int{1}[0] + + err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle) + if err != nil { + return err + } + + // Create resubmission log + stepLog := &entity.ArticleApprovalStepLogs{ + ApprovalFlowId: flow.ID, + StepOrder: 1, + StepName: "Resubmission", + ApprovedById: &resubmittedById, + Action: "resubmit", + Message: &[]string{"Article resubmitted after revision"}[0], + ProcessedAt: time.Now(), + } + + _, err = _i.ArticleApprovalStepLogsRepository.Create(clientId, stepLog) + if err != nil { + return err + } + + return nil +} + +// Dashboard and queue methods +func (_i *articleApprovalFlowsService) GetPendingApprovals(clientId *uuid.UUID, userLevelId uint, page, limit int, filters map[string]interface{}) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error) { + return _i.ArticleApprovalFlowsRepository.GetPendingApprovals(clientId, userLevelId, page, limit, filters) +} + +func (_i *articleApprovalFlowsService) GetMyApprovalQueue(clientId *uuid.UUID, userLevelId uint, page, limit int, includePreview bool, urgentOnly bool) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error) { + return _i.ArticleApprovalFlowsRepository.GetMyApprovalQueue(clientId, userLevelId, page, limit, includePreview, urgentOnly) +} + +func (_i *articleApprovalFlowsService) GetApprovalHistory(clientId *uuid.UUID, articleId uint, page, limit int) (logs []*entity.ArticleApprovalStepLogs, paging paginator.Pagination, err error) { + return _i.ArticleApprovalStepLogsRepository.GetApprovalHistory(clientId, articleId, page, limit) +} + +// Statistics and analytics +func (_i *articleApprovalFlowsService) GetPendingCountByLevel(clientId *uuid.UUID, userLevelId uint) (count int64, err error) { + return _i.ArticleApprovalFlowsRepository.GetPendingCountByLevel(clientId, userLevelId) +} + +func (_i *articleApprovalFlowsService) GetOverdueCountByLevel(clientId *uuid.UUID, userLevelId uint) (count int64, err error) { + return _i.ArticleApprovalFlowsRepository.GetOverdueCountByLevel(clientId, userLevelId) +} + +func (_i *articleApprovalFlowsService) GetApprovalStatistics(clientId *uuid.UUID, userLevelId uint, startDate, endDate time.Time) (stats map[string]interface{}, err error) { + stats = make(map[string]interface{}) + + // Get approved count + approvedCount, err := _i.ArticleApprovalFlowsRepository.GetApprovedCountByPeriod(clientId, userLevelId, startDate, endDate) + if err != nil { + return nil, err + } + + // Get rejected count + rejectedCount, err := _i.ArticleApprovalFlowsRepository.GetRejectedCountByPeriod(clientId, userLevelId, startDate, endDate) + if err != nil { + return nil, err + } + + // Get revision request count + revisionCount, err := _i.ArticleApprovalFlowsRepository.GetRevisionRequestCountByPeriod(clientId, userLevelId, startDate, endDate) + if err != nil { + return nil, err + } + + stats["approved_count"] = approvedCount + stats["rejected_count"] = rejectedCount + stats["revision_requested_count"] = revisionCount + stats["total_processed"] = approvedCount + rejectedCount + revisionCount + stats["period_start"] = startDate + stats["period_end"] = endDate + + return stats, nil +} + +func (_i *articleApprovalFlowsService) GetWorkloadAnalytics(clientId *uuid.UUID, userLevelId uint) (analytics map[string]interface{}, err error) { + return _i.ArticleApprovalFlowsRepository.GetLevelWorkload(clientId, userLevelId) +} + +// Workflow management +func (_i *articleApprovalFlowsService) CanUserApproveStep(clientId *uuid.UUID, flowId uint, userId uint, userLevelId uint) (canApprove bool, reason string, err error) { + // Get approval flow + flow, err := _i.ArticleApprovalFlowsRepository.FindOne(clientId, flowId) + if err != nil { + return false, "", err + } + + if flow == nil { + return false, "approval flow not found", nil + } + + if flow.StatusId != 1 && flow.StatusId != 4 { // not pending or revision_requested + return false, "approval flow is not in pending state", nil + } + + // Get current step + currentStep, err := _i.ApprovalWorkflowStepsRepository.FindByWorkflowAndStep(clientId, flow.WorkflowId, flow.CurrentStep) + if err != nil { + return false, "", err + } + + if currentStep == nil { + return false, "current step not found", nil + } + + // Check if user level matches required level + if currentStep.RequiredUserLevelId != userLevelId { + return false, "user level does not match required level for this step", nil + } + + // Check if user submitted the article (cannot approve own submission) + if flow.SubmittedById == userId { + return false, "cannot approve own submission", nil + } + + return true, "", nil +} + +func (_i *articleApprovalFlowsService) GetCurrentStepInfo(clientId *uuid.UUID, flowId uint) (stepInfo map[string]interface{}, err error) { + stepInfo = make(map[string]interface{}) + + // Get approval flow + flow, err := _i.ArticleApprovalFlowsRepository.FindOne(clientId, flowId) + if err != nil { + return nil, err + } + + if flow == nil { + return nil, errors.New("approval flow not found") + } + + // Get current step + currentStep, err := _i.ApprovalWorkflowStepsRepository.FindByWorkflowAndStep(clientId, flow.WorkflowId, flow.CurrentStep) + if err != nil { + return nil, err + } + + stepInfo["current_step"] = flow.CurrentStep + stepInfo["step_name"] = currentStep.StepName + stepInfo["required_user_level_id"] = currentStep.RequiredUserLevelId + stepInfo["can_skip"] = currentStep.CanSkip + stepInfo["auto_approve_after_hours"] = currentStep.AutoApproveAfterHours + stepInfo["status"] = flow.StatusId + + return stepInfo, nil +} + +func (_i *articleApprovalFlowsService) GetNextStepPreview(clientId *uuid.UUID, flowId uint) (nextStep *entity.ApprovalWorkflowSteps, err error) { + // Get approval flow + flow, err := _i.ArticleApprovalFlowsRepository.FindOne(clientId, flowId) + if err != nil { + return nil, err + } + + if flow == nil { + return nil, errors.New("approval flow not found") + } + + // Get next step + nextStep, err = _i.ApprovalWorkflowStepsRepository.GetNextStep(clientId, flow.WorkflowId, flow.CurrentStep) + if err != nil { + return nil, err + } + + return nextStep, nil +} diff --git a/app/module/article_approval_step_logs/article_approval_step_logs.module.go b/app/module/article_approval_step_logs/article_approval_step_logs.module.go new file mode 100644 index 0000000..3a604ee --- /dev/null +++ b/app/module/article_approval_step_logs/article_approval_step_logs.module.go @@ -0,0 +1,71 @@ +package article_approval_step_logs + +import ( + "web-qudo-be/app/module/article_approval_step_logs/controller" + "web-qudo-be/app/module/article_approval_step_logs/repository" + "web-qudo-be/app/module/article_approval_step_logs/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// ArticleApprovalStepLogsRouter struct of ArticleApprovalStepLogsRouter +type ArticleApprovalStepLogsRouter struct { + App fiber.Router + Controller controller.ArticleApprovalStepLogsController +} + +// NewArticleApprovalStepLogsModule register bulky of ArticleApprovalStepLogs module +var NewArticleApprovalStepLogsModule = fx.Options( + // register repository of ArticleApprovalStepLogs module + fx.Provide(repository.NewArticleApprovalStepLogsRepository), + + // register service of ArticleApprovalStepLogs module + fx.Provide(service.NewArticleApprovalStepLogsService), + + // register controller of ArticleApprovalStepLogs module + fx.Provide(controller.NewArticleApprovalStepLogsController), + + // register router of ArticleApprovalStepLogs module + fx.Provide(NewArticleApprovalStepLogsRouter), +) + +// NewArticleApprovalStepLogsRouter init ArticleApprovalStepLogsRouter +func NewArticleApprovalStepLogsRouter(fiber *fiber.App, controller controller.ArticleApprovalStepLogsController) *ArticleApprovalStepLogsRouter { + return &ArticleApprovalStepLogsRouter{ + App: fiber, + Controller: controller, + } +} + +// RegisterArticleApprovalStepLogsRoutes register routes of ArticleApprovalStepLogs module +func (_i *ArticleApprovalStepLogsRouter) RegisterArticleApprovalStepLogsRoutes() { + // define controllers + articleApprovalStepLogsController := _i.Controller + + // define routes + _i.App.Route("/article-approval-step-logs", func(router fiber.Router) { + router.Get("/", articleApprovalStepLogsController.All) + router.Get("/:id", articleApprovalStepLogsController.Show) + router.Post("/", articleApprovalStepLogsController.Save) + router.Put("/:id", articleApprovalStepLogsController.Update) + router.Delete("/:id", articleApprovalStepLogsController.Delete) + + // Approval process management routes + router.Get("/flow/:flow_id", articleApprovalStepLogsController.GetByApprovalFlow) + router.Get("/step/:step_id", articleApprovalStepLogsController.GetByWorkflowStep) + router.Get("/approver/:user_id", articleApprovalStepLogsController.GetByApprover) + router.Get("/pending", articleApprovalStepLogsController.GetPendingApprovals) + router.Get("/overdue", articleApprovalStepLogsController.GetOverdueApprovals) + + // Approval action routes + router.Post("/:id/process", articleApprovalStepLogsController.ProcessApproval) + router.Post("/bulk-process", articleApprovalStepLogsController.BulkProcessApproval) + router.Post("/:id/auto-approve", articleApprovalStepLogsController.AutoApprove) + + // History and analytics routes + router.Get("/history", articleApprovalStepLogsController.GetApprovalHistory) + router.Get("/stats", articleApprovalStepLogsController.GetApprovalStats) + router.Get("/user/:user_id/workload", articleApprovalStepLogsController.GetUserWorkload) + }) +} diff --git a/app/module/article_approval_step_logs/controller/article_approval_step_logs.controller.go b/app/module/article_approval_step_logs/controller/article_approval_step_logs.controller.go new file mode 100644 index 0000000..73634d0 --- /dev/null +++ b/app/module/article_approval_step_logs/controller/article_approval_step_logs.controller.go @@ -0,0 +1,678 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/article_approval_step_logs/request" + "web-qudo-be/app/module/article_approval_step_logs/service" + "web-qudo-be/utils/paginator" + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" +) + +type articleApprovalStepLogsController struct { + articleApprovalStepLogsService service.ArticleApprovalStepLogsService + Log zerolog.Logger +} + +type ArticleApprovalStepLogsController interface { + // Basic CRUD + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + + // Approval process management + GetByApprovalFlow(c *fiber.Ctx) error + GetByWorkflowStep(c *fiber.Ctx) error + GetByApprover(c *fiber.Ctx) error + GetPendingApprovals(c *fiber.Ctx) error + GetOverdueApprovals(c *fiber.Ctx) error + + // Approval actions + ProcessApproval(c *fiber.Ctx) error + BulkProcessApproval(c *fiber.Ctx) error + AutoApprove(c *fiber.Ctx) error + + // History and analytics + GetApprovalHistory(c *fiber.Ctx) error + GetApprovalStats(c *fiber.Ctx) error + GetUserWorkload(c *fiber.Ctx) error +} + +func NewArticleApprovalStepLogsController( + articleApprovalStepLogsService service.ArticleApprovalStepLogsService, + log zerolog.Logger, +) ArticleApprovalStepLogsController { + return &articleApprovalStepLogsController{ + articleApprovalStepLogsService: articleApprovalStepLogsService, + Log: log, + } +} + +// All ArticleApprovalStepLogs +// @Summary Get all ArticleApprovalStepLogs +// @Description API for getting all ArticleApprovalStepLogs +// @Tags ArticleApprovalStepLogs +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param req query request.ArticleApprovalStepLogsQueryRequest false "query parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-step-logs [get] +func (_i *articleApprovalStepLogsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.ArticleApprovalStepLogsQueryRequestContext{ + ApprovalFlowId: c.Query("approvalFlowId"), + StepId: c.Query("stepId"), + ActionById: c.Query("actionById"), + ActionType: c.Query("actionType"), + StatusId: c.Query("statusId"), + ActionDateFrom: c.Query("actionDateFrom"), + ActionDateTo: c.Query("actionDateTo"), + UserLevelId: c.Query("userLevelId"), + IsUrgent: c.Query("isUrgent"), + Search: c.Query("search"), + OrderBy: c.Query("orderBy"), + OrderDirection: c.Query("orderDirection"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + _i.Log.Info().Interface("clientId", clientId).Msg("") + + articleApprovalStepLogsData, paging, err := _i.articleApprovalStepLogsService.GetAll(clientId, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleApprovalStepLogs list successfully retrieved"}, + Data: articleApprovalStepLogsData, + Meta: paging, + }) +} + +// Show ArticleApprovalStepLogs +// @Summary Get one ArticleApprovalStepLogs +// @Description API for getting one ArticleApprovalStepLogs +// @Tags ArticleApprovalStepLogs +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "ArticleApprovalStepLogs ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-step-logs/{id} [get] +func (_i *articleApprovalStepLogsController) Show(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + articleApprovalStepLogsData, err := _i.articleApprovalStepLogsService.FindOne(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleApprovalStepLogs successfully retrieved"}, + Data: articleApprovalStepLogsData, + }) +} + +// Save ArticleApprovalStepLogs +// @Summary Save ArticleApprovalStepLogs +// @Description API for saving ArticleApprovalStepLogs +// @Tags ArticleApprovalStepLogs +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param payload body request.ArticleApprovalStepLogsCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-step-logs [post] +func (_i *articleApprovalStepLogsController) Save(c *fiber.Ctx) error { + req := new(request.ArticleApprovalStepLogsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Convert request to entity + entity := req.ToEntity() + + articleApprovalStepLogsData, err := _i.articleApprovalStepLogsService.Create(clientId, entity) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleApprovalStepLogs successfully created"}, + Data: articleApprovalStepLogsData, + }) +} + +// Update ArticleApprovalStepLogs +// @Summary Update ArticleApprovalStepLogs +// @Description API for updating ArticleApprovalStepLogs +// @Tags ArticleApprovalStepLogs +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "ArticleApprovalStepLogs ID" +// @Param payload body request.ArticleApprovalStepLogsUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-step-logs/{id} [put] +func (_i *articleApprovalStepLogsController) Update(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + req := new(request.ArticleApprovalStepLogsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Convert request to entity + entity := req.ToEntity() + + err = _i.articleApprovalStepLogsService.Update(clientId, uint(id), entity) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleApprovalStepLogs successfully updated"}, + }) +} + +// Delete ArticleApprovalStepLogs +// @Summary Delete ArticleApprovalStepLogs +// @Description API for deleting ArticleApprovalStepLogs +// @Tags ArticleApprovalStepLogs +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "ArticleApprovalStepLogs ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-step-logs/{id} [delete] +func (_i *articleApprovalStepLogsController) Delete(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.articleApprovalStepLogsService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleApprovalStepLogs successfully deleted"}, + }) +} + +// GetByApprovalFlow ArticleApprovalStepLogs +// @Summary Get ArticleApprovalStepLogs by Approval Flow ID +// @Description API for getting ArticleApprovalStepLogs by Approval Flow ID +// @Tags ArticleApprovalStepLogs +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param flow_id path int true "Approval Flow ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-step-logs/flow/{flow_id} [get] +func (_i *articleApprovalStepLogsController) GetByApprovalFlow(c *fiber.Ctx) error { + flowId, err := strconv.Atoi(c.Params("flow_id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid flow ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + articleApprovalStepLogsData, err := _i.articleApprovalStepLogsService.GetByApprovalFlowID(clientId, uint(flowId)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleApprovalStepLogs by approval flow successfully retrieved"}, + Data: articleApprovalStepLogsData, + }) +} + +// GetByWorkflowStep ArticleApprovalStepLogs +// @Summary Get ArticleApprovalStepLogs by Workflow Step ID +// @Description API for getting ArticleApprovalStepLogs by Workflow Step ID +// @Tags ArticleApprovalStepLogs +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param step_id path int true "Workflow Step ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-step-logs/step/{step_id} [get] +func (_i *articleApprovalStepLogsController) GetByWorkflowStep(c *fiber.Ctx) error { + stepId, err := strconv.Atoi(c.Params("step_id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid step ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + articleApprovalStepLogsData, err := _i.articleApprovalStepLogsService.GetByWorkflowStepID(clientId, uint(stepId)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleApprovalStepLogs by workflow step successfully retrieved"}, + Data: articleApprovalStepLogsData, + }) +} + +// GetByApprover ArticleApprovalStepLogs +// @Summary Get ArticleApprovalStepLogs by Approver User ID +// @Description API for getting ArticleApprovalStepLogs by Approver User ID +// @Tags ArticleApprovalStepLogs +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param user_id path int true "Approver User ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-step-logs/approver/{user_id} [get] +func (_i *articleApprovalStepLogsController) GetByApprover(c *fiber.Ctx) error { + userId, err := strconv.Atoi(c.Params("user_id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid user ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + articleApprovalStepLogsData, err := _i.articleApprovalStepLogsService.GetByApproverUserID(clientId, uint(userId)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleApprovalStepLogs by approver successfully retrieved"}, + Data: articleApprovalStepLogsData, + }) +} + +// GetPendingApprovals ArticleApprovalStepLogs +// @Summary Get Pending Approvals +// @Description API for getting pending approvals +// @Tags ArticleApprovalStepLogs +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param user_id query int false "Filter by user ID" +// @Param role_id query int false "Filter by role ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-step-logs/pending [get] +func (_i *articleApprovalStepLogsController) GetPendingApprovals(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + var userID *uint + var roleID *uint + + if userIDStr := c.Query("user_id"); userIDStr != "" { + if id, err := strconv.Atoi(userIDStr); err == nil { + userIDVal := uint(id) + userID = &userIDVal + } + } + + if roleIDStr := c.Query("role_id"); roleIDStr != "" { + if id, err := strconv.Atoi(roleIDStr); err == nil { + roleIDVal := uint(id) + roleID = &roleIDVal + } + } + + articleApprovalStepLogsData, err := _i.articleApprovalStepLogsService.GetPendingApprovals(clientId, userID, roleID) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Pending approvals successfully retrieved"}, + Data: articleApprovalStepLogsData, + }) +} + +// GetOverdueApprovals ArticleApprovalStepLogs +// @Summary Get Overdue Approvals +// @Description API for getting overdue approvals +// @Tags ArticleApprovalStepLogs +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param user_id query int false "Filter by user ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-step-logs/overdue [get] +func (_i *articleApprovalStepLogsController) GetOverdueApprovals(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + var userID *uint + if userIDStr := c.Query("user_id"); userIDStr != "" { + if id, err := strconv.Atoi(userIDStr); err == nil { + userIDVal := uint(id) + userID = &userIDVal + } + } + + articleApprovalStepLogsData, err := _i.articleApprovalStepLogsService.GetOverdueApprovals(clientId, userID) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Overdue approvals successfully retrieved"}, + Data: articleApprovalStepLogsData, + }) +} + +// ProcessApproval ArticleApprovalStepLogs +// @Summary Process Approval +// @Description API for processing approval step +// @Tags ArticleApprovalStepLogs +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "Step Log ID" +// @Param payload body request.ProcessApprovalRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-step-logs/{id}/process [post] +func (_i *articleApprovalStepLogsController) ProcessApproval(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + req := new(request.ProcessApprovalRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.articleApprovalStepLogsService.ProcessApproval(clientId, uint(id), req.UserID, req.StatusID, req.Comments) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Approval processed successfully"}, + }) +} + +// BulkProcessApproval ArticleApprovalStepLogs +// @Summary Bulk Process Approvals +// @Description API for bulk processing approval steps +// @Tags ArticleApprovalStepLogs +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param payload body request.BulkProcessApprovalRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-step-logs/bulk-process [post] +func (_i *articleApprovalStepLogsController) BulkProcessApproval(c *fiber.Ctx) error { + req := new(request.BulkProcessApprovalRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err := _i.articleApprovalStepLogsService.BulkProcessApproval(clientId, req.LogIDs, req.UserID, req.StatusID, req.Comments) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Approvals processed successfully"}, + }) +} + +// AutoApprove ArticleApprovalStepLogs +// @Summary Auto Approve Step +// @Description API for automatically approving a step +// @Tags ArticleApprovalStepLogs +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "Step Log ID" +// @Param reason query string true "Auto approval reason" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-step-logs/{id}/auto-approve [post] +func (_i *articleApprovalStepLogsController) AutoApprove(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") + } + + reason := c.Query("reason") + if reason == "" { + return utilRes.ErrorBadRequest(c, "Reason is required") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.articleApprovalStepLogsService.AutoApprove(clientId, uint(id), reason) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Step auto approved successfully"}, + }) +} + +// GetApprovalHistory ArticleApprovalStepLogs +// @Summary Get Approval History +// @Description API for getting approval history +// @Tags ArticleApprovalStepLogs +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param article_id query int false "Filter by article ID" +// @Param user_id query int false "Filter by user ID" +// @Param from_date query string false "Filter from date (YYYY-MM-DD)" +// @Param to_date query string false "Filter to date (YYYY-MM-DD)" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-step-logs/history [get] +func (_i *articleApprovalStepLogsController) GetApprovalHistory(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + var articleID *uint + var userID *uint + filters := make(map[string]interface{}) + + if articleIDStr := c.Query("article_id"); articleIDStr != "" { + if id, err := strconv.Atoi(articleIDStr); err == nil { + articleIDVal := uint(id) + articleID = &articleIDVal + } + } + + if userIDStr := c.Query("user_id"); userIDStr != "" { + if id, err := strconv.Atoi(userIDStr); err == nil { + userIDVal := uint(id) + userID = &userIDVal + } + } + + if fromDate := c.Query("from_date"); fromDate != "" { + filters["from_date"] = fromDate + } + + if toDate := c.Query("to_date"); toDate != "" { + filters["to_date"] = toDate + } + + articleApprovalStepLogsData, err := _i.articleApprovalStepLogsService.GetApprovalHistory(clientId, articleID, userID, filters) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Approval history successfully retrieved"}, + Data: articleApprovalStepLogsData, + }) +} + +// GetApprovalStats ArticleApprovalStepLogs +// @Summary Get Approval Statistics +// @Description API for getting approval statistics +// @Tags ArticleApprovalStepLogs +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param from_date query string false "Filter from date (YYYY-MM-DD)" +// @Param to_date query string false "Filter to date (YYYY-MM-DD)" +// @Param workflow_id query int false "Filter by workflow ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-step-logs/stats [get] +func (_i *articleApprovalStepLogsController) GetApprovalStats(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + filters := make(map[string]interface{}) + + if fromDate := c.Query("from_date"); fromDate != "" { + filters["from_date"] = fromDate + } + + if toDate := c.Query("to_date"); toDate != "" { + filters["to_date"] = toDate + } + + if workflowID := c.Query("workflow_id"); workflowID != "" { + if id, err := strconv.Atoi(workflowID); err == nil { + filters["workflow_id"] = id + } + } + + articleApprovalStepLogsData, err := _i.articleApprovalStepLogsService.GetApprovalStats(clientId, filters) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Approval statistics successfully retrieved"}, + Data: articleApprovalStepLogsData, + }) +} + +// GetUserWorkload ArticleApprovalStepLogs +// @Summary Get User Workload +// @Description API for getting user workload statistics +// @Tags ArticleApprovalStepLogs +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param user_id path int true "User ID" +// @Param include_stats query bool false "Include detailed statistics" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approval-step-logs/user/{user_id}/workload [get] +func (_i *articleApprovalStepLogsController) GetUserWorkload(c *fiber.Ctx) error { + userId, err := strconv.Atoi(c.Params("user_id")) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid user ID format") + } + + includeStats := c.Query("include_stats") == "true" + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + articleApprovalStepLogsData, err := _i.articleApprovalStepLogsService.GetUserWorkload(clientId, uint(userId), includeStats) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"User workload successfully retrieved"}, + Data: articleApprovalStepLogsData, + }) +} diff --git a/app/module/article_approval_step_logs/mapper/article_approval_step_logs.mapper.go b/app/module/article_approval_step_logs/mapper/article_approval_step_logs.mapper.go new file mode 100644 index 0000000..1179fb7 --- /dev/null +++ b/app/module/article_approval_step_logs/mapper/article_approval_step_logs.mapper.go @@ -0,0 +1,148 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/article_approval_step_logs/response" + usersRepository "web-qudo-be/app/module/users/repository" + + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +func ArticleApprovalStepLogsResponseMapper( + log zerolog.Logger, + clientId *uuid.UUID, + articleApprovalStepLogsReq *entity.ArticleApprovalStepLogs, + usersRepo usersRepository.UsersRepository, +) (articleApprovalStepLogsRes *res.ArticleApprovalStepLogsResponse) { + + if articleApprovalStepLogsReq != nil { + // Map entity fields to response fields + // approvalStatusID := uint(1) // Default status + // if articleApprovalStepLogsReq.Action == "approved" { + // approvalStatusID = 2 + // } else if articleApprovalStepLogsReq.Action == "rejected" { + // approvalStatusID = 3 + // } + + articleApprovalStepLogsRes = &res.ArticleApprovalStepLogsResponse{ + ID: articleApprovalStepLogsReq.ID, + ApprovalFlowId: articleApprovalStepLogsReq.ApprovalFlowId, + StepOrder: articleApprovalStepLogsReq.StepOrder, + StepName: articleApprovalStepLogsReq.StepName, + ApprovedById: articleApprovalStepLogsReq.ApprovedById, + Action: articleApprovalStepLogsReq.Action, + Message: articleApprovalStepLogsReq.Message, + ProcessedAt: articleApprovalStepLogsReq.ProcessedAt, + UserLevelId: articleApprovalStepLogsReq.UserLevelId, + ClientId: func() *string { + if articleApprovalStepLogsReq.ClientId != nil { + s := articleApprovalStepLogsReq.ClientId.String() + return &s + } + return nil + }(), + CreatedAt: articleApprovalStepLogsReq.CreatedAt, + } + } + + return articleApprovalStepLogsRes +} + +func ArticleApprovalStepLogsDetailResponseMapper( + log zerolog.Logger, + clientId *uuid.UUID, + articleApprovalStepLogsReq *entity.ArticleApprovalStepLogs, + usersRepo usersRepository.UsersRepository, +) (articleApprovalStepLogsRes *res.ArticleApprovalStepLogsDetailResponse) { + + if articleApprovalStepLogsReq != nil { + // Map entity fields to response fields + approvalStatusID := uint(1) // Default status + if articleApprovalStepLogsReq.Action == "approved" { + approvalStatusID = 2 + } else if articleApprovalStepLogsReq.Action == "rejected" { + approvalStatusID = 3 + } + + articleApprovalStepLogsRes = &res.ArticleApprovalStepLogsDetailResponse{ + ID: articleApprovalStepLogsReq.ID, + ArticleApprovalFlowID: articleApprovalStepLogsReq.ApprovalFlowId, + WorkflowStepID: uint(articleApprovalStepLogsReq.StepOrder), + ApproverUserID: articleApprovalStepLogsReq.ApprovedById, + ApprovalStatusID: approvalStatusID, + Comments: articleApprovalStepLogsReq.Message, + ApprovedAt: &articleApprovalStepLogsReq.ProcessedAt, + IsAutoApproved: false, // Default value + CreatedAt: articleApprovalStepLogsReq.CreatedAt, + UpdatedAt: articleApprovalStepLogsReq.CreatedAt, // Use CreatedAt as UpdatedAt + // Relations would be populated separately if needed + } + } + + return articleApprovalStepLogsRes +} + +func ArticleApprovalStepLogsSummaryResponseMapper( + log zerolog.Logger, + clientId *uuid.UUID, + articleApprovalStepLogsReq *entity.ArticleApprovalStepLogs, + usersRepo usersRepository.UsersRepository, +) (articleApprovalStepLogsRes *res.ArticleApprovalStepLogsSummaryResponse) { + + approverName := "" + if articleApprovalStepLogsReq.ApprovedById != nil { + findUser, _ := usersRepo.FindOne(clientId, *articleApprovalStepLogsReq.ApprovedById) + if findUser != nil { + approverName = findUser.Fullname + } + } + + if articleApprovalStepLogsReq != nil { + // Map entity fields to response fields + approvalStatusName := "Pending" + if articleApprovalStepLogsReq.Action == "approved" { + approvalStatusName = "Approved" + } else if articleApprovalStepLogsReq.Action == "rejected" { + approvalStatusName = "Rejected" + } + + articleApprovalStepLogsRes = &res.ArticleApprovalStepLogsSummaryResponse{ + ID: articleApprovalStepLogsReq.ID, + WorkflowStepID: uint(articleApprovalStepLogsReq.StepOrder), + StepName: articleApprovalStepLogsReq.StepName, + ApproverUserID: articleApprovalStepLogsReq.ApprovedById, + ApproverUserName: &approverName, + ApprovalStatusID: uint(1), // Default status ID + ApprovalStatusName: approvalStatusName, + ApprovedAt: &articleApprovalStepLogsReq.ProcessedAt, + IsAutoApproved: false, // Default value + } + } + + return articleApprovalStepLogsRes +} + +func ApprovalHistoryResponseMapper( + log zerolog.Logger, + clientId *uuid.UUID, + articleApprovalStepLogsReq *entity.ArticleApprovalStepLogs, + usersRepo usersRepository.UsersRepository, +) (approvalHistoryRes *res.ApprovalHistoryResponse) { + + if articleApprovalStepLogsReq != nil { + // Create a basic ApprovalHistoryResponse structure + // This would typically be built from multiple step logs, not a single one + approvalHistoryRes = &res.ApprovalHistoryResponse{ + ArticleID: 0, // Would need article information + ArticleTitle: "", // Would need article information + WorkflowName: "", // Would need workflow information + StartedAt: articleApprovalStepLogsReq.CreatedAt, + CompletedAt: nil, // Would be set when workflow is complete + CurrentStep: &articleApprovalStepLogsReq.StepName, + Steps: []res.ArticleApprovalStepLogsSummaryResponse{}, // Would be populated with all steps + } + } + + return approvalHistoryRes +} diff --git a/app/module/article_approval_step_logs/repository/article_approval_step_logs.repository.go b/app/module/article_approval_step_logs/repository/article_approval_step_logs.repository.go new file mode 100644 index 0000000..42b4fa7 --- /dev/null +++ b/app/module/article_approval_step_logs/repository/article_approval_step_logs.repository.go @@ -0,0 +1,438 @@ +package repository + +import ( + "fmt" + "time" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/article_approval_step_logs/request" + "web-qudo-be/utils/paginator" + + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +type articleApprovalStepLogsRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ArticleApprovalStepLogsRepository define interface of IArticleApprovalStepLogsRepository +type ArticleApprovalStepLogsRepository interface { + // Basic CRUD + GetAll(clientId *uuid.UUID, req request.ArticleApprovalStepLogsQueryRequest) (logs []*entity.ArticleApprovalStepLogs, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (log *entity.ArticleApprovalStepLogs, err error) + GetByApprovalFlowId(clientId *uuid.UUID, approvalFlowId uint) (logs []*entity.ArticleApprovalStepLogs, err error) + Create(clientId *uuid.UUID, log *entity.ArticleApprovalStepLogs) (logReturn *entity.ArticleApprovalStepLogs, err error) + Update(id uint, log *entity.ArticleApprovalStepLogs) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) + + // Approval History Methods + GetApprovalHistory(clientId *uuid.UUID, articleId uint, page, limit int) (logs []*entity.ArticleApprovalStepLogs, paging paginator.Pagination, err error) + GetUserApprovalHistory(clientId *uuid.UUID, userId uint, page, limit int, filters map[string]interface{}) (logs []*entity.ArticleApprovalStepLogs, paging paginator.Pagination, err error) + GetLevelApprovalHistory(clientId *uuid.UUID, userLevelId uint, page, limit int, filters map[string]interface{}) (logs []*entity.ArticleApprovalStepLogs, paging paginator.Pagination, err error) + + // Analytics Methods + GetApprovalTimeAnalytics(clientId *uuid.UUID, userLevelId uint, startDate, endDate time.Time) (analytics map[string]interface{}, err error) + GetUserPerformanceMetrics(clientId *uuid.UUID, userId uint, startDate, endDate time.Time) (metrics map[string]interface{}, err error) + GetWorkflowStepAnalytics(clientId *uuid.UUID, workflowId uint, stepOrder int) (analytics map[string]interface{}, err error) + + // Audit Methods + GetAuditTrail(clientId *uuid.UUID, articleId uint) (trail []*entity.ArticleApprovalStepLogs, err error) + GetRecentActions(clientId *uuid.UUID, userId uint, limit int) (logs []*entity.ArticleApprovalStepLogs, err error) +} + +func NewArticleApprovalStepLogsRepository(db *database.Database, log zerolog.Logger) ArticleApprovalStepLogsRepository { + return &articleApprovalStepLogsRepository{ + DB: db, + Log: log, + } +} + +// Basic CRUD implementations +func (_i *articleApprovalStepLogsRepository) GetAll(clientId *uuid.UUID, req request.ArticleApprovalStepLogsQueryRequest) (logs []*entity.ArticleApprovalStepLogs, paging paginator.Pagination, err error) { + var count int64 + query := _i.DB.DB.Model(&entity.ArticleApprovalStepLogs{}) + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", *clientId) + } + + // Apply filters based on request + if req.ArticleApprovalFlowID != nil { + query = query.Where("article_approval_flow_id = ?", *req.ArticleApprovalFlowID) + } + if req.WorkflowStepID != nil { + query = query.Where("workflow_step_id = ?", *req.WorkflowStepID) + } + if req.ApproverUserID != nil { + query = query.Where("approver_user_id = ?", *req.ApproverUserID) + } + if req.ApprovalStatusID != nil { + query = query.Where("approval_status_id = ?", *req.ApprovalStatusID) + } + if req.DateFrom != nil { + query = query.Where("approved_at >= ?", *req.DateFrom) + } + if req.DateTo != nil { + query = query.Where("approved_at <= ?", *req.DateTo) + } + if req.IsAutoApproved != nil { + query = query.Where("is_auto_approved = ?", *req.IsAutoApproved) + } + + // Count total records + query.Count(&count) + + // Apply sorting + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } else { + direction := "DESC" + sortBy := "article_approval_step_logs.approved_at" + query.Order(fmt.Sprintf("%s %s", sortBy, direction)) + } + + // Apply pagination (manual calculation for better performance) + page := req.Pagination.Page + limit := req.Pagination.Limit + if page <= 0 { + page = 1 + } + if limit <= 0 { + limit = 10 + } + + offset := (page - 1) * limit + err = query.Offset(offset).Limit(limit).Preload("ApprovalFlow").Preload("Step").Preload("ActionBy").Preload("Status").Find(&logs).Error + if err != nil { + return + } + + // Create pagination response + paging = paginator.Pagination{ + Page: page, + Limit: limit, + Count: count, + TotalPage: int((count + int64(limit) - 1) / int64(limit)), + } + + return logs, paging, nil +} + +func (_i *articleApprovalStepLogsRepository) FindOne(clientId *uuid.UUID, id uint) (log *entity.ArticleApprovalStepLogs, err error) { + query := _i.DB.DB.Model(&entity.ArticleApprovalStepLogs{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Preload("ApprovalFlow").Preload("ApprovedBy").Preload("UserLevel") + + err = query.First(&log, id).Error + return log, err +} + +func (_i *articleApprovalStepLogsRepository) GetByApprovalFlowId(clientId *uuid.UUID, approvalFlowId uint) (logs []*entity.ArticleApprovalStepLogs, err error) { + query := _i.DB.DB.Model(&entity.ArticleApprovalStepLogs{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("approval_flow_id = ?", approvalFlowId) + query = query.Preload("ApprovalFlow").Preload("ApprovedBy").Preload("UserLevel") + query = query.Order("step_order ASC, processed_at ASC") + + err = query.Find(&logs).Error + return logs, err +} + +func (_i *articleApprovalStepLogsRepository) Create(clientId *uuid.UUID, log *entity.ArticleApprovalStepLogs) (logReturn *entity.ArticleApprovalStepLogs, err error) { + log.ClientId = clientId + log.ProcessedAt = time.Now() + err = _i.DB.DB.Create(&log).Error + return log, err +} + +func (_i *articleApprovalStepLogsRepository) Update(id uint, log *entity.ArticleApprovalStepLogs) (err error) { + err = _i.DB.DB.Model(&entity.ArticleApprovalStepLogs{}).Where("id = ?", id).Updates(log).Error + return err +} + +func (_i *articleApprovalStepLogsRepository) Delete(clientId *uuid.UUID, id uint) (err error) { + query := _i.DB.DB.Model(&entity.ArticleApprovalStepLogs{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + err = query.Delete(&entity.ArticleApprovalStepLogs{}, id).Error + return err +} + +// Approval History Methods +func (_i *articleApprovalStepLogsRepository) GetApprovalHistory(clientId *uuid.UUID, articleId uint, page, limit int) (logs []*entity.ArticleApprovalStepLogs, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ArticleApprovalStepLogs{}) + + if clientId != nil { + query = query.Where("article_approval_step_logs.client_id = ?", clientId) + } + + // Join with approval flows to filter by article + query = query.Joins("JOIN article_approval_flows ON article_approval_step_logs.approval_flow_id = article_approval_flows.id") + query = query.Where("article_approval_flows.article_id = ?", articleId) + query = query.Preload("ApprovalFlow").Preload("ApprovedBy").Preload("UserLevel") + query = query.Order("article_approval_step_logs.processed_at DESC") + + err = query.Count(&count).Error + if err != nil { + return nil, paginator.Pagination{}, err + } + + offset := (page - 1) * limit + err = query.Offset(offset).Limit(limit).Find(&logs).Error + if err != nil { + return nil, paginator.Pagination{}, err + } + + paging = paginator.Pagination{ + Page: page, + Limit: limit, + Count: count, + TotalPage: int((count + int64(limit) - 1) / int64(limit)), + } + + return logs, paging, nil +} + +func (_i *articleApprovalStepLogsRepository) GetUserApprovalHistory(clientId *uuid.UUID, userId uint, page, limit int, filters map[string]interface{}) (logs []*entity.ArticleApprovalStepLogs, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ArticleApprovalStepLogs{}) + + if clientId != nil { + query = query.Where("article_approval_step_logs.client_id = ?", clientId) + } + + query = query.Where("approved_by_id = ?", userId) + + // Apply filters + if action, ok := filters["action"]; ok { + query = query.Where("action = ?", action) + } + + if startDate, ok := filters["start_date"]; ok { + query = query.Where("processed_at >= ?", startDate) + } + + if endDate, ok := filters["end_date"]; ok { + query = query.Where("processed_at <= ?", endDate) + } + + query = query.Preload("ApprovalFlow").Preload("ApprovedBy").Preload("UserLevel") + query = query.Order("processed_at DESC") + + err = query.Count(&count).Error + if err != nil { + return nil, paginator.Pagination{}, err + } + + offset := (page - 1) * limit + err = query.Offset(offset).Limit(limit).Find(&logs).Error + if err != nil { + return nil, paginator.Pagination{}, err + } + + paging = paginator.Pagination{ + Page: page, + Limit: limit, + Count: count, + TotalPage: int((count + int64(limit) - 1) / int64(limit)), + } + + return logs, paging, nil +} + +func (_i *articleApprovalStepLogsRepository) GetLevelApprovalHistory(clientId *uuid.UUID, userLevelId uint, page, limit int, filters map[string]interface{}) (logs []*entity.ArticleApprovalStepLogs, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ArticleApprovalStepLogs{}) + + if clientId != nil { + query = query.Where("article_approval_step_logs.client_id = ?", clientId) + } + + query = query.Where("user_level_id = ?", userLevelId) + + // Apply filters + if action, ok := filters["action"]; ok { + query = query.Where("action = ?", action) + } + + if startDate, ok := filters["start_date"]; ok { + query = query.Where("processed_at >= ?", startDate) + } + + if endDate, ok := filters["end_date"]; ok { + query = query.Where("processed_at <= ?", endDate) + } + + query = query.Preload("ApprovalFlow").Preload("ApprovedBy").Preload("UserLevel") + query = query.Order("processed_at DESC") + + err = query.Count(&count).Error + if err != nil { + return nil, paginator.Pagination{}, err + } + + offset := (page - 1) * limit + err = query.Offset(offset).Limit(limit).Find(&logs).Error + if err != nil { + return nil, paginator.Pagination{}, err + } + + paging = paginator.Pagination{ + Page: page, + Limit: limit, + Count: count, + TotalPage: int((count + int64(limit) - 1) / int64(limit)), + } + + return logs, paging, nil +} + +// Analytics Methods +func (_i *articleApprovalStepLogsRepository) GetApprovalTimeAnalytics(clientId *uuid.UUID, userLevelId uint, startDate, endDate time.Time) (analytics map[string]interface{}, err error) { + analytics = make(map[string]interface{}) + + // Get average approval time for this level + var avgTime float64 + query := _i.DB.DB.Model(&entity.ArticleApprovalStepLogs{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("user_level_id = ? AND action IN ('approve', 'reject') AND processed_at BETWEEN ? AND ?", userLevelId, startDate, endDate) + query = query.Select("AVG(EXTRACT(EPOCH FROM (processed_at - created_at))) as avg_time") + + err = query.Scan(&avgTime).Error + if err != nil { + return nil, err + } + + analytics["average_approval_time_seconds"] = avgTime + analytics["level_id"] = userLevelId + analytics["period_start"] = startDate + analytics["period_end"] = endDate + + return analytics, nil +} + +func (_i *articleApprovalStepLogsRepository) GetUserPerformanceMetrics(clientId *uuid.UUID, userId uint, startDate, endDate time.Time) (metrics map[string]interface{}, err error) { + metrics = make(map[string]interface{}) + + // Get counts by action + var approvedCount, rejectedCount, revisionCount int64 + + query := _i.DB.DB.Model(&entity.ArticleApprovalStepLogs{}) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + query = query.Where("approved_by_id = ? AND processed_at BETWEEN ? AND ?", userId, startDate, endDate) + + // Approved count + query.Where("action = 'approve'").Count(&approvedCount) + + // Rejected count + query.Where("action = 'reject'").Count(&rejectedCount) + + // Revision requested count + query.Where("action = 'request_revision'").Count(&revisionCount) + + metrics["approved_count"] = approvedCount + metrics["rejected_count"] = rejectedCount + metrics["revision_requested_count"] = revisionCount + metrics["total_processed"] = approvedCount + rejectedCount + revisionCount + metrics["user_id"] = userId + metrics["period_start"] = startDate + metrics["period_end"] = endDate + + return metrics, nil +} + +func (_i *articleApprovalStepLogsRepository) GetWorkflowStepAnalytics(clientId *uuid.UUID, workflowId uint, stepOrder int) (analytics map[string]interface{}, err error) { + analytics = make(map[string]interface{}) + + // Get step performance metrics + var totalProcessed, approvedCount, rejectedCount int64 + + query := _i.DB.DB.Model(&entity.ArticleApprovalStepLogs{}) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + // Join with approval flows to filter by workflow + query = query.Joins("JOIN article_approval_flows ON article_approval_step_logs.approval_flow_id = article_approval_flows.id") + query = query.Where("article_approval_flows.workflow_id = ? AND article_approval_step_logs.step_order = ?", workflowId, stepOrder) + + // Total processed + query.Count(&totalProcessed) + + // Approved count + query.Where("article_approval_step_logs.action = 'approve'").Count(&approvedCount) + + // Rejected count + query.Where("article_approval_step_logs.action = 'reject'").Count(&rejectedCount) + + analytics["workflow_id"] = workflowId + analytics["step_order"] = stepOrder + analytics["total_processed"] = totalProcessed + analytics["approved_count"] = approvedCount + analytics["rejected_count"] = rejectedCount + analytics["approval_rate"] = float64(approvedCount) / float64(totalProcessed) * 100 + + return analytics, nil +} + +// Audit Methods +func (_i *articleApprovalStepLogsRepository) GetAuditTrail(clientId *uuid.UUID, articleId uint) (trail []*entity.ArticleApprovalStepLogs, err error) { + query := _i.DB.DB.Model(&entity.ArticleApprovalStepLogs{}) + + if clientId != nil { + query = query.Where("article_approval_step_logs.client_id = ?", clientId) + } + + // Join with approval flows to filter by article + query = query.Joins("JOIN article_approval_flows ON article_approval_step_logs.approval_flow_id = article_approval_flows.id") + query = query.Where("article_approval_flows.article_id = ?", articleId) + query = query.Preload("ApprovalFlow").Preload("ApprovedBy").Preload("UserLevel") + query = query.Order("article_approval_step_logs.processed_at ASC") + + err = query.Find(&trail).Error + return trail, err +} + +func (_i *articleApprovalStepLogsRepository) GetRecentActions(clientId *uuid.UUID, userId uint, limit int) (logs []*entity.ArticleApprovalStepLogs, err error) { + query := _i.DB.DB.Model(&entity.ArticleApprovalStepLogs{}) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("approved_by_id = ?", userId) + query = query.Preload("ApprovalFlow").Preload("ApprovedBy").Preload("UserLevel") + query = query.Order("processed_at DESC") + query = query.Limit(limit) + + err = query.Find(&logs).Error + return logs, err +} diff --git a/app/module/article_approval_step_logs/request/article_approval_step_logs.request.go b/app/module/article_approval_step_logs/request/article_approval_step_logs.request.go new file mode 100644 index 0000000..6e693c6 --- /dev/null +++ b/app/module/article_approval_step_logs/request/article_approval_step_logs.request.go @@ -0,0 +1,243 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type CreateArticleApprovalStepLogsRequest struct { + ArticleApprovalFlowID uint `json:"articleApprovalFlowId" validate:"required"` + WorkflowStepID uint `json:"workflowStepId" validate:"required"` + ApproverUserID *uint `json:"approverUserId"` + ApprovalStatusID uint `json:"approvalStatusId" validate:"required"` + Comments *string `json:"comments" validate:"omitempty,max=1000"` + ApprovedAt *time.Time `json:"approvedAt"` + DueDate *time.Time `json:"dueDate"` + IsAutoApproved bool `json:"isAutoApproved"` +} + +type UpdateArticleApprovalStepLogsRequest struct { + ApproverUserID *uint `json:"approverUserId"` + ApprovalStatusID *uint `json:"approvalStatusId"` + Comments *string `json:"comments" validate:"omitempty,max=1000"` + ApprovedAt *time.Time `json:"approvedAt"` + DueDate *time.Time `json:"dueDate"` + IsAutoApproved *bool `json:"isAutoApproved"` +} + +type ArticleApprovalStepLogsQueryRequest struct { + ArticleApprovalFlowID *uint `json:"articleApprovalFlowId" form:"articleApprovalFlowId"` + WorkflowStepID *uint `json:"workflowStepId" form:"workflowStepId"` + ApproverUserID *uint `json:"approverUserId" form:"approverUserId"` + ApprovalStatusID *uint `json:"approvalStatusId" form:"approvalStatusId"` + IsAutoApproved *bool `json:"isAutoApproved" form:"isAutoApproved"` + DateFrom *time.Time `json:"dateFrom" form:"dateFrom"` + DateTo *time.Time `json:"dateTo" form:"dateTo"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ProcessApprovalRequest struct { + UserID uint `json:"userId" validate:"required"` + StatusID uint `json:"statusId" validate:"required"` + ApprovalStatusID uint `json:"approvalStatusId" validate:"required"` + Comments *string `json:"comments" validate:"omitempty,max=1000"` +} + +type BulkProcessApprovalRequest struct { + LogIDs []uint `json:"logIds" validate:"required,min=1,max=50,dive,required"` + UserID uint `json:"userId" validate:"required"` + StatusID uint `json:"statusId" validate:"required"` + StepLogIDs []uint `json:"stepLogIds" validate:"required,min=1,max=50,dive,required"` + ApprovalStatusID uint `json:"approvalStatusId" validate:"required"` + Comments *string `json:"comments" validate:"omitempty,max=1000"` +} + +type GetApprovalHistoryRequest struct { + ArticleID *uint `json:"articleId" form:"articleId"` + UserID *uint `json:"userId" form:"userId"` + WorkflowID *uint `json:"workflowId" form:"workflowId"` + StatusID *uint `json:"statusId" form:"statusId"` + DateFrom *time.Time `json:"dateFrom" form:"dateFrom"` + DateTo *time.Time `json:"dateTo" form:"dateTo"` + Page int `json:"page" form:"page" validate:"min=1"` + Limit int `json:"limit" form:"limit" validate:"min=1,max=100"` + SortBy *string `json:"sortBy" form:"sortBy"` + SortOrder *string `json:"sortOrder" form:"sortOrder" validate:"omitempty,oneof=asc desc"` +} + +type GetApprovalStatsRequest struct { + WorkflowID *uint `json:"workflowId" form:"workflowId"` + StepID *uint `json:"stepId" form:"stepId"` + UserID *uint `json:"userId" form:"userId"` + DateFrom *time.Time `json:"dateFrom" form:"dateFrom"` + DateTo *time.Time `json:"dateTo" form:"dateTo"` + GroupBy *string `json:"groupBy" form:"groupBy" validate:"omitempty,oneof=step user workflow status"` +} + +type GetUserWorkloadRequest struct { + UserID *uint `json:"userId" form:"userId"` + RoleID *uint `json:"roleId" form:"roleId"` + StatusID *uint `json:"statusId" form:"statusId"` + DateFrom *time.Time `json:"dateFrom" form:"dateFrom"` + DateTo *time.Time `json:"dateTo" form:"dateTo"` + IncludeStats bool `json:"includeStats" form:"includeStats"` + Page int `json:"page" form:"page" validate:"min=1"` + Limit int `json:"limit" form:"limit" validate:"min=1,max=100"` +} + +// Missing request types that are referenced in controller +type ArticleApprovalStepLogsQueryRequestContext struct { + ApprovalFlowId string `json:"approvalFlowId" form:"approvalFlowId"` + StepId string `json:"stepId" form:"stepId"` + ActionById string `json:"actionById" form:"actionById"` + ActionType string `json:"actionType" form:"actionType"` + StatusId string `json:"statusId" form:"statusId"` + ActionDateFrom string `json:"actionDateFrom" form:"actionDateFrom"` + ActionDateTo string `json:"actionDateTo" form:"actionDateTo"` + UserLevelId string `json:"userLevelId" form:"userLevelId"` + IsUrgent string `json:"isUrgent" form:"isUrgent"` + Search string `json:"search" form:"search"` + OrderBy string `json:"orderBy" form:"orderBy"` + OrderDirection string `json:"orderDirection" form:"orderDirection"` +} + +func (r *ArticleApprovalStepLogsQueryRequestContext) ToParamRequest() *ArticleApprovalStepLogsQueryRequest { + // Convert string parameters to appropriate types using helper functions + approvalFlowId := parseStringToUintPtr(r.ApprovalFlowId) + workflowStepId := parseStringToUintPtr(r.StepId) + approverUserId := parseStringToUintPtr(r.ActionById) + approvalStatusId := parseStringToUintPtr(r.StatusId) + isAutoApproved := parseStringToBoolPtr(r.IsUrgent) + dateFrom := parseStringToTimePtr(r.ActionDateFrom) + dateTo := parseStringToTimePtr(r.ActionDateTo) + + // Handle string parameters + var sortBy *string + var sortOrder *string + + if r.OrderBy != "" { + sortBy = &r.OrderBy + } + + if r.OrderDirection != "" { + sortOrder = &r.OrderDirection + } + + // Set default pagination + page := 1 + limit := 10 + + // Create pagination object + pagination := &paginator.Pagination{ + Page: page, + Limit: limit, + } + + // Set sorting if provided + if sortBy != nil { + pagination.SortBy = *sortBy + } + if sortOrder != nil { + pagination.Sort = *sortOrder + } + + return &ArticleApprovalStepLogsQueryRequest{ + ArticleApprovalFlowID: approvalFlowId, + WorkflowStepID: workflowStepId, + ApproverUserID: approverUserId, + ApprovalStatusID: approvalStatusId, + IsAutoApproved: isAutoApproved, + DateFrom: dateFrom, + DateTo: dateTo, + Pagination: pagination, + } +} + +// Helper function to parse string to uint pointer +func parseStringToUintPtr(s string) *uint { + if s == "" { + return nil + } + if val, err := strconv.ParseUint(s, 10, 32); err == nil { + uintVal := uint(val) + return &uintVal + } + return nil +} + +// Helper function to parse string to bool pointer +func parseStringToBoolPtr(s string) *bool { + if s == "" { + return nil + } + if val, err := strconv.ParseBool(s); err == nil { + return &val + } + return nil +} + +// Helper function to parse string to time pointer +func parseStringToTimePtr(s string) *time.Time { + if s == "" { + return nil + } + // Try different date formats + formats := []string{ + "2006-01-02", + "2006-01-02T15:04:05Z", + "2006-01-02T15:04:05Z07:00", + "2006-01-02 15:04:05", + } + + for _, format := range formats { + if val, err := time.Parse(format, s); err == nil { + return &val + } + } + return nil +} + +type ArticleApprovalStepLogsCreateRequest struct { + ArticleApprovalFlowID uint `json:"articleApprovalFlowId" validate:"required"` + WorkflowStepID uint `json:"workflowStepId" validate:"required"` + ApproverUserID *uint `json:"approverUserId"` + ApprovalStatusID uint `json:"approvalStatusId" validate:"required"` + Comments *string `json:"comments" validate:"omitempty,max=1000"` + ApprovedAt *time.Time `json:"approvedAt"` + DueDate *time.Time `json:"dueDate"` + IsAutoApproved bool `json:"isAutoApproved"` +} + +func (r *ArticleApprovalStepLogsCreateRequest) ToEntity() *entity.ArticleApprovalStepLogs { + // Return the entity representation + return &entity.ArticleApprovalStepLogs{ + ApprovalFlowId: r.ArticleApprovalFlowID, + StepOrder: int(r.WorkflowStepID), + ApprovedById: r.ApproverUserID, + Action: "pending", // Default action + Message: r.Comments, + ProcessedAt: time.Now(), + UserLevelId: 1, // Default user level + } +} + +type ArticleApprovalStepLogsUpdateRequest struct { + ApproverUserID *uint `json:"approverUserId"` + ApprovalStatusID *uint `json:"approvalStatusId"` + Comments *string `json:"comments" validate:"omitempty,max=1000"` + ApprovedAt *time.Time `json:"approvedAt"` + DueDate *time.Time `json:"dueDate"` + IsAutoApproved *bool `json:"isAutoApproved"` +} + +func (r *ArticleApprovalStepLogsUpdateRequest) ToEntity() *entity.ArticleApprovalStepLogs { + // Return the entity representation + return &entity.ArticleApprovalStepLogs{ + ApprovedById: r.ApproverUserID, + Action: "updated", // Default action + Message: r.Comments, + ProcessedAt: time.Now(), + } +} diff --git a/app/module/article_approval_step_logs/response/article_approval_step_logs.response.go b/app/module/article_approval_step_logs/response/article_approval_step_logs.response.go new file mode 100644 index 0000000..7cd3e38 --- /dev/null +++ b/app/module/article_approval_step_logs/response/article_approval_step_logs.response.go @@ -0,0 +1,91 @@ +package response + +import ( + "time" + approvalWorkflowStepsResponse "web-qudo-be/app/module/approval_workflow_steps/response" + articlesResponse "web-qudo-be/app/module/articles/response" + usersResponse "web-qudo-be/app/module/users/response" +) + +type ArticleApprovalStepLogsResponse struct { + ID uint `json:"id"` + ApprovalFlowId uint `json:"approvalFlowId"` + StepOrder int `json:"stepOrder"` + StepName string `json:"stepName"` + ApprovedById *uint `json:"approvedById"` + Action string `json:"action"` + Message *string `json:"message"` + ProcessedAt time.Time `json:"processedAt"` + UserLevelId uint `json:"userLevelId"` + ClientId *string `json:"clientId"` + CreatedAt time.Time `json:"createdAt"` + + // Relations + ApprovedBy *usersResponse.UsersResponse `json:"approvedBy,omitempty"` +} + +type ArticleApprovalStepLogsDetailResponse struct { + ID uint `json:"id"` + ArticleApprovalFlowID uint `json:"articleApprovalFlowId"` + WorkflowStepID uint `json:"workflowStepId"` + ApproverUserID *uint `json:"approverUserId"` + ApprovalStatusID uint `json:"approvalStatusId"` + ApprovalStatusName *string `json:"approvalStatusName,omitempty"` + Comments *string `json:"comments"` + ApprovedAt *time.Time `json:"approvedAt"` + DueDate *time.Time `json:"dueDate"` + IsAutoApproved bool `json:"isAutoApproved"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + // Relations with full details + WorkflowStep *approvalWorkflowStepsResponse.ApprovalWorkflowStepsResponse `json:"workflowStep,omitempty"` + ApproverUser *usersResponse.UsersResponse `json:"approverUser,omitempty"` + Article *articlesResponse.ArticlesResponse `json:"article,omitempty"` +} + +type ArticleApprovalStepLogsSummaryResponse struct { + ID uint `json:"id"` + WorkflowStepID uint `json:"workflowStepId"` + StepName string `json:"stepName"` + ApproverUserID *uint `json:"approverUserId"` + ApproverUserName *string `json:"approverUserName,omitempty"` + ApprovalStatusID uint `json:"approvalStatusId"` + ApprovalStatusName string `json:"approvalStatusName"` + ApprovedAt *time.Time `json:"approvedAt"` + DueDate *time.Time `json:"dueDate"` + IsAutoApproved bool `json:"isAutoApproved"` +} + +type ApprovalHistoryResponse struct { + ArticleID uint `json:"articleId"` + ArticleTitle string `json:"articleTitle"` + WorkflowName string `json:"workflowName"` + StartedAt time.Time `json:"startedAt"` + CompletedAt *time.Time `json:"completedAt"` + CurrentStep *string `json:"currentStep"` + Steps []ArticleApprovalStepLogsSummaryResponse `json:"steps"` +} + +type ApprovalStepStatsResponse struct { + StepName string `json:"stepName"` + TotalApprovals int `json:"totalApprovals"` + PendingApprovals int `json:"pendingApprovals"` + ApprovedCount int `json:"approvedCount"` + RejectedCount int `json:"rejectedCount"` + AutoApprovedCount int `json:"autoApprovedCount"` + AverageProcessingTime float64 `json:"averageProcessingTime"` // in hours + OverdueCount int `json:"overdueCount"` +} + +type UserApprovalStatsResponse struct { + UserID uint `json:"userId"` + UserName string `json:"userName"` + TotalAssigned int `json:"totalAssigned"` + PendingApprovals int `json:"pendingApprovals"` + CompletedApprovals int `json:"completedApprovals"` + ApprovedCount int `json:"approvedCount"` + RejectedCount int `json:"rejectedCount"` + AverageProcessingTime float64 `json:"averageProcessingTime"` // in hours + OverdueCount int `json:"overdueCount"` +} \ No newline at end of file diff --git a/app/module/article_approval_step_logs/service/article_approval_step_logs.service.go b/app/module/article_approval_step_logs/service/article_approval_step_logs.service.go new file mode 100644 index 0000000..df39ad6 --- /dev/null +++ b/app/module/article_approval_step_logs/service/article_approval_step_logs.service.go @@ -0,0 +1,296 @@ +package service + +import ( + "errors" + "fmt" + "time" + "web-qudo-be/app/database/entity" + stepRepo "web-qudo-be/app/module/approval_workflow_steps/repository" + flowRepo "web-qudo-be/app/module/article_approval_flows/repository" + "web-qudo-be/app/module/article_approval_step_logs/repository" + "web-qudo-be/app/module/article_approval_step_logs/request" + "web-qudo-be/utils/paginator" + + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +type articleApprovalStepLogsService struct { + ArticleApprovalStepLogsRepository repository.ArticleApprovalStepLogsRepository + ArticleApprovalFlowsRepository flowRepo.ArticleApprovalFlowsRepository + ApprovalWorkflowStepsRepository stepRepo.ApprovalWorkflowStepsRepository + Log zerolog.Logger +} + +// ArticleApprovalStepLogsService define interface of IArticleApprovalStepLogsService +type ArticleApprovalStepLogsService interface { + // Basic CRUD + GetAll(clientId *uuid.UUID, req request.ArticleApprovalStepLogsQueryRequest) (logs []*entity.ArticleApprovalStepLogs, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (log *entity.ArticleApprovalStepLogs, err error) + Create(clientId *uuid.UUID, log *entity.ArticleApprovalStepLogs) (logReturn *entity.ArticleApprovalStepLogs, err error) + Update(clientId *uuid.UUID, id uint, log *entity.ArticleApprovalStepLogs) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) + + // Approval process management + GetByApprovalFlowID(clientId *uuid.UUID, flowID uint) (logs []*entity.ArticleApprovalStepLogs, err error) + GetByWorkflowStepID(clientId *uuid.UUID, stepID uint) (logs []*entity.ArticleApprovalStepLogs, err error) + GetByApproverUserID(clientId *uuid.UUID, userID uint) (logs []*entity.ArticleApprovalStepLogs, err error) + GetPendingApprovals(clientId *uuid.UUID, userID *uint, roleID *uint) (logs []*entity.ArticleApprovalStepLogs, err error) + GetOverdueApprovals(clientId *uuid.UUID, userID *uint) (logs []*entity.ArticleApprovalStepLogs, err error) + + // Approval actions + ProcessApproval(clientId *uuid.UUID, logID uint, userID uint, statusID uint, comments *string) (err error) + BulkProcessApproval(clientId *uuid.UUID, logIDs []uint, userID uint, statusID uint, comments *string) (err error) + AutoApprove(clientId *uuid.UUID, logID uint, reason string) (err error) + + // History and analytics + GetApprovalHistory(clientId *uuid.UUID, articleID *uint, userID *uint, filters map[string]interface{}) (logs []*entity.ArticleApprovalStepLogs, err error) + GetApprovalStats(clientId *uuid.UUID, filters map[string]interface{}) (stats map[string]interface{}, err error) + GetUserWorkload(clientId *uuid.UUID, userID uint, includeStats bool) (workload map[string]interface{}, err error) + + // Validation + ValidateStepLog(clientId *uuid.UUID, log *entity.ArticleApprovalStepLogs) (isValid bool, errors []string, err error) + CanProcessApproval(clientId *uuid.UUID, logID uint, userID uint) (canProcess bool, reason string, err error) +} + +func NewArticleApprovalStepLogsService( + articleApprovalStepLogsRepository repository.ArticleApprovalStepLogsRepository, + articleApprovalFlowsRepository flowRepo.ArticleApprovalFlowsRepository, + approvalWorkflowStepsRepository stepRepo.ApprovalWorkflowStepsRepository, + log zerolog.Logger, +) ArticleApprovalStepLogsService { + return &articleApprovalStepLogsService{ + ArticleApprovalStepLogsRepository: articleApprovalStepLogsRepository, + ArticleApprovalFlowsRepository: articleApprovalFlowsRepository, + ApprovalWorkflowStepsRepository: approvalWorkflowStepsRepository, + Log: log, + } +} + +func (_i *articleApprovalStepLogsService) GetAll(clientId *uuid.UUID, req request.ArticleApprovalStepLogsQueryRequest) (logs []*entity.ArticleApprovalStepLogs, paging paginator.Pagination, err error) { + return _i.ArticleApprovalStepLogsRepository.GetAll(clientId, req) +} + +func (_i *articleApprovalStepLogsService) FindOne(clientId *uuid.UUID, id uint) (log *entity.ArticleApprovalStepLogs, err error) { + return _i.ArticleApprovalStepLogsRepository.FindOne(clientId, id) +} + +func (_i *articleApprovalStepLogsService) Create(clientId *uuid.UUID, log *entity.ArticleApprovalStepLogs) (logReturn *entity.ArticleApprovalStepLogs, err error) { + // Validate business rules + if log.ApprovalFlowId == 0 { + return nil, errors.New("approval flow ID is required") + } + + // Validate approval flow exists + flow, err := _i.ArticleApprovalFlowsRepository.FindOne(clientId, log.ApprovalFlowId) + if err != nil { + return nil, fmt.Errorf("approval flow not found: %w", err) + } + if flow == nil { + return nil, errors.New("approval flow not found") + } + + // Validate step log data + isValid, validationErrors, err := _i.ValidateStepLog(clientId, log) + if err != nil { + return nil, err + } + if !isValid { + return nil, fmt.Errorf("validation failed: %v", validationErrors) + } + + return _i.ArticleApprovalStepLogsRepository.Create(clientId, log) +} + +func (_i *articleApprovalStepLogsService) Update(clientId *uuid.UUID, id uint, log *entity.ArticleApprovalStepLogs) (err error) { + // Check if log exists + existingLog, err := _i.ArticleApprovalStepLogsRepository.FindOne(clientId, id) + if err != nil { + return err + } + if existingLog == nil { + return errors.New("step log not found") + } + + return _i.ArticleApprovalStepLogsRepository.Update(id, log) +} + +func (_i *articleApprovalStepLogsService) Delete(clientId *uuid.UUID, id uint) (err error) { + // Check if log exists + existingLog, err := _i.ArticleApprovalStepLogsRepository.FindOne(clientId, id) + if err != nil { + return err + } + if existingLog == nil { + return errors.New("step log not found") + } + + return _i.ArticleApprovalStepLogsRepository.Delete(clientId, id) +} + +func (_i *articleApprovalStepLogsService) GetByApprovalFlowID(clientId *uuid.UUID, flowID uint) (logs []*entity.ArticleApprovalStepLogs, err error) { + return _i.ArticleApprovalStepLogsRepository.GetByApprovalFlowId(clientId, flowID) +} + +func (_i *articleApprovalStepLogsService) GetByWorkflowStepID(clientId *uuid.UUID, stepID uint) (logs []*entity.ArticleApprovalStepLogs, err error) { + // This method is not implemented in repository, return empty slice for now + return []*entity.ArticleApprovalStepLogs{}, nil +} + +func (_i *articleApprovalStepLogsService) GetByApproverUserID(clientId *uuid.UUID, userID uint) (logs []*entity.ArticleApprovalStepLogs, err error) { + // This method is not implemented in repository, return empty slice for now + return []*entity.ArticleApprovalStepLogs{}, nil +} + +func (_i *articleApprovalStepLogsService) GetPendingApprovals(clientId *uuid.UUID, userID *uint, roleID *uint) (logs []*entity.ArticleApprovalStepLogs, err error) { + // This method is not implemented in repository, return empty slice for now + return []*entity.ArticleApprovalStepLogs{}, nil +} + +func (_i *articleApprovalStepLogsService) GetOverdueApprovals(clientId *uuid.UUID, userID *uint) (logs []*entity.ArticleApprovalStepLogs, err error) { + // This method is not implemented in repository, return empty slice for now + return []*entity.ArticleApprovalStepLogs{}, nil +} + +func (_i *articleApprovalStepLogsService) ProcessApproval(clientId *uuid.UUID, logID uint, userID uint, statusID uint, comments *string) (err error) { + // Check if user can process this approval + canProcess, reason, err := _i.CanProcessApproval(clientId, logID, userID) + if err != nil { + return err + } + if !canProcess { + return fmt.Errorf("cannot process approval: %s", reason) + } + + // Update the step log + now := time.Now() + updateLog := &entity.ArticleApprovalStepLogs{ + ApprovedById: &userID, + Action: "approve", // This should be determined based on statusID + Message: comments, + ProcessedAt: now, + } + + return _i.ArticleApprovalStepLogsRepository.Update(logID, updateLog) +} + +func (_i *articleApprovalStepLogsService) BulkProcessApproval(clientId *uuid.UUID, logIDs []uint, userID uint, statusID uint, comments *string) (err error) { + // Validate all logs can be processed by this user + for _, logID := range logIDs { + canProcess, reason, err := _i.CanProcessApproval(clientId, logID, userID) + if err != nil { + return err + } + if !canProcess { + return fmt.Errorf("cannot process approval for log %d: %s", logID, reason) + } + } + + // Note: BulkUpdate method is not available in repository + // This functionality would need to be implemented when repository is updated + // For now, we'll process each log individually + now := time.Now() + for _, logID := range logIDs { + updateLog := &entity.ArticleApprovalStepLogs{ + ApprovedById: &userID, + Action: "approve", + Message: comments, + ProcessedAt: now, + } + err := _i.ArticleApprovalStepLogsRepository.Update(logID, updateLog) + if err != nil { + return err + } + } + return nil +} + +func (_i *articleApprovalStepLogsService) AutoApprove(clientId *uuid.UUID, logID uint, reason string) (err error) { + // Get the step log + log, err := _i.ArticleApprovalStepLogsRepository.FindOne(clientId, logID) + if err != nil { + return err + } + if log == nil { + return errors.New("step log not found") + } + + // Note: WorkflowStepID and AutoApprove fields are not available in current entity structure + // This functionality would need to be implemented when entity is updated + + // Auto approve with current entity structure + now := time.Now() + updateLog := &entity.ArticleApprovalStepLogs{ + Action: "approve", + Message: &reason, + ProcessedAt: now, + } + + return _i.ArticleApprovalStepLogsRepository.Update(logID, updateLog) +} + +func (_i *articleApprovalStepLogsService) GetApprovalHistory(clientId *uuid.UUID, articleID *uint, userID *uint, filters map[string]interface{}) (logs []*entity.ArticleApprovalStepLogs, err error) { + // This method signature doesn't match repository, return empty slice for now + return []*entity.ArticleApprovalStepLogs{}, nil +} + +func (_i *articleApprovalStepLogsService) GetApprovalStats(clientId *uuid.UUID, filters map[string]interface{}) (stats map[string]interface{}, err error) { + // This method is not implemented in repository, return empty map for now + return make(map[string]interface{}), nil +} + +func (_i *articleApprovalStepLogsService) GetUserWorkload(clientId *uuid.UUID, userID uint, includeStats bool) (workload map[string]interface{}, err error) { + // This method is not implemented in repository, return empty map for now + return make(map[string]interface{}), nil +} + +func (_i *articleApprovalStepLogsService) ValidateStepLog(clientId *uuid.UUID, log *entity.ArticleApprovalStepLogs) (isValid bool, errors []string, err error) { + var validationErrors []string + + // Validate required fields + if log.ApprovalFlowId == 0 { + validationErrors = append(validationErrors, "approval flow ID is required") + } + + // Note: WorkflowStepID field is not available in current entity structure + // This validation would need to be implemented when entity is updated + + // Note: ApprovalStatusID field is not available in current entity structure + // This validation would need to be implemented when entity is updated + + // Validate message length if provided + if log.Message != nil && len(*log.Message) > 1000 { + validationErrors = append(validationErrors, "message must not exceed 1000 characters") + } + + // Note: DueDate field is not available in current entity structure + // This validation would need to be implemented when entity is updated + + return len(validationErrors) == 0, validationErrors, nil +} + +func (_i *articleApprovalStepLogsService) CanProcessApproval(clientId *uuid.UUID, logID uint, userID uint) (canProcess bool, reason string, err error) { + // Get the step log + log, err := _i.ArticleApprovalStepLogsRepository.FindOne(clientId, logID) + if err != nil { + return false, "", err + } + if log == nil { + return false, "step log not found", nil + } + + // Check if already processed (using ProcessedAt field) + if !log.ProcessedAt.IsZero() { + return false, "approval already processed", nil + } + + // Check if user has permission to approve this step + // This would require checking user roles against step approver role + // For now, we'll allow any user to process + // TODO: Implement proper role-based authorization + + // Note: DueDate field is not available in current entity structure + // This check would need to be implemented when entity is updated + + return true, "", nil +} diff --git a/app/module/article_approvals/article_approvals.module.go b/app/module/article_approvals/article_approvals.module.go new file mode 100644 index 0000000..02ad5bf --- /dev/null +++ b/app/module/article_approvals/article_approvals.module.go @@ -0,0 +1,53 @@ +package article_approvals + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/article_approvals/controller" + "web-qudo-be/app/module/article_approvals/repository" + "web-qudo-be/app/module/article_approvals/service" +) + +// struct of ArticleApprovalsRouter +type ArticleApprovalsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of ArticleApprovals module +var NewArticleApprovalsModule = fx.Options( + // register repository of ArticleApprovals module + fx.Provide(repository.NewArticleApprovalsRepository), + + // register service of ArticleApprovals module + fx.Provide(service.NewArticleApprovalsService), + + // register controller of ArticleApprovals module + fx.Provide(controller.NewController), + + // register router of ArticleApprovals module + fx.Provide(NewArticleApprovalsRouter), +) + +// init ArticleApprovalsRouter +func NewArticleApprovalsRouter(fiber *fiber.App, controller *controller.Controller) *ArticleApprovalsRouter { + return &ArticleApprovalsRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of ArticleApprovals module +func (_i *ArticleApprovalsRouter) RegisterArticleApprovalsRoutes() { + // define controllers + articleApprovalsController := _i.Controller.ArticleApprovals + + // define routes + _i.App.Route("/article-approvals", func(router fiber.Router) { + router.Get("/", articleApprovalsController.All) + router.Get("/:id", articleApprovalsController.Show) + router.Post("/", articleApprovalsController.Save) + router.Put("/:id", articleApprovalsController.Update) + router.Delete("/:id", articleApprovalsController.Delete) + }) +} diff --git a/app/module/article_approvals/controller/article_approvals.controller.go b/app/module/article_approvals/controller/article_approvals.controller.go new file mode 100644 index 0000000..7b1a765 --- /dev/null +++ b/app/module/article_approvals/controller/article_approvals.controller.go @@ -0,0 +1,206 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/article_approvals/request" + "web-qudo-be/app/module/article_approvals/service" + "web-qudo-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type articleApprovalsController struct { + articleApprovalsService service.ArticleApprovalsService + Log zerolog.Logger +} + +type ArticleApprovalsController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewArticleApprovalsController(articleApprovalsService service.ArticleApprovalsService, log zerolog.Logger) ArticleApprovalsController { + return &articleApprovalsController{ + articleApprovalsService: articleApprovalsService, + Log: log, + } +} + +// All get all ArticleApprovals +// @Summary Get all ArticleApprovals +// @Description API for getting all ArticleApprovals +// @Tags ArticleApprovals +// @Security Bearer +// @Param req query request.ArticleApprovalsQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approvals [get] +func (_i *articleApprovalsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.ArticleApprovalsQueryRequestContext{ + ArticleId: c.Query("articleId"), + ApprovalBy: c.Query("approvalBy"), + StatusId: c.Query("statusId"), + Message: c.Query("message"), + ApprovalAtLevel: c.Query("approvalAtLevel"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + articleApprovalsData, paging, err := _i.articleApprovalsService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleApprovals list successfully retrieved"}, + Data: articleApprovalsData, + Meta: paging, + }) +} + +// Show get one ArticleApprovals +// @Summary Get one ArticleApprovals +// @Description API for getting one ArticleApprovals +// @Tags ArticleApprovals +// @Security Bearer +// @Param id path int true "ArticleApprovals ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approvals/{id} [get] +func (_i *articleApprovalsController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + articleApprovalsData, err := _i.articleApprovalsService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleApprovals successfully retrieved"}, + Data: articleApprovalsData, + }) +} + +// Save create ArticleApprovals +// @Summary Create ArticleApprovals +// @Description API for create ArticleApprovals +// @Tags ArticleApprovals +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.ArticleApprovalsCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approvals [post] +func (_i *articleApprovalsController) Save(c *fiber.Ctx) error { + req := new(request.ArticleApprovalsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + dataResult, err := _i.articleApprovalsService.Save(clientId, *req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleApprovals successfully created"}, + Data: dataResult, + }) +} + +// Update update ArticleApprovals +// @Summary update ArticleApprovals +// @Description API for update ArticleApprovals +// @Tags ArticleApprovals +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.ArticleApprovalsUpdateRequest true "Required payload" +// @Param id path int true "ArticleApprovals ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approvals/{id} [put] +func (_i *articleApprovalsController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ArticleApprovalsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.articleApprovalsService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleApprovals successfully updated"}, + }) +} + +// Delete delete ArticleApprovals +// @Summary delete ArticleApprovals +// @Description API for delete ArticleApprovals +// @Tags ArticleApprovals +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "ArticleApprovals ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approvals/{id} [delete] +func (_i *articleApprovalsController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.articleApprovalsService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleApprovals successfully deleted"}, + }) +} diff --git a/app/module/article_approvals/controller/controller.go b/app/module/article_approvals/controller/controller.go new file mode 100644 index 0000000..d369eb2 --- /dev/null +++ b/app/module/article_approvals/controller/controller.go @@ -0,0 +1,16 @@ +package controller + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/article_approvals/service" +) + +type Controller struct { + ArticleApprovals ArticleApprovalsController +} + +func NewController(ArticleApprovalsService service.ArticleApprovalsService, log zerolog.Logger) *Controller { + return &Controller{ + ArticleApprovals: NewArticleApprovalsController(ArticleApprovalsService, log), + } +} diff --git a/app/module/article_approvals/mapper/article_approvals.mapper.go b/app/module/article_approvals/mapper/article_approvals.mapper.go new file mode 100644 index 0000000..c7d76a4 --- /dev/null +++ b/app/module/article_approvals/mapper/article_approvals.mapper.go @@ -0,0 +1,21 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/article_approvals/response" +) + +func ArticleApprovalsResponseMapper(articleApprovalsReq *entity.ArticleApprovals) (articleApprovalsRes *res.ArticleApprovalsResponse) { + if articleApprovalsReq != nil { + articleApprovalsRes = &res.ArticleApprovalsResponse{ + ID: articleApprovalsReq.ID, + ArticleId: articleApprovalsReq.ArticleId, + ApprovalBy: articleApprovalsReq.ApprovalBy, + StatusId: articleApprovalsReq.StatusId, + Message: articleApprovalsReq.Message, + ApprovalAtLevel: articleApprovalsReq.ApprovalAtLevel, + CreatedAt: articleApprovalsReq.CreatedAt, + } + } + return articleApprovalsRes +} diff --git a/app/module/article_approvals/repository/article_approvals.repository.go b/app/module/article_approvals/repository/article_approvals.repository.go new file mode 100644 index 0000000..6f507ef --- /dev/null +++ b/app/module/article_approvals/repository/article_approvals.repository.go @@ -0,0 +1,104 @@ +package repository + +import ( + "fmt" + "github.com/rs/zerolog" + "strings" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/article_approvals/request" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" +) + +type articleApprovalsRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ArticleApprovalsRepository define interface of IArticleApprovalsRepository +type ArticleApprovalsRepository interface { + GetAll(req request.ArticleApprovalsQueryRequest) (articleApprovalss []*entity.ArticleApprovals, paging paginator.Pagination, err error) + FindOne(id uint) (articleApprovals *entity.ArticleApprovals, err error) + Create(articleApprovals *entity.ArticleApprovals) (articleApprovalsReturn *entity.ArticleApprovals, err error) + Update(id uint, articleApprovals *entity.ArticleApprovals) (err error) + Delete(id uint) (err error) +} + +func NewArticleApprovalsRepository(db *database.Database, logger zerolog.Logger) ArticleApprovalsRepository { + return &articleApprovalsRepository{ + DB: db, + Log: logger, + } +} + +// implement interface of IArticleApprovalsRepository +func (_i *articleApprovalsRepository) GetAll(req request.ArticleApprovalsQueryRequest) (articleApprovalss []*entity.ArticleApprovals, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ArticleApprovals{}) + if req.ArticleId != nil { + query = query.Where("article_id = ?", req.ArticleId) + } + if req.ApprovalBy != nil { + query = query.Where("approval_by = ?", req.ApprovalBy) + } + if req.StatusId != nil { + query = query.Where("status_id = ?", req.StatusId) + } + if req.Message != nil && *req.Message != "" { + message := strings.ToLower(*req.Message) + query = query.Where("LOWER(message) LIKE ?", "%"+strings.ToLower(message)+"%") + } + if req.ApprovalAtLevel != nil { + query = query.Where("approval_at_level = ?", req.ApprovalAtLevel) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articleApprovalss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *articleApprovalsRepository) FindOne(id uint) (articleApprovals *entity.ArticleApprovals, err error) { + if err := _i.DB.DB.First(&articleApprovals, id).Error; err != nil { + return nil, err + } + + return articleApprovals, nil +} + +func (_i *articleApprovalsRepository) Create(articleApprovals *entity.ArticleApprovals) (articleApprovalsReturn *entity.ArticleApprovals, err error) { + result := _i.DB.DB.Create(articleApprovals) + return articleApprovals, result.Error +} + +func (_i *articleApprovalsRepository) Update(id uint, articleApprovals *entity.ArticleApprovals) (err error) { + articleApprovalsMap, err := utilSvc.StructToMap(articleApprovals) + if err != nil { + return err + } + return _i.DB.DB.Model(&entity.ArticleApprovals{}). + Where(&entity.ArticleApprovals{ID: id}). + Updates(articleApprovalsMap).Error +} + +func (_i *articleApprovalsRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.ArticleApprovals{}, id).Error +} diff --git a/app/module/article_approvals/request/article_approvals.request.go b/app/module/article_approvals/request/article_approvals.request.go new file mode 100644 index 0000000..46644ea --- /dev/null +++ b/app/module/article_approvals/request/article_approvals.request.go @@ -0,0 +1,92 @@ +package request + +import ( + "strconv" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type ArticleApprovalsGeneric interface { + ToEntity() +} + +type ArticleApprovalsQueryRequest struct { + ArticleId *int `json:"articleId"` + ApprovalBy *int `json:"approvalBy"` + StatusId *int `json:"statusId"` + Message *string `json:"message"` + ApprovalAtLevel *int `json:"approvalAtLevel"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ArticleApprovalsCreateRequest struct { + ArticleId uint `json:"articleId" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + Message string `json:"message" validate:"required"` +} + +func (req ArticleApprovalsCreateRequest) ToEntity() *entity.ArticleApprovals { + return &entity.ArticleApprovals{ + ArticleId: req.ArticleId, + StatusId: req.StatusId, + Message: req.Message, + } +} + +type ArticleApprovalsUpdateRequest struct { + ID uint `json:"id" validate:"required"` + ArticleId uint `json:"articleId" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + Message string `json:"message" validate:"required"` +} + +func (req ArticleApprovalsUpdateRequest) ToEntity() *entity.ArticleApprovals { + return &entity.ArticleApprovals{ + ID: req.ID, + ArticleId: req.ArticleId, + StatusId: req.StatusId, + Message: req.Message, + } +} + +type ArticleApprovalsQueryRequestContext struct { + ArticleId string `json:"articleId"` + ApprovalBy string `json:"approvalBy"` + StatusId string `json:"statusId"` + Message string `json:"message"` + ApprovalAtLevel string `json:"approvalAtLevel"` +} + +func (req ArticleApprovalsQueryRequestContext) ToParamRequest() ArticleApprovalsQueryRequest { + var request ArticleApprovalsQueryRequest + + if articleIdStr := req.ArticleId; articleIdStr != "" { + articleId, err := strconv.Atoi(articleIdStr) + if err == nil { + request.ArticleId = &articleId + } + } + if approvalByStr := req.ApprovalBy; approvalByStr != "" { + approvalBy, err := strconv.Atoi(approvalByStr) + if err == nil { + request.ApprovalBy = &approvalBy + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + if message := req.Message; message != "" { + request.Message = &message + } + if approvalAtLevelStr := req.ApprovalAtLevel; approvalAtLevelStr != "" { + approvalAtLevel, err := strconv.Atoi(approvalAtLevelStr) + if err == nil { + request.ApprovalAtLevel = &approvalAtLevel + } + } + + return request +} diff --git a/app/module/article_approvals/response/article_approvals.response.go b/app/module/article_approvals/response/article_approvals.response.go new file mode 100644 index 0000000..4b56b0a --- /dev/null +++ b/app/module/article_approvals/response/article_approvals.response.go @@ -0,0 +1,13 @@ +package response + +import "time" + +type ArticleApprovalsResponse struct { + ID uint `json:"id"` + ArticleId uint `json:"articleId"` + ApprovalBy uint `json:"approvalBy"` + StatusId int `json:"statusId"` + Message string `json:"message"` + ApprovalAtLevel *int `json:"approvalAtLevel"` + CreatedAt time.Time `json:"createdAt"` +} diff --git a/app/module/article_approvals/service/article_approvals.service.go b/app/module/article_approvals/service/article_approvals.service.go new file mode 100644 index 0000000..6e740e1 --- /dev/null +++ b/app/module/article_approvals/service/article_approvals.service.go @@ -0,0 +1,96 @@ +package service + +import ( + "github.com/google/uuid" + "github.com/rs/zerolog" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/article_approvals/mapper" + "web-qudo-be/app/module/article_approvals/repository" + "web-qudo-be/app/module/article_approvals/request" + "web-qudo-be/app/module/article_approvals/response" + articlesService "web-qudo-be/app/module/articles/service" + usersRepository "web-qudo-be/app/module/users/repository" + "web-qudo-be/utils/paginator" + + utilSvc "web-qudo-be/utils/service" +) + +// ArticleApprovalsService +type articleApprovalsService struct { + Repo repository.ArticleApprovalsRepository + UsersRepo usersRepository.UsersRepository + ArticlesService articlesService.ArticlesService + Log zerolog.Logger +} + +// ArticleApprovalsService define interface of IArticleApprovalsService +type ArticleApprovalsService interface { + All(req request.ArticleApprovalsQueryRequest) (articleApprovals []*response.ArticleApprovalsResponse, paging paginator.Pagination, err error) + Show(id uint) (articleApprovals *response.ArticleApprovalsResponse, err error) + Save(clientId *uuid.UUID, req request.ArticleApprovalsCreateRequest, authToken string) (articleApprovals *entity.ArticleApprovals, err error) + Update(id uint, req request.ArticleApprovalsUpdateRequest) (err error) + Delete(id uint) error +} + +// NewArticleApprovalsService init ArticleApprovalsService +func NewArticleApprovalsService(repo repository.ArticleApprovalsRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository, articlesSvc articlesService.ArticlesService) ArticleApprovalsService { + + return &articleApprovalsService{ + Repo: repo, + Log: log, + UsersRepo: usersRepo, + ArticlesService: articlesSvc, + } +} + +// All implement interface of ArticleApprovalsService +func (_i *articleApprovalsService) All(req request.ArticleApprovalsQueryRequest) (articleApprovalss []*response.ArticleApprovalsResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + articleApprovalss = append(articleApprovalss, mapper.ArticleApprovalsResponseMapper(result)) + } + + return +} + +func (_i *articleApprovalsService) Show(id uint) (articleApprovals *response.ArticleApprovalsResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.ArticleApprovalsResponseMapper(result), nil +} + +func (_i *articleApprovalsService) Save(clientId *uuid.UUID, req request.ArticleApprovalsCreateRequest, authToken string) (articleApprovals *entity.ArticleApprovals, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + newReq := req.ToEntity() + + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + newReq.ApprovalBy = createdBy.ID + newReq.ApprovalAtLevel = &createdBy.UserLevel.LevelNumber + + approvalByUserLevelId := createdBy.UserLevelId + approvalParentLevelId := createdBy.UserLevel.ParentLevelId + + err = _i.ArticlesService.UpdateApproval(clientId, newReq.ArticleId, newReq.StatusId, int(approvalByUserLevelId), *newReq.ApprovalAtLevel, *approvalParentLevelId) + if err != nil { + return nil, err + } + + return _i.Repo.Create(newReq) +} + +func (_i *articleApprovalsService) Update(id uint, req request.ArticleApprovalsUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *articleApprovalsService) Delete(id uint) error { + return _i.Repo.Delete(id) +} diff --git a/app/module/article_categories/article_categories.module.go b/app/module/article_categories/article_categories.module.go new file mode 100644 index 0000000..4dec073 --- /dev/null +++ b/app/module/article_categories/article_categories.module.go @@ -0,0 +1,57 @@ +package article_categories + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/article_categories/controller" + "web-qudo-be/app/module/article_categories/repository" + "web-qudo-be/app/module/article_categories/service" +) + +// struct of ArticleCategoriesRouter +type ArticleCategoriesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of ArticleCategories module +var NewArticleCategoriesModule = fx.Options( + // register repository of ArticleCategories module + fx.Provide(repository.NewArticleCategoriesRepository), + + // register service of ArticleCategories module + fx.Provide(service.NewArticleCategoriesService), + + // register controller of ArticleCategories module + fx.Provide(controller.NewController), + + // register router of ArticleCategories module + fx.Provide(NewArticleCategoriesRouter), +) + +// init ArticleCategoriesRouter +func NewArticleCategoriesRouter(fiber *fiber.App, controller *controller.Controller) *ArticleCategoriesRouter { + return &ArticleCategoriesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of ArticleCategories module +func (_i *ArticleCategoriesRouter) RegisterArticleCategoriesRoutes() { + // define controllers + articleCategoriesController := _i.Controller.ArticleCategories + + // define routes + _i.App.Route("/article-categories", func(router fiber.Router) { + router.Get("/", articleCategoriesController.All) + router.Get("/:id", articleCategoriesController.Show) + router.Get("/old/:id", articleCategoriesController.ShowByOldId) + router.Get("/slug/:slug", articleCategoriesController.ShowBySlug) + router.Post("/", articleCategoriesController.Save) + router.Put("/:id", articleCategoriesController.Update) + router.Post("/thumbnail/:id", articleCategoriesController.SaveThumbnail) + router.Get("/thumbnail/viewer/:id", articleCategoriesController.Viewer) + router.Delete("/:id", articleCategoriesController.Delete) + }) +} diff --git a/app/module/article_categories/controller/article_categories.controller.go b/app/module/article_categories/controller/article_categories.controller.go new file mode 100644 index 0000000..8cd6964 --- /dev/null +++ b/app/module/article_categories/controller/article_categories.controller.go @@ -0,0 +1,321 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/article_categories/request" + "web-qudo-be/app/module/article_categories/service" + "web-qudo-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type articleCategoriesController struct { + articleCategoriesService service.ArticleCategoriesService +} + +type ArticleCategoriesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + ShowByOldId(c *fiber.Ctx) error + ShowBySlug(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + SaveThumbnail(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + Viewer(c *fiber.Ctx) error +} + +func NewArticleCategoriesController(articleCategoriesService service.ArticleCategoriesService) ArticleCategoriesController { + return &articleCategoriesController{ + articleCategoriesService: articleCategoriesService, + } +} + +// All ArticleCategories +// @Summary Get all ArticleCategories +// @Description API for getting all ArticleCategories +// @Tags Article Categories +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param req query request.ArticleCategoriesQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories [get] +func (_i *articleCategoriesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + clientId := middleware.GetClientID(c) + + reqContext := request.ArticleCategoriesQueryRequestContext{ + Title: c.Query("title"), + Description: c.Query("description"), + ParentId: c.Query("parentId"), + IsPublish: c.Query("isPublish"), + StatusId: c.Query("statusId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + articleCategoriesData, paging, err := _i.articleCategoriesService.All(clientId, req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleCategories list successfully retrieved"}, + Data: articleCategoriesData, + Meta: paging, + }) +} + +// Show ArticleCategories +// @Summary Get one ArticleCategories +// @Description API for getting one ArticleCategories +// @Tags Article Categories +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param id path int true "ArticleCategories ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories/{id} [get] +func (_i *articleCategoriesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + clientId := middleware.GetClientID(c) + + articleCategoriesData, err := _i.articleCategoriesService.Show(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleCategories successfully retrieved"}, + Data: articleCategoriesData, + }) +} + +// ShowByOldId ArticleCategories +// @Summary Get one ArticleCategories +// @Description API for getting one ArticleCategories +// @Tags Article Categories +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param id path int true "ArticleCategories Old ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories/old/{id} [get] +func (_i *articleCategoriesController) ShowByOldId(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + clientId := middleware.GetClientID(c) + + articleCategoriesData, err := _i.articleCategoriesService.ShowByOldId(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleCategories successfully retrieved"}, + Data: articleCategoriesData, + }) +} + +// ShowBySlug ArticleCategories +// @Summary Get one ArticleCategories +// @Description API for getting one ArticleCategories +// @Tags Article Categories +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param slug path string true "ArticleCategories Slug" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories/slug/{slug} [get] +func (_i *articleCategoriesController) ShowBySlug(c *fiber.Ctx) error { + slug := c.Params("slug") + clientId := middleware.GetClientID(c) + + articleCategoriesData, err := _i.articleCategoriesService.ShowBySlug(clientId, slug) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleCategories successfully retrieved"}, + Data: articleCategoriesData, + }) +} + +// Save ArticleCategories +// @Summary Create ArticleCategories +// @Description API for create ArticleCategories +// @Tags Article Categories +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.ArticleCategoriesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories [post] +func (_i *articleCategoriesController) Save(c *fiber.Ctx) error { + req := new(request.ArticleCategoriesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + clientId := middleware.GetClientID(c) + + dataResult, err := _i.articleCategoriesService.Save(clientId, *req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleCategories successfully created"}, + Data: dataResult, + }) +} + +// SaveThumbnail ArticleCategories +// @Summary Upload ArticleCategories Thumbnail +// @Description API for Upload ArticleCategories Thumbnail +// @Tags Article Categories +// @Security Bearer +// @Produce json +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param files formData file true "Upload thumbnail" +// @Param id path int true "ArticleCategories ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories/thumbnail/{id} [post] +func (_i *articleCategoriesController) SaveThumbnail(c *fiber.Ctx) error { + clientId := middleware.GetClientID(c) + err := _i.articleCategoriesService.SaveThumbnail(clientId, c) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Thumbnail of ArticleCategories successfully created"}, + }) +} + +// Update ArticleCategories +// @Summary update ArticleCategories +// @Description API for update ArticleCategories +// @Tags Article Categories +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.ArticleCategoriesUpdateRequest true "Required payload" +// @Param id path int true "ArticleCategories ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories/{id} [put] +func (_i *articleCategoriesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ArticleCategoriesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + clientId := middleware.GetClientID(c) + err = _i.articleCategoriesService.Update(clientId, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleCategories successfully updated"}, + }) +} + +// Delete ArticleCategories +// @Summary delete ArticleCategories +// @Description API for delete ArticleCategories +// @Tags Article Categories +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "ArticleCategories ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories/{id} [delete] +func (_i *articleCategoriesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + clientId := middleware.GetClientID(c) + err = _i.articleCategoriesService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleCategories successfully deleted"}, + }) +} + +// Viewer ArticleCategories +// @Summary Viewer ArticleCategories +// @Description API for View Thumbnail of ArticleCategories +// @Tags Article Categories +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param id path string true "ArticleCategories ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories/thumbnail/viewer/{id} [get] +func (_i *articleCategoriesController) Viewer(c *fiber.Ctx) error { + return _i.articleCategoriesService.Viewer(c) +} diff --git a/app/module/article_categories/controller/controller.go b/app/module/article_categories/controller/controller.go new file mode 100644 index 0000000..408d072 --- /dev/null +++ b/app/module/article_categories/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "web-qudo-be/app/module/article_categories/service" + +type Controller struct { + ArticleCategories ArticleCategoriesController +} + +func NewController(ArticleCategoriesService service.ArticleCategoriesService) *Controller { + return &Controller{ + ArticleCategories: NewArticleCategoriesController(ArticleCategoriesService), + } +} diff --git a/app/module/article_categories/mapper/article_categories.mapper.go b/app/module/article_categories/mapper/article_categories.mapper.go new file mode 100644 index 0000000..855eaa3 --- /dev/null +++ b/app/module/article_categories/mapper/article_categories.mapper.go @@ -0,0 +1,39 @@ +package mapper + +import ( + "strconv" + "strings" + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/article_categories/response" +) + +func ArticleCategoriesResponseMapper(articleCategoriesReq *entity.ArticleCategories, host string) (articleCategoriesRes *res.ArticleCategoriesResponse) { + if articleCategoriesReq != nil { + articleCategoriesRes = &res.ArticleCategoriesResponse{ + ID: articleCategoriesReq.ID, + Title: articleCategoriesReq.Title, + Description: articleCategoriesReq.Description, + Slug: articleCategoriesReq.Slug, + ThumbnailPath: articleCategoriesReq.ThumbnailPath, + ParentId: articleCategoriesReq.ParentId, + OldCategoryId: articleCategoriesReq.OldCategoryId, + CreatedById: articleCategoriesReq.CreatedById, + StatusId: articleCategoriesReq.StatusId, + IsPublish: articleCategoriesReq.IsPublish, + PublishedAt: articleCategoriesReq.PublishedAt, + IsActive: articleCategoriesReq.IsActive, + CreatedAt: articleCategoriesReq.CreatedAt, + UpdatedAt: articleCategoriesReq.UpdatedAt, + } + + if articleCategoriesReq.Tags != nil { + tagsValue := *articleCategoriesReq.Tags + articleCategoriesRes.Tags = strings.Split(tagsValue, ",") + } + + if articleCategoriesRes.ThumbnailPath != nil { + articleCategoriesRes.ThumbnailUrl = host + "/article-categories/thumbnail/viewer/" + strconv.Itoa(int(articleCategoriesReq.ID)) + } + } + return articleCategoriesRes +} diff --git a/app/module/article_categories/repository/article_categories.repository.go b/app/module/article_categories/repository/article_categories.repository.go new file mode 100644 index 0000000..142895b --- /dev/null +++ b/app/module/article_categories/repository/article_categories.repository.go @@ -0,0 +1,172 @@ +package repository + +import ( + "fmt" + "github.com/google/uuid" + "github.com/rs/zerolog" + "strings" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/article_categories/request" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" +) + +type articleCategoriesRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ArticleCategoriesRepository define interface of IArticleCategoriesRepository +type ArticleCategoriesRepository interface { + GetAll(clientId *uuid.UUID, req request.ArticleCategoriesQueryRequest) (articleCategoriess []*entity.ArticleCategories, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (articleCategories *entity.ArticleCategories, err error) + FindOneByOldId(clientId *uuid.UUID, id uint) (articleCategories *entity.ArticleCategories, err error) + FindOneBySlug(clientId *uuid.UUID, slug string) (articleCategories *entity.ArticleCategories, err error) + Create(articleCategories *entity.ArticleCategories) (articleCategoriesReturn *entity.ArticleCategories, err error) + Update(clientId *uuid.UUID, id uint, articleCategories *entity.ArticleCategories) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) +} + +func NewArticleCategoriesRepository(db *database.Database, log zerolog.Logger) ArticleCategoriesRepository { + return &articleCategoriesRepository{ + DB: db, + Log: log, + } +} + +// implement interface of IArticleCategoriesRepository +func (_i *articleCategoriesRepository) GetAll(clientId *uuid.UUID, req request.ArticleCategoriesQueryRequest) (articleCategoriess []*entity.ArticleCategories, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ArticleCategories{}) + + // Add ClientId filter + if clientId != nil { + query = query.Where("article_categories.client_id = ?", clientId) + } + + if req.UserLevelId != nil { + query = _i.DB.DB.Model(&entity.ArticleCategories{}). + Joins("LEFT JOIN users ON article_categories.created_by_id = users.id"). + Joins("LEFT JOIN user_levels ON users.user_level_id = user_levels.id"). + Where("user_levels.id = ? or user_levels.parent_level_id = ?", *req.UserLevelId, *req.UserLevelId) + + // Add ClientId filter for joined query + if clientId != nil { + query = query.Where("article_categories.client_id = ?", clientId) + } + } + + query = query.Where("article_categories.is_active = ?", true) + + if req.Title != nil && *req.Title != "" { + title := strings.ToLower(*req.Title) + query = query.Where("LOWER(article_categories.title) LIKE ?", "%"+strings.ToLower(title)+"%") + } + if req.Description != nil && *req.Description != "" { + description := strings.ToLower(*req.Description) + query = query.Where("LOWER(article_categories.description) LIKE ?", "%"+strings.ToLower(description)+"%") + } + if req.ParentId != nil { + query = query.Where("article_categories.parent_id = ?", req.ParentId) + } + if req.IsPublish != nil { + query = query.Where("article_categories.is_publish = ?", req.IsPublish) + } + if req.StatusId != nil { + query = query.Where("article_categories.status_id = ?", req.StatusId) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articleCategoriess).Error + + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *articleCategoriesRepository) FindOne(clientId *uuid.UUID, id uint) (articleCategories *entity.ArticleCategories, err error) { + query := _i.DB.DB.Where("id = ?", id) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err := query.First(&articleCategories).Error; err != nil { + return nil, err + } + + return articleCategories, nil +} + +func (_i *articleCategoriesRepository) FindOneByOldId(clientId *uuid.UUID, id uint) (articleCategories *entity.ArticleCategories, err error) { + query := _i.DB.DB.Where("old_category_id = ?", id) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err := query.First(&articleCategories).Error; err != nil { + return nil, err + } + + return articleCategories, nil +} + +func (_i *articleCategoriesRepository) FindOneBySlug(clientId *uuid.UUID, slug string) (articleCategories *entity.ArticleCategories, err error) { + query := _i.DB.DB.Where("slug = ?", slug) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err := query.First(&articleCategories).Error; err != nil { + return nil, err + } + + return articleCategories, nil +} + +func (_i *articleCategoriesRepository) Create(articleCategories *entity.ArticleCategories) (articleCategoriesReturn *entity.ArticleCategories, err error) { + result := _i.DB.DB.Create(articleCategories) + return articleCategories, result.Error +} + +func (_i *articleCategoriesRepository) Update(clientId *uuid.UUID, id uint, articleCategories *entity.ArticleCategories) (err error) { + articleCategoriesMap, err := utilSvc.StructToMap(articleCategories) + if err != nil { + return err + } + query := _i.DB.DB.Model(&entity.ArticleCategories{}).Where(&entity.ArticleCategories{ID: id}) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + return query.Updates(articleCategoriesMap).Error +} + +func (_i *articleCategoriesRepository) Delete(clientId *uuid.UUID, id uint) error { + query := _i.DB.DB.Model(&entity.ArticleCategories{}).Where("id = ?", id) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + return query.Delete(&entity.ArticleCategories{}).Error +} diff --git a/app/module/article_categories/request/article_categories.request.go b/app/module/article_categories/request/article_categories.request.go new file mode 100644 index 0000000..d5e64a3 --- /dev/null +++ b/app/module/article_categories/request/article_categories.request.go @@ -0,0 +1,128 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type ArticleCategoriesGeneric interface { + ToEntity() +} + +type ArticleCategoriesQueryRequest struct { + Title *string `json:"title"` + Description *string `json:"description"` + UserLevelId *uint `json:"UserLevelId"` + UserLevelNumber *int `json:"UserLevelNumber"` + ParentId *int `json:"parentId"` + StatusId *int `json:"statusId"` + IsPublish *bool `json:"isPublish"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ArticleCategoriesCreateRequest struct { + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + Tags *string `json:"tags"` + Slug *string `json:"slug"` + CreatedById *uint `json:"createdById"` + ParentId *int `json:"parentId"` + OldCategoryId *uint `json:"oldCategoryId"` +} + +func (req ArticleCategoriesCreateRequest) ToEntity() *entity.ArticleCategories { + return &entity.ArticleCategories{ + Title: req.Title, + Description: req.Description, + Tags: req.Tags, + ParentId: req.ParentId, + Slug: req.Slug, + OldCategoryId: req.OldCategoryId, + StatusId: req.StatusId, + } +} + +type ArticleCategoriesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + Tags *string `json:"tags"` + Slug *string `json:"slug"` + ParentId *int `json:"parentId"` + CreatedById *uint `json:"createdById"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` +} + +func (req ArticleCategoriesUpdateRequest) ToEntity() *entity.ArticleCategories { + return &entity.ArticleCategories{ + ID: req.ID, + Title: req.Title, + Description: req.Description, + ParentId: req.ParentId, + Slug: req.Slug, + Tags: req.Tags, + StatusId: req.StatusId, + IsPublish: req.IsPublish, + PublishedAt: req.PublishedAt, + UpdatedAt: time.Now(), + } +} + +type ArticleCategoriesQueryRequestContext struct { + Title string `json:"title"` + Description string `json:"description"` + ParentId string `json:"parentId"` + StatusId string `json:"statusId"` + IsPublish string `json:"isPublish"` + UserLevelId string `json:"UserLevelId"` + UserLevelNumber string `json:"UserLevelNumber"` +} + +func (req ArticleCategoriesQueryRequestContext) ToParamRequest() ArticleCategoriesQueryRequest { + var request ArticleCategoriesQueryRequest + + if title := req.Title; title != "" { + request.Title = &title + } + if description := req.Description; description != "" { + request.Description = &description + } + if parentIdStr := req.ParentId; parentIdStr != "" { + parentId, err := strconv.Atoi(parentIdStr) + if err == nil { + request.ParentId = &parentId + } + } + if isPublishStr := req.IsPublish; isPublishStr != "" { + isPublish, err := strconv.ParseBool(isPublishStr) + if err == nil { + request.IsPublish = &isPublish + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + if userLevelIdStr := req.UserLevelId; userLevelIdStr != "" { + userLevelId, err := strconv.Atoi(userLevelIdStr) + if err == nil { + userLevelIdUint := uint(userLevelId) + request.UserLevelId = &userLevelIdUint + } + } + if userLevelNumberStr := req.UserLevelNumber; userLevelNumberStr != "" { + userLevelNumber, err := strconv.Atoi(userLevelNumberStr) + if err == nil { + request.UserLevelNumber = &userLevelNumber + } + } + + return request +} diff --git a/app/module/article_categories/response/article_categories.response.go b/app/module/article_categories/response/article_categories.response.go new file mode 100644 index 0000000..27c012e --- /dev/null +++ b/app/module/article_categories/response/article_categories.response.go @@ -0,0 +1,23 @@ +package response + +import "time" + +type ArticleCategoriesResponse struct { + ID uint `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + ThumbnailUrl string `json:"thumbnailUrl"` + Slug *string `json:"slug"` + Tags []string `json:"tags"` + ThumbnailPath *string `json:"thumbnailPath"` + ParentId *int `json:"parentId"` + OldCategoryId *uint `json:"oldCategoryId"` + CreatedById *uint `json:"createdById"` + StatusId int `json:"statusId"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` + IsEnabled *bool `json:"isEnabled"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/article_categories/service/article_categories.service.go b/app/module/article_categories/service/article_categories.service.go new file mode 100644 index 0000000..d763239 --- /dev/null +++ b/app/module/article_categories/service/article_categories.service.go @@ -0,0 +1,290 @@ +package service + +import ( + "context" + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" + "io" + "log" + "math/rand" + "mime" + "path/filepath" + "strconv" + "strings" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/article_categories/mapper" + "web-qudo-be/app/module/article_categories/repository" + "web-qudo-be/app/module/article_categories/request" + "web-qudo-be/app/module/article_categories/response" + usersRepository "web-qudo-be/app/module/users/repository" + config "web-qudo-be/config/config" + minioStorage "web-qudo-be/config/config" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" +) + +// ArticleCategoriesService +type articleCategoriesService struct { + Repo repository.ArticleCategoriesRepository + UsersRepo usersRepository.UsersRepository + MinioStorage *minioStorage.MinioStorage + Log zerolog.Logger + Cfg *config.Config +} + +// ArticleCategoriesService define interface of IArticleCategoriesService +type ArticleCategoriesService interface { + All(clientId *uuid.UUID, req request.ArticleCategoriesQueryRequest, authToken string) (articleCategories []*response.ArticleCategoriesResponse, paging paginator.Pagination, err error) + Show(clientId *uuid.UUID, id uint) (articleCategories *response.ArticleCategoriesResponse, err error) + ShowByOldId(clientId *uuid.UUID, id uint) (articleCategories *response.ArticleCategoriesResponse, err error) + ShowBySlug(clientId *uuid.UUID, slug string) (articleCategories *response.ArticleCategoriesResponse, err error) + Save(clientId *uuid.UUID, req request.ArticleCategoriesCreateRequest, authToken string) (articleCategories *entity.ArticleCategories, err error) + SaveThumbnail(clientId *uuid.UUID, c *fiber.Ctx) (err error) + Update(clientId *uuid.UUID, id uint, req request.ArticleCategoriesUpdateRequest) (err error) + Delete(clientId *uuid.UUID, id uint) error + Viewer(c *fiber.Ctx) error +} + +// NewArticleCategoriesService init ArticleCategoriesService +func NewArticleCategoriesService(repo repository.ArticleCategoriesRepository, usersRepo usersRepository.UsersRepository, minioStorage *minioStorage.MinioStorage, log zerolog.Logger, cfg *config.Config) ArticleCategoriesService { + + return &articleCategoriesService{ + Repo: repo, + UsersRepo: usersRepo, + MinioStorage: minioStorage, + Log: log, + Cfg: cfg, + } +} + +// All implement interface of ArticleCategoriesService +func (_i *articleCategoriesService) All(clientId *uuid.UUID, req request.ArticleCategoriesQueryRequest, authToken string) (articleCategoriess []*response.ArticleCategoriesResponse, paging paginator.Pagination, err error) { + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if createdBy != nil { + if createdBy.UserLevel.LevelNumber > 1 { + req.UserLevelId = &createdBy.UserLevelId + } + } + + results, paging, err := _i.Repo.GetAll(clientId, req) + if err != nil { + return + } + + host := _i.Cfg.App.Domain + for _, result := range results { + articleCategoriess = append(articleCategoriess, mapper.ArticleCategoriesResponseMapper(result, host)) + } + + return +} + +func (_i *articleCategoriesService) Show(clientId *uuid.UUID, id uint) (articleCategories *response.ArticleCategoriesResponse, err error) { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return nil, err + } + host := _i.Cfg.App.Domain + return mapper.ArticleCategoriesResponseMapper(result, host), nil +} + +func (_i *articleCategoriesService) ShowByOldId(clientId *uuid.UUID, id uint) (articleCategories *response.ArticleCategoriesResponse, err error) { + result, err := _i.Repo.FindOneByOldId(clientId, id) + if err != nil { + return nil, err + } + host := _i.Cfg.App.Domain + return mapper.ArticleCategoriesResponseMapper(result, host), nil +} + +func (_i *articleCategoriesService) ShowBySlug(clientId *uuid.UUID, slug string) (articleCategories *response.ArticleCategoriesResponse, err error) { + result, err := _i.Repo.FindOneBySlug(clientId, slug) + if err != nil { + return nil, err + } + host := _i.Cfg.App.Domain + return mapper.ArticleCategoriesResponseMapper(result, host), nil +} + +func (_i *articleCategoriesService) Save(clientId *uuid.UUID, req request.ArticleCategoriesCreateRequest, authToken string) (articleCategories *entity.ArticleCategories, err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + if clientId != nil { + newReq.ClientId = clientId + } + + if req.CreatedById != nil { + createdBy, err := _i.UsersRepo.FindOne(clientId, *req.CreatedById) + if err != nil { + return nil, err + } + newReq.CreatedById = &createdBy.ID + } else { + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + newReq.CreatedById = &createdBy.ID + } + + return _i.Repo.Create(newReq) +} + +func (_i *articleCategoriesService) SaveThumbnail(clientId *uuid.UUID, c *fiber.Ctx) (err error) { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + + form, err := c.MultipartForm() + if err != nil { + return err + } + files := form.File["files"] + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + // Iterasi semua file yang diunggah + for _, file := range files { + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop1"). + Interface("data", file).Msg("") + + src, err := file.Open() + if err != nil { + return err + } + defer src.Close() + + filename := filepath.Base(file.Filename) + filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + extension := filepath.Ext(file.Filename)[1:] + + rand.New(rand.NewSource(time.Now().UnixNano())) + randUniqueId := rand.Intn(1000000) + + newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId) + newFilename := newFilenameWithoutExt + "." + extension + objectName := "articles/category/thumbnail/" + newFilename + + findCategory, err := _i.Repo.FindOne(clientId, uint(id)) + findCategory.ThumbnailPath = &objectName + err = _i.Repo.Update(clientId, uint(id), findCategory) + if err != nil { + return err + } + + // Upload file ke MinIO + _, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, file.Size, minio.PutObjectOptions{}) + if err != nil { + return err + } + } + + return +} + +func (_i *articleCategoriesService) Update(clientId *uuid.UUID, id uint, req request.ArticleCategoriesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + newReq := req.ToEntity() + if clientId != nil { + newReq.ClientId = clientId + } + if req.CreatedById != nil { + createdBy, err := _i.UsersRepo.FindOne(clientId, *req.CreatedById) + if err != nil { + return err + } + newReq.CreatedById = &createdBy.ID + } + + return _i.Repo.Update(clientId, id, newReq) +} + +func (_i *articleCategoriesService) Delete(clientId *uuid.UUID, id uint) error { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return err + } + + isActive := false + result.IsActive = &isActive + if clientId != nil { + result.ClientId = clientId + } + return _i.Repo.Update(clientId, id, result) +} + +func (_i *articleCategoriesService) Viewer(c *fiber.Ctx) (err error) { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + result, err := _i.Repo.FindOne(nil, uint(id)) + if err != nil { + return err + } + + if result.ThumbnailPath == nil { + return nil + } + + ctx := context.Background() + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + objectName := result.ThumbnailPath + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:Uploads"). + Interface("data", objectName).Msg("") + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + fileContent, err := minioClient.GetObject(ctx, bucketName, *objectName, minio.GetObjectOptions{}) + if err != nil { + log.Fatalln(err) + } + defer fileContent.Close() + + contentType := mime.TypeByExtension("." + getFileExtension(*objectName)) + if contentType == "" { + contentType = "application/octet-stream" + } + + c.Set("Content-Type", contentType) + + if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil { + return err + } + + return +} + +func getFileExtension(filename string) string { + // split file name + parts := strings.Split(filename, ".") + + // jika tidak ada ekstensi, kembalikan string kosong + if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") { + return "" + } + + // ambil ekstensi terakhir + return parts[len(parts)-1] +} diff --git a/app/module/article_category_details/article_category_details.module.go b/app/module/article_category_details/article_category_details.module.go new file mode 100644 index 0000000..f82132a --- /dev/null +++ b/app/module/article_category_details/article_category_details.module.go @@ -0,0 +1,53 @@ +package article_category_details + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/article_category_details/controller" + "web-qudo-be/app/module/article_category_details/repository" + "web-qudo-be/app/module/article_category_details/service" +) + +// struct of ArticleCategoryDetailsRouter +type ArticleCategoryDetailsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of ArticleCategoryDetails module +var NewArticleCategoryDetailsModule = fx.Options( + // register repository of ArticleCategoryDetails module + fx.Provide(repository.NewArticleCategoryDetailsRepository), + + // register service of ArticleCategoryDetails module + fx.Provide(service.NewArticleCategoryDetailsService), + + // register controller of ArticleCategoryDetails module + fx.Provide(controller.NewController), + + // register router of ArticleCategoryDetails module + fx.Provide(NewArticleCategoryDetailsRouter), +) + +// init ArticleCategoryDetailsRouter +func NewArticleCategoryDetailsRouter(fiber *fiber.App, controller *controller.Controller) *ArticleCategoryDetailsRouter { + return &ArticleCategoryDetailsRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of ArticleCategoryDetails module +func (_i *ArticleCategoryDetailsRouter) RegisterArticleCategoryDetailsRoutes() { + // define controllers + articleCategoryDetailsController := _i.Controller.ArticleCategoryDetails + + // define routes + _i.App.Route("/article-category-details", func(router fiber.Router) { + router.Get("/", articleCategoryDetailsController.All) + router.Get("/:id", articleCategoryDetailsController.Show) + router.Post("/", articleCategoryDetailsController.Save) + router.Put("/:id", articleCategoryDetailsController.Update) + router.Delete("/:id", articleCategoryDetailsController.Delete) + }) +} diff --git a/app/module/article_category_details/controller/article_category_details.controller.go b/app/module/article_category_details/controller/article_category_details.controller.go new file mode 100644 index 0000000..1ba255f --- /dev/null +++ b/app/module/article_category_details/controller/article_category_details.controller.go @@ -0,0 +1,185 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/module/article_category_details/request" + "web-qudo-be/app/module/article_category_details/service" + "web-qudo-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type articleCategoryDetailsController struct { + articleCategoryDetailsService service.ArticleCategoryDetailsService +} + +type ArticleCategoryDetailsController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewArticleCategoryDetailsController(articleCategoryDetailsService service.ArticleCategoryDetailsService) ArticleCategoryDetailsController { + return &articleCategoryDetailsController{ + articleCategoryDetailsService: articleCategoryDetailsService, + } +} + +// All get all ArticleCategoryDetails +// @Summary Get all ArticleCategoryDetails +// @Description API for getting all ArticleCategoryDetails +// @Tags Untags +// @Security Bearer +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /article-category-details [get] +func (_i *articleCategoryDetailsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + var req request.ArticleCategoryDetailsQueryRequest + req.Pagination = paginate + + articleCategoryDetailsData, paging, err := _i.articleCategoryDetailsService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"ArticleCategoryDetails list successfully retrieved"}, + Data: articleCategoryDetailsData, + Meta: paging, + }) +} + +// Show get one ArticleCategoryDetails +// @Summary Get one ArticleCategoryDetails +// @Description API for getting one ArticleCategoryDetails +// @Tags Untags +// @Security Bearer +// @Param id path int true "ArticleCategoryDetails ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /article-category-details/{id} [get] +func (_i *articleCategoryDetailsController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + articleCategoryDetailsData, err := _i.articleCategoryDetailsService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"ArticleCategoryDetails successfully retrieved"}, + Data: articleCategoryDetailsData, + }) +} + +// Save create ArticleCategoryDetails +// @Summary Create ArticleCategoryDetails +// @Description API for create ArticleCategoryDetails +// @Tags Untags +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Body request.ArticleCategoryDetailsCreateRequest +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /article-category-details [post] +func (_i *articleCategoryDetailsController) Save(c *fiber.Ctx) error { + req := new(request.ArticleCategoryDetailsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.articleCategoryDetailsService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"ArticleCategoryDetails successfully created"}, + }) +} + +// Update update ArticleCategoryDetails +// @Summary update ArticleCategoryDetails +// @Description API for update ArticleCategoryDetails +// @Tags Untags +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Body request.ArticleCategoryDetailsUpdateRequest +// @Param id path int true "ArticleCategoryDetails ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /article-category-details/{id} [put] +func (_i *articleCategoryDetailsController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ArticleCategoryDetailsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.articleCategoryDetailsService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"ArticleCategoryDetails successfully updated"}, + }) +} + +// Delete delete ArticleCategoryDetails +// @Summary delete ArticleCategoryDetails +// @Description API for delete ArticleCategoryDetails +// @Tags Untags +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "ArticleCategoryDetails ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /article-category-details/{id} [delete] +func (_i *articleCategoryDetailsController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.articleCategoryDetailsService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"ArticleCategoryDetails successfully deleted"}, + }) +} diff --git a/app/module/article_category_details/controller/controller.go b/app/module/article_category_details/controller/controller.go new file mode 100644 index 0000000..cc6d8dd --- /dev/null +++ b/app/module/article_category_details/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "web-qudo-be/app/module/article_category_details/service" + +type Controller struct { + ArticleCategoryDetails ArticleCategoryDetailsController +} + +func NewController(ArticleCategoryDetailsService service.ArticleCategoryDetailsService) *Controller { + return &Controller{ + ArticleCategoryDetails: NewArticleCategoryDetailsController(ArticleCategoryDetailsService), + } +} diff --git a/app/module/article_category_details/mapper/article_category_details.mapper.go b/app/module/article_category_details/mapper/article_category_details.mapper.go new file mode 100644 index 0000000..030753b --- /dev/null +++ b/app/module/article_category_details/mapper/article_category_details.mapper.go @@ -0,0 +1,21 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity/article_category_details" + res "web-qudo-be/app/module/article_category_details/response" +) + +func ArticleCategoryDetailsResponseMapper(articleCategoryDetailsReq *article_category_details.ArticleCategoryDetails) (articleCategoryDetailsRes *res.ArticleCategoryDetailsResponse) { + if articleCategoryDetailsReq != nil { + + articleCategoryDetailsRes = &res.ArticleCategoryDetailsResponse{ + ID: articleCategoryDetailsReq.ID, + ArticleId: articleCategoryDetailsReq.ArticleId, + CategoryId: articleCategoryDetailsReq.CategoryId, + IsActive: articleCategoryDetailsReq.IsActive, + CreatedAt: articleCategoryDetailsReq.CreatedAt, + UpdatedAt: articleCategoryDetailsReq.UpdatedAt, + } + } + return articleCategoryDetailsRes +} diff --git a/app/module/article_category_details/repository/article_category_details.repository.go b/app/module/article_category_details/repository/article_category_details.repository.go new file mode 100644 index 0000000..b811b2e --- /dev/null +++ b/app/module/article_category_details/repository/article_category_details.repository.go @@ -0,0 +1,78 @@ +package repository + +import ( + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity/article_category_details" + "web-qudo-be/app/module/article_category_details/request" + "web-qudo-be/utils/paginator" +) + +type articleCategoryDetailsRepository struct { + DB *database.Database +} + +// ArticleCategoryDetailsRepository define interface of IArticleCategoryDetailsRepository +type ArticleCategoryDetailsRepository interface { + GetAll(req request.ArticleCategoryDetailsQueryRequest) (articleCategoryDetailss []*article_category_details.ArticleCategoryDetails, paging paginator.Pagination, err error) + FindOne(id uint) (articleCategoryDetails *article_category_details.ArticleCategoryDetails, err error) + FindByArticleId(articleId uint) (articleCategoryDetailss []*article_category_details.ArticleCategoryDetails, err error) + Create(articleCategoryDetails *article_category_details.ArticleCategoryDetails) (err error) + Update(id uint, articleCategoryDetails *article_category_details.ArticleCategoryDetails) (err error) + Delete(id uint) (err error) +} + +func NewArticleCategoryDetailsRepository(db *database.Database) ArticleCategoryDetailsRepository { + return &articleCategoryDetailsRepository{ + DB: db, + } +} + +// implement interface of IArticleCategoryDetailsRepository +func (_i *articleCategoryDetailsRepository) GetAll(req request.ArticleCategoryDetailsQueryRequest) (articleCategoryDetailss []*article_category_details.ArticleCategoryDetails, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&article_category_details.ArticleCategoryDetails{}) + query.Count(&count) + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articleCategoryDetailss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *articleCategoryDetailsRepository) FindOne(id uint) (articleCategoryDetails *article_category_details.ArticleCategoryDetails, err error) { + if err := _i.DB.DB.First(&articleCategoryDetails, id).Error; err != nil { + return nil, err + } + + return articleCategoryDetails, nil +} + +func (_i *articleCategoryDetailsRepository) FindByArticleId(articleId uint) (articleCategoryDetailss []*article_category_details.ArticleCategoryDetails, err error) { + if err := _i.DB.DB.Where("article_id = ?", articleId).Preload("Category").Find(&articleCategoryDetailss).Error; err != nil { + return nil, err + } + + return articleCategoryDetailss, nil +} + +func (_i *articleCategoryDetailsRepository) Create(articleCategoryDetails *article_category_details.ArticleCategoryDetails) (err error) { + return _i.DB.DB.Create(articleCategoryDetails).Error +} + +func (_i *articleCategoryDetailsRepository) Update(id uint, articleCategoryDetails *article_category_details.ArticleCategoryDetails) (err error) { + return _i.DB.DB.Model(&article_category_details.ArticleCategoryDetails{}). + Where(&article_category_details.ArticleCategoryDetails{ID: id}). + Updates(articleCategoryDetails).Error +} + +func (_i *articleCategoryDetailsRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&article_category_details.ArticleCategoryDetails{}, id).Error +} diff --git a/app/module/article_category_details/request/article_category_details.request.go b/app/module/article_category_details/request/article_category_details.request.go new file mode 100644 index 0000000..dfce189 --- /dev/null +++ b/app/module/article_category_details/request/article_category_details.request.go @@ -0,0 +1,52 @@ +package request + +import ( + "time" + "web-qudo-be/app/database/entity/article_category_details" + "web-qudo-be/utils/paginator" +) + +type ArticleCategoryDetailsGeneric interface { + ToEntity() +} + +type ArticleCategoryDetailsQueryRequest struct { + ArticleId int `json:"article_id" validate:"required"` + CategoryId int `json:"category_id" validate:"required"` + IsActive bool `json:"is_active" validate:"required"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ArticleCategoryDetailsCreateRequest struct { + ArticleId uint `json:"article_id" validate:"required"` + CategoryId int `json:"category_id" validate:"required"` + IsActive bool `json:"is_active" validate:"required"` +} + +func (req ArticleCategoryDetailsCreateRequest) ToEntity() *article_category_details.ArticleCategoryDetails { + return &article_category_details.ArticleCategoryDetails{ + ArticleId: req.ArticleId, + CategoryId: req.CategoryId, + IsActive: req.IsActive, + } +} + +type ArticleCategoryDetailsUpdateRequest struct { + ID uint `json:"id" validate:"required"` + ArticleId uint `json:"article_id" validate:"required"` + CategoryId int `json:"category_id" validate:"required"` + IsActive bool `json:"is_active" validate:"required"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (req ArticleCategoryDetailsUpdateRequest) ToEntity() *article_category_details.ArticleCategoryDetails { + return &article_category_details.ArticleCategoryDetails{ + ID: req.ID, + ArticleId: req.ArticleId, + CategoryId: req.CategoryId, + IsActive: req.IsActive, + CreatedAt: req.CreatedAt, + UpdatedAt: req.UpdatedAt, + } +} diff --git a/app/module/article_category_details/response/article_category_details.response.go b/app/module/article_category_details/response/article_category_details.response.go new file mode 100644 index 0000000..d2e1bf7 --- /dev/null +++ b/app/module/article_category_details/response/article_category_details.response.go @@ -0,0 +1,12 @@ +package response + +import "time" + +type ArticleCategoryDetailsResponse struct { + ID uint `json:"id"` + ArticleId uint `json:"article_id"` + CategoryId int `json:"category_id"` + IsActive bool `json:"is_active"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} diff --git a/app/module/article_category_details/service/article_category_details.service.go b/app/module/article_category_details/service/article_category_details.service.go new file mode 100644 index 0000000..a15d2ec --- /dev/null +++ b/app/module/article_category_details/service/article_category_details.service.go @@ -0,0 +1,72 @@ +package service + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/article_category_details/mapper" + "web-qudo-be/app/module/article_category_details/repository" + "web-qudo-be/app/module/article_category_details/request" + "web-qudo-be/app/module/article_category_details/response" + "web-qudo-be/utils/paginator" +) + +// ArticleCategoryDetailsService +type articleCategoryDetailsService struct { + Repo repository.ArticleCategoryDetailsRepository + Log zerolog.Logger +} + +// ArticleCategoryDetailsService define interface of IArticleCategoryDetailsService +type ArticleCategoryDetailsService interface { + All(req request.ArticleCategoryDetailsQueryRequest) (articleCategoryDetails []*response.ArticleCategoryDetailsResponse, paging paginator.Pagination, err error) + Show(id uint) (articleCategoryDetails *response.ArticleCategoryDetailsResponse, err error) + Save(req request.ArticleCategoryDetailsCreateRequest) (err error) + Update(id uint, req request.ArticleCategoryDetailsUpdateRequest) (err error) + Delete(id uint) error +} + +// NewArticleCategoryDetailsService init ArticleCategoryDetailsService +func NewArticleCategoryDetailsService(repo repository.ArticleCategoryDetailsRepository, log zerolog.Logger) ArticleCategoryDetailsService { + + return &articleCategoryDetailsService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of ArticleCategoryDetailsService +func (_i *articleCategoryDetailsService) All(req request.ArticleCategoryDetailsQueryRequest) (articleCategoryDetailss []*response.ArticleCategoryDetailsResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + articleCategoryDetailss = append(articleCategoryDetailss, mapper.ArticleCategoryDetailsResponseMapper(result)) + } + + return +} + +func (_i *articleCategoryDetailsService) Show(id uint) (articleCategoryDetails *response.ArticleCategoryDetailsResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.ArticleCategoryDetailsResponseMapper(result), nil +} + +func (_i *articleCategoryDetailsService) Save(req request.ArticleCategoryDetailsCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + return _i.Repo.Create(req.ToEntity()) +} + +func (_i *articleCategoryDetailsService) Update(id uint, req request.ArticleCategoryDetailsUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *articleCategoryDetailsService) Delete(id uint) error { + return _i.Repo.Delete(id) +} diff --git a/app/module/article_comments/article_comments.module.go b/app/module/article_comments/article_comments.module.go new file mode 100644 index 0000000..2af799e --- /dev/null +++ b/app/module/article_comments/article_comments.module.go @@ -0,0 +1,54 @@ +package article_comments + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/article_comments/controller" + "web-qudo-be/app/module/article_comments/repository" + "web-qudo-be/app/module/article_comments/service" +) + +// struct of ArticleCommentsRouter +type ArticleCommentsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of ArticleComments module +var NewArticleCommentsModule = fx.Options( + // register repository of ArticleComments module + fx.Provide(repository.NewArticleCommentsRepository), + + // register service of ArticleComments module + fx.Provide(service.NewArticleCommentsService), + + // register controller of ArticleComments module + fx.Provide(controller.NewController), + + // register router of ArticleComments module + fx.Provide(NewArticleCommentsRouter), +) + +// init ArticleCommentsRouter +func NewArticleCommentsRouter(fiber *fiber.App, controller *controller.Controller) *ArticleCommentsRouter { + return &ArticleCommentsRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of ArticleComments module +func (_i *ArticleCommentsRouter) RegisterArticleCommentsRoutes() { + // define controllers + articleCommentsController := _i.Controller.ArticleComments + + // define routes + _i.App.Route("/article-comments", func(router fiber.Router) { + router.Get("/", articleCommentsController.All) + router.Get("/:id", articleCommentsController.Show) + router.Post("/", articleCommentsController.Save) + router.Put("/:id", articleCommentsController.Update) + router.Delete("/:id", articleCommentsController.Delete) + router.Post("/approval", articleCommentsController.Approval) + }) +} diff --git a/app/module/article_comments/controller/article_comments.controller.go b/app/module/article_comments/controller/article_comments.controller.go new file mode 100644 index 0000000..dba5c29 --- /dev/null +++ b/app/module/article_comments/controller/article_comments.controller.go @@ -0,0 +1,254 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/article_comments/request" + "web-qudo-be/app/module/article_comments/service" + "web-qudo-be/utils/paginator" + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" +) + +type articleCommentsController struct { + articleCommentsService service.ArticleCommentsService + Log zerolog.Logger +} + +type ArticleCommentsController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + Approval(c *fiber.Ctx) error +} + +func NewArticleCommentsController(articleCommentsService service.ArticleCommentsService, log zerolog.Logger) ArticleCommentsController { + return &articleCommentsController{ + articleCommentsService: articleCommentsService, + Log: log, + } +} + +// All get all ArticleComments +// @Summary Get all ArticleComments +// @Description API for getting all ArticleComments +// @Tags ArticleComments +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param req query request.ArticleCommentsQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-comments [get] +func (_i *articleCommentsController) All(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.ArticleCommentsQueryRequestContext{ + Message: c.Query("message"), + ArticleId: c.Query("articleId"), + CommentFrom: c.Query("commentFrom"), + ParentId: c.Query("parentId"), + IsPublic: c.Query("isPublic"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + articleCommentsData, paging, err := _i.articleCommentsService.All(clientId, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleComments list successfully retrieved"}, + Data: articleCommentsData, + Meta: paging, + }) +} + +// Show get one ArticleComments +// @Summary Get one ArticleComments +// @Description API for getting one ArticleComments +// @Tags ArticleComments +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param id path int true "ArticleComments ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-comments/{id} [get] +func (_i *articleCommentsController) Show(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + articleCommentsData, err := _i.articleCommentsService.Show(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleComments successfully retrieved"}, + Data: articleCommentsData, + }) +} + +// Save create ArticleComments +// @Summary Create ArticleComments +// @Description API for create ArticleComments +// @Tags ArticleComments +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.ArticleCommentsCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-comments [post] +func (_i *articleCommentsController) Save(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + req := new(request.ArticleCommentsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + dataResult, err := _i.articleCommentsService.Save(clientId, *req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleComments successfully created"}, + Data: dataResult, + }) +} + +// Update update ArticleComments +// @Summary update ArticleComments +// @Description API for update ArticleComments +// @Tags ArticleComments +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.ArticleCommentsUpdateRequest true "Required payload" +// @Param id path int true "ArticleComments ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-comments/{id} [put] +func (_i *articleCommentsController) Update(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ArticleCommentsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.articleCommentsService.Update(clientId, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleComments successfully updated"}, + }) +} + +// Delete delete ArticleComments +// @Summary delete ArticleComments +// @Description API for delete ArticleComments +// @Tags ArticleComments +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "ArticleComments ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-comments/{id} [delete] +func (_i *articleCommentsController) Delete(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.articleCommentsService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleComments successfully deleted"}, + }) +} + +// Approval ArticleComments +// @Summary Approval ArticleComments +// @Description API for Approval ArticleComments +// @Tags ArticleComments +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.ArticleCommentsApprovalRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-comments/approval [post] +func (_i *articleCommentsController) Approval(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + req := new(request.ArticleCommentsApprovalRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.articleCommentsService.Approval(clientId, req.ID, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleComments successfully reviewed"}, + }) +} diff --git a/app/module/article_comments/controller/controller.go b/app/module/article_comments/controller/controller.go new file mode 100644 index 0000000..52cb316 --- /dev/null +++ b/app/module/article_comments/controller/controller.go @@ -0,0 +1,16 @@ +package controller + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/article_comments/service" +) + +type Controller struct { + ArticleComments ArticleCommentsController +} + +func NewController(ArticleCommentsService service.ArticleCommentsService, log zerolog.Logger) *Controller { + return &Controller{ + ArticleComments: NewArticleCommentsController(ArticleCommentsService, log), + } +} diff --git a/app/module/article_comments/mapper/article_comments.mapper.go b/app/module/article_comments/mapper/article_comments.mapper.go new file mode 100644 index 0000000..5579fd7 --- /dev/null +++ b/app/module/article_comments/mapper/article_comments.mapper.go @@ -0,0 +1,36 @@ +package mapper + +import ( + "github.com/google/uuid" + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/article_comments/response" + usersRepository "web-qudo-be/app/module/users/repository" +) + +func ArticleCommentsResponseMapper(clientId *uuid.UUID, articleCommentsReq *entity.ArticleComments, usersRepo usersRepository.UsersRepository) (articleCommentsRes *res.ArticleCommentsResponse) { + if articleCommentsReq != nil { + + commentFromName := "" + if articleCommentsReq.CommentFrom != nil { + findUser, _ := usersRepo.FindOne(clientId, *articleCommentsReq.CommentFrom) + if findUser != nil { + commentFromName = findUser.Fullname + } + } + + articleCommentsRes = &res.ArticleCommentsResponse{ + ID: articleCommentsReq.ID, + Message: articleCommentsReq.Message, + ArticleId: articleCommentsReq.ArticleId, + CommentFromId: articleCommentsReq.CommentFrom, + CommentFromName: &commentFromName, + ParentId: articleCommentsReq.ParentId, + IsPublic: articleCommentsReq.IsPublic, + StatusId: articleCommentsReq.StatusId, + IsActive: articleCommentsReq.IsActive, + CreatedAt: articleCommentsReq.CreatedAt, + UpdatedAt: articleCommentsReq.UpdatedAt, + } + } + return articleCommentsRes +} diff --git a/app/module/article_comments/repository/article_comments.repository.go b/app/module/article_comments/repository/article_comments.repository.go new file mode 100644 index 0000000..8110cc4 --- /dev/null +++ b/app/module/article_comments/repository/article_comments.repository.go @@ -0,0 +1,142 @@ +package repository + +import ( + "fmt" + "github.com/google/uuid" + "github.com/rs/zerolog" + "strings" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/article_comments/request" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" +) + +type articleCommentsRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ArticleCommentsRepository define interface of IArticleCommentsRepository +type ArticleCommentsRepository interface { + GetAll(clientId *uuid.UUID, req request.ArticleCommentsQueryRequest) (articleCommentss []*entity.ArticleComments, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (articleComments *entity.ArticleComments, err error) + Create(clientId *uuid.UUID, articleComments *entity.ArticleComments) (articleCommentsReturn *entity.ArticleComments, err error) + Update(clientId *uuid.UUID, id uint, articleComments *entity.ArticleComments) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) +} + +func NewArticleCommentsRepository(db *database.Database, logger zerolog.Logger) ArticleCommentsRepository { + return &articleCommentsRepository{ + DB: db, + Log: logger, + } +} + +// implement interface of IArticleCommentsRepository +func (_i *articleCommentsRepository) GetAll(clientId *uuid.UUID, req request.ArticleCommentsQueryRequest) (articleCommentss []*entity.ArticleComments, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ArticleComments{}) + query = query.Where("is_active = ?", true) + + // Add client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if req.Message != nil && *req.Message != "" { + message := strings.ToLower(*req.Message) + query = query.Where("LOWER(message) LIKE ?", "%"+strings.ToLower(message)+"%") + } + if req.ArticleId != nil { + query = query.Where("article_id = ?", req.ArticleId) + } + if req.CommentFrom != nil { + query = query.Where("comment_from = ?", req.CommentFrom) + } + if req.ParentId != nil { + query = query.Where("parent_id = ?", req.ParentId) + } + if req.IsPublic != nil { + query = query.Where("is_public = ?", req.IsPublic) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articleCommentss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *articleCommentsRepository) FindOne(clientId *uuid.UUID, id uint) (articleComments *entity.ArticleComments, err error) { + query := _i.DB.DB + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + if err := query.First(&articleComments, id).Error; err != nil { + return nil, err + } + + return articleComments, nil +} + +func (_i *articleCommentsRepository) Create(clientId *uuid.UUID, articleComments *entity.ArticleComments) (articleCommentsReturn *entity.ArticleComments, err error) { + // Set client ID + if clientId != nil { + articleComments.ClientId = clientId + } + result := _i.DB.DB.Create(articleComments) + return articleComments, result.Error +} + +func (_i *articleCommentsRepository) Update(clientId *uuid.UUID, id uint, articleComments *entity.ArticleComments) (err error) { + // Validate client access + if clientId != nil { + var count int64 + if err := _i.DB.DB.Model(&entity.ArticleComments{}).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") + } + } + + articleCommentsMap, err := utilSvc.StructToMap(articleComments) + if err != nil { + return err + } + return _i.DB.DB.Model(&entity.ArticleComments{}). + Where(&entity.ArticleComments{ID: id}). + Updates(articleCommentsMap).Error +} + +func (_i *articleCommentsRepository) Delete(clientId *uuid.UUID, id uint) error { + // Validate client access + if clientId != nil { + var count int64 + if err := _i.DB.DB.Model(&entity.ArticleComments{}).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.ArticleComments{}, id).Error +} diff --git a/app/module/article_comments/request/article_comments.request.go b/app/module/article_comments/request/article_comments.request.go new file mode 100644 index 0000000..19aaba4 --- /dev/null +++ b/app/module/article_comments/request/article_comments.request.go @@ -0,0 +1,111 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type ArticleCommentsGeneric interface { + ToEntity() +} + +type ArticleCommentsQueryRequest struct { + Message *string `json:"message"` + ArticleId *int `json:"articleId"` + CommentFrom *int `json:"commentFrom"` + ParentId *int `json:"parentId"` + IsPublic *bool `json:"isPublic"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ArticleCommentsCreateRequest struct { + Message string `json:"message" validate:"required"` + ArticleId uint `json:"articleId" validate:"required"` + ParentId *int `json:"parentId"` + IsPublic *bool `json:"isPublic"` +} + +func (req ArticleCommentsCreateRequest) ToEntity() *entity.ArticleComments { + return &entity.ArticleComments{ + Message: req.Message, + ArticleId: req.ArticleId, + ParentId: req.ParentId, + StatusId: 0, + } +} + +type ArticleCommentsUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Message string `json:"message" validate:"required"` + ArticleId uint `json:"articleId" validate:"required"` + ParentId *int `json:"parentId"` + IsPublic *bool `json:"isPublic"` +} + +func (req ArticleCommentsUpdateRequest) ToEntity() *entity.ArticleComments { + return &entity.ArticleComments{ + ID: req.ID, + Message: req.Message, + ArticleId: req.ArticleId, + ParentId: req.ParentId, + UpdatedAt: time.Now(), + } +} + +type ArticleCommentsApprovalRequest struct { + ID uint `json:"id" validate:"required"` + StatusId int `json:"statusId" validate:"required"` +} + +func (req ArticleCommentsApprovalRequest) ToEntity() *entity.ArticleComments { + approvedNow := time.Now() + return &entity.ArticleComments{ + ID: req.ID, + StatusId: req.StatusId, + ApprovedAt: &approvedNow, + } +} + +type ArticleCommentsQueryRequestContext struct { + Message string `json:"message"` + ArticleId string `json:"articleId"` + CommentFrom string `json:"commentFrom"` + ParentId string `json:"parentId"` + IsPublic string `json:"isPublic"` +} + +func (req ArticleCommentsQueryRequestContext) ToParamRequest() ArticleCommentsQueryRequest { + var request ArticleCommentsQueryRequest + + if message := req.Message; message != "" { + request.Message = &message + } + if articleIdStr := req.ArticleId; articleIdStr != "" { + articleId, err := strconv.Atoi(articleIdStr) + if err == nil { + request.ArticleId = &articleId + } + } + if commentFromStr := req.CommentFrom; commentFromStr != "" { + commentFrom, err := strconv.Atoi(commentFromStr) + if err == nil { + request.CommentFrom = &commentFrom + } + } + if parentIdStr := req.ParentId; parentIdStr != "" { + parentId, err := strconv.Atoi(parentIdStr) + if err == nil { + request.ParentId = &parentId + } + } + if isPublicStr := req.IsPublic; isPublicStr != "" { + isPublic, err := strconv.ParseBool(isPublicStr) + if err == nil { + request.IsPublic = &isPublic + } + } + + return request +} diff --git a/app/module/article_comments/response/article_comments.response.go b/app/module/article_comments/response/article_comments.response.go new file mode 100644 index 0000000..ff86554 --- /dev/null +++ b/app/module/article_comments/response/article_comments.response.go @@ -0,0 +1,17 @@ +package response + +import "time" + +type ArticleCommentsResponse struct { + ID uint `json:"id"` + Message string `json:"message"` + ArticleId uint `json:"articleId"` + CommentFromId *uint `json:"commentFromId"` + CommentFromName *string `json:"commentFromName"` + ParentId *int `json:"parentId"` + StatusId int `json:"statusId"` + IsPublic bool `json:"isPublic"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/article_comments/service/article_comments.service.go b/app/module/article_comments/service/article_comments.service.go new file mode 100644 index 0000000..f0f5d2c --- /dev/null +++ b/app/module/article_comments/service/article_comments.service.go @@ -0,0 +1,105 @@ +package service + +import ( + "github.com/google/uuid" + "github.com/rs/zerolog" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/article_comments/mapper" + "web-qudo-be/app/module/article_comments/repository" + "web-qudo-be/app/module/article_comments/request" + "web-qudo-be/app/module/article_comments/response" + usersRepository "web-qudo-be/app/module/users/repository" + "web-qudo-be/utils/paginator" + + utilSvc "web-qudo-be/utils/service" +) + +// ArticleCommentsService +type articleCommentsService struct { + Repo repository.ArticleCommentsRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +// ArticleCommentsService define interface of IArticleCommentsService +type ArticleCommentsService interface { + All(clientId *uuid.UUID, req request.ArticleCommentsQueryRequest) (articleComments []*response.ArticleCommentsResponse, paging paginator.Pagination, err error) + Show(clientId *uuid.UUID, id uint) (articleComments *response.ArticleCommentsResponse, err error) + Save(clientId *uuid.UUID, req request.ArticleCommentsCreateRequest, authToken string) (articleComments *entity.ArticleComments, err error) + Update(clientId *uuid.UUID, id uint, req request.ArticleCommentsUpdateRequest) (err error) + Delete(clientId *uuid.UUID, id uint) error + Approval(clientId *uuid.UUID, id uint, req request.ArticleCommentsApprovalRequest) (err error) +} + +// NewArticleCommentsService init ArticleCommentsService +func NewArticleCommentsService(repo repository.ArticleCommentsRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository) ArticleCommentsService { + + return &articleCommentsService{ + Repo: repo, + Log: log, + UsersRepo: usersRepo, + } +} + +// All implement interface of ArticleCommentsService +func (_i *articleCommentsService) All(clientId *uuid.UUID, req request.ArticleCommentsQueryRequest) (articleCommentss []*response.ArticleCommentsResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(clientId, req) + if err != nil { + return + } + + for _, result := range results { + articleCommentss = append(articleCommentss, mapper.ArticleCommentsResponseMapper(clientId, result, _i.UsersRepo)) + } + + return +} + +func (_i *articleCommentsService) Show(clientId *uuid.UUID, id uint) (articleComments *response.ArticleCommentsResponse, err error) { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return nil, err + } + + return mapper.ArticleCommentsResponseMapper(clientId, result, _i.UsersRepo), nil +} + +func (_i *articleCommentsService) Save(clientId *uuid.UUID, req request.ArticleCommentsCreateRequest, authToken string) (articleComments *entity.ArticleComments, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + newReq := req.ToEntity() + + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + newReq.CommentFrom = &createdBy.ID + newReq.IsActive = true + + return _i.Repo.Create(clientId, newReq) +} + +func (_i *articleCommentsService) Update(clientId *uuid.UUID, id uint, req request.ArticleCommentsUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(clientId, id, req.ToEntity()) +} + +func (_i *articleCommentsService) Delete(clientId *uuid.UUID, id uint) error { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return err + } + + result.IsActive = false + return _i.Repo.Update(clientId, id, result) +} + +func (_i *articleCommentsService) Approval(clientId *uuid.UUID, id uint, req request.ArticleCommentsApprovalRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + if newReq.StatusId == 1 || newReq.StatusId == 2 { + newReq.IsPublic = true + } else { + newReq.IsPublic = false + } + + return _i.Repo.Update(clientId, id, newReq) +} diff --git a/app/module/article_files/article_files.module.go b/app/module/article_files/article_files.module.go new file mode 100644 index 0000000..53488a9 --- /dev/null +++ b/app/module/article_files/article_files.module.go @@ -0,0 +1,57 @@ +package article_files + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/article_files/controller" + "web-qudo-be/app/module/article_files/repository" + "web-qudo-be/app/module/article_files/service" +) + +// struct of ArticleFilesRouter +type ArticleFilesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of ArticleFiles module +var NewArticleFilesModule = fx.Options( + // register repository of ArticleFiles module + fx.Provide(repository.NewArticleFilesRepository), + + // register service of ArticleFiles module + fx.Provide(service.NewArticleFilesService), + fx.Provide(service.NewUploadService), + fx.Provide(service.NewUploadManager), + + // register controller of ArticleFiles module + fx.Provide(controller.NewController), + + // register router of ArticleFiles module + fx.Provide(NewArticleFilesRouter), +) + +// init ArticleFilesRouter +func NewArticleFilesRouter(fiber *fiber.App, controller *controller.Controller) *ArticleFilesRouter { + return &ArticleFilesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of ArticleFiles module +func (_i *ArticleFilesRouter) RegisterArticleFilesRoutes() { + // define controllers + articleFilesController := _i.Controller.ArticleFiles + + // define routes + _i.App.Route("/article-files", func(router fiber.Router) { + router.Get("/", articleFilesController.All) + router.Get("/:id", articleFilesController.Show) + router.Post("/:articleId", articleFilesController.Save) + router.Put("/:id", articleFilesController.Update) + router.Delete("/:id", articleFilesController.Delete) + router.Get("/viewer/:filename", articleFilesController.Viewer) + router.Get("/upload-status/:uploadId", articleFilesController.GetUploadStatus) + }) +} diff --git a/app/module/article_files/controller/article_files.controller.go b/app/module/article_files/controller/article_files.controller.go new file mode 100644 index 0000000..f1faa8a --- /dev/null +++ b/app/module/article_files/controller/article_files.controller.go @@ -0,0 +1,259 @@ +package controller + +import ( + "fmt" + "strconv" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/article_files/request" + "web-qudo-be/app/module/article_files/service" + "web-qudo-be/utils/paginator" + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" + + "github.com/gofiber/fiber/v2" +) + +type articleFilesController struct { + articleFilesService service.ArticleFilesService +} + +type ArticleFilesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + Viewer(c *fiber.Ctx) error + GetUploadStatus(c *fiber.Ctx) error +} + +func NewArticleFilesController(articleFilesService service.ArticleFilesService) ArticleFilesController { + return &articleFilesController{ + articleFilesService: articleFilesService, + } +} + +// All ArticleFiles +// @Summary Get all ArticleFiles +// @Description API for getting all ArticleFiles +// @Tags Article Files +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param req query request.ArticleFilesQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-files [get] +func (_i *articleFilesController) All(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.ArticleFilesQueryRequestContext{ + ArticleId: c.Query("articleId"), + FileName: c.Query("fileName"), + StatusId: c.Query("statusId"), + IsPublish: c.Query("isPublish"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + articleFilesData, paging, err := _i.articleFilesService.All(clientId, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleFiles list successfully retrieved"}, + Data: articleFilesData, + Meta: paging, + }) +} + +// Show ArticleFiles +// @Summary Get one ArticleFiles +// @Description API for getting one ArticleFiles +// @Tags Article Files +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param id path int true "ArticleFiles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-files/{id} [get] +func (_i *articleFilesController) Show(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + articleFilesData, err := _i.articleFilesService.Show(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleFiles successfully retrieved"}, + Data: articleFilesData, + }) +} + +// Save ArticleFiles +// @Summary Upload ArticleFiles +// @Description API for create ArticleFiles +// @Tags Article Files +// @Security Bearer +// @Produce json +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param files formData file true "Upload file" multiple true +// @Param articleId path int true "Article ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-files/{articleId} [post] +func (_i *articleFilesController) Save(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + id, err := strconv.ParseUint(c.Params("articleId"), 10, 0) + if err != nil { + return err + } + + err = _i.articleFilesService.Save(clientId, c, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleFiles successfully upload"}, + }) +} + +// Update ArticleFiles +// @Summary Update ArticleFiles +// @Description API for update ArticleFiles +// @Tags Article Files +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.ArticleFilesUpdateRequest true "Required payload" +// @Param id path int true "ArticleFiles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-files/{id} [put] +func (_i *articleFilesController) Update(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ArticleFilesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.articleFilesService.Update(clientId, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleFiles successfully updated"}, + }) +} + +// Delete ArticleFiles +// @Summary Delete ArticleFiles +// @Description API for delete ArticleFiles +// @Tags Article Files +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "ArticleFiles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-files/{id} [delete] +func (_i *articleFilesController) Delete(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.articleFilesService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleFiles successfully deleted"}, + }) +} + +// Viewer ArticleFiles +// @Summary Viewer ArticleFiles +// @Description API for Viewer ArticleFiles +// @Tags Article Files +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param filename path string true "Article File Name" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-files/viewer/{filename} [get] +func (_i *articleFilesController) Viewer(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + return _i.articleFilesService.Viewer(clientId, c) +} + +// GetUploadStatus ArticleFiles +// @Summary GetUploadStatus ArticleFiles +// @Description API for GetUploadStatus ArticleFiles +// @Tags Article Files +// @Security Bearer +// @Param uploadId path string true "Upload ID of ArticleFiles" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-files/upload-status/{uploadId} [get] +func (_i *articleFilesController) GetUploadStatus(c *fiber.Ctx) error { + progress, _ := _i.articleFilesService.GetUploadStatus(c) + progressMessage := fmt.Sprintf("Upload Progress: %d%%", progress) + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Upload Status Retrieve"}, + Data: progressMessage, + }) +} diff --git a/app/module/article_files/controller/controller.go b/app/module/article_files/controller/controller.go new file mode 100644 index 0000000..d43121d --- /dev/null +++ b/app/module/article_files/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "web-qudo-be/app/module/article_files/service" + +type Controller struct { + ArticleFiles ArticleFilesController +} + +func NewController(ArticleFilesService service.ArticleFilesService) *Controller { + return &Controller{ + ArticleFiles: NewArticleFilesController(ArticleFilesService), + } +} diff --git a/app/module/article_files/mapper/article_files.mapper.go b/app/module/article_files/mapper/article_files.mapper.go new file mode 100644 index 0000000..6661eee --- /dev/null +++ b/app/module/article_files/mapper/article_files.mapper.go @@ -0,0 +1,37 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/article_files/response" +) + +func ArticleFilesResponseMapper(articleFilesReq *entity.ArticleFiles, host string) (articleFilesRes *res.ArticleFilesResponse) { + fileUrl := host + "/article-files/viewer/" + if articleFilesReq.FileName != nil { + fileUrl += *articleFilesReq.FileName + } + + if articleFilesReq != nil { + articleFilesRes = &res.ArticleFilesResponse{ + ID: articleFilesReq.ID, + ArticleId: articleFilesReq.ArticleId, + FilePath: articleFilesReq.FilePath, + FileUrl: &fileUrl, + FileName: articleFilesReq.FileName, + FileThumbnail: articleFilesReq.FileThumbnail, + FileAlt: articleFilesReq.FileAlt, + WidthPixel: articleFilesReq.WidthPixel, + HeightPixel: articleFilesReq.HeightPixel, + Size: articleFilesReq.Size, + DownloadCount: articleFilesReq.DownloadCount, + CreatedById: articleFilesReq.CreatedById, + StatusId: articleFilesReq.StatusId, + IsPublish: articleFilesReq.IsPublish, + PublishedAt: articleFilesReq.PublishedAt, + IsActive: articleFilesReq.IsActive, + CreatedAt: articleFilesReq.CreatedAt, + UpdatedAt: articleFilesReq.UpdatedAt, + } + } + return articleFilesRes +} diff --git a/app/module/article_files/repository/article_files.repository.go b/app/module/article_files/repository/article_files.repository.go new file mode 100644 index 0000000..7bc7077 --- /dev/null +++ b/app/module/article_files/repository/article_files.repository.go @@ -0,0 +1,197 @@ +package repository + +import ( + "fmt" + "strings" + "time" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/article_files/request" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" + + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +type articleFilesRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ArticleFilesRepository define interface of IArticleFilesRepository +type ArticleFilesRepository interface { + GetAll(clientId *uuid.UUID, req request.ArticleFilesQueryRequest) (articleFiless []*entity.ArticleFiles, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (articleFiles *entity.ArticleFiles, err error) + FindByArticle(clientId *uuid.UUID, articleId uint) (articleFiles []*entity.ArticleFiles, err error) + FindByFilename(clientId *uuid.UUID, filename string) (articleFiles *entity.ArticleFiles, err error) + Create(clientId *uuid.UUID, articleFiles *entity.ArticleFiles) (err error) + Update(clientId *uuid.UUID, id uint, articleFiles *entity.ArticleFiles) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) +} + +func NewArticleFilesRepository(db *database.Database, log zerolog.Logger) ArticleFilesRepository { + return &articleFilesRepository{ + DB: db, + Log: log, + } +} + +// implement interface of IArticleFilesRepository +func (_i *articleFilesRepository) GetAll(clientId *uuid.UUID, req request.ArticleFilesQueryRequest) (articleFiless []*entity.ArticleFiles, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ArticleFiles{}) + query = query.Where("is_active = ?", true) + + // Add client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if req.ArticleId != nil { + query = query.Where("article_id = ?", req.ArticleId) + } + if req.FileName != nil && *req.FileName != "" { + fileName := strings.ToLower(*req.FileName) + query = query.Where("LOWER(file_name) LIKE ?", "%"+strings.ToLower(fileName)+"%") + } + if req.IsPublish != nil { + query = query.Where("is_publish = ?", req.IsPublish) + } + if req.StatusId != nil { + query = query.Where("status_id = ?", req.StatusId) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articleFiless).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *articleFilesRepository) FindOne(clientId *uuid.UUID, id uint) (articleFiles *entity.ArticleFiles, err error) { + query := _i.DB.DB + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + if err := query.First(&articleFiles, id).Error; err != nil { + return nil, err + } + + return articleFiles, nil +} + +func (_i *articleFilesRepository) FindByArticle(clientId *uuid.UUID, articleId uint) (articleFiles []*entity.ArticleFiles, err error) { + query := _i.DB.DB.Where("article_id = ?", articleId) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + if err := query.Find(&articleFiles).Error; err != nil { + return nil, err + } + + return articleFiles, nil +} + +func (_i *articleFilesRepository) FindByFilename(clientId *uuid.UUID, articleFilename string) (articleFiles *entity.ArticleFiles, err error) { + query := _i.DB.DB.Where("file_name = ?", articleFilename) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + if err := query.First(&articleFiles).Error; err != nil { + return nil, err + } + + return articleFiles, nil +} + +func (_i *articleFilesRepository) Create(clientId *uuid.UUID, articleFiles *entity.ArticleFiles) (err error) { + // Set client ID + if clientId != nil { + articleFiles.ClientId = clientId + } + return _i.DB.DB.Create(articleFiles).Error +} + +func (_i *articleFilesRepository) Update(clientId *uuid.UUID, id uint, articleFiles *entity.ArticleFiles) (err error) { + // Validate client access + if clientId != nil { + var count int64 + if err := _i.DB.DB.Model(&entity.ArticleFiles{}).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") + } + } + + // Create a copy without the relation field to avoid updating non-existent columns + updateData := &entity.ArticleFiles{ + ID: articleFiles.ID, + ArticleId: articleFiles.ArticleId, + UploadID: articleFiles.UploadID, + FilePath: articleFiles.FilePath, + FileUrl: articleFiles.FileUrl, + FileName: articleFiles.FileName, + FileThumbnail: articleFiles.FileThumbnail, + FileAlt: articleFiles.FileAlt, + WidthPixel: articleFiles.WidthPixel, + HeightPixel: articleFiles.HeightPixel, + Size: articleFiles.Size, + DownloadCount: articleFiles.DownloadCount, + CreatedById: articleFiles.CreatedById, + StatusId: articleFiles.StatusId, + IsPublish: articleFiles.IsPublish, + PublishedAt: articleFiles.PublishedAt, + ClientId: articleFiles.ClientId, + IsActive: articleFiles.IsActive, + CreatedAt: articleFiles.CreatedAt, + UpdatedAt: time.Time{}, + // Exclude Article relation field + } + + articleFilesMap, err := utilSvc.StructToMap(updateData) + if err != nil { + return err + } + + // Remove any relation fields that might have been included + delete(articleFilesMap, "article") + + _i.Log.Info().Interface("articleFilesMap : ", articleFilesMap).Msg("") + + return _i.DB.DB.Model(&entity.ArticleFiles{}). + Where(&entity.ArticleFiles{ID: id}). + Updates(articleFilesMap).Error +} + +func (_i *articleFilesRepository) Delete(clientId *uuid.UUID, id uint) error { + // Validate client access + if clientId != nil { + var count int64 + if err := _i.DB.DB.Model(&entity.ArticleFiles{}).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.ArticleFiles{}, id).Error +} diff --git a/app/module/article_files/request/article_files.request.go b/app/module/article_files/request/article_files.request.go new file mode 100644 index 0000000..17ee24c --- /dev/null +++ b/app/module/article_files/request/article_files.request.go @@ -0,0 +1,120 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type ArticleFilesGeneric interface { + ToEntity() +} + +type ArticleFilesQueryRequest struct { + ArticleId *int `json:"articleId"` + FileName *string `json:"fileName"` + StatusId *int `json:"statusId"` + IsPublish *bool `json:"isPublish"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ArticleFilesCreateRequest struct { + ArticleId uint `json:"articleId" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + UploadId *string `json:"uploadId"` + FilePath *string `json:"filePath"` + FileUrl *string `json:"fileUrl"` + FileName *string `json:"fileName"` + FileThumbnail *string `json:"fileThumbnail"` + FileAlt *string `json:"fileAlt"` + WidthPixel *string `json:"widthPixel"` + HeightPixel *string `json:"heightPixel"` + Size *string `json:"size"` +} + +func (req ArticleFilesCreateRequest) ToEntity() *entity.ArticleFiles { + return &entity.ArticleFiles{ + ArticleId: req.ArticleId, + UploadID: req.UploadId, + FilePath: req.FilePath, + FileUrl: req.FileUrl, + FileName: req.FileName, + FileThumbnail: req.FileThumbnail, + FileAlt: req.FileAlt, + WidthPixel: req.WidthPixel, + HeightPixel: req.HeightPixel, + Size: req.Size, + StatusId: req.StatusId, + } +} + +type ArticleFilesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + ArticleId uint `json:"articleId" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + FilePath *string `json:"filePath"` + FileUrl *string `json:"fileUrl"` + FileName *string `json:"fileName"` + FileThumbnail *string `json:"fileThumbnail"` + FileAlt *string `json:"fileAlt"` + WidthPixel *string `json:"widthPixel"` + HeightPixel *string `json:"heightPixel"` + Size *string `json:"size"` + IsPublish *bool `json:"isPublish" validate:"required"` + PublishedAt *time.Time `json:"publishedAt" validate:"required"` +} + +func (req ArticleFilesUpdateRequest) ToEntity() *entity.ArticleFiles { + return &entity.ArticleFiles{ + ID: req.ID, + ArticleId: req.ArticleId, + FilePath: req.FilePath, + FileUrl: req.FileUrl, + FileName: req.FileName, + FileThumbnail: req.FileThumbnail, + FileAlt: req.FileAlt, + WidthPixel: req.WidthPixel, + HeightPixel: req.HeightPixel, + Size: req.Size, + StatusId: req.StatusId, + IsPublish: req.IsPublish, + PublishedAt: req.PublishedAt, + UpdatedAt: time.Now(), + } +} + +type ArticleFilesQueryRequestContext struct { + ArticleId string `json:"articleId"` + FileName string `json:"fileName"` + StatusId string `json:"statusId"` + IsPublish string `json:"isPublish"` +} + +func (req ArticleFilesQueryRequestContext) ToParamRequest() ArticleFilesQueryRequest { + var request ArticleFilesQueryRequest + + if articleIdStr := req.ArticleId; articleIdStr != "" { + articleId, err := strconv.Atoi(articleIdStr) + if err == nil { + request.ArticleId = &articleId + } + } + if fileName := req.FileName; fileName != "" { + request.FileName = &fileName + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + if isPublishStr := req.IsPublish; isPublishStr != "" { + isPublish, err := strconv.ParseBool(isPublishStr) + if err == nil { + request.IsPublish = &isPublish + } + } + + return request +} diff --git a/app/module/article_files/response/article_files.response.go b/app/module/article_files/response/article_files.response.go new file mode 100644 index 0000000..0993739 --- /dev/null +++ b/app/module/article_files/response/article_files.response.go @@ -0,0 +1,24 @@ +package response + +import "time" + +type ArticleFilesResponse struct { + ID uint `json:"id"` + ArticleId uint `json:"articleId"` + FilePath *string `json:"filePath"` + FileUrl *string `json:"fileUrl"` + FileName *string `json:"fileName"` + FileThumbnail *string `json:"fileThumbnail"` + FileAlt *string `json:"fileAlt"` + WidthPixel *string `json:"widthPixel"` + HeightPixel *string `json:"heightPixel"` + Size *string `json:"size"` + DownloadCount *int `json:"downloadCount"` + CreatedById int `json:"createdById"` + StatusId int `json:"statusId"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/article_files/service/article_files.service.go b/app/module/article_files/service/article_files.service.go new file mode 100644 index 0000000..9710e6f --- /dev/null +++ b/app/module/article_files/service/article_files.service.go @@ -0,0 +1,446 @@ +package service + +import ( + "context" + "fmt" + "io" + "log" + "math/rand" + "mime" + "mime/multipart" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "time" + "web-qudo-be/app/module/article_files/mapper" + "web-qudo-be/app/module/article_files/repository" + "web-qudo-be/app/module/article_files/request" + "web-qudo-be/app/module/article_files/response" + config "web-qudo-be/config/config" + "web-qudo-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" +) + +// ArticleFilesService +type articleFilesService struct { + Repo repository.ArticleFilesRepository + Log zerolog.Logger + Cfg *config.Config + MinioStorage *config.MinioStorage +} + +// ArticleFilesService define interface of IArticleFilesService +type ArticleFilesService interface { + All(clientId *uuid.UUID, req request.ArticleFilesQueryRequest) (articleFiles []*response.ArticleFilesResponse, paging paginator.Pagination, err error) + Show(clientId *uuid.UUID, id uint) (articleFiles *response.ArticleFilesResponse, err error) + Save(clientId *uuid.UUID, c *fiber.Ctx, id uint) error + SaveAsync(clientId *uuid.UUID, c *fiber.Ctx, id uint) error + Update(clientId *uuid.UUID, id uint, req request.ArticleFilesUpdateRequest) (err error) + GetUploadStatus(c *fiber.Ctx) (progress int, err error) + Delete(clientId *uuid.UUID, id uint) error + Viewer(clientId *uuid.UUID, c *fiber.Ctx) error +} + +// NewArticleFilesService init ArticleFilesService +func NewArticleFilesService(repo repository.ArticleFilesRepository, log zerolog.Logger, cfg *config.Config, minioStorage *config.MinioStorage) ArticleFilesService { + + return &articleFilesService{ + Repo: repo, + Log: log, + Cfg: cfg, + MinioStorage: minioStorage, + } +} + +var ( + progressMap = make(map[string]int) // Menyimpan progress upload per UploadID + progressLock = sync.Mutex{} +) + +type progressWriter struct { + uploadID string + totalSize int64 + uploadedSize *int64 +} + +// All implement interface of ArticleFilesService +func (_i *articleFilesService) All(clientId *uuid.UUID, req request.ArticleFilesQueryRequest) (articleFiless []*response.ArticleFilesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(clientId, req) + if err != nil { + return + } + + host := _i.Cfg.App.Domain + + for _, result := range results { + articleFiless = append(articleFiless, mapper.ArticleFilesResponseMapper(result, host)) + } + + return +} + +func (_i *articleFilesService) Show(clientId *uuid.UUID, id uint) (articleFiles *response.ArticleFilesResponse, err error) { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return nil, err + } + + host := _i.Cfg.App.Domain + + return mapper.ArticleFilesResponseMapper(result, host), nil +} + +func (_i *articleFilesService) SaveAsync(clientId *uuid.UUID, c *fiber.Ctx, id uint) (err error) { + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + + ctx := context.Background() + + form, err := c.MultipartForm() + + if err != nil { + return err + } + //filess := form.File["files"] + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + for _, files := range form.File { + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: top"). + Interface("files", files).Msg("") + + for _, fileHeader := range files { + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop"). + Interface("data", fileHeader).Msg("") + + filename := filepath.Base(fileHeader.Filename) + filenameAlt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + filename = strings.ReplaceAll(filename, " ", "") + filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + extension := filepath.Ext(fileHeader.Filename)[1:] + + now := time.Now() + rand.New(rand.NewSource(now.UnixNano())) + randUniqueId := rand.Intn(1000000) + uploadID := strconv.Itoa(randUniqueId) + + newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId) + newFilename := newFilenameWithoutExt + "." + extension + + objectName := fmt.Sprintf("articles/upload/%d/%d/%s", now.Year(), now.Month(), newFilename) + fileSize := strconv.FormatInt(fileHeader.Size, 10) + fileSizeInt := fileHeader.Size + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: top"). + Interface("Start upload", uploadID).Msg("") + + req := request.ArticleFilesCreateRequest{ + ArticleId: id, + UploadId: &uploadID, + FilePath: &objectName, + FileName: &newFilename, + FileAlt: &filenameAlt, + Size: &fileSize, + } + + err = _i.Repo.Create(clientId, req.ToEntity()) + if err != nil { + return err + } + + src, err := fileHeader.Open() + if err != nil { + return err + } + defer src.Close() + + tempFilePath := fmt.Sprintf("/tmp/%s", newFilename) + tempFile, err := os.Create(tempFilePath) + if err != nil { + return err + } + defer tempFile.Close() + + // Copy file ke direktori sementara + _, err = io.Copy(tempFile, src) + if err != nil { + return err + } + + go uploadToMinIO(ctx, _i.Log, minioClient, uploadID, tempFilePath, bucketName, objectName, fileSizeInt) + } + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "User:All"). + Interface("data", "Successfully uploaded").Msg("") + + return +} + +func (_i *articleFilesService) Save(clientId *uuid.UUID, c *fiber.Ctx, id uint) (err error) { + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + + form, err := c.MultipartForm() + + if err != nil { + return err + } + //filess := form.File["files"] + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + for _, files := range form.File { + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: top"). + Interface("files", files).Msg("") + + for _, fileHeader := range files { + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop"). + Interface("data", fileHeader).Msg("") + + src, err := fileHeader.Open() + if err != nil { + return err + } + defer src.Close() + + filename := filepath.Base(fileHeader.Filename) + filenameAlt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + filename = strings.ReplaceAll(filename, " ", "") + filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + extension := filepath.Ext(fileHeader.Filename)[1:] + + now := time.Now() + rand.New(rand.NewSource(now.UnixNano())) + randUniqueId := rand.Intn(1000000) + + newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId) + newFilename := newFilenameWithoutExt + "." + extension + + objectName := fmt.Sprintf("articles/upload/%d/%d/%s", now.Year(), now.Month(), newFilename) + fileSize := strconv.FormatInt(fileHeader.Size, 10) + + req := request.ArticleFilesCreateRequest{ + ArticleId: id, + FilePath: &objectName, + FileName: &newFilename, + FileAlt: &filenameAlt, + Size: &fileSize, + } + + err = _i.Repo.Create(clientId, req.ToEntity()) + if err != nil { + return err + } + + // Upload file ke MinIO + _, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{}) + if err != nil { + return err + } + } + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "User:All"). + Interface("data", "Successfully uploaded").Msg("") + + return +} + +func (_i *articleFilesService) Update(clientId *uuid.UUID, id uint, req request.ArticleFilesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(clientId, id, req.ToEntity()) +} + +func (_i *articleFilesService) Delete(clientId *uuid.UUID, id uint) error { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return err + } + result.IsActive = false + return _i.Repo.Update(clientId, id, result) +} + +func (_i *articleFilesService) Viewer(clientId *uuid.UUID, c *fiber.Ctx) (err error) { + filename := c.Params("filename") + result, err := _i.Repo.FindByFilename(clientId, filename) + if err != nil { + return err + } + + ctx := context.Background() + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + objectName := *result.FilePath + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:Uploads"). + Interface("data", objectName).Msg("") + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{}) + if err != nil { + log.Fatalln(err) + } + defer fileContent.Close() + + // Tentukan Content-Type berdasarkan ekstensi file + contentType := mime.TypeByExtension("." + getFileExtension(objectName)) + if contentType == "" { + contentType = "application/octet-stream" // fallback jika tidak ada tipe MIME yang cocok + } + + c.Set("Content-Type", contentType) + + if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil { + return err + } + + return +} + +func getFileExtension(filename string) string { + // split file name + parts := strings.Split(filename, ".") + + // jika tidak ada ekstensi, kembalikan string kosong + if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") { + return "" + } + + // ambil ekstensi terakhir + return parts[len(parts)-1] +} + +func uploadTempFile(log zerolog.Logger, fileHeader *multipart.FileHeader, filePath string) { + src, err := fileHeader.Open() + if err != nil { + log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-0"). + Interface("err", err).Msg("") + } + defer src.Close() + + tempFile, err := os.Create(filePath) + if err != nil { + log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-1"). + Interface("err", err).Msg("") + } + defer tempFile.Close() + + // Copy file ke direktori sementara + _, err = io.Copy(tempFile, src) + if err != nil { + log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-2"). + Interface("err", err).Msg("") + } +} + +func uploadToMinIO(ctx context.Context, log zerolog.Logger, minioClient *minio.Client, uploadID, filePath, bucketName string, objectName string, fileSize int64) { + file, err := os.Open(filePath) + if err != nil { + log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-3"). + Interface("err", err).Msg("") + return + } + defer file.Close() + + // Upload file ke MinIO dengan progress tracking + uploadProgress := int64(0) + reader := io.TeeReader(file, &progressWriter{uploadID: uploadID, totalSize: fileSize, uploadedSize: &uploadProgress}) + + _, err = minioClient.PutObject(ctx, bucketName, objectName, reader, fileSize, minio.PutObjectOptions{}) + if err != nil { + + log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-4"). + Interface("err", err).Msg("") + return + } + + // Upload selesai, update progress menjadi 100 + progressLock.Lock() + progressMap[uploadID] = 100 + progressLock.Unlock() + + go removeFileTemp(log, filePath) +} + +func removeFileTemp(log zerolog.Logger, filePath string) { + err := os.Remove(filePath) + if err != nil { + log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-5"). + Interface("Failed to remove temporary file", err).Msg("") + } else { + log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-6"). + Interface("err", "Temporary file removed").Msg("") + } +} + +func (p *progressWriter) Write(data []byte) (int, error) { + n := len(data) + progressLock.Lock() + defer progressLock.Unlock() + + *p.uploadedSize += int64(n) + progress := int(float64(*p.uploadedSize) / float64(p.totalSize) * 100) + + // Update progress di map + progressMap[p.uploadID] = progress + + return n, nil +} + +func (_i *articleFilesService) GetUploadStatus(c *fiber.Ctx) (progress int, err error) { + uploadID := c.Params("uploadId") + + // Ambil progress dari map + progressLock.Lock() + progress, _ = progressMap[uploadID] + progressLock.Unlock() + + return progress, nil +} diff --git a/app/module/article_files/service/async_uploader.service.go b/app/module/article_files/service/async_uploader.service.go new file mode 100644 index 0000000..17c627b --- /dev/null +++ b/app/module/article_files/service/async_uploader.service.go @@ -0,0 +1,139 @@ +package service + +import ( + "context" + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" + "io" + "time" +) + +// AsyncUploader menangani proses upload secara asynchronous +type UploadService interface { + UploadFile(ctx context.Context, minioClient *minio.Client, uploadID string, reader io.Reader, bucketName string, objectName string, size int64, contentType string) error +} + +type uploadService struct { + uploadManager UploadManager + Log zerolog.Logger +} + +func NewUploadService(uploadManager UploadManager, log zerolog.Logger) UploadService { + return &uploadService{ + uploadManager: uploadManager, + Log: log, + } +} + +func (u *uploadService) UploadFile(ctx context.Context, minioClient *minio.Client, uploadID string, reader io.Reader, bucketName string, objectName string, size int64, contentType string) error { + status := &UploadStatus{ + FileName: objectName, + Size: size, + Progress: 0, + Status: "uploading", + ObjectName: objectName, + BucketName: bucketName, + StartTime: time.Now(), + } + + u.uploadManager.Add(uploadID, status) + + u.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "UploadService::UploadFile"). + Interface("add status", status).Msg("") + + // Upload ke Minio + _, err := minioClient.PutObject( + ctx, + bucketName, + objectName, + reader, + size, + minio.PutObjectOptions{ + ContentType: contentType, + PartSize: 10 * 1024 * 1024, // 10MB part size + }, + ) + + if err != nil { + u.uploadManager.UpdateStatus(uploadID, "error", err) + + u.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "UploadService::UploadFile"). + Interface("error when upload", err).Msg("") + } + + u.uploadManager.UpdateStatus(uploadID, "completed", nil) + return nil +} + +//func (au *UploadService) UploadFile() { +// // Buat context dengan timeout +// ctx, cancel := context.WithTimeout(au.ctx, 30*time.Minute) +// defer cancel() +// +// // Buat reader dari byte slice +// reader := bytes.NewReader(au.fileData) +// pipeReader, pipeWriter := io.Pipe() +// +// au.progressMap.Store(au.uploadID, 0.0) +// +// // Start goroutine to read from reader and write to pipe +// go func() { +// defer pipeWriter.Close() +// buf := make([]byte, au.partSize) +// +// totalParts := int(reader.Size() / au.partSize) +// if reader.Size()%au.partSize != 0 { +// totalParts++ +// } +// +// for i := 0; i < totalParts; i++ { +// n, err := reader.Read(buf) +// if err != nil && err != io.EOF { +// log.Println("Error reading file:", err) +// return +// } +// +// if _, err := pipeWriter.Write(buf[:n]); err != nil { +// log.Println("Error writing to pipe:", err) +// return +// } +// +// progress := float64(i+1) / float64(totalParts) * 100 +// au.progressMap.Store(au.uploadID, progress) +// au.uploadManager.UpdateProgress(au.uploadID, int(progress)) +// } +// }() +// +// // Upload ke Minio +// _, err := au.minioClient.PutObject( +// ctx, +// au.bucketName, +// au.objectName, +// pipeReader, +// reader.Size(), +// minio.PutObjectOptions{ +// ContentType: au.contentType, +// PartSize: 10 * 1024 * 1024, // 10MB part size +// }, +// ) +// +// if err != nil { +// log.Println("Error uploading file:", err) +// au.progressMap.Store(au.uploadID, "error") +// return +// } +// +// fmt.Printf("Uploading process for %s", au.objectName) +// +// if err != nil { +// uploadManager.UpdateStatus(au.uploadID, "error", err) +// fmt.Printf("Upload error for %s: %v\n", au.objectName, err) +// return +// } +// +// au.progressMap.Store(au.uploadID, 100) +// au.uploadManager.UpdateProgress(au.uploadID, 100) +// au.uploadManager.UpdateStatus(au.uploadID, "completed", nil) +//} diff --git a/app/module/article_files/service/upload_manager.service.go b/app/module/article_files/service/upload_manager.service.go new file mode 100644 index 0000000..f02ec96 --- /dev/null +++ b/app/module/article_files/service/upload_manager.service.go @@ -0,0 +1,71 @@ +package service + +import ( + "sync" + "time" +) + +type UploadStatus struct { + FileName string `json:"fileName"` + Size int64 `json:"size"` + Progress int `json:"progress"` + Status string `json:"status"` + ObjectName string `json:"objectName"` + BucketName string `json:"bucketName"` + StartTime time.Time `json:"startTime"` + Error string `json:"error,omitempty"` +} + +type UploadManager interface { + Add(uploadID string, status *UploadStatus) + UpdateProgress(uploadID string, progress int) + UpdateStatus(uploadID string, status string, err error) + Get(uploadID string) (*UploadStatus, bool) +} + +type uploadManager struct { + uploads map[string]*UploadStatus + mutex sync.RWMutex +} + +func NewUploadManager() UploadManager { + return &uploadManager{ + uploads: make(map[string]*UploadStatus), + } +} + +// Add menambahkan status upload baru +func (um *uploadManager) Add(uploadID string, status *UploadStatus) { + um.mutex.Lock() + defer um.mutex.Unlock() + um.uploads[uploadID] = status +} + +// UpdateProgress memperbarui progress upload +func (um *uploadManager) UpdateProgress(uploadID string, progress int) { + um.mutex.Lock() + defer um.mutex.Unlock() + if status, exists := um.uploads[uploadID]; exists { + status.Progress = progress + } +} + +// UpdateStatus memperbarui status upload +func (um *uploadManager) UpdateStatus(uploadID string, status string, err error) { + um.mutex.Lock() + defer um.mutex.Unlock() + if upload, exists := um.uploads[uploadID]; exists { + upload.Status = status + if err != nil { + upload.Error = err.Error() + } + } +} + +// Get mendapatkan status upload berdasarkan ID +func (um *uploadManager) Get(uploadID string) (*UploadStatus, bool) { + um.mutex.RLock() + defer um.mutex.RUnlock() + status, exists := um.uploads[uploadID] + return status, exists +} diff --git a/app/module/article_nulis_ai/article_nulis_ai.module.go b/app/module/article_nulis_ai/article_nulis_ai.module.go new file mode 100644 index 0000000..da1d6b7 --- /dev/null +++ b/app/module/article_nulis_ai/article_nulis_ai.module.go @@ -0,0 +1,54 @@ +package article_nulis_ai + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/article_nulis_ai/controller" + "web-qudo-be/app/module/article_nulis_ai/repository" + "web-qudo-be/app/module/article_nulis_ai/service" +) + +// struct of ArticleNulisAIRouter +type ArticleNulisAIRouter struct { + App *fiber.App + Controller *controller.Controller +} + +// register bulky of ArticleNulisAI module +var NewArticleNulisAIModule = fx.Options( + // register repository of ArticleNulisAI module + fx.Provide(repository.NewArticleNulisAIRepository), + + // register service of ArticleNulisAI module + fx.Provide(service.NewArticleNulisAIService), + + // register controller of ArticleNulisAI module + fx.Provide(controller.NewController), + + // register router of ArticleNulisAI module + fx.Provide(NewArticleNulisAIRouter), +) + +// init ArticleNulisAIRouter +func NewArticleNulisAIRouter(fiber *fiber.App, controller *controller.Controller) *ArticleNulisAIRouter { + return &ArticleNulisAIRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of ArticleNulisAI module +func (_i *ArticleNulisAIRouter) RegisterArticleNulisAIRoutes() { + // define controllers + articleNulisAIController := _i.Controller.ArticleNulisAI + + // define routes + _i.App.Route("/article-nulis-ai", func(router fiber.Router) { + router.Get("/", articleNulisAIController.All) + router.Get("/:id", articleNulisAIController.Show) + router.Post("/", articleNulisAIController.Save) + router.Post("/publish", articleNulisAIController.Publish) + router.Put("/:id", articleNulisAIController.Update) + router.Delete("/:id", articleNulisAIController.Delete) + }) +} diff --git a/app/module/article_nulis_ai/controller/article_nulis_ai.controller.go b/app/module/article_nulis_ai/controller/article_nulis_ai.controller.go new file mode 100644 index 0000000..01b76ed --- /dev/null +++ b/app/module/article_nulis_ai/controller/article_nulis_ai.controller.go @@ -0,0 +1,235 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/module/article_nulis_ai/request" + "web-qudo-be/app/module/article_nulis_ai/service" + "web-qudo-be/utils/paginator" + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" +) + +type articleNulisAIController struct { + articleNulisAIService service.ArticleNulisAIService + Log zerolog.Logger +} + +type ArticleNulisAIController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Publish(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewArticleNulisAIController(articleNulisAIService service.ArticleNulisAIService, log zerolog.Logger) ArticleNulisAIController { + return &articleNulisAIController{ + articleNulisAIService: articleNulisAIService, + Log: log, + } +} + +// All get all ArticleNulisAI +// @Summary Get all ArticleNulisAI +// @Description API for getting all ArticleNulisAI +// @Tags ArticleNulisAI +// @Security Bearer +// @Param req query request.ArticleNulisAIQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-nulis-ai [get] +func (_i *articleNulisAIController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.ArticleNulisAIQueryRequestContext{ + NulisAiId: c.Query("nulisAiId"), + ArticleId: c.Query("articleId"), + Title: c.Query("title"), + Description: c.Query("description"), + HtmlDescription: c.Query("htmlDescription"), + CategoryId: c.Query("categoryId"), + CreatorId: c.Query("creatorId"), + Tags: c.Query("tags"), + ThumbnailPath: c.Query("thumbnailPath"), + IsPublish: c.Query("isPublish"), + PublishedAt: c.Query("publishedAt"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + articleNulisAIData, paging, err := _i.articleNulisAIService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleNulisAI list successfully retrieved"}, + Data: articleNulisAIData, + Meta: paging, + }) +} + +// Show get one ArticleNulisAI +// @Summary Get one ArticleNulisAI +// @Description API for getting one ArticleNulisAI +// @Tags ArticleNulisAI +// @Security Bearer +// @Param id path int true "ArticleNulisAI ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-nulis-ai/{id} [get] +func (_i *articleNulisAIController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + articleNulisAIData, err := _i.articleNulisAIService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleNulisAI successfully retrieved"}, + Data: articleNulisAIData, + }) +} + +// Save create ArticleNulisAI +// @Summary Create ArticleNulisAI +// @Description API for create ArticleNulisAI +// @Tags ArticleNulisAI +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.ArticleNulisAICreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-nulis-ai [post] +func (_i *articleNulisAIController) Save(c *fiber.Ctx) error { + req := new(request.ArticleNulisAICreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + dataResult, err := _i.articleNulisAIService.Save(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleNulisAI successfully created"}, + Data: dataResult, + }) +} + +// Update update ArticleNulisAI +// @Summary update ArticleNulisAI +// @Description API for update ArticleNulisAI +// @Tags ArticleNulisAI +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.ArticleNulisAIUpdateRequest true "Required payload" +// @Param id path int true "ArticleNulisAI ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-nulis-ai/{id} [put] +func (_i *articleNulisAIController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ArticleNulisAIUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.articleNulisAIService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleNulisAI successfully updated"}, + }) +} + +// Publish publish ArticleNulisAI +// @Summary publish ArticleNulisAI +// @Description API for publish ArticleNulisAI +// @Tags ArticleNulisAI +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.ArticleNulisAIUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-nulis-ai/publish [post] +func (_i *articleNulisAIController) Publish(c *fiber.Ctx) error { + req := new(request.ArticleNulisAICreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + err := _i.articleNulisAIService.Publish(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleNulisAI successfully published"}, + }) +} + +// Delete delete ArticleNulisAI +// @Summary delete ArticleNulisAI +// @Description API for delete ArticleNulisAI +// @Tags ArticleNulisAI +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "ArticleNulisAI ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-nulis-ai/{id} [delete] +func (_i *articleNulisAIController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.articleNulisAIService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleNulisAI successfully deleted"}, + }) +} diff --git a/app/module/article_nulis_ai/controller/controller.go b/app/module/article_nulis_ai/controller/controller.go new file mode 100644 index 0000000..ae909a8 --- /dev/null +++ b/app/module/article_nulis_ai/controller/controller.go @@ -0,0 +1,16 @@ +package controller + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/article_nulis_ai/service" +) + +type Controller struct { + ArticleNulisAI ArticleNulisAIController +} + +func NewController(ArticleNulisAIService service.ArticleNulisAIService, log zerolog.Logger) *Controller { + return &Controller{ + ArticleNulisAI: NewArticleNulisAIController(ArticleNulisAIService, log), + } +} diff --git a/app/module/article_nulis_ai/mapper/article_nulis_ai.mapper.go b/app/module/article_nulis_ai/mapper/article_nulis_ai.mapper.go new file mode 100644 index 0000000..61199e3 --- /dev/null +++ b/app/module/article_nulis_ai/mapper/article_nulis_ai.mapper.go @@ -0,0 +1,29 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/article_nulis_ai/response" +) + +func ArticleNulisAIResponseMapper(articleNulisAIReq *entity.ArticleNulisAI) (articleNulisAIRes *res.ArticleNulisAIResponse) { + if articleNulisAIReq != nil { + articleNulisAIRes = &res.ArticleNulisAIResponse{ + ID: articleNulisAIReq.ID, + NulisAiId: articleNulisAIReq.NulisAiId, + ArticleId: articleNulisAIReq.ArticleId, + Title: articleNulisAIReq.Title, + Description: articleNulisAIReq.Description, + HtmlDescription: articleNulisAIReq.HtmlDescription, + CategoryId: articleNulisAIReq.CategoryId, + CreatorId: articleNulisAIReq.CreatorId, + Tags: articleNulisAIReq.Tags, + ThumbnailPath: articleNulisAIReq.ThumbnailPath, + IsPublish: articleNulisAIReq.IsPublish, + PublishedAt: articleNulisAIReq.PublishedAt, + IsActive: articleNulisAIReq.IsActive, + CreatedAt: articleNulisAIReq.CreatedAt, + UpdatedAt: articleNulisAIReq.UpdatedAt, + } + } + return articleNulisAIRes +} diff --git a/app/module/article_nulis_ai/repository/article_nulis_ai.repository.go b/app/module/article_nulis_ai/repository/article_nulis_ai.repository.go new file mode 100644 index 0000000..67617bd --- /dev/null +++ b/app/module/article_nulis_ai/repository/article_nulis_ai.repository.go @@ -0,0 +1,113 @@ +package repository + +import ( + "fmt" + "github.com/rs/zerolog" + "strings" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/article_nulis_ai/request" + "web-qudo-be/utils/paginator" +) + +type articleNulisAIRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ArticleNulisAIRepository define interface of IArticleNulisAIRepository +type ArticleNulisAIRepository interface { + GetAll(req request.ArticleNulisAIQueryRequest) (articleNulisAIs []*entity.ArticleNulisAI, paging paginator.Pagination, err error) + FindOne(id uint) (articleNulisAI *entity.ArticleNulisAI, err error) + Create(articleNulisAI *entity.ArticleNulisAI) (articleNulisAIReturn *entity.ArticleNulisAI, err error) + Update(id uint, articleNulisAI *entity.ArticleNulisAI) (err error) + Delete(id uint) (err error) +} + +func NewArticleNulisAIRepository(db *database.Database, logger zerolog.Logger) ArticleNulisAIRepository { + return &articleNulisAIRepository{ + DB: db, + Log: logger, + } +} + +// implement interface of IArticleNulisAIRepository +func (_i *articleNulisAIRepository) GetAll(req request.ArticleNulisAIQueryRequest) (articleNulisAIs []*entity.ArticleNulisAI, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ArticleNulisAI{}) + query = query.Where("is_active = ?", true) + + if req.NulisAiId != nil { + query = query.Where("nulis_ai_id = ?", req.NulisAiId) + } + if req.ArticleId != nil { + query = query.Where("article_id = ?", req.ArticleId) + } + if req.Title != nil && *req.Title != "" { + title := strings.ToLower(*req.Title) + query = query.Where("LOWER(title) LIKE ?", "%"+strings.ToLower(title)+"%") + } + if req.Description != nil && *req.Description != "" { + description := strings.ToLower(*req.Description) + query = query.Where("LOWER(description) LIKE ?", "%"+strings.ToLower(description)+"%") + } + if req.HtmlDescription != nil && *req.HtmlDescription != "" { + htmlDescription := strings.ToLower(*req.HtmlDescription) + query = query.Where("LOWER(html_description) LIKE ?", "%"+strings.ToLower(htmlDescription)+"%") + } + if req.CategoryId != nil { + query = query.Where("category_id = ?", req.CategoryId) + } + if req.CreatorId != nil { + query = query.Where("creator_id = ?", req.CreatorId) + } + if req.Tags != nil && *req.Tags != "" { + tags := strings.ToLower(*req.Tags) + query = query.Where("LOWER(tags) LIKE ?", "%"+strings.ToLower(tags)+"%") + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articleNulisAIs).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *articleNulisAIRepository) FindOne(id uint) (articleNulisAI *entity.ArticleNulisAI, err error) { + if err := _i.DB.DB.First(&articleNulisAI, id).Error; err != nil { + return nil, err + } + + return articleNulisAI, nil +} + +func (_i *articleNulisAIRepository) Create(articleNulisAI *entity.ArticleNulisAI) (articleNulisAIReturn *entity.ArticleNulisAI, err error) { + result := _i.DB.DB.Create(articleNulisAI) + return articleNulisAI, result.Error +} + +func (_i *articleNulisAIRepository) Update(id uint, articleNulisAI *entity.ArticleNulisAI) (err error) { + return _i.DB.DB.Model(&entity.ArticleNulisAI{}). + Where(&entity.ArticleNulisAI{ID: id}). + Updates(articleNulisAI).Error +} + +func (_i *articleNulisAIRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.ArticleNulisAI{}, id).Error +} diff --git a/app/module/article_nulis_ai/request/article_nulis_ai.request.go b/app/module/article_nulis_ai/request/article_nulis_ai.request.go new file mode 100644 index 0000000..d78e468 --- /dev/null +++ b/app/module/article_nulis_ai/request/article_nulis_ai.request.go @@ -0,0 +1,133 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type ArticleNulisAIGeneric interface { + ToEntity() +} + +type ArticleNulisAIQueryRequest struct { + NulisAiId *int `json:"nulisAiId"` + ArticleId *int `json:"articleId"` + Title *string `json:"title"` + Description *string `json:"description"` + HtmlDescription *string `json:"htmlDescription"` + CategoryId *int `json:"categoryId"` + CreatorId *int `json:"creatorId"` + Tags *string `json:"tags"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ArticleNulisAICreateRequest struct { + NulisAiId int `json:"nulisAiId" validate:"required"` + ArticleId int `json:"articleId" validate:"required"` + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + HtmlDescription string `json:"htmlDescription" validate:"required"` + CategoryId int `json:"categoryId" validate:"required"` + CreatorId uint `json:"creatorId" validate:"required"` + Tags string `json:"tags" validate:"required"` +} + +func (req ArticleNulisAICreateRequest) ToEntity() *entity.ArticleNulisAI { + return &entity.ArticleNulisAI{ + NulisAiId: req.NulisAiId, + ArticleId: req.ArticleId, + Title: req.Title, + Description: req.Description, + HtmlDescription: req.HtmlDescription, + CategoryId: req.CategoryId, + CreatorId: req.CreatorId, + Tags: req.Tags, + } +} + +type ArticleNulisAIUpdateRequest struct { + ID uint `json:"id" validate:"required"` + NulisAiId int `json:"nulisAiId" validate:"required"` + ArticleId int `json:"articleId" validate:"required"` + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + HtmlDescription string `json:"htmlDescription" validate:"required"` + CategoryId int `json:"categoryId" validate:"required"` + CreatorId uint `json:"creatorId" validate:"required"` + Tags string `json:"tags" validate:"required"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (req ArticleNulisAIUpdateRequest) ToEntity() *entity.ArticleNulisAI { + return &entity.ArticleNulisAI{ + ID: req.ID, + NulisAiId: req.NulisAiId, + ArticleId: req.ArticleId, + Title: req.Title, + Description: req.Description, + HtmlDescription: req.HtmlDescription, + CategoryId: req.CategoryId, + CreatorId: req.CreatorId, + Tags: req.Tags, + UpdatedAt: time.Now(), + } +} + +type ArticleNulisAIQueryRequestContext struct { + NulisAiId string `json:"nulisAiId"` + ArticleId string `json:"articleId"` + Title string `json:"title"` + Description string `json:"description"` + HtmlDescription string `json:"htmlDescription"` + CategoryId string `json:"categoryId"` + CreatorId string `json:"creatorId"` + Tags string `json:"tags"` + ThumbnailPath string `json:"thumbnailPath"` + IsPublish string `json:"isPublish"` + PublishedAt string `json:"publishedAt"` +} + +func (req ArticleNulisAIQueryRequestContext) ToParamRequest() ArticleNulisAIQueryRequest { + var request ArticleNulisAIQueryRequest + + if nulisAiIdStr := req.NulisAiId; nulisAiIdStr != "" { + nulisAiId, err := strconv.Atoi(nulisAiIdStr) + if err == nil { + request.NulisAiId = &nulisAiId + } + } + if articleIdStr := req.ArticleId; articleIdStr != "" { + articleId, err := strconv.Atoi(articleIdStr) + if err == nil { + request.ArticleId = &articleId + } + } + if title := req.Title; title != "" { + request.Title = &title + } + if description := req.Description; description != "" { + request.Description = &description + } + if htmlDescription := req.HtmlDescription; htmlDescription != "" { + request.HtmlDescription = &htmlDescription + } + if categoryIdStr := req.CategoryId; categoryIdStr != "" { + categoryId, err := strconv.Atoi(categoryIdStr) + if err == nil { + request.CategoryId = &categoryId + } + } + if creatorIdStr := req.CreatorId; creatorIdStr != "" { + creatorId, err := strconv.Atoi(creatorIdStr) + if err == nil { + request.CreatorId = &creatorId + } + } + if tags := req.Tags; tags != "" { + request.Tags = &tags + } + + return request +} diff --git a/app/module/article_nulis_ai/response/article_nulis_ai.response.go b/app/module/article_nulis_ai/response/article_nulis_ai.response.go new file mode 100644 index 0000000..7d26e52 --- /dev/null +++ b/app/module/article_nulis_ai/response/article_nulis_ai.response.go @@ -0,0 +1,21 @@ +package response + +import "time" + +type ArticleNulisAIResponse struct { + ID uint `json:"id"` + NulisAiId int `json:"nulisAiId"` + ArticleId int `json:"articleId"` + Title string `json:"title"` + Description string `json:"description"` + HtmlDescription string `json:"htmlDescription"` + CategoryId int `json:"categoryId"` + CreatorId uint `json:"creatorId"` + Tags string `json:"tags"` + ThumbnailPath string `json:"thumbnailPath"` + IsPublish bool `json:"isPublish"` + PublishedAt time.Time `json:"publishedAt"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/article_nulis_ai/service/article_nulis_ai.service.go b/app/module/article_nulis_ai/service/article_nulis_ai.service.go new file mode 100644 index 0000000..8571b11 --- /dev/null +++ b/app/module/article_nulis_ai/service/article_nulis_ai.service.go @@ -0,0 +1,120 @@ +package service + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/article_nulis_ai/mapper" + "web-qudo-be/app/module/article_nulis_ai/repository" + "web-qudo-be/app/module/article_nulis_ai/request" + "web-qudo-be/app/module/article_nulis_ai/response" + articlesRepository "web-qudo-be/app/module/articles/repository" + articles "web-qudo-be/app/module/articles/request" + usersRepository "web-qudo-be/app/module/users/repository" + "web-qudo-be/utils/paginator" + + utilSvc "web-qudo-be/utils/service" +) + +// ArticleNulisAIService +type articleNulisAIService struct { + Repo repository.ArticleNulisAIRepository + UsersRepo usersRepository.UsersRepository + ArticlesRepo articlesRepository.ArticlesRepository + Log zerolog.Logger +} + +// ArticleNulisAIService define interface of IArticleNulisAIService +type ArticleNulisAIService interface { + All(req request.ArticleNulisAIQueryRequest) (articleNulisAI []*response.ArticleNulisAIResponse, paging paginator.Pagination, err error) + Show(id uint) (articleNulisAI *response.ArticleNulisAIResponse, err error) + Save(req request.ArticleNulisAICreateRequest, authToken string) (articleNulisAI *entity.ArticleNulisAI, err error) + Update(id uint, req request.ArticleNulisAIUpdateRequest) (err error) + Publish(req request.ArticleNulisAICreateRequest, authToken string) (err error) + Delete(id uint) error +} + +// NewArticleNulisAIService init ArticleNulisAIService +func NewArticleNulisAIService(repo repository.ArticleNulisAIRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository, articlesRepo articlesRepository.ArticlesRepository) ArticleNulisAIService { + + return &articleNulisAIService{ + Repo: repo, + Log: log, + UsersRepo: usersRepo, + ArticlesRepo: articlesRepo, + } +} + +// All implement interface of ArticleNulisAIService +func (_i *articleNulisAIService) All(req request.ArticleNulisAIQueryRequest) (articleNulisAIs []*response.ArticleNulisAIResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + articleNulisAIs = append(articleNulisAIs, mapper.ArticleNulisAIResponseMapper(result)) + } + + return +} + +func (_i *articleNulisAIService) Show(id uint) (articleNulisAI *response.ArticleNulisAIResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.ArticleNulisAIResponseMapper(result), nil +} + +func (_i *articleNulisAIService) Save(req request.ArticleNulisAICreateRequest, authToken string) (articleNulisAI *entity.ArticleNulisAI, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + newReq := req.ToEntity() + + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + newReq.CreatorId = createdBy.ID + + return _i.Repo.Create(newReq) +} + +func (_i *articleNulisAIService) Publish(req request.ArticleNulisAICreateRequest, authToken string) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + newReq := req.ToEntity() + articleReq := articles.ArticlesCreateRequest{ + Title: newReq.Title, + Slug: utilSvc.MakeSlug(req.Title), + Tags: newReq.Tags, + TypeId: 1, + Description: newReq.Description, + HtmlDescription: newReq.HtmlDescription, + } + + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + newArticleReq := articleReq.ToEntity() + newArticleReq.CreatedById = &createdBy.ID + + _, err = _i.ArticlesRepo.Create(nil, newArticleReq) + if err != nil { + return err + } else { + return nil + } +} + +func (_i *articleNulisAIService) Update(id uint, req request.ArticleNulisAIUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *articleNulisAIService) Delete(id uint) error { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + result.IsActive = false + return _i.Repo.Update(id, result) +} diff --git a/app/module/articles/articles.module.go b/app/module/articles/articles.module.go new file mode 100644 index 0000000..8d7bb65 --- /dev/null +++ b/app/module/articles/articles.module.go @@ -0,0 +1,80 @@ +package articles + +import ( + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/articles/controller" + "web-qudo-be/app/module/articles/repository" + "web-qudo-be/app/module/articles/service" + usersRepo "web-qudo-be/app/module/users/repository" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// ArticlesRouter struct of ArticlesRouter +type ArticlesRouter struct { + App fiber.Router + Controller *controller.Controller + UsersRepo usersRepo.UsersRepository +} + +// NewArticlesModule register bulky of Articles module +var NewArticlesModule = fx.Options( + // register repository of Articles module + fx.Provide(repository.NewArticlesRepository), + + // register service of Articles module + fx.Provide(service.NewArticlesService), + + // register controller of Articles module + fx.Provide(controller.NewController), + + // register router of Articles module + fx.Provide(NewArticlesRouter), +) + +// NewArticlesRouter init ArticlesRouter +func NewArticlesRouter(fiber *fiber.App, controller *controller.Controller, usersRepo usersRepo.UsersRepository) *ArticlesRouter { + return &ArticlesRouter{ + App: fiber, + Controller: controller, + UsersRepo: usersRepo, + } +} + +// RegisterArticlesRoutes register routes of Articles module +func (_i *ArticlesRouter) RegisterArticlesRoutes() { + // define controllers + articlesController := _i.Controller.Articles + + // define routes + _i.App.Route("/articles", func(router fiber.Router) { + // Add user middleware to extract user level from JWT token + router.Use(middleware.UserMiddleware(_i.UsersRepo)) + + // Dynamic approval system routes + router.Post("/:id/submit-approval", articlesController.SubmitForApproval) + router.Get("/:id/approval-status", articlesController.GetApprovalStatus) + router.Get("/pending-approval", articlesController.GetPendingApprovals) + router.Get("/waiting-for-approval", articlesController.GetArticlesWaitingForApproval) + + // Publish/Unpublish routes + router.Put("/:id/publish", articlesController.Publish) + router.Put("/:id/unpublish", articlesController.Unpublish) + + router.Get("/", articlesController.All) + router.Get("/old-id/:id", articlesController.ShowByOldId) + router.Get("/slug/:slug", articlesController.ShowBySlug) + router.Get("/:id", articlesController.Show) + router.Post("/", articlesController.Save) + router.Put("/:id", articlesController.Update) + router.Put("/banner/:id", articlesController.UpdateBanner) + router.Post("/thumbnail/:id", articlesController.SaveThumbnail) + router.Get("/thumbnail/viewer/:thumbnailName", articlesController.Viewer) + router.Post("/publish-scheduling", articlesController.PublishScheduling) + router.Delete("/:id", articlesController.Delete) + router.Get("/statistic/summary", articlesController.SummaryStats) + router.Get("/statistic/user-levels", articlesController.ArticlePerUserLevelStats) + router.Get("/statistic/monthly", articlesController.ArticleMonthlyStats) + }) +} diff --git a/app/module/articles/controller/articles.controller.go b/app/module/articles/controller/articles.controller.go new file mode 100644 index 0000000..7d5e906 --- /dev/null +++ b/app/module/articles/controller/articles.controller.go @@ -0,0 +1,797 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/articles/request" + "web-qudo-be/app/module/articles/service" + "web-qudo-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type articlesController struct { + articlesService service.ArticlesService + Log zerolog.Logger +} + +type ArticlesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + ShowByOldId(c *fiber.Ctx) error + ShowBySlug(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + SaveThumbnail(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + UpdateBanner(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + Viewer(c *fiber.Ctx) error + SummaryStats(c *fiber.Ctx) error + ArticlePerUserLevelStats(c *fiber.Ctx) error + ArticleMonthlyStats(c *fiber.Ctx) error + PublishScheduling(c *fiber.Ctx) error + + // Dynamic approval system methods + SubmitForApproval(c *fiber.Ctx) error + GetApprovalStatus(c *fiber.Ctx) error + GetPendingApprovals(c *fiber.Ctx) error + GetArticlesWaitingForApproval(c *fiber.Ctx) error + + // Publish/Unpublish methods + Publish(c *fiber.Ctx) error + Unpublish(c *fiber.Ctx) error +} + +func NewArticlesController(articlesService service.ArticlesService, log zerolog.Logger) ArticlesController { + return &articlesController{ + articlesService: articlesService, + Log: log, + } +} + +// All Articles +// @Summary Get all Articles +// @Description API for getting all Articles +// @Tags Articles +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param req query request.ArticlesQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles [get] +func (_i *articlesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.ArticlesQueryRequestContext{ + Title: c.Query("title"), + Description: c.Query("description"), + Tags: c.Query("tags"), + Category: c.Query("category"), + CategoryId: c.Query("categoryId"), + TypeId: c.Query("typeId"), + StatusId: c.Query("statusId"), + IsPublish: c.Query("isPublish"), + IsDraft: c.Query("isDraft"), + IsBanner: c.Query("isBanner"), + CustomCreatorName: c.Query("customCreatorName"), + Source: c.Query("source"), + StartDate: c.Query("startDate"), + EndDate: c.Query("endDate"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Get Authorization token from header + authToken := c.Get("Authorization") + _i.Log.Info().Interface("clientId", clientId).Msg("") + _i.Log.Info().Str("authToken", authToken).Msg("") + + articlesData, paging, err := _i.articlesService.All(clientId, authToken, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Articles list successfully retrieved"}, + Data: articlesData, + Meta: paging, + }) +} + +// Show Articles +// @Summary Get one Articles +// @Description API for getting one Articles +// @Tags Articles +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "Articles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/{id} [get] +func (_i *articlesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + articlesData, err := _i.articlesService.Show(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Articles successfully retrieved"}, + Data: articlesData, + }) +} + +// ShowByOldId Articles +// @Summary Get one Articles +// @Description API for getting one Articles +// @Tags Articles +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "Articles Old ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/old-id/{id} [get] +func (_i *articlesController) ShowByOldId(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + articlesData, err := _i.articlesService.ShowByOldId(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Articles successfully retrieved"}, + Data: articlesData, + }) +} + +// ShowBySlug Articles +// @Summary Get one Articles by Slug +// @Description API for getting one Articles by slug +// @Tags Articles +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param slug path string true "Articles Slug" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/slug/{slug} [get] +func (_i *articlesController) ShowBySlug(c *fiber.Ctx) error { + slug := c.Params("slug") + if slug == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": true, + "msg": "Slug parameter is required", + }) + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + articlesData, err := _i.articlesService.ShowBySlug(clientId, slug) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Articles successfully retrieved"}, + Data: articlesData, + }) +} + +// Save Articles +// @Summary Create Articles +// @Description API for create Articles +// @Tags Articles +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.ArticlesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles [post] +func (_i *articlesController) Save(c *fiber.Ctx) error { + req := new(request.ArticlesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + // Get ClientId from context + 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) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Articles successfully created"}, + Data: dataResult, + }) +} + +// SaveThumbnail Articles +// @Summary Save Thumbnail Articles +// @Description API for Save Thumbnail of Articles +// @Tags Articles +// @Security Bearer +// @Produce json +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param files formData file true "Upload thumbnail" +// @Param id path int true "Articles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/thumbnail/{id} [post] +func (_i *articlesController) SaveThumbnail(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err := _i.articlesService.SaveThumbnail(clientId, c) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Thumbnail of Articles successfully created"}, + }) +} + +// Update Articles +// @Summary Update Articles +// @Description API for update Articles +// @Tags Articles +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.ArticlesUpdateRequest true "Required payload" +// @Param id path int true "Articles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/{id} [put] +func (_i *articlesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ArticlesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.articlesService.Update(clientId, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Articles successfully updated"}, + }) +} + +// UpdateBanner Articles +// @Summary Update Banner Articles +// @Description API for Update Banner Articles +// @Tags Articles +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "Articles ID" +// @Param isBanner query bool true "Articles Banner Status" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/banner/{id} [put] +func (_i *articlesController) UpdateBanner(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + isBanner, err := strconv.ParseBool(c.Query("isBanner")) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.articlesService.UpdateBanner(clientId, uint(id), isBanner) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Articles successfully banner updated"}, + }) +} + +// Delete Articles +// @Summary Delete Articles +// @Description API for delete Articles +// @Tags Articles +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "Articles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/{id} [delete] +func (_i *articlesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.articlesService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Articles successfully deleted"}, + }) +} + +// Viewer Articles +// @Summary Viewer Articles Thumbnail +// @Description API for View Thumbnail of Article +// @Tags Articles +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param thumbnailName path string true "Articles Thumbnail Name" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/thumbnail/viewer/{thumbnailName} [get] +func (_i *articlesController) Viewer(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + return _i.articlesService.Viewer(clientId, c) +} + +// SummaryStats Articles +// @Summary SummaryStats Articles +// @Description API for Summary Stats of Article +// @Tags Articles +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/statistic/summary [get] +func (_i *articlesController) SummaryStats(c *fiber.Ctx) error { + authToken := c.Get("Authorization") + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + response, err := _i.articlesService.SummaryStats(clientId, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Summary Stats of Articles successfully retrieved"}, + Data: response, + }) +} + +// ArticlePerUserLevelStats Articles +// @Summary ArticlePerUserLevelStats Articles +// @Description API for ArticlePerUserLevelStats of Article +// @Tags Articles +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param startDate query string false "start date" +// @Param endDate query string false "start date" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/statistic/user-levels [get] +func (_i *articlesController) ArticlePerUserLevelStats(c *fiber.Ctx) error { + authToken := c.Get("Authorization") + startDate := c.Query("startDate") + endDate := c.Query("endDate") + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + response, err := _i.articlesService.ArticlePerUserLevelStats(clientId, authToken, &startDate, &endDate) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticlePerUserLevelStats of Articles successfully retrieved"}, + Data: response, + }) +} + +// ArticleMonthlyStats Articles +// @Summary ArticleMonthlyStats Articles +// @Description API for ArticleMonthlyStats of Article +// @Tags Articles +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param year query int false "year" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/statistic/monthly [get] +func (_i *articlesController) ArticleMonthlyStats(c *fiber.Ctx) error { + authToken := c.Get("Authorization") + year := c.Query("year") + yearInt, err := strconv.Atoi(year) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + response, err := _i.articlesService.ArticleMonthlyStats(clientId, authToken, &yearInt) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleMonthlyStats of Articles successfully retrieved"}, + Data: response, + }) +} + +// PublishScheduling Articles +// @Summary PublishScheduling Articles +// @Description API for Publish Schedule of Article. Supports both date-only format (2006-01-02) and datetime format (2006-01-02 15:04:05) +// @Tags Articles +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id query int false "article id" +// @Param date query string false "publish date/time (format: 2006-01-02 or 2006-01-02 15:04:05)" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/publish-scheduling [post] +func (_i *articlesController) PublishScheduling(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Query("id"), 10, 0) + if err != nil { + return err + } + date := c.Query("date") + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.articlesService.PublishScheduling(clientId, uint(id), date) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Publish Scheduling of Articles successfully saved"}, + }) +} + +// SubmitForApproval Articles +// @Summary Submit Article for Approval +// @Description API for submitting article for approval workflow +// @Tags Articles +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "article id" +// @Param req body request.SubmitForApprovalRequest false "approval request data" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/{id}/submit-approval [post] +func (_i *articlesController) SubmitForApproval(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + var req request.SubmitForApprovalRequest + if err := c.BodyParser(&req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Get Authorization token from header + authToken := c.Get("Authorization") + + err = _i.articlesService.SubmitForApproval(clientId, uint(id), authToken, req.WorkflowId) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Article successfully submitted for approval"}, + }) +} + +// GetApprovalStatus Articles +// @Summary Get Article Approval Status +// @Description API for getting article approval status and workflow progress +// @Tags Articles +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "article id" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/{id}/approval-status [get] +func (_i *articlesController) GetApprovalStatus(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + response, err := _i.articlesService.GetApprovalStatus(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Article approval status successfully retrieved"}, + Data: response, + }) +} + +// GetPendingApprovals Articles +// @Summary Get Pending Approvals +// @Description API for getting articles pending approval for current user level +// @Tags Articles +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param page query int false "page number" +// @Param limit query int false "items per page" +// @Param typeId query int false "article type id" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/pending-approval [get] +func (_i *articlesController) GetPendingApprovals(c *fiber.Ctx) error { + page, err := strconv.Atoi(c.Query("page", "1")) + if err != nil { + page = 1 + } + + limit, err := strconv.Atoi(c.Query("limit", "10")) + if err != nil { + limit = 10 + } + + // Parse typeId filter + var typeId *int + if typeIdStr := c.Query("typeId"); typeIdStr != "" { + if parsedTypeId, err := strconv.Atoi(typeIdStr); err == nil { + typeId = &parsedTypeId + } + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Get Authorization token from header + authToken := c.Get("Authorization") + + response, paging, err := _i.articlesService.GetPendingApprovals(clientId, authToken, page, limit, typeId) // Updated with typeId filter + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Pending approvals successfully retrieved"}, + Data: response, + 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 Authorization header string false "Insert your access token" default(Bearer ) +// @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 Authorization token from header + authToken := c.Get("Authorization") + + responses, paging, err := _i.articlesService.GetArticlesWaitingForApproval(clientId, authToken, 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, + }) +} + +// Publish Articles +// @Summary Publish Article +// @Description API for publishing an article +// @Tags Articles +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "article id" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/{id}/publish [put] +func (_i *articlesController) Publish(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Get Authorization token from header + authToken := c.Get("Authorization") + + err = _i.articlesService.Publish(clientId, uint(id), authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Article successfully published"}, + }) +} + +// Unpublish Articles +// @Summary Unpublish Article +// @Description API for unpublishing an article +// @Tags Articles +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "article id" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/{id}/unpublish [put] +func (_i *articlesController) Unpublish(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Get Authorization token from header + authToken := c.Get("Authorization") + + err = _i.articlesService.Unpublish(clientId, uint(id), authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Article successfully unpublished"}, + }) +} diff --git a/app/module/articles/controller/controller.go b/app/module/articles/controller/controller.go new file mode 100644 index 0000000..eb5b156 --- /dev/null +++ b/app/module/articles/controller/controller.go @@ -0,0 +1,16 @@ +package controller + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/articles/service" +) + +type Controller struct { + Articles ArticlesController +} + +func NewController(ArticlesService service.ArticlesService, log zerolog.Logger) *Controller { + return &Controller{ + Articles: NewArticlesController(ArticlesService, log), + } +} diff --git a/app/module/articles/mapper/articles.mapper.go b/app/module/articles/mapper/articles.mapper.go new file mode 100644 index 0000000..8712533 --- /dev/null +++ b/app/module/articles/mapper/articles.mapper.go @@ -0,0 +1,115 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + articleCategoriesMapper "web-qudo-be/app/module/article_categories/mapper" + articleCategoriesRepository "web-qudo-be/app/module/article_categories/repository" + articleCategoriesResponse "web-qudo-be/app/module/article_categories/response" + articleCategoryDetailsRepository "web-qudo-be/app/module/article_category_details/repository" + articleFilesMapper "web-qudo-be/app/module/article_files/mapper" + articleFilesRepository "web-qudo-be/app/module/article_files/repository" + articleFilesResponse "web-qudo-be/app/module/article_files/response" + res "web-qudo-be/app/module/articles/response" + usersRepository "web-qudo-be/app/module/users/repository" + + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +func ArticlesResponseMapper( + log zerolog.Logger, + host string, + clientId *uuid.UUID, + articlesReq *entity.Articles, + articleCategoriesRepo articleCategoriesRepository.ArticleCategoriesRepository, + articleCategoryDetailsRepo articleCategoryDetailsRepository.ArticleCategoryDetailsRepository, + articleFilesRepo articleFilesRepository.ArticleFilesRepository, + usersRepo usersRepository.UsersRepository, +) (articlesRes *res.ArticlesResponse) { + + createdByName := "" + if articlesReq.CreatedById != nil { + findUser, _ := usersRepo.FindOne(clientId, *articlesReq.CreatedById) + if findUser != nil { + createdByName = findUser.Fullname + } + } + + categoryName := "" + articleCategories, _ := articleCategoryDetailsRepo.FindByArticleId(articlesReq.ID) + var articleCategoriesArr []*articleCategoriesResponse.ArticleCategoriesResponse + if articleCategories != nil && len(articleCategories) > 0 { + for _, result := range articleCategories { + articleCategoriesArr = append(articleCategoriesArr, articleCategoriesMapper.ArticleCategoriesResponseMapper(result.Category, host)) + } + log.Info().Interface("articleCategoriesArr", articleCategoriesArr).Msg("") + } + + articleFiles, _ := articleFilesRepo.FindByArticle(clientId, articlesReq.ID) + var articleFilesArr []*articleFilesResponse.ArticleFilesResponse + if articleFiles != nil && len(articleFiles) > 0 { + for _, result := range articleFiles { + articleFilesArr = append(articleFilesArr, articleFilesMapper.ArticleFilesResponseMapper(result, host)) + } + } + + // Calculate PublishStatus based on conditions + publishStatus := "Cancel" // Default status + + if articlesReq.IsPublish != nil && *articlesReq.IsPublish { + // Published: isPublish = true + publishStatus = "Published" + } else if articlesReq.PublishSchedule != nil && *articlesReq.PublishSchedule != "" { + // On Schedule: has publishSchedule and isPublish = false + if articlesReq.IsPublish == nil || !*articlesReq.IsPublish { + publishStatus = "On Schedule" + } + } else if articlesReq.IsPublish != nil && !*articlesReq.IsPublish && + articlesReq.IsDraft != nil && !*articlesReq.IsDraft { + // Cancel: isPublish = false and isDraft = false + publishStatus = "Cancel" + } + + if articlesReq != nil { + articlesRes = &res.ArticlesResponse{ + ID: articlesReq.ID, + Title: articlesReq.Title, + Slug: articlesReq.Slug, + Description: articlesReq.Description, + HtmlDescription: articlesReq.HtmlDescription, + TypeId: articlesReq.TypeId, + Tags: articlesReq.Tags, + CategoryId: articlesReq.CategoryId, + AiArticleId: articlesReq.AiArticleId, + CategoryName: categoryName, + PageUrl: articlesReq.PageUrl, + CreatedById: articlesReq.CreatedById, + CreatedByName: &createdByName, + ShareCount: articlesReq.ShareCount, + ViewCount: articlesReq.ViewCount, + CommentCount: articlesReq.CommentCount, + OldId: articlesReq.OldId, + StatusId: articlesReq.StatusId, + IsDraft: articlesReq.IsDraft, + DraftedAt: articlesReq.DraftedAt, + IsBanner: articlesReq.IsBanner, + IsPublish: articlesReq.IsPublish, + PublishSchedule: articlesReq.PublishSchedule, + PublishedAt: articlesReq.PublishedAt, + PublishStatus: publishStatus, + IsActive: articlesReq.IsActive, + Source: articlesReq.Source, + CustomCreatorName: articlesReq.CustomCreatorName, + CreatedAt: articlesReq.CreatedAt, + UpdatedAt: articlesReq.UpdatedAt, + ArticleFiles: articleFilesArr, + ArticleCategories: articleCategoriesArr, + } + + if articlesReq.ThumbnailName != nil { + articlesRes.ThumbnailUrl = host + "/articles/thumbnail/viewer/" + *articlesReq.ThumbnailName + } + } + + return articlesRes +} diff --git a/app/module/articles/repository/articles.repository.go b/app/module/articles/repository/articles.repository.go new file mode 100644 index 0000000..831427b --- /dev/null +++ b/app/module/articles/repository/articles.repository.go @@ -0,0 +1,518 @@ +package repository + +import ( + "fmt" + "strings" + "time" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/articles/request" + "web-qudo-be/app/module/articles/response" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" + + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +type articlesRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ArticlesRepository define interface of IArticlesRepository +type ArticlesRepository interface { + 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) + FindOne(clientId *uuid.UUID, id uint) (articles *entity.Articles, err error) + FindByFilename(clientId *uuid.UUID, thumbnailName string) (articleReturn *entity.Articles, err error) + FindByOldId(clientId *uuid.UUID, oldId uint) (articles *entity.Articles, err error) + FindBySlug(clientId *uuid.UUID, slug string) (articles *entity.Articles, err error) + Create(clientId *uuid.UUID, articles *entity.Articles) (articleReturn *entity.Articles, err error) + Update(clientId *uuid.UUID, id uint, articles *entity.Articles) (err error) + UpdateSkipNull(clientId *uuid.UUID, id uint, articles *entity.Articles) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) + SummaryStats(clientId *uuid.UUID, userID uint) (articleSummaryStats *response.ArticleSummaryStats, err error) + ArticlePerUserLevelStats(clientId *uuid.UUID, userLevelId *uint, levelNumber *int, startDate *time.Time, endDate *time.Time) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error) + ArticleMonthlyStats(clientId *uuid.UUID, userLevelId *uint, levelNumber *int, year int) (articleMontlyStats []*response.ArticleMonthlyStats, err error) +} + +func NewArticlesRepository(db *database.Database, log zerolog.Logger) ArticlesRepository { + return &articlesRepository{ + DB: db, + Log: log, + } +} + +// implement interface of IArticlesRepository +func (_i *articlesRepository) GetAll(clientId *uuid.UUID, userLevelId *uint, req request.ArticlesQueryRequest) (articless []*entity.Articles, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Articles{}) + + _i.Log.Info().Interface("userLevelId", userLevelId).Msg("") + // Add approval workflow filtering based on user level + if userLevelId != nil { + // Strict filtering logic for article visibility based on approval workflow + query = query.Where(` + ( + -- Articles that don't require approval (bypass or exempt) + (bypass_approval = true OR approval_exempt = true) + OR + -- Articles that are published AND approved through workflow + (is_publish = true AND status_id = 2) + OR + -- Articles created by users at HIGHER hierarchy only (not same or lower) + 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 the CURRENT approver in the workflow + ( + workflow_id IS NOT NULL + 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 -- Only in progress + AND aws.required_user_level_id = ? + AND aws.step_order = aaf.current_step -- Must be current step + ) + ) + OR + -- Articles that have been approved by this user level + ( + workflow_id IS NOT NULL + AND EXISTS ( + SELECT 1 FROM article_approval_flows aaf + JOIN article_approval_step_logs aasl ON aaf.id = aasl.approval_flow_id + WHERE aaf.article_id = articles.id + AND aasl.user_level_id = ? + AND aasl.action = 'approve' + ) + ) + ) + `, *userLevelId, *userLevelId, *userLevelId) + } + + if req.CategoryId != nil { + query = query.Joins("JOIN article_category_details acd ON acd.article_id = articles.id"). + Where("acd.category_id = ?", req.CategoryId) + } + query = query.Where("articles.is_active = ?", true) + + // Add client filter + if clientId != nil { + query = query.Where("articles.client_id = ?", clientId) + } + + if req.Title != nil && *req.Title != "" { + title := strings.ToLower(*req.Title) + query = query.Where("LOWER(articles.title) LIKE ?", "%"+strings.ToLower(title)+"%") + } + if req.Description != nil && *req.Description != "" { + description := strings.ToLower(*req.Description) + query = query.Where("LOWER(articles.description) LIKE ?", "%"+strings.ToLower(description)+"%") + } + if req.Tags != nil && *req.Tags != "" { + tags := strings.ToLower(*req.Tags) + query = query.Where("LOWER(articles.tags) LIKE ?", "%"+strings.ToLower(tags)+"%") + } + if req.TypeId != nil { + query = query.Where("articles.type_id = ?", req.TypeId) + } + if req.IsPublish != nil { + query = query.Where("articles.is_publish = ?", req.IsPublish) + } + if req.IsBanner != nil { + query = query.Where("articles.is_banner = ?", req.IsBanner) + } + if req.IsDraft != nil { + query = query.Where("articles.is_draft = ?", req.IsDraft) + } + if req.StatusId != nil { + query = query.Where("articles.status_id = ?", req.StatusId) + } + if req.CreatedById != nil { + query = query.Where("articles.created_by_id = ?", req.CreatedById) + } + if req.Source != nil && *req.Source != "" { + source := strings.ToLower(*req.Source) + query = query.Where("LOWER(articles.source) = ?", strings.ToLower(source)) + } + if req.CustomCreatorName != nil && *req.CustomCreatorName != "" { + customCreatorName := strings.ToLower(*req.CustomCreatorName) + query = query.Where("LOWER(articles.custom_creator_name) LIKE ?", "%"+strings.ToLower(customCreatorName)+"%") + } + if req.StartDate != nil { + query = query.Where("DATE(articles.created_at) >= ?", req.StartDate.Format("2006-01-02")) + } + if req.EndDate != nil { + query = query.Where("DATE(articles.created_at) <= ?", req.EndDate.Format("2006-01-02")) + } + + // Count total records + query.Count(&count) + + // Apply sorting + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } else { + direction := "DESC" + sortBy := "articles.created_at" + query.Order(fmt.Sprintf("%s %s", sortBy, direction)) + } + + // Apply pagination (manual calculation for better performance) + page := req.Pagination.Page + limit := req.Pagination.Limit + if page <= 0 { + page = 1 + } + if limit <= 0 { + limit = 10 + } + + offset := (page - 1) * limit + err = query.Offset(offset).Limit(limit).Find(&articless).Error + if err != nil { + return + } + + // Create pagination response + paging = paginator.Pagination{ + Page: page, + Limit: limit, + Count: count, + TotalPage: int((count + int64(limit) - 1) / int64(limit)), + } + + return +} + +func (_i *articlesRepository) GetAllPublishSchedule(clientId *uuid.UUID) (articles []*entity.Articles, err error) { + query := _i.DB.DB.Where("publish_schedule IS NOT NULL and is_publish = false") + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + err = query.Find(&articles).Error + if err != nil { + return nil, err + } + return articles, nil +} + +func (_i *articlesRepository) FindOne(clientId *uuid.UUID, id uint) (articles *entity.Articles, err error) { + query := _i.DB.DB + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + if err := query.First(&articles, id).Error; err != nil { + return nil, err + } + + return articles, nil +} + +func (_i *articlesRepository) FindByFilename(clientId *uuid.UUID, thumbnailName string) (articles *entity.Articles, err error) { + query := _i.DB.DB.Where("thumbnail_name = ?", thumbnailName) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + if err := query.First(&articles).Error; err != nil { + return nil, err + } + + return articles, nil +} + +func (_i *articlesRepository) FindByOldId(clientId *uuid.UUID, oldId uint) (articles *entity.Articles, err error) { + query := _i.DB.DB.Where("old_id = ?", oldId) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + if err := query.First(&articles).Error; err != nil { + return nil, err + } + + return articles, nil +} + +func (_i *articlesRepository) FindBySlug(clientId *uuid.UUID, slug string) (articles *entity.Articles, err error) { + query := _i.DB.DB.Where("slug = ?", slug) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + if err := query.First(&articles).Error; err != nil { + return nil, err + } + + return articles, nil +} + +func (_i *articlesRepository) Create(clientId *uuid.UUID, articles *entity.Articles) (articleReturn *entity.Articles, err error) { + // Set client ID + if clientId != nil { + articles.ClientId = clientId + } + + result := _i.DB.DB.Create(articles) + return articles, result.Error +} + +func (_i *articlesRepository) Update(clientId *uuid.UUID, id uint, articles *entity.Articles) (err error) { + // Validate client access + if clientId != nil { + var count int64 + if err := _i.DB.DB.Model(&entity.Articles{}).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") + } + } + + articlesMap, err := utilSvc.StructToMap(articles) + if err != nil { + 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{}). + Where(&entity.Articles{ID: id}). + Updates(articlesMap).Error +} + +func (_i *articlesRepository) UpdateSkipNull(clientId *uuid.UUID, id uint, articles *entity.Articles) (err error) { + // Validate client access + if clientId != nil { + var count int64 + if err := _i.DB.DB.Model(&entity.Articles{}).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") + } + } + + // 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.UpdatedAt = time.Time{} + + return _i.DB.DB.Model(&entity.Articles{}). + Where(&entity.Articles{ID: id}). + Updates(&updateData).Error +} + +func (_i *articlesRepository) Delete(clientId *uuid.UUID, id uint) error { + // Validate client access + if clientId != nil { + var count int64 + if err := _i.DB.DB.Model(&entity.Articles{}).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") + } + } + + // 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) { + now := time.Now() + startOfDay := now.Truncate(24 * time.Hour) + startOfWeek := now.AddDate(0, 0, -int(now.Weekday())+1).Truncate(24 * time.Hour) + + // Query + query := _i.DB.DB.Model(&entity.Articles{}). + Select( + "COUNT(*) AS total_all, "+ + "COALESCE(SUM(view_count), 0) AS total_views, "+ + "COALESCE(SUM(share_count), 0) AS total_shares, "+ + "COALESCE(SUM(comment_count), 0) AS total_comments, "+ + "COUNT(CASE WHEN created_at >= ? THEN 1 END) AS total_today, "+ + "COUNT(CASE WHEN created_at >= ? THEN 1 END) AS total_this_week", + startOfDay, startOfWeek, + ). + Where("created_by_id = ?", userID) + + // Add client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + err = query.Scan(&articleSummaryStats).Error + + return articleSummaryStats, err +} + +func (_i *articlesRepository) ArticlePerUserLevelStats(clientId *uuid.UUID, userLevelId *uint, levelNumber *int, startDate *time.Time, endDate *time.Time) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error) { + + levelNumberTop := 1 + + query := _i.DB.DB.Model(&entity.Articles{}). + Select("user_levels.id as user_level_id", "user_levels.name as user_level_name", "COUNT(articles.id) as total_article"). + Joins("LEFT JOIN users ON articles.created_by_id = users.id"). + Joins("LEFT JOIN user_levels ON users.user_level_id = user_levels.id"). + Where("articles.is_active = true") + + // Add client filter + if clientId != nil { + query = query.Where("articles.client_id = ?", clientId) + } + + if userLevelId != nil && *levelNumber != levelNumberTop { + query = query.Where("user_levels.id = ? or user_levels.parent_level_id = ?", *userLevelId, *userLevelId) + } else { + query = _i.DB.DB.Raw(` + WITH LevelHierarchy AS ( + SELECT + id, + name, + level_number, + parent_level_id, + CASE + WHEN level_number = 1 THEN id + WHEN level_number = 2 and name ILIKE '%polda%' THEN id + WHEN level_number = 2 and name NOT ILIKE '%polda%' THEN parent_level_id + WHEN level_number = 3 THEN parent_level_id + END AS level_2_id, + CASE + WHEN level_number = 1 THEN name + WHEN level_number = 2 and name ILIKE '%polda%' THEN name + WHEN level_number = 2 and name NOT ILIKE '%polda%' THEN (SELECT name FROM user_levels ul2 WHERE ul2.id = user_levels.parent_level_id) + WHEN level_number = 3 THEN (SELECT name FROM user_levels ul2 WHERE ul2.id = user_levels.parent_level_id) + END AS level_2_name + FROM user_levels + ) + SELECT + lh.level_2_id AS user_level_id, + UPPER(lh.level_2_name) AS user_level_name, + COUNT(articles.id) AS total_article + FROM articles + JOIN users ON articles.created_by_id = users.id + JOIN LevelHierarchy lh ON users.user_level_id = lh.id + WHERE articles.is_active = true AND lh.level_2_id > 0`) + + // Add client filter to raw query + if clientId != nil { + query = query.Where("articles.client_id = ?", clientId) + } + + query = query.Group("lh.level_2_id, lh.level_2_name").Order("total_article DESC") + } + + // Apply date filters if provided + if startDate != nil { + query = query.Where("articles.created_at >= ?", *startDate) + } + if endDate != nil { + query = query.Where("articles.created_at <= ?", *endDate) + } + + // Group by all non-aggregated columns + err = query.Group("user_levels.id, user_levels.name"). + Order("total_article DESC"). + Scan(&articlePerUserLevelStats).Error + + return articlePerUserLevelStats, err +} + +func (_i *articlesRepository) ArticleMonthlyStats(clientId *uuid.UUID, userLevelId *uint, levelNumber *int, year int) (articleMontlyStats []*response.ArticleMonthlyStats, err error) { + levelNumberTop := 1 + + if year < 1900 || year > 2100 { + return nil, fmt.Errorf("invalid year") + } + + var results []struct { + Month int + Day int + TotalView int + TotalComment int + TotalShare int + } + + query := _i.DB.DB.Model(&entity.Articles{}). + Select("EXTRACT(MONTH FROM created_at) as month, EXTRACT(DAY FROM created_at) as day, "+ + "SUM(view_count) as total_view, "+ + "SUM(comment_count) as total_comment, "+ + "SUM(share_count) as total_share"). + Where("EXTRACT(YEAR FROM created_at) = ?", year) + + // Add client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if userLevelId != nil && *levelNumber != levelNumberTop { + query = _i.DB.DB.Model(&entity.Articles{}). + Select("EXTRACT(MONTH FROM articles.created_at) as month, EXTRACT(DAY FROM articles.created_at) as day, "+ + "SUM(articles.view_count) as total_view, "+ + "SUM(articles.comment_count) as total_comment, "+ + "SUM(articles.share_count) as total_share"). + Joins("LEFT JOIN users ON articles.created_by_id = users.id"). + Joins("LEFT JOIN user_levels ON users.user_level_id = user_levels.id"). + Where("articles.is_active = true"). + Where("EXTRACT(YEAR FROM articles.created_at) = ?", year). + Where("(user_levels.id = ? OR user_levels.parent_level_id = ?)", *userLevelId, *userLevelId) + + // Add client filter + if clientId != nil { + query = query.Where("articles.client_id = ?", clientId) + } + } + + err = query.Group("month, day").Scan(&results).Error + if err != nil { + return nil, err + } + + // Siapkan struktur untuk menyimpan data bulanan + monthlyAnalytics := make([]*response.ArticleMonthlyStats, 12) + for i := 0; i < 12; i++ { + daysInMonth := time.Date(year, time.Month(i+1), 0, 0, 0, 0, 0, time.UTC).Day() + monthlyAnalytics[i] = &response.ArticleMonthlyStats{ + Year: year, + Month: i + 1, + View: make([]int, daysInMonth), + Comment: make([]int, daysInMonth), + Share: make([]int, daysInMonth), + } + } + + // Isi data dari hasil agregasi + for _, result := range results { + monthIndex := result.Month - 1 + dayIndex := result.Day - 1 + + if monthIndex >= 0 && monthIndex < 12 { + if dayIndex >= 0 && dayIndex < len(monthlyAnalytics[monthIndex].View) { + monthlyAnalytics[monthIndex].View[dayIndex] = result.TotalView + monthlyAnalytics[monthIndex].Comment[dayIndex] = result.TotalComment + monthlyAnalytics[monthIndex].Share[dayIndex] = result.TotalShare + } + } + } + + return monthlyAnalytics, nil +} diff --git a/app/module/articles/request/articles.request.go b/app/module/articles/request/articles.request.go new file mode 100644 index 0000000..6cc1e55 --- /dev/null +++ b/app/module/articles/request/articles.request.go @@ -0,0 +1,235 @@ +package request + +import ( + "errors" + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type ArticlesGeneric interface { + ToEntity() +} + +type ArticlesQueryRequest struct { + Title *string `json:"title"` + Description *string `json:"description"` + CategoryId *uint `json:"categoryId"` + Category *string `json:"category"` + TypeId *int `json:"typeId"` + Tags *string `json:"tags"` + CreatedById *int `json:"createdById"` + StatusId *int `json:"statusId"` + IsBanner *bool `json:"isBanner"` + IsPublish *bool `json:"isPublish"` + IsDraft *bool `json:"isDraft"` + Source *string `json:"source"` + CustomCreatorName *string `json:"customCreatorName"` + StartDate *time.Time `json:"startDate"` + EndDate *time.Time `json:"endDate"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ArticlesCreateRequest struct { + Title string `json:"title" validate:"required"` + Slug string `json:"slug" validate:"required"` + Description string `json:"description" validate:"required"` + HtmlDescription string `json:"htmlDescription" validate:"required"` + CategoryIds string `json:"categoryIds" validate:"required"` + TypeId int `json:"typeId" validate:"required"` + Tags string `json:"tags" validate:"required"` + AiArticleId *int `json:"aiArticleId"` + CreatedAt *string `json:"createdAt"` + CreatedById *uint `json:"createdById"` + IsPublish *bool `json:"isPublish"` + IsDraft *bool `json:"isDraft"` + OldId *uint `json:"oldId"` + Source *string `json:"source"` + CustomCreatorName *string `json:"customCreatorName"` +} + +func (req ArticlesCreateRequest) ToEntity() *entity.Articles { + return &entity.Articles{ + Title: req.Title, + Slug: req.Slug, + Description: req.Description, + HtmlDescription: req.HtmlDescription, + TypeId: req.TypeId, + Tags: req.Tags, + AiArticleId: req.AiArticleId, + IsPublish: req.IsPublish, + IsDraft: req.IsDraft, + OldId: req.OldId, + Source: req.Source, + CustomCreatorName: req.CustomCreatorName, + } +} + +type ArticlesUpdateRequest struct { + Title string `json:"title" validate:"required"` + Slug string `json:"slug" validate:"required"` + Description string `json:"description" validate:"required"` + HtmlDescription string `json:"htmlDescription" validate:"required"` + CategoryIds string `json:"categoryIds" validate:"required"` + TypeId int `json:"typeId" validate:"required"` + Tags string `json:"tags" validate:"required"` + CreatedAt *string `json:"createdAt"` + CreatedById *uint `json:"createdById"` + AiArticleId *int `json:"aiArticleId"` + IsPublish *bool `json:"isPublish"` + IsDraft *bool `json:"isDraft"` + StatusId *int `json:"statusId"` + Source *string `json:"source"` + CustomCreatorName *string `json:"customCreatorName"` +} + +func (req ArticlesUpdateRequest) ToEntity() *entity.Articles { + if req.CreatedById == nil { + return &entity.Articles{ + Title: req.Title, + Slug: req.Slug, + Description: req.Description, + HtmlDescription: req.HtmlDescription, + TypeId: req.TypeId, + Tags: req.Tags, + StatusId: req.StatusId, + AiArticleId: req.AiArticleId, + IsPublish: req.IsPublish, + IsDraft: req.IsDraft, + Source: req.Source, + CustomCreatorName: req.CustomCreatorName, + UpdatedAt: time.Now(), + } + } else { + return &entity.Articles{ + Title: req.Title, + Slug: req.Slug, + Description: req.Description, + HtmlDescription: req.HtmlDescription, + TypeId: req.TypeId, + Tags: req.Tags, + StatusId: req.StatusId, + CreatedById: req.CreatedById, + AiArticleId: req.AiArticleId, + IsPublish: req.IsPublish, + IsDraft: req.IsDraft, + Source: req.Source, + CustomCreatorName: req.CustomCreatorName, + UpdatedAt: time.Now(), + } + } +} + +type ArticlesQueryRequestContext struct { + Title string `json:"title"` + Description string `json:"description"` + CategoryId string `json:"categoryId"` + Category string `json:"category"` + TypeId string `json:"typeId"` + Tags string `json:"tags"` + CreatedById string `json:"createdById"` + IsBanner string `json:"isBanner"` + IsPublish string `json:"isPublish"` + IsDraft string `json:"isDraft"` + StatusId string `json:"statusId"` + Source string `json:"source"` + CustomCreatorName string `json:"customCreatorName"` + StartDate string `json:"startDate"` + EndDate string `json:"endDate"` +} + +func (req ArticlesQueryRequestContext) ToParamRequest() ArticlesQueryRequest { + var request ArticlesQueryRequest + + if title := req.Title; title != "" { + request.Title = &title + } + if description := req.Description; description != "" { + request.Description = &description + } + if category := req.Category; category != "" { + request.Category = &category + } + if categoryIdStr := req.CategoryId; categoryIdStr != "" { + categoryId, err := strconv.Atoi(categoryIdStr) + if err == nil { + categoryIdUint := uint(categoryId) + request.CategoryId = &categoryIdUint + } + } + if typeIdStr := req.TypeId; typeIdStr != "" { + typeId, err := strconv.Atoi(typeIdStr) + if err == nil { + request.TypeId = &typeId + } + } + if tags := req.Tags; tags != "" { + request.Tags = &tags + } + if isPublishStr := req.IsPublish; isPublishStr != "" { + isPublish, err := strconv.ParseBool(isPublishStr) + if err == nil { + request.IsPublish = &isPublish + } + } + if isBannerStr := req.IsBanner; isBannerStr != "" { + isBanner, err := strconv.ParseBool(isBannerStr) + if err == nil { + request.IsBanner = &isBanner + } + } + if isDraftStr := req.IsDraft; isDraftStr != "" { + isDraft, err := strconv.ParseBool(isDraftStr) + if err == nil { + request.IsDraft = &isDraft + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + if createdByIdStr := req.CreatedById; createdByIdStr != "" { + createdById, err := strconv.Atoi(createdByIdStr) + if err == nil { + request.CreatedById = &createdById + } + } + if source := req.Source; source != "" { + request.Source = &source + } + if customCreatorName := req.CustomCreatorName; customCreatorName != "" { + request.CustomCreatorName = &customCreatorName + } + if startDateStr := req.StartDate; startDateStr != "" { + if startDate, err := time.Parse("2006-01-02", startDateStr); err == nil { + request.StartDate = &startDate + } + } + if endDateStr := req.EndDate; endDateStr != "" { + if endDate, err := time.Parse("2006-01-02", endDateStr); err == nil { + request.EndDate = &endDate + } + } + + return request +} + +// SubmitForApprovalRequest represents the request for submitting an article for approval +type SubmitForApprovalRequest struct { + WorkflowId *uint `json:"workflow_id" validate:"omitempty,min=1"` + Message *string `json:"message" validate:"omitempty,max=500"` +} + +// Validate validates the SubmitForApprovalRequest +func (req *SubmitForApprovalRequest) Validate() error { + if req.WorkflowId != nil && *req.WorkflowId == 0 { + return errors.New("workflow_id must be greater than 0") + } + if req.Message != nil && len(*req.Message) > 500 { + return errors.New("message must be less than 500 characters") + } + return nil +} diff --git a/app/module/articles/response/articles.response.go b/app/module/articles/response/articles.response.go new file mode 100644 index 0000000..f0f01b0 --- /dev/null +++ b/app/module/articles/response/articles.response.go @@ -0,0 +1,118 @@ +package response + +import ( + "time" + articleCategoriesResponse "web-qudo-be/app/module/article_categories/response" + articleFilesResponse "web-qudo-be/app/module/article_files/response" +) + +type ArticlesResponse struct { + ID uint `json:"id"` + Title string `json:"title"` + Slug string `json:"slug"` + Description string `json:"description"` + HtmlDescription string `json:"htmlDescription"` + CategoryId int `json:"categoryId"` + CategoryName string `json:"categoryName"` + TypeId int `json:"typeId"` + Tags string `json:"tags"` + ThumbnailUrl string `json:"thumbnailUrl"` + PageUrl *string `json:"pageUrl"` + CreatedById *uint `json:"createdById"` + CreatedByName *string `json:"createdByName"` + ShareCount *int `json:"shareCount"` + ViewCount *int `json:"viewCount"` + CommentCount *int `json:"commentCount"` + AiArticleId *int `json:"aiArticleId"` + OldId *uint `json:"oldId"` + StatusId *int `json:"statusId"` + IsDraft *bool `json:"isDraft"` + DraftedAt *time.Time `json:"draftedAt"` + IsBanner *bool `json:"isBanner"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` + PublishSchedule *string `json:"publishSchedule"` + PublishStatus string `json:"publishStatus"` // "On Schedule", "Published", "Cancel" + IsActive *bool `json:"isActive"` + Source *string `json:"source"` + CustomCreatorName *string `json:"customCreatorName"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + ArticleFiles []*articleFilesResponse.ArticleFilesResponse `json:"files"` + ArticleCategories []*articleCategoriesResponse.ArticleCategoriesResponse `json:"categories"` +} + +type ArticleSummaryStats struct { + TotalToday int `json:"totalToday"` + TotalThisWeek int `json:"totalThisWeek"` + TotalAll int `json:"totalAll"` + TotalViews int `json:"totalViews"` + TotalShares int `json:"totalShares"` + TotalComments int `json:"totalComments"` +} + +type ArticlePerUserLevelStats struct { + UserLevelID uint `json:"userLevelId"` + UserLevelName string `json:"userLevelName"` + TotalArticle int64 `json:"totalArticle"` +} + +type ArticleMonthlyStats struct { + Year int `json:"year"` + Month int `json:"month"` + View []int `json:"view"` + Comment []int `json:"comment"` + Share []int `json:"share"` +} + +// ArticleApprovalStatusResponse represents the approval status of an article +type ArticleApprovalStatusResponse struct { + ArticleId uint `json:"articleId"` + WorkflowId *uint `json:"workflowId"` + WorkflowName *string `json:"workflowName"` + CurrentStep int `json:"currentStep"` + TotalSteps int `json:"totalSteps"` + Status string `json:"status"` // "pending", "in_progress", "approved", "rejected", "revision_requested" + CurrentApprover *string `json:"currentApprover"` + SubmittedAt *time.Time `json:"submittedAt"` + LastActionAt *time.Time `json:"lastActionAt"` + Progress float64 `json:"progress"` // percentage complete + CanApprove bool `json:"canApprove"` // whether current user can approve + NextStep *string `json:"nextStep"` +} + +// ArticleApprovalQueueResponse represents an article in the approval queue +type ArticleApprovalQueueResponse struct { + ID uint `json:"id"` + Title string `json:"title"` + Slug string `json:"slug"` + Description string `json:"description"` + CategoryName string `json:"categoryName"` + AuthorName string `json:"authorName"` + SubmittedAt time.Time `json:"submittedAt"` + CurrentStep int `json:"currentStep"` + TotalSteps int `json:"totalSteps"` + Priority string `json:"priority"` // "low", "medium", "high", "urgent" + DaysInQueue int `json:"daysInQueue"` + WorkflowName string `json:"workflowName"` + CanApprove bool `json:"canApprove"` + EstimatedTime string `json:"estimatedTime"` // estimated time to complete approval +} + +// ClientApprovalSettingsResponse represents client-level approval settings +type ClientApprovalSettingsResponse struct { + ClientId string `json:"clientId"` + RequiresApproval bool `json:"requiresApproval"` + DefaultWorkflowId *uint `json:"defaultWorkflowId"` + DefaultWorkflowName *string `json:"defaultWorkflowName"` + AutoPublishArticles bool `json:"autoPublishArticles"` + ApprovalExemptUsers []uint `json:"approvalExemptUsers"` + ApprovalExemptRoles []uint `json:"approvalExemptRoles"` + ApprovalExemptCategories []uint `json:"approvalExemptCategories"` + RequireApprovalFor []string `json:"requireApprovalFor"` + SkipApprovalFor []string `json:"skipApprovalFor"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/articles/service/articles.service.go b/app/module/articles/service/articles.service.go new file mode 100644 index 0000000..82f532f --- /dev/null +++ b/app/module/articles/service/articles.service.go @@ -0,0 +1,1368 @@ +package service + +import ( + "context" + "errors" + "fmt" + "io" + "log" + "math/rand" + "mime" + "path/filepath" + "strconv" + "strings" + "time" + "web-qudo-be/app/database/entity" + approvalWorkflowsRepository "web-qudo-be/app/module/approval_workflows/repository" + articleApprovalFlowsRepository "web-qudo-be/app/module/article_approval_flows/repository" + articleApprovalsRepository "web-qudo-be/app/module/article_approvals/repository" + articleCategoriesRepository "web-qudo-be/app/module/article_categories/repository" + articleCategoryDetailsRepository "web-qudo-be/app/module/article_category_details/repository" + articleCategoryDetailsReq "web-qudo-be/app/module/article_category_details/request" + articleFilesRepository "web-qudo-be/app/module/article_files/repository" + "web-qudo-be/app/module/articles/mapper" + "web-qudo-be/app/module/articles/repository" + "web-qudo-be/app/module/articles/request" + "web-qudo-be/app/module/articles/response" + usersRepository "web-qudo-be/app/module/users/repository" + config "web-qudo-be/config/config" + minioStorage "web-qudo-be/config/config" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" + + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" +) + +// ArticlesService +type articlesService struct { + Repo repository.ArticlesRepository + ArticleCategoriesRepo articleCategoriesRepository.ArticleCategoriesRepository + ArticleFilesRepo articleFilesRepository.ArticleFilesRepository + ArticleApprovalsRepo articleApprovalsRepository.ArticleApprovalsRepository + ArticleCategoryDetailsRepo articleCategoryDetailsRepository.ArticleCategoryDetailsRepository + Log zerolog.Logger + Cfg *config.Config + UsersRepo usersRepository.UsersRepository + MinioStorage *minioStorage.MinioStorage + + // Dynamic approval system dependencies + ArticleApprovalFlowsRepo articleApprovalFlowsRepository.ArticleApprovalFlowsRepository + ApprovalWorkflowsRepo approvalWorkflowsRepository.ApprovalWorkflowsRepository +} + +// ArticlesService define interface of IArticlesService +type ArticlesService interface { + 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) + ShowByOldId(clientId *uuid.UUID, oldId uint) (articles *response.ArticlesResponse, err error) + ShowBySlug(clientId *uuid.UUID, slug string) (articles *response.ArticlesResponse, err error) + Save(clientId *uuid.UUID, req request.ArticlesCreateRequest, authToken string) (articles *entity.Articles, err error) + SaveThumbnail(clientId *uuid.UUID, c *fiber.Ctx) (err error) + Update(clientId *uuid.UUID, id uint, req request.ArticlesUpdateRequest) (err error) + Delete(clientId *uuid.UUID, id uint) error + UpdateActivityCount(clientId *uuid.UUID, id uint, activityTypeId int) (err error) + UpdateApproval(clientId *uuid.UUID, id uint, statusId int, userLevelId int, userLevelNumber int, userParentLevelId int) (err error) + UpdateBanner(clientId *uuid.UUID, id uint, isBanner bool) error + Viewer(clientId *uuid.UUID, c *fiber.Ctx) error + SummaryStats(clientId *uuid.UUID, authToken string) (summaryStats *response.ArticleSummaryStats, err error) + ArticlePerUserLevelStats(clientId *uuid.UUID, authToken string, startDate *string, endDate *string) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error) + ArticleMonthlyStats(clientId *uuid.UUID, authToken string, year *int) (articleMonthlyStats []*response.ArticleMonthlyStats, err error) + PublishScheduling(clientId *uuid.UUID, id uint, publishSchedule string) error + ExecuteScheduling() error + + // Dynamic approval system methods + SubmitForApproval(clientId *uuid.UUID, articleId uint, authToken string, workflowId *uint) error + GetApprovalStatus(clientId *uuid.UUID, articleId uint) (*response.ArticleApprovalStatusResponse, error) + GetArticlesWaitingForApproval(clientId *uuid.UUID, authToken string, page, limit int) ([]*response.ArticleApprovalQueueResponse, paginator.Pagination, error) + GetPendingApprovals(clientId *uuid.UUID, authToken string, page, limit int, typeId *int) ([]*response.ArticleApprovalQueueResponse, paginator.Pagination, error) // Updated with typeId filter + + // No-approval system methods + CheckApprovalRequired(clientId *uuid.UUID, articleId uint, userId uint, userLevelId uint) (bool, error) + AutoApproveArticle(clientId *uuid.UUID, articleId uint, reason string) error + GetClientApprovalSettings(clientId *uuid.UUID) (*response.ClientApprovalSettingsResponse, error) + SetArticleApprovalExempt(clientId *uuid.UUID, articleId uint, exempt bool, reason string) error + + // Publish/Unpublish methods + Publish(clientId *uuid.UUID, articleId uint, authToken string) error + Unpublish(clientId *uuid.UUID, articleId uint, authToken string) error +} + +// NewArticlesService init ArticlesService +func NewArticlesService( + repo repository.ArticlesRepository, + articleCategoriesRepo articleCategoriesRepository.ArticleCategoriesRepository, + articleCategoryDetailsRepo articleCategoryDetailsRepository.ArticleCategoryDetailsRepository, + articleFilesRepo articleFilesRepository.ArticleFilesRepository, + articleApprovalsRepo articleApprovalsRepository.ArticleApprovalsRepository, + articleApprovalFlowsRepo articleApprovalFlowsRepository.ArticleApprovalFlowsRepository, + approvalWorkflowsRepo approvalWorkflowsRepository.ApprovalWorkflowsRepository, + log zerolog.Logger, + cfg *config.Config, + usersRepo usersRepository.UsersRepository, + minioStorage *minioStorage.MinioStorage, +) ArticlesService { + + return &articlesService{ + Repo: repo, + ArticleCategoriesRepo: articleCategoriesRepo, + ArticleCategoryDetailsRepo: articleCategoryDetailsRepo, + ArticleFilesRepo: articleFilesRepo, + ArticleApprovalsRepo: articleApprovalsRepo, + ArticleApprovalFlowsRepo: articleApprovalFlowsRepo, + ApprovalWorkflowsRepo: approvalWorkflowsRepo, + Log: log, + UsersRepo: usersRepo, + MinioStorage: minioStorage, + Cfg: cfg, + } +} + +// All implement interface of ArticlesService +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 { + findCategory, err := _i.ArticleCategoriesRepo.FindOneBySlug(clientId, *req.Category) + if err != nil { + return nil, paging, err + } + req.CategoryId = &findCategory.ID + } + + results, paging, err := _i.Repo.GetAll(clientId, userLevelId, req) + if err != nil { + return + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:articlesService", "Methods:All"). + Interface("results", results).Msg("") + + host := _i.Cfg.App.Domain + + for _, result := range results { + articleRes := mapper.ArticlesResponseMapper(_i.Log, host, clientId, result, _i.ArticleCategoriesRepo, _i.ArticleCategoryDetailsRepo, _i.ArticleFilesRepo, _i.UsersRepo) + articless = append(articless, articleRes) + } + + return +} + +func (_i *articlesService) Show(clientId *uuid.UUID, id uint) (articles *response.ArticlesResponse, err error) { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return nil, err + } + + host := _i.Cfg.App.Domain + + return mapper.ArticlesResponseMapper(_i.Log, host, clientId, result, _i.ArticleCategoriesRepo, _i.ArticleCategoryDetailsRepo, _i.ArticleFilesRepo, _i.UsersRepo), nil +} + +func (_i *articlesService) ShowByOldId(clientId *uuid.UUID, oldId uint) (articles *response.ArticlesResponse, err error) { + result, err := _i.Repo.FindByOldId(clientId, oldId) + if err != nil { + return nil, err + } + + host := _i.Cfg.App.Domain + + return mapper.ArticlesResponseMapper(_i.Log, host, clientId, result, _i.ArticleCategoriesRepo, _i.ArticleCategoryDetailsRepo, _i.ArticleFilesRepo, _i.UsersRepo), nil +} + +func (_i *articlesService) ShowBySlug(clientId *uuid.UUID, slug string) (articles *response.ArticlesResponse, err error) { + result, err := _i.Repo.FindBySlug(clientId, slug) + if err != nil { + return nil, err + } + + host := _i.Cfg.App.Domain + + return mapper.ArticlesResponseMapper(_i.Log, host, clientId, result, _i.ArticleCategoriesRepo, _i.ArticleCategoryDetailsRepo, _i.ArticleFilesRepo, _i.UsersRepo), nil +} + +func (_i *articlesService) Save(clientId *uuid.UUID, req request.ArticlesCreateRequest, authToken string) (articles *entity.Articles, err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + var userLevelNumber int + var approvalLevelId int + if req.CreatedById != nil { + createdBy, err := _i.UsersRepo.FindOne(clientId, *req.CreatedById) + if err != nil { + return nil, fmt.Errorf("User not found") + } + newReq.CreatedById = &createdBy.ID + userLevelNumber = createdBy.UserLevel.LevelNumber + + // Find the next higher level for approval (level_number should be smaller) + approvalLevelId = _i.findNextApprovalLevel(clientId, userLevelNumber) + } else { + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + newReq.CreatedById = &createdBy.ID + userLevelNumber = createdBy.UserLevel.LevelNumber + + // Find the next higher level for approval (level_number should be smaller) + approvalLevelId = _i.findNextApprovalLevel(clientId, userLevelNumber) + } + + isDraft := true + if req.IsDraft == &isDraft { + draftedAt := time.Now() + newReq.IsDraft = &isDraft + newReq.DraftedAt = &draftedAt + isPublishFalse := false + newReq.IsPublish = &isPublishFalse + newReq.PublishedAt = nil + } + + isPublish := true + if req.IsPublish == &isPublish { + publishedAt := time.Now() + newReq.IsPublish = &isPublish + newReq.PublishedAt = &publishedAt + isDraftFalse := false + newReq.IsDraft = &isDraftFalse + newReq.DraftedAt = nil + } + + if req.CreatedAt != nil { + layout := "2006-01-02 15:04:05" + parsedTime, err := time.Parse(layout, *req.CreatedAt) + if err != nil { + return nil, fmt.Errorf("Error parsing time:", err) + } + newReq.CreatedAt = parsedTime + } + + // Dynamic Approval Workflow System + statusIdOne := 1 + statusIdTwo := 2 + isPublishFalse := false + + // Get user info for approval logic + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + // Check if user level requires approval + if createdBy != nil && *createdBy.UserLevel.IsApprovalActive == false { + // User level doesn't require approval - auto publish + newReq.NeedApprovalFrom = nil + newReq.StatusId = &statusIdTwo + newReq.IsPublish = &isPublishFalse + newReq.PublishedAt = nil + newReq.BypassApproval = &[]bool{true}[0] + } else { + // User level requires approval - set to pending + newReq.NeedApprovalFrom = &approvalLevelId + newReq.StatusId = &statusIdOne + newReq.IsPublish = &isPublishFalse + newReq.PublishedAt = nil + newReq.BypassApproval = &[]bool{false}[0] + } + + saveArticleRes, err := _i.Repo.Create(clientId, newReq) + if err != nil { + return nil, err + } + + // Dynamic Approval Workflow Assignment + if createdBy != nil && *createdBy.UserLevel.IsApprovalActive == true { + // Get default workflow for the client + defaultWorkflow, err := _i.ApprovalWorkflowsRepo.GetDefault(clientId) + if err == nil && defaultWorkflow != nil { + // Assign workflow to article + saveArticleRes.WorkflowId = &defaultWorkflow.ID + saveArticleRes.CurrentApprovalStep = &[]int{1}[0] // Start at step 1 + + // Update article with workflow info + err = _i.Repo.Update(clientId, saveArticleRes.ID, saveArticleRes) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to update article with workflow") + } + + // Create approval flow + approvalFlow := &entity.ArticleApprovalFlows{ + ArticleId: saveArticleRes.ID, + WorkflowId: defaultWorkflow.ID, + CurrentStep: 1, + StatusId: 1, // In Progress + SubmittedById: *newReq.CreatedById, + SubmittedAt: time.Now(), + ClientId: clientId, + } + + _, err = _i.ArticleApprovalFlowsRepo.Create(clientId, approvalFlow) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to create approval flow") + } + } + + // Create legacy approval record for backward compatibility + articleApproval := &entity.ArticleApprovals{ + ArticleId: saveArticleRes.ID, + ApprovalBy: *newReq.CreatedById, + StatusId: statusIdOne, + Message: "Need Approval", + ApprovalAtLevel: &approvalLevelId, + } + _, err = _i.ArticleApprovalsRepo.Create(articleApproval) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to create legacy approval record") + } + } else { + // Auto-publish for users who don't require approval + articleApproval := &entity.ArticleApprovals{ + ArticleId: saveArticleRes.ID, + ApprovalBy: *newReq.CreatedById, + StatusId: statusIdTwo, + Message: "Publish Otomatis", + ApprovalAtLevel: nil, + } + _, err = _i.ArticleApprovalsRepo.Create(articleApproval) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to create auto-approval record") + } + } + + var categoryIds []string + if req.CategoryIds != "" { + categoryIds = strings.Split(req.CategoryIds, ",") + } + + _i.Log.Info().Interface("categoryIds", categoryIds).Msg("") + + for _, categoryId := range categoryIds { + categoryIdInt, _ := strconv.Atoi(categoryId) + + _i.Log.Info().Interface("categoryIdUint", uint(categoryIdInt)).Msg("") + + findCategory, err := _i.ArticleCategoriesRepo.FindOne(clientId, uint(categoryIdInt)) + + _i.Log.Info().Interface("findCategory", findCategory).Msg("") + + if err != nil { + return nil, err + } + + if findCategory == nil { + return nil, errors.New("category not found") + } + + categoryReq := articleCategoryDetailsReq.ArticleCategoryDetailsCreateRequest{ + ArticleId: saveArticleRes.ID, + CategoryId: categoryIdInt, + IsActive: true, + } + newCategoryReq := categoryReq.ToEntity() + + err = _i.ArticleCategoryDetailsRepo.Create(newCategoryReq) + if err != nil { + return nil, err + } + } + + return saveArticleRes, nil +} + +func (_i *articlesService) SaveThumbnail(clientId *uuid.UUID, c *fiber.Ctx) (err error) { + + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:articlesService", "Methods:SaveThumbnail"). + Interface("id", id).Msg("") + + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + + form, err := c.MultipartForm() + if err != nil { + return err + } + files := form.File["files"] + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + // Iterasi semua file yang diunggah + for _, file := range files { + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop1"). + Interface("data", file).Msg("") + + src, err := file.Open() + if err != nil { + return err + } + defer src.Close() + + filename := filepath.Base(file.Filename) + filename = strings.ReplaceAll(filename, " ", "") + filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + extension := filepath.Ext(file.Filename)[1:] + + now := time.Now() + rand.New(rand.NewSource(now.UnixNano())) + randUniqueId := rand.Intn(1000000) + + newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId) + newFilename := newFilenameWithoutExt + "." + extension + objectName := fmt.Sprintf("articles/thumbnail/%d/%d/%s", now.Year(), now.Month(), newFilename) + + findCategory, err := _i.Repo.FindOne(clientId, uint(id)) + findCategory.ThumbnailName = &newFilename + findCategory.ThumbnailPath = &objectName + err = _i.Repo.Update(clientId, uint(id), findCategory) + if err != nil { + return err + } + + // Upload file ke MinIO + _, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, file.Size, minio.PutObjectOptions{}) + if err != nil { + return err + } + } + + return +} + +func (_i *articlesService) Update(clientId *uuid.UUID, id uint, req request.ArticlesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + if req.CreatedAt != nil { + layout := "2006-01-02 15:04:05" + parsedTime, err := time.Parse(layout, *req.CreatedAt) + if err != nil { + return fmt.Errorf("Error parsing time:", err) + } + newReq.CreatedAt = parsedTime + } + + return _i.Repo.UpdateSkipNull(clientId, id, newReq) +} + +func (_i *articlesService) Delete(clientId *uuid.UUID, id uint) error { + return _i.Repo.Delete(clientId, id) +} + +func (_i *articlesService) Viewer(clientId *uuid.UUID, c *fiber.Ctx) (err error) { + thumbnailName := c.Params("thumbnailName") + + emptyImage := "empty-image.jpg" + searchThumbnail := emptyImage + if thumbnailName != emptyImage { + result, err := _i.Repo.FindByFilename(clientId, thumbnailName) + if err != nil { + return err + } + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "articlesService:Viewer"). + Interface("resultThumbnail", result.ThumbnailPath).Msg("") + + if result.ThumbnailPath != nil { + searchThumbnail = *result.ThumbnailPath + } else { + searchThumbnail = "articles/thumbnail/" + emptyImage + } + } else { + searchThumbnail = "articles/thumbnail/" + emptyImage + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "articlesService:Viewer"). + Interface("searchThumbnail", searchThumbnail).Msg("") + + ctx := context.Background() + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + objectName := searchThumbnail + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{}) + if err != nil { + log.Fatalln(err) + } + defer fileContent.Close() + + contentType := mime.TypeByExtension("." + getFileExtension(objectName)) + if contentType == "" { + contentType = "application/octet-stream" + } + + c.Set("Content-Type", contentType) + + if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil { + return err + } + + return +} + +func (_i *articlesService) UpdateActivityCount(clientId *uuid.UUID, id uint, activityTypeId int) error { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return err + } + + viewCount := 0 + if result.ViewCount != nil { + viewCount = *result.ViewCount + } + shareCount := 0 + if result.ShareCount != nil { + shareCount = *result.ShareCount + } + commentCount := 0 + if result.CommentCount != nil { + commentCount = *result.CommentCount + } + + if activityTypeId == 2 { + viewCount++ + } else if activityTypeId == 3 { + shareCount++ + } else if activityTypeId == 4 { + commentCount++ + } + result.ViewCount = &viewCount + result.ShareCount = &shareCount + result.CommentCount = &commentCount + return _i.Repo.Update(clientId, id, result) +} + +func (_i *articlesService) SummaryStats(clientId *uuid.UUID, authToken string) (summaryStats *response.ArticleSummaryStats, err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + result, err := _i.Repo.SummaryStats(clientId, user.ID) + if err != nil { + return nil, err + } + return result, nil +} + +func (_i *articlesService) ArticlePerUserLevelStats(clientId *uuid.UUID, authToken string, startDate *string, endDate *string) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats"). + Interface("startDate", startDate).Msg("") + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats"). + Interface("endDate", endDate).Msg("") + + var userLevelId *uint + var userLevelNumber *int + + if user != nil { + userLevelId = &user.UserLevelId + userLevelNumber = &user.UserLevel.LevelNumber + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats"). + Interface("userLevelId", userLevelId).Msg("") + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats"). + Interface("userLevelNumber", userLevelNumber).Msg("") + + result, err := _i.Repo.ArticlePerUserLevelStats(clientId, userLevelId, userLevelNumber, nil, nil) + if err != nil { + return nil, err + } + return result, nil +} + +func (_i *articlesService) ArticleMonthlyStats(clientId *uuid.UUID, authToken string, year *int) (articleMonthlyStats []*response.ArticleMonthlyStats, err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + var userLevelId *uint + var userLevelNumber *int + + if user != nil { + userLevelId = &user.UserLevelId + userLevelNumber = &user.UserLevel.LevelNumber + } + + result, err := _i.Repo.ArticleMonthlyStats(clientId, userLevelId, userLevelNumber, *year) + if err != nil { + return nil, err + } + return result, nil +} + +func (_i *articlesService) UpdateApproval(clientId *uuid.UUID, id uint, statusId int, userLevelId int, userLevelNumber int, userParentLevelId int) (err error) { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return err + } + + _i.Log.Info().Interface("statusId", statusId).Msg("") + + statusIdOne := 1 + statusIdTwo := 2 + statusIdThree := 3 + isPublish := true + isDraftFalse := false + + if statusId == 2 { + if userLevelNumber == 2 || userLevelNumber == 3 { + result.NeedApprovalFrom = &userParentLevelId + result.StatusId = &statusIdOne + } else { + result.NeedApprovalFrom = nil + result.StatusId = &statusIdTwo + + result.IsPublish = &isPublish + publishedAt := time.Now() + result.PublishedAt = &publishedAt + + result.IsDraft = &isDraftFalse + result.DraftedAt = nil + } + + userLevelIdStr := strconv.Itoa(userLevelId) + if result.HasApprovedBy == nil { + result.HasApprovedBy = &userLevelIdStr + } else { + hasApprovedBySlice := strings.Split(*result.HasApprovedBy, ",") + hasApprovedBySlice = append(hasApprovedBySlice, userLevelIdStr) + hasApprovedByJoin := strings.Join(hasApprovedBySlice, ",") + result.HasApprovedBy = &hasApprovedByJoin + } + } else if statusId == 3 { + result.StatusId = &statusIdThree + result.NeedApprovalFrom = nil + result.HasApprovedBy = nil + } + + err = _i.Repo.Update(clientId, id, result) + if err != nil { + return err + } + + return +} + +func (_i *articlesService) PublishScheduling(clientId *uuid.UUID, id uint, publishSchedule string) error { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return err + } + + // Validate publish schedule format + dateLayout := "2006-01-02" + datetimeLayout := "2006-01-02 15:04:05" + + // Try parsing as datetime first (with time) + _, parseErr := time.Parse(datetimeLayout, publishSchedule) + if parseErr != nil { + // If datetime parsing fails, try parsing as date only + _, parseErr = time.Parse(dateLayout, publishSchedule) + if parseErr != nil { + return fmt.Errorf("invalid publish schedule format. Supported formats: '2006-01-02' or '2006-01-02 15:04:05'") + } + } + + result.PublishSchedule = &publishSchedule + return _i.Repo.Update(clientId, id, result) +} + +func (_i *articlesService) UpdateBanner(clientId *uuid.UUID, id uint, isBanner bool) error { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return err + } + result.IsBanner = &isBanner + return _i.Repo.Update(clientId, id, result) +} + +func (_i *articlesService) ExecuteScheduling() error { + // For background jobs, we don't have context, so pass nil for clientId + articles, err := _i.Repo.GetAllPublishSchedule(nil) + if err != nil { + return err + } + + // Support both date-only and datetime formats + dateLayout := "2006-01-02" + datetimeLayout := "2006-01-02 15:04:05" + now := time.Now() + + for _, article := range articles { // Looping setiap artikel + if article.PublishSchedule == nil { + continue + } + + var scheduledTime time.Time + var parseErr error + + // Try parsing as datetime first (with time) + scheduledTime, parseErr = time.Parse(datetimeLayout, *article.PublishSchedule) + if parseErr != nil { + // If datetime parsing fails, try parsing as date only + scheduledTime, parseErr = time.Parse(dateLayout, *article.PublishSchedule) + if parseErr != nil { + _i.Log.Warn().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:articlesService", "Methods:ExecuteScheduling"). + Str("Invalid schedule format", *article.PublishSchedule). + Interface("Article ID", article.ID).Msg("") + continue + } + // If parsed as date only, set time to start of day (00:00:00) in local timezone + scheduledTime = time.Date(scheduledTime.Year(), scheduledTime.Month(), scheduledTime.Day(), 0, 0, 0, 0, now.Location()) + } else { + // For datetime format, parse in local timezone + scheduledTime = time.Date(scheduledTime.Year(), scheduledTime.Month(), scheduledTime.Day(), + scheduledTime.Hour(), scheduledTime.Minute(), scheduledTime.Second(), 0, now.Location()) + } + + // Check if the scheduled time has passed (for datetime) or if it's today (for date only) + shouldPublish := false + if len(*article.PublishSchedule) > 10 { // Contains time (datetime format) + // For datetime format, check if scheduled time has passed + shouldPublish = now.After(scheduledTime) || now.Equal(scheduledTime) + } else { + // For date-only format, check if it's today + today := now.Truncate(24 * time.Hour) + scheduledDate := scheduledTime.Truncate(24 * time.Hour) + shouldPublish = scheduledDate.Equal(today) || scheduledDate.Before(today) + } + + if shouldPublish { + isPublish := true + statusIdTwo := 2 + + article.PublishSchedule = nil + article.IsPublish = &isPublish + article.PublishedAt = &now + article.StatusId = &statusIdTwo + + // For background jobs, we don't have context, so pass nil for clientId + if err := _i.Repo.UpdateSkipNull(nil, article.ID, article); err != nil { + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:articlesService", "Methods:ExecuteScheduling"). + Interface("Failed to publish Article ID : ", article.ID).Msg("") + } else { + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:articlesService", "Methods:ExecuteScheduling"). + Interface("Successfully published Article ID : ", article.ID).Msg("") + } + } + } + + return err +} + +func getFileExtension(filename string) string { + // split file name + parts := strings.Split(filename, ".") + + // jika tidak ada ekstensi, kembalikan string kosong + if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") { + return "" + } + + // ambil ekstensi terakhir + return parts[len(parts)-1] +} + +// SubmitForApproval submits an article for approval using the dynamic workflow system +func (_i *articlesService) SubmitForApproval(clientId *uuid.UUID, articleId uint, authToken string, workflowId *uint) error { + // Extract user info from auth token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return errors.New("user not found from auth token") + } + + // Check if article exists + article, err := _i.Repo.FindOne(clientId, articleId) + if err != nil { + return err + } + + // If no workflow specified, get the default workflow + if workflowId == nil { + defaultWorkflow, err := _i.ApprovalWorkflowsRepo.GetDefault(clientId) + if err != nil { + return err + } + workflowId = &defaultWorkflow.ID + } + + // Validate workflow exists and is active + _, err = _i.ApprovalWorkflowsRepo.FindOne(clientId, *workflowId) + if err != nil { + return err + } + + // Create approval flow + approvalFlow := &entity.ArticleApprovalFlows{ + ArticleId: articleId, + WorkflowId: *workflowId, + CurrentStep: 1, + StatusId: 1, // 1 = In Progress + ClientId: clientId, + SubmittedById: user.ID, + } + + _, err = _i.ArticleApprovalFlowsRepo.Create(clientId, approvalFlow) + if err != nil { + return err + } + + // Update article status to pending approval + statusId := 1 // Pending approval + article.StatusId = &statusId + article.WorkflowId = workflowId + + err = _i.Repo.Update(clientId, articleId, article) + if err != nil { + return err + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:articlesService", "Methods:SubmitForApproval"). + Interface("Article submitted for approval", articleId).Msg("") + + return nil +} + +// GetApprovalStatus gets the current approval status of an article +func (_i *articlesService) GetApprovalStatus(clientId *uuid.UUID, articleId uint) (*response.ArticleApprovalStatusResponse, error) { + // Check if article exists + _, err := _i.Repo.FindOne(clientId, articleId) + if err != nil { + return nil, err + } + + // Get approval flow + approvalFlow, err := _i.ArticleApprovalFlowsRepo.FindByArticleId(clientId, articleId) + if err != nil { + // Article might not be in approval process + return &response.ArticleApprovalStatusResponse{ + ArticleId: articleId, + Status: "not_submitted", + CurrentStep: 0, + TotalSteps: 0, + Progress: 0, + }, nil + } + + // Get workflow details + workflow, err := _i.ApprovalWorkflowsRepo.FindOne(clientId, approvalFlow.WorkflowId) + if err != nil { + return nil, err + } + + // Get workflow steps + workflowSteps, err := _i.ApprovalWorkflowsRepo.GetWorkflowSteps(clientId, approvalFlow.WorkflowId) + if err != nil { + return nil, err + } + + totalSteps := len(workflowSteps) + progress := 0.0 + if totalSteps > 0 { + progress = float64(approvalFlow.CurrentStep-1) / float64(totalSteps) * 100 + } + + // Determine status + status := "in_progress" + if approvalFlow.StatusId == 2 { + status = "approved" + } else if approvalFlow.StatusId == 3 { + status = "rejected" + } else if approvalFlow.StatusId == 4 { + status = "revision_requested" + } + + // Get current approver info + var currentApprover *string + var nextStep *string + if approvalFlow.CurrentStep <= totalSteps && approvalFlow.StatusId == 1 { + if approvalFlow.CurrentStep < totalSteps { + // Array indexing starts from 0, so subtract 1 from CurrentStep + nextStepIndex := approvalFlow.CurrentStep - 1 + if nextStepIndex >= 0 && nextStepIndex < len(workflowSteps) { + nextStepInfo := workflowSteps[nextStepIndex] + nextStep = &nextStepInfo.RequiredUserLevel.Name + } + } + } + + return &response.ArticleApprovalStatusResponse{ + ArticleId: articleId, + WorkflowId: &workflow.ID, + WorkflowName: &workflow.Name, + CurrentStep: approvalFlow.CurrentStep, + TotalSteps: totalSteps, + Status: status, + CurrentApprover: currentApprover, + SubmittedAt: &approvalFlow.CreatedAt, + LastActionAt: &approvalFlow.UpdatedAt, + Progress: progress, + CanApprove: false, // TODO: Implement based on user permissions + NextStep: nextStep, + }, nil +} + +// GetPendingApprovals gets articles pending approval for a specific user level +func (_i *articlesService) GetPendingApprovals(clientId *uuid.UUID, authToken string, page, limit int, typeId *int) ([]*response.ArticleApprovalQueueResponse, paginator.Pagination, error) { + // Extract user info from auth token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return nil, paginator.Pagination{}, errors.New("user not found from auth token") + } + + // Prepare filters + filters := make(map[string]interface{}) + if typeId != nil { + filters["type_id"] = *typeId + } + + // Get pending approvals for the user level + approvalFlows, paging, err := _i.ArticleApprovalFlowsRepo.GetPendingApprovals(clientId, user.UserLevelId, page, limit, filters) + if err != nil { + return nil, paging, err + } + + var responses []*response.ArticleApprovalQueueResponse + for _, flow := range approvalFlows { + // Get article details + article, err := _i.Repo.FindOne(clientId, flow.ArticleId) + if err != nil { + continue + } + + // Get workflow details + workflow, err := _i.ApprovalWorkflowsRepo.FindOne(clientId, flow.WorkflowId) + if err != nil { + continue + } + + // Get workflow steps + workflowSteps, err := _i.ApprovalWorkflowsRepo.GetWorkflowSteps(clientId, flow.WorkflowId) + if err != nil { + continue + } + + // Calculate days in queue + daysInQueue := int(time.Since(flow.CreatedAt).Hours() / 24) + + // Determine priority based on days in queue + priority := "low" + if daysInQueue > 7 { + priority = "urgent" + } else if daysInQueue > 3 { + priority = "high" + } else if daysInQueue > 1 { + priority = "medium" + } + + // Get author name + var authorName string + if article.CreatedById != nil { + user, err := _i.UsersRepo.FindOne(clientId, *article.CreatedById) + if err == nil && user != nil { + authorName = user.Fullname + } + } + + // Get category name + var categoryName string + if article.CategoryId != 0 { + category, err := _i.ArticleCategoriesRepo.FindOne(clientId, uint(article.CategoryId)) + if err == nil && category != nil { + categoryName = category.Title + } + } + + response := &response.ArticleApprovalQueueResponse{ + ID: article.ID, + Title: article.Title, + Slug: article.Slug, + Description: article.Description, + CategoryName: categoryName, + AuthorName: authorName, + SubmittedAt: flow.CreatedAt, + CurrentStep: flow.CurrentStep, + TotalSteps: len(workflowSteps), + Priority: priority, + DaysInQueue: daysInQueue, + WorkflowName: workflow.Name, + CanApprove: true, // TODO: Implement based on user permissions + EstimatedTime: "2-3 days", // TODO: Calculate based on historical data + } + + responses = append(responses, response) + } + + return responses, paging, nil +} + +// GetArticlesWaitingForApproval gets articles that are waiting for approval by a specific user level +func (_i *articlesService) GetArticlesWaitingForApproval(clientId *uuid.UUID, authToken string, page, limit int) ([]*response.ArticleApprovalQueueResponse, paginator.Pagination, error) { + // Extract user info from auth token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return nil, paginator.Pagination{}, errors.New("user not found from auth token") + } + + // Use the existing repository method with proper filtering + pagination := paginator.Pagination{ + Page: page, + Limit: limit, + } + req := request.ArticlesQueryRequest{ + Pagination: &pagination, + } + + articles, paging, err := _i.Repo.GetAll(clientId, &user.UserLevelId, req) + if err != nil { + return nil, paging, err + } + + // Build response + var responses []*response.ArticleApprovalQueueResponse + for _, article := range articles { + response := &response.ArticleApprovalQueueResponse{ + ID: article.ID, + Title: article.Title, + Slug: article.Slug, + Description: article.Description, + SubmittedAt: article.CreatedAt, + CurrentStep: 1, // Will be updated with actual step + CanApprove: true, + } + responses = append(responses, response) + } + + return responses, paging, nil +} + +// CheckApprovalRequired checks if an article requires approval based on client settings +func (_i *articlesService) CheckApprovalRequired(clientId *uuid.UUID, articleId uint, userId uint, userLevelId uint) (bool, error) { + // Get article to check category and other properties + article, err := _i.Repo.FindOne(clientId, articleId) + if err != nil { + return true, err // Default to requiring approval on error + } + + // Check if article is already exempt + if article.ApprovalExempt != nil && *article.ApprovalExempt { + return false, nil + } + + // Check if article should bypass approval + if article.BypassApproval != nil && *article.BypassApproval { + return false, nil + } + + // Check client-level settings (this would require the client approval settings service) + // For now, we'll use a simple check + // TODO: Integrate with ClientApprovalSettingsService + + // Check if workflow is set to no approval + if article.WorkflowId != nil { + workflow, err := _i.ApprovalWorkflowsRepo.FindOne(clientId, *article.WorkflowId) + if err == nil && workflow != nil { + if workflow.RequiresApproval != nil && !*workflow.RequiresApproval { + return false, nil + } + if workflow.AutoPublish != nil && *workflow.AutoPublish { + return false, nil + } + } + } + + // Default to requiring approval + return true, nil +} + +// AutoApproveArticle automatically approves an article (for no-approval scenarios) +func (_i *articlesService) AutoApproveArticle(clientId *uuid.UUID, articleId uint, reason string) error { + article, err := _i.Repo.FindOne(clientId, articleId) + if err != nil { + return err + } + + // Update article status to approved + updates := map[string]interface{}{ + "status_id": 2, // Assuming 2 = approved + "is_publish": true, + "published_at": time.Now(), + "current_approval_step": 0, // Reset approval step + } + + // Convert updates map to article entity + articleUpdate := &entity.Articles{} + if isPublish, ok := updates["is_publish"].(bool); ok { + articleUpdate.IsPublish = &isPublish + } + if publishedAt, ok := updates["published_at"].(time.Time); ok { + articleUpdate.PublishedAt = &publishedAt + } + if currentApprovalStep, ok := updates["current_approval_step"].(int); ok { + articleUpdate.CurrentApprovalStep = ¤tApprovalStep + } + + err = _i.Repo.Update(clientId, articleId, articleUpdate) + if err != nil { + return err + } + + // Create approval flow record for audit trail + approvalFlow := &entity.ArticleApprovalFlows{ + ArticleId: articleId, + WorkflowId: *article.WorkflowId, + CurrentStep: 0, + StatusId: 2, // approved + SubmittedById: *article.CreatedById, + SubmittedAt: time.Now(), + CompletedAt: &[]time.Time{time.Now()}[0], + ClientId: clientId, + } + + _, err = _i.ArticleApprovalFlowsRepo.Create(clientId, approvalFlow) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to create approval flow for auto-approved article") + // Don't return error as article was already updated + } + + _i.Log.Info(). + Str("article_id", fmt.Sprintf("%d", articleId)). + Str("client_id", clientId.String()). + Str("reason", reason). + Msg("Article auto-approved") + + return nil +} + +// GetClientApprovalSettings gets the approval settings for a client +func (_i *articlesService) GetClientApprovalSettings(clientId *uuid.UUID) (*response.ClientApprovalSettingsResponse, error) { + // This would require the ClientApprovalSettingsService + // For now, return default settings + return &response.ClientApprovalSettingsResponse{ + ClientId: clientId.String(), + RequiresApproval: true, // Default to requiring approval + AutoPublishArticles: false, + IsActive: true, + }, nil +} + +// SetArticleApprovalExempt sets whether an article is exempt from approval +func (_i *articlesService) SetArticleApprovalExempt(clientId *uuid.UUID, articleId uint, exempt bool, reason string) error { + updates := map[string]interface{}{ + "approval_exempt": &exempt, + } + + if exempt { + // If exempt, also set bypass approval + bypass := true + updates["bypass_approval"] = &bypass + updates["current_approval_step"] = 0 + } + + // Convert updates map to article entity + articleUpdate := &entity.Articles{} + if approvalExempt, ok := updates["approval_exempt"].(*bool); ok { + articleUpdate.ApprovalExempt = approvalExempt + } + if bypassApproval, ok := updates["bypass_approval"].(*bool); ok { + articleUpdate.BypassApproval = bypassApproval + } + if currentApprovalStep, ok := updates["current_approval_step"].(int); ok { + articleUpdate.CurrentApprovalStep = ¤tApprovalStep + } + + err := _i.Repo.Update(clientId, articleId, articleUpdate) + if err != nil { + return err + } + + _i.Log.Info(). + Str("article_id", fmt.Sprintf("%d", articleId)). + Str("client_id", clientId.String()). + Bool("exempt", exempt). + Str("reason", reason). + Msg("Article approval exemption updated") + + return nil +} + +// Publish publishes an article +func (_i *articlesService) Publish(clientId *uuid.UUID, articleId uint, authToken string) error { + // Extract user info from auth token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return errors.New("user not found from auth token") + } + + // Check if article exists + article, err := _i.Repo.FindOne(clientId, articleId) + if err != nil { + return err + } + + // Check if article is already published + if article.IsPublish != nil && *article.IsPublish { + return errors.New("article is already published") + } + + // Check if user has permission to publish + // For now, we'll allow any authenticated user to publish + // You can add more sophisticated permission checks here + + // Update article to published status + isPublish := true + publishedAt := time.Now() + isDraftFalse := false + statusIdTwo := 2 // Published status + + article.IsPublish = &isPublish + article.PublishedAt = &publishedAt + article.IsDraft = &isDraftFalse + article.DraftedAt = nil + article.StatusId = &statusIdTwo + article.PublishSchedule = nil // Clear any scheduled publish time + + err = _i.Repo.Update(clientId, articleId, article) + if err != nil { + return err + } + + // Create approval record for audit trail + articleApproval := &entity.ArticleApprovals{ + ArticleId: articleId, + ApprovalBy: user.ID, + StatusId: statusIdTwo, + Message: "Article published", + ApprovalAtLevel: nil, + } + _, err = _i.ArticleApprovalsRepo.Create(articleApproval) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to create approval record for published article") + // Don't return error as article was already updated + } + + _i.Log.Info(). + Str("article_id", fmt.Sprintf("%d", articleId)). + Str("client_id", clientId.String()). + Str("user_id", fmt.Sprintf("%d", user.ID)). + Msg("Article published successfully") + + return nil +} + +// Unpublish unpublishes an article +func (_i *articlesService) Unpublish(clientId *uuid.UUID, articleId uint, authToken string) error { + // Extract user info from auth token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return errors.New("user not found from auth token") + } + + // Check if article exists + article, err := _i.Repo.FindOne(clientId, articleId) + if err != nil { + return err + } + + // Check if article is already unpublished + if article.IsPublish == nil || !*article.IsPublish { + return errors.New("article is already unpublished") + } + + // Check if user has permission to unpublish + // For now, we'll allow any authenticated user to unpublish + // You can add more sophisticated permission checks here + + // Update article to unpublished status + isPublishFalse := false + isDraftTrue := true + draftedAt := time.Now() + statusIdOne := 1 // Draft status + + article.IsPublish = &isPublishFalse + article.PublishedAt = nil + article.PublishSchedule = nil + article.IsDraft = &isDraftTrue + article.DraftedAt = &draftedAt + article.StatusId = &statusIdOne + + err = _i.Repo.Update(clientId, articleId, article) + if err != nil { + return err + } + + // Create approval record for audit trail + articleApproval := &entity.ArticleApprovals{ + ArticleId: articleId, + ApprovalBy: user.ID, + StatusId: statusIdOne, + Message: "Article unpublished", + ApprovalAtLevel: nil, + } + _, err = _i.ArticleApprovalsRepo.Create(articleApproval) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to create approval record for unpublished article") + // Don't return error as article was already updated + } + + _i.Log.Info(). + Str("article_id", fmt.Sprintf("%d", articleId)). + Str("client_id", clientId.String()). + Str("user_id", fmt.Sprintf("%d", user.ID)). + Msg("Article unpublished successfully") + + 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 + } +} diff --git a/app/module/bookmarks/bookmarks.module.go b/app/module/bookmarks/bookmarks.module.go new file mode 100644 index 0000000..bb1ae94 --- /dev/null +++ b/app/module/bookmarks/bookmarks.module.go @@ -0,0 +1,66 @@ +package bookmarks + +import ( + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/bookmarks/controller" + "web-qudo-be/app/module/bookmarks/repository" + "web-qudo-be/app/module/bookmarks/service" + usersRepo "web-qudo-be/app/module/users/repository" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// BookmarksRouter struct of BookmarksRouter +type BookmarksRouter struct { + App fiber.Router + Controller controller.BookmarksController + UsersRepo usersRepo.UsersRepository +} + +// NewBookmarksModule register bulky of Bookmarks module +var NewBookmarksModule = fx.Options( + // register repository of Bookmarks module + fx.Provide(repository.NewBookmarksRepository), + + // register service of Bookmarks module + fx.Provide(service.NewBookmarksService), + + // register controller of Bookmarks module + fx.Provide(controller.NewBookmarksController), + + // register router of Bookmarks module + fx.Provide(NewBookmarksRouter), +) + +// NewBookmarksRouter init BookmarksRouter +func NewBookmarksRouter(fiber *fiber.App, controller controller.BookmarksController, usersRepo usersRepo.UsersRepository) *BookmarksRouter { + return &BookmarksRouter{ + App: fiber, + Controller: controller, + UsersRepo: usersRepo, + } +} + +// RegisterBookmarksRoutes register routes of Bookmarks module +func (_i *BookmarksRouter) RegisterBookmarksRoutes() { + // define controllers + bookmarksController := _i.Controller + + // define routes + _i.App.Route("/bookmarks", func(router fiber.Router) { + // Add user middleware to extract user level from JWT token + router.Use(middleware.UserMiddleware(_i.UsersRepo)) + + // Public routes (require authentication) + router.Get("/", bookmarksController.All) + router.Get("/:id", bookmarksController.Show) + router.Post("/", bookmarksController.Save) + router.Delete("/:id", bookmarksController.Delete) + + // User-specific routes + router.Get("/user", bookmarksController.GetByUserId) + router.Post("/toggle/:articleId", bookmarksController.ToggleBookmark) + router.Get("/summary", bookmarksController.GetBookmarkSummary) + }) +} diff --git a/app/module/bookmarks/controller/bookmarks.controller.go b/app/module/bookmarks/controller/bookmarks.controller.go new file mode 100644 index 0000000..bece205 --- /dev/null +++ b/app/module/bookmarks/controller/bookmarks.controller.go @@ -0,0 +1,342 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/bookmarks/request" + "web-qudo-be/app/module/bookmarks/service" + "web-qudo-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type bookmarksController struct { + bookmarksService service.BookmarksService + Log zerolog.Logger +} + +type BookmarksController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + GetByUserId(c *fiber.Ctx) error + ToggleBookmark(c *fiber.Ctx) error + GetBookmarkSummary(c *fiber.Ctx) error +} + +func NewBookmarksController(bookmarksService service.BookmarksService, log zerolog.Logger) BookmarksController { + return &bookmarksController{ + bookmarksService: bookmarksService, + Log: log, + } +} + +// All Bookmarks +// @Summary Get all Bookmarks +// @Description API for getting all Bookmarks +// @Tags Bookmarks +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param req query request.BookmarksQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /bookmarks [get] +func (_i *bookmarksController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.BookmarksQueryRequestContext{ + ArticleId: c.Query("articleId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + _i.Log.Info().Interface("clientId", clientId).Msg("") + + // Get Authorization token from header + authToken := c.Get("Authorization") + + bookmarksData, paging, err := _i.bookmarksService.All(clientId, authToken, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Successfully retrieved bookmarks"}, + Data: bookmarksData, + Meta: &paging, + }) +} + +// Show Bookmark +// @Summary Get Bookmark by ID +// @Description API for getting Bookmark by ID +// @Tags Bookmarks +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Bookmark ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /bookmarks/{id} [get] +func (_i *bookmarksController) Show(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Invalid bookmark ID"}, + }) + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + _i.Log.Info().Interface("clientId", clientId).Msg("") + + bookmarkData, err := _i.bookmarksService.Show(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Successfully retrieved bookmark"}, + Data: bookmarkData, + }) +} + +// Save Bookmark +// @Summary Create new Bookmark +// @Description API for creating new Bookmark +// @Tags Bookmarks +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param req body request.BookmarksCreateRequest true "Bookmark data" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /bookmarks [post] +func (_i *bookmarksController) Save(c *fiber.Ctx) error { + var req request.BookmarksCreateRequest + if err := c.BodyParser(&req); err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Invalid request body"}, + }) + } + + // Validate request + if err := utilVal.ValidateStruct(req); err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Validation error"}, + Data: err, + }) + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Get Authorization token from header + authToken := c.Get("Authorization") + _i.Log.Info().Interface("clientId", clientId).Msg("") + _i.Log.Info().Str("authToken", authToken).Msg("") + + bookmarkData, err := _i.bookmarksService.Save(clientId, req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Successfully created bookmark"}, + Data: bookmarkData, + }) +} + +// Delete Bookmark +// @Summary Delete Bookmark +// @Description API for deleting Bookmark +// @Tags Bookmarks +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Bookmark ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /bookmarks/{id} [delete] +func (_i *bookmarksController) Delete(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Invalid bookmark ID"}, + }) + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + _i.Log.Info().Interface("clientId", clientId).Msg("") + + err = _i.bookmarksService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Successfully deleted bookmark"}, + }) +} + +// Get Bookmarks by User ID +// @Summary Get Bookmarks by User ID +// @Description API for getting Bookmarks by User ID +// @Tags Bookmarks +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param req query request.BookmarksQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /bookmarks/user [get] +func (_i *bookmarksController) GetByUserId(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.BookmarksQueryRequestContext{ + ArticleId: c.Query("articleId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Get Authorization token from header + authToken := c.Get("Authorization") + _i.Log.Info().Interface("clientId", clientId).Msg("") + _i.Log.Info().Str("authToken", authToken).Msg("") + + bookmarksData, paging, err := _i.bookmarksService.GetByUserId(clientId, authToken, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Successfully retrieved user bookmarks"}, + Data: bookmarksData, + Meta: &paging, + }) +} + +// Toggle Bookmark +// @Summary Toggle Bookmark (Add/Remove) +// @Description API for toggling bookmark status for an article +// @Tags Bookmarks +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param articleId path int true "Article ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /bookmarks/toggle/{articleId} [post] +func (_i *bookmarksController) ToggleBookmark(c *fiber.Ctx) error { + articleId, err := strconv.Atoi(c.Params("articleId")) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Invalid article ID"}, + }) + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Get Authorization token from header + authToken := c.Get("Authorization") + _i.Log.Info().Interface("clientId", clientId).Msg("") + _i.Log.Info().Str("authToken", authToken).Msg("") + + isBookmarked, err := _i.bookmarksService.ToggleBookmark(clientId, authToken, uint(articleId)) + if err != nil { + return err + } + + message := "Bookmark removed" + if isBookmarked { + message = "Bookmark added" + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{message}, + Data: map[string]interface{}{ + "isBookmarked": isBookmarked, + "articleId": articleId, + }, + }) +} + +// Get Bookmark Summary +// @Summary Get Bookmark Summary for User +// @Description API for getting bookmark summary including total count and recent bookmarks +// @Tags Bookmarks +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /bookmarks/summary [get] +func (_i *bookmarksController) GetBookmarkSummary(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Get Authorization token from header + authToken := c.Get("Authorization") + _i.Log.Info().Interface("clientId", clientId).Msg("") + _i.Log.Info().Str("authToken", authToken).Msg("") + + summaryData, err := _i.bookmarksService.GetBookmarkSummary(clientId, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Successfully retrieved bookmark summary"}, + Data: summaryData, + }) +} diff --git a/app/module/bookmarks/mapper/bookmarks.mapper.go b/app/module/bookmarks/mapper/bookmarks.mapper.go new file mode 100644 index 0000000..94faba7 --- /dev/null +++ b/app/module/bookmarks/mapper/bookmarks.mapper.go @@ -0,0 +1,55 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/bookmarks/response" +) + +func ToBookmarksResponse(bookmark *entity.Bookmarks) *response.BookmarksResponse { + return &response.BookmarksResponse{ + ID: bookmark.ID, + UserId: bookmark.UserId, + ArticleId: bookmark.ArticleId, + IsActive: bookmark.IsActive, + CreatedAt: bookmark.CreatedAt, + UpdatedAt: bookmark.UpdatedAt, + Article: response.ArticleDetails{ + ID: bookmark.Article.ID, + Title: bookmark.Article.Title, + Slug: bookmark.Article.Slug, + Description: bookmark.Article.Description, + HtmlDescription: bookmark.Article.HtmlDescription, + CategoryId: bookmark.Article.CategoryId, + TypeId: bookmark.Article.TypeId, + Tags: bookmark.Article.Tags, + ThumbnailUrl: getThumbnailUrl(bookmark.Article.ThumbnailPath), + PageUrl: bookmark.Article.PageUrl, + CreatedById: bookmark.Article.CreatedById, + ShareCount: bookmark.Article.ShareCount, + ViewCount: bookmark.Article.ViewCount, + CommentCount: bookmark.Article.CommentCount, + StatusId: bookmark.Article.StatusId, + IsBanner: bookmark.Article.IsBanner, + IsPublish: bookmark.Article.IsPublish, + PublishedAt: bookmark.Article.PublishedAt, + IsActive: bookmark.Article.IsActive, + CreatedAt: bookmark.Article.CreatedAt, + UpdatedAt: bookmark.Article.UpdatedAt, + }, + } +} + +func ToBookmarksResponseList(bookmarks []entity.Bookmarks) []*response.BookmarksResponse { + var responses []*response.BookmarksResponse + for _, bookmark := range bookmarks { + responses = append(responses, ToBookmarksResponse(&bookmark)) + } + return responses +} + +func getThumbnailUrl(thumbnailPath *string) string { + if thumbnailPath != nil && *thumbnailPath != "" { + return *thumbnailPath + } + return "" +} diff --git a/app/module/bookmarks/repository/bookmarks.repository.go b/app/module/bookmarks/repository/bookmarks.repository.go new file mode 100644 index 0000000..02fae34 --- /dev/null +++ b/app/module/bookmarks/repository/bookmarks.repository.go @@ -0,0 +1,217 @@ +package repository + +import ( + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/bookmarks/request" + "web-qudo-be/utils/paginator" + + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +type bookmarksRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// BookmarksRepository define interface of IBookmarksRepository +type BookmarksRepository interface { + GetAll(clientId *uuid.UUID, req request.BookmarksQueryRequest) (bookmarks []*entity.Bookmarks, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (bookmark *entity.Bookmarks, err error) + FindByUserAndArticle(clientId *uuid.UUID, userId uint, articleId uint) (bookmark *entity.Bookmarks, err error) + Create(clientId *uuid.UUID, bookmark *entity.Bookmarks) (bookmarkReturn *entity.Bookmarks, err error) + Update(clientId *uuid.UUID, id uint, bookmark *entity.Bookmarks) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) + GetByUserId(clientId *uuid.UUID, userId uint, req request.BookmarksQueryRequest) (bookmarks []*entity.Bookmarks, paging paginator.Pagination, err error) + CountByUserId(clientId *uuid.UUID, userId uint) (count int64, err error) +} + +func NewBookmarksRepository(db *database.Database, log zerolog.Logger) BookmarksRepository { + return &bookmarksRepository{ + DB: db, + Log: log, + } +} + +// implement interface of IBookmarksRepository +func (_i *bookmarksRepository) GetAll(clientId *uuid.UUID, req request.BookmarksQueryRequest) (bookmarks []*entity.Bookmarks, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Bookmarks{}).Preload("User").Preload("Article") + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + // Apply filters + if req.UserId != nil { + query = query.Where("user_id = ?", *req.UserId) + } + + if req.ArticleId != nil { + query = query.Where("article_id = ?", *req.ArticleId) + } + + // Count total records + if err = query.Count(&count).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to count bookmarks") + return nil, paging, err + } + + // Apply pagination + if req.Pagination != nil { + offset := (req.Pagination.Page - 1) * req.Pagination.Limit + query = query.Offset(offset).Limit(req.Pagination.Limit) + paging = *req.Pagination + } + + // Execute query + if err = query.Find(&bookmarks).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to get bookmarks") + return nil, paging, err + } + + paging.Count = count + paging = *paginator.Paging(&paging) + + return bookmarks, paging, nil +} + +func (_i *bookmarksRepository) FindOne(clientId *uuid.UUID, id uint) (bookmark *entity.Bookmarks, err error) { + query := _i.DB.DB.Model(&entity.Bookmarks{}).Preload("User").Preload("Article") + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err = query.Where("id = ?", id).First(&bookmark).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to find bookmark") + return nil, err + } + + return bookmark, nil +} + +func (_i *bookmarksRepository) FindByUserAndArticle(clientId *uuid.UUID, userId uint, articleId uint) (bookmark *entity.Bookmarks, err error) { + query := _i.DB.DB.Model(&entity.Bookmarks{}) + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err = query.Where("user_id = ? AND article_id = ?", userId, articleId).First(&bookmark).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to find bookmark by user and article") + return nil, err + } + + return bookmark, nil +} + +func (_i *bookmarksRepository) Create(clientId *uuid.UUID, bookmark *entity.Bookmarks) (bookmarkReturn *entity.Bookmarks, err error) { + bookmark.ClientId = clientId + + if err = _i.DB.DB.Create(bookmark).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to create bookmark") + return nil, err + } + + return bookmark, nil +} + +func (_i *bookmarksRepository) Update(clientId *uuid.UUID, id uint, bookmark *entity.Bookmarks) (err error) { + query := _i.DB.DB.Model(&entity.Bookmarks{}) + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err = query.Where("id = ?", id).Updates(bookmark).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to update bookmark") + return err + } + + return nil +} + +func (_i *bookmarksRepository) Delete(clientId *uuid.UUID, id uint) (err error) { + query := _i.DB.DB.Model(&entity.Bookmarks{}) + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err = query.Where("id = ?", id).Delete(&entity.Bookmarks{}).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to delete bookmark") + return err + } + + return nil +} + +func (_i *bookmarksRepository) GetByUserId(clientId *uuid.UUID, userId uint, req request.BookmarksQueryRequest) (bookmarks []*entity.Bookmarks, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Bookmarks{}).Preload("User").Preload("Article") + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + // Apply user filter + query = query.Where("user_id = ?", userId) + + // Apply additional filters + if req.ArticleId != nil { + query = query.Where("article_id = ?", *req.ArticleId) + } + + // Count total records + if err = query.Count(&count).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to count user bookmarks") + return nil, paging, err + } + + // Apply pagination + if req.Pagination != nil { + offset := (req.Pagination.Page - 1) * req.Pagination.Limit + query = query.Offset(offset).Limit(req.Pagination.Limit) + paging = *req.Pagination + } + + // Execute query + if err = query.Find(&bookmarks).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to get user bookmarks") + return nil, paging, err + } + + paging.Count = count + paging = *paginator.Paging(&paging) + + return bookmarks, paging, nil +} + +func (_i *bookmarksRepository) CountByUserId(clientId *uuid.UUID, userId uint) (count int64, err error) { + query := _i.DB.DB.Model(&entity.Bookmarks{}) + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + // Apply user filter + query = query.Where("user_id = ?", userId) + + if err = query.Count(&count).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to count user bookmarks") + return 0, err + } + + return count, nil +} diff --git a/app/module/bookmarks/request/bookmarks.request.go b/app/module/bookmarks/request/bookmarks.request.go new file mode 100644 index 0000000..1e98155 --- /dev/null +++ b/app/module/bookmarks/request/bookmarks.request.go @@ -0,0 +1,63 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type BookmarksGeneric interface { + ToEntity() +} + +type BookmarksQueryRequest struct { + UserId *uint `json:"userId"` + ArticleId *uint `json:"articleId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type BookmarksCreateRequest struct { + ArticleId uint `json:"articleId" validate:"required"` +} + +func (req BookmarksCreateRequest) ToEntity(userId uint) *entity.Bookmarks { + return &entity.Bookmarks{ + UserId: userId, + ArticleId: req.ArticleId, + IsActive: boolPtr(true), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } +} + +type BookmarksQueryRequestContext struct { + UserId string `json:"userId"` + ArticleId string `json:"articleId"` +} + +func (req BookmarksQueryRequestContext) ToParamRequest() BookmarksQueryRequest { + var request BookmarksQueryRequest + + if userIdStr := req.UserId; userIdStr != "" { + userId, err := strconv.Atoi(userIdStr) + if err == nil { + userIdUint := uint(userId) + request.UserId = &userIdUint + } + } + if articleIdStr := req.ArticleId; articleIdStr != "" { + articleId, err := strconv.Atoi(articleIdStr) + if err == nil { + articleIdUint := uint(articleId) + request.ArticleId = &articleIdUint + } + } + + return request +} + +// Helper function to create bool pointer +func boolPtr(b bool) *bool { + return &b +} diff --git a/app/module/bookmarks/response/bookmarks.response.go b/app/module/bookmarks/response/bookmarks.response.go new file mode 100644 index 0000000..3bd3520 --- /dev/null +++ b/app/module/bookmarks/response/bookmarks.response.go @@ -0,0 +1,46 @@ +package response + +import ( + "time" +) + +type BookmarksResponse struct { + ID uint `json:"id"` + UserId uint `json:"userId"` + ArticleId uint `json:"articleId"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + // Article details + Article ArticleDetails `json:"article"` +} + +type ArticleDetails struct { + ID uint `json:"id"` + Title string `json:"title"` + Slug string `json:"slug"` + Description string `json:"description"` + HtmlDescription string `json:"htmlDescription"` + CategoryId int `json:"categoryId"` + TypeId int `json:"typeId"` + Tags string `json:"tags"` + ThumbnailUrl string `json:"thumbnailUrl"` + PageUrl *string `json:"pageUrl"` + CreatedById *uint `json:"createdById"` + ShareCount *int `json:"shareCount"` + ViewCount *int `json:"viewCount"` + CommentCount *int `json:"commentCount"` + StatusId *int `json:"statusId"` + IsBanner *bool `json:"isBanner"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type BookmarksSummaryResponse struct { + TotalBookmarks int `json:"totalBookmarks"` + RecentBookmarks []*BookmarksResponse `json:"recentBookmarks"` +} diff --git a/app/module/bookmarks/service/bookmarks.service.go b/app/module/bookmarks/service/bookmarks.service.go new file mode 100644 index 0000000..cd7a035 --- /dev/null +++ b/app/module/bookmarks/service/bookmarks.service.go @@ -0,0 +1,244 @@ +package service + +import ( + "errors" + "web-qudo-be/app/database/entity" + articlesRepository "web-qudo-be/app/module/articles/repository" + "web-qudo-be/app/module/bookmarks/mapper" + "web-qudo-be/app/module/bookmarks/repository" + "web-qudo-be/app/module/bookmarks/request" + "web-qudo-be/app/module/bookmarks/response" + usersRepository "web-qudo-be/app/module/users/repository" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" + + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +// BookmarksService +type bookmarksService struct { + Repo repository.BookmarksRepository + ArticlesRepo articlesRepository.ArticlesRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +// BookmarksService define interface of IBookmarksService +type BookmarksService interface { + All(clientId *uuid.UUID, authToken string, req request.BookmarksQueryRequest) (bookmarks []*response.BookmarksResponse, paging paginator.Pagination, err error) + Show(clientId *uuid.UUID, id uint) (bookmark *response.BookmarksResponse, err error) + Save(clientId *uuid.UUID, req request.BookmarksCreateRequest, authToken string) (bookmark *entity.Bookmarks, err error) + Delete(clientId *uuid.UUID, id uint) error + GetByUserId(clientId *uuid.UUID, authToken string, req request.BookmarksQueryRequest) (bookmarks []*response.BookmarksResponse, paging paginator.Pagination, err error) + ToggleBookmark(clientId *uuid.UUID, authToken string, articleId uint) (isBookmarked bool, err error) + GetBookmarkSummary(clientId *uuid.UUID, authToken string) (summary *response.BookmarksSummaryResponse, err error) +} + +// NewBookmarksService init BookmarksService +func NewBookmarksService( + repo repository.BookmarksRepository, + articlesRepo articlesRepository.ArticlesRepository, + usersRepo usersRepository.UsersRepository, + log zerolog.Logger, +) BookmarksService { + return &bookmarksService{ + Repo: repo, + ArticlesRepo: articlesRepo, + UsersRepo: usersRepo, + Log: log, + } +} + +// implement interface of IBookmarksService +func (_i *bookmarksService) All(clientId *uuid.UUID, authToken string, req request.BookmarksQueryRequest) (bookmarks []*response.BookmarksResponse, paging paginator.Pagination, err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + _i.Log.Error().Msg("User not found from auth token") + return nil, paging, errors.New("user not found") + } + req.UserId = &user.ID + + bookmarksEntity, paging, err := _i.Repo.GetAll(clientId, req) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to get all bookmarks") + return nil, paging, err + } + + // Convert []*entity.Bookmarks to []entity.Bookmarks + var bookmarksSlice []entity.Bookmarks + for _, b := range bookmarksEntity { + bookmarksSlice = append(bookmarksSlice, *b) + } + bookmarks = mapper.ToBookmarksResponseList(bookmarksSlice) + return bookmarks, paging, nil +} + +func (_i *bookmarksService) Show(clientId *uuid.UUID, id uint) (bookmark *response.BookmarksResponse, err error) { + bookmarkEntity, err := _i.Repo.FindOne(clientId, id) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to show bookmark") + return nil, err + } + + bookmark = mapper.ToBookmarksResponse(bookmarkEntity) + return bookmark, nil +} + +func (_i *bookmarksService) Save(clientId *uuid.UUID, req request.BookmarksCreateRequest, authToken string) (bookmark *entity.Bookmarks, err error) { + // Extract user info from auth token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + _i.Log.Error().Msg("User not found from auth token") + return nil, errors.New("user not found") + } + + // Check if article exists + _, err = _i.ArticlesRepo.FindOne(clientId, req.ArticleId) + if err != nil { + _i.Log.Error().Err(err).Msg("Article not found") + return nil, errors.New("article not found") + } + + // Check if bookmark already exists + existingBookmark, err := _i.Repo.FindByUserAndArticle(clientId, user.ID, req.ArticleId) + if err == nil && existingBookmark != nil { + _i.Log.Error().Msg("Bookmark already exists") + return nil, errors.New("article already bookmarked") + } + + // Create new bookmark + bookmarkEntity := req.ToEntity(user.ID) + bookmark, err = _i.Repo.Create(clientId, bookmarkEntity) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to create bookmark") + return nil, err + } + + return bookmark, nil +} + +func (_i *bookmarksService) Delete(clientId *uuid.UUID, id uint) error { + err := _i.Repo.Delete(clientId, id) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to delete bookmark") + return err + } + + return nil +} + +func (_i *bookmarksService) GetByUserId(clientId *uuid.UUID, authToken string, req request.BookmarksQueryRequest) (bookmarks []*response.BookmarksResponse, paging paginator.Pagination, err error) { + // Extract user info from auth token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + _i.Log.Error().Msg("User not found from auth token") + return nil, paging, errors.New("user not found") + } + + bookmarksEntity, paging, err := _i.Repo.GetByUserId(clientId, user.ID, req) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to get bookmarks by user ID") + return nil, paging, err + } + + // Convert []*entity.Bookmarks to []entity.Bookmarks + var bookmarksSlice []entity.Bookmarks + for _, b := range bookmarksEntity { + bookmarksSlice = append(bookmarksSlice, *b) + } + bookmarks = mapper.ToBookmarksResponseList(bookmarksSlice) + return bookmarks, paging, nil +} + +func (_i *bookmarksService) ToggleBookmark(clientId *uuid.UUID, authToken string, articleId uint) (isBookmarked bool, err error) { + // Extract user info from auth token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + _i.Log.Error().Msg("User not found from auth token") + return false, errors.New("user not found") + } + + // Check if article exists + _, err = _i.ArticlesRepo.FindOne(clientId, articleId) + if err != nil { + _i.Log.Error().Err(err).Msg("Article not found") + return false, errors.New("article not found") + } + + // Check if bookmark already exists + existingBookmark, err := _i.Repo.FindByUserAndArticle(clientId, user.ID, articleId) + if err == nil && existingBookmark != nil { + // Bookmark exists, delete it + err = _i.Repo.Delete(clientId, existingBookmark.ID) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to delete existing bookmark") + return false, err + } + return false, nil // Bookmark removed + } + + // Bookmark doesn't exist, create it + bookmarkEntity := &entity.Bookmarks{ + UserId: user.ID, + ArticleId: articleId, + IsActive: boolPtr(true), + } + + _, err = _i.Repo.Create(clientId, bookmarkEntity) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to create bookmark") + return false, err + } + + return true, nil // Bookmark added +} + +func (_i *bookmarksService) GetBookmarkSummary(clientId *uuid.UUID, authToken string) (summary *response.BookmarksSummaryResponse, err error) { + // Extract user info from auth token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + _i.Log.Error().Msg("User not found from auth token") + return nil, errors.New("user not found") + } + + // Get total count + totalCount, err := _i.Repo.CountByUserId(clientId, user.ID) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to count user bookmarks") + return nil, err + } + + // Get recent bookmarks (last 5) + req := request.BookmarksQueryRequest{ + Pagination: &paginator.Pagination{ + Page: 1, + Limit: 5, + }, + } + + recentBookmarksEntity, _, err := _i.Repo.GetByUserId(clientId, user.ID, req) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to get recent bookmarks") + return nil, err + } + + // Convert []*entity.Bookmarks to []entity.Bookmarks + var bookmarksSlice []entity.Bookmarks + for _, b := range recentBookmarksEntity { + bookmarksSlice = append(bookmarksSlice, *b) + } + recentBookmarks := mapper.ToBookmarksResponseList(bookmarksSlice) + + summary = &response.BookmarksSummaryResponse{ + TotalBookmarks: int(totalCount), + RecentBookmarks: recentBookmarks, + } + + return summary, nil +} + +// Helper function to create bool pointer +func boolPtr(b bool) *bool { + return &b +} diff --git a/app/module/cities/cities.module.go b/app/module/cities/cities.module.go new file mode 100644 index 0000000..4a8db10 --- /dev/null +++ b/app/module/cities/cities.module.go @@ -0,0 +1,53 @@ +package cities + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/cities/controller" + "web-qudo-be/app/module/cities/repository" + "web-qudo-be/app/module/cities/service" +) + +// struct of CitiesRouter +type CitiesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of Cities module +var NewCitiesModule = fx.Options( + // register repository of Cities module + fx.Provide(repository.NewCitiesRepository), + + // register service of Cities module + fx.Provide(service.NewCitiesService), + + // register controller of Cities module + fx.Provide(controller.NewController), + + // register router of Cities module + fx.Provide(NewCitiesRouter), +) + +// init CitiesRouter +func NewCitiesRouter(fiber *fiber.App, controller *controller.Controller) *CitiesRouter { + return &CitiesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of Cities module +func (_i *CitiesRouter) RegisterCitiesRoutes() { + // define controllers + citiesController := _i.Controller.Cities + + // define routes + _i.App.Route("/cities", func(router fiber.Router) { + router.Get("/", citiesController.All) + router.Get("/:id", citiesController.Show) + router.Post("/", citiesController.Save) + router.Put("/:id", citiesController.Update) + router.Delete("/:id", citiesController.Delete) + }) +} diff --git a/app/module/cities/controller/cities.controller.go b/app/module/cities/controller/cities.controller.go new file mode 100644 index 0000000..2a7e9af --- /dev/null +++ b/app/module/cities/controller/cities.controller.go @@ -0,0 +1,184 @@ +package controller + +import ( + "github.com/gofiber/fiber/v2" + "strconv" + "web-qudo-be/app/module/cities/request" + "web-qudo-be/app/module/cities/service" + "web-qudo-be/utils/paginator" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type citiesController struct { + citiesService service.CitiesService +} + +type CitiesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewCitiesController(citiesService service.CitiesService) CitiesController { + return &citiesController{ + citiesService: citiesService, + } +} + +// All Cities +// @Summary Get all Cities +// @Description API for getting all Cities +// @Tags Untags +// @Security Bearer +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /cities [get] +func (_i *citiesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + var req request.CitiesQueryRequest + req.Pagination = paginate + + citiesData, paging, err := _i.citiesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Cities list successfully retrieved"}, + Data: citiesData, + Meta: paging, + }) +} + +// Show Cities +// @Summary Get one Cities +// @Description API for getting one Cities +// @Tags Untags +// @Security Bearer +// @Param id path int true "Cities ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /cities/{id} [get] +func (_i *citiesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + citiesData, err := _i.citiesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Cities successfully retrieved"}, + Data: citiesData, + }) +} + +// Save Cities +// @Summary Create Cities +// @Description API for create Cities +// @Tags Untags +// @Security Bearer +// @Param payload body request.CitiesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /cities [post] +func (_i *citiesController) Save(c *fiber.Ctx) error { + req := new(request.CitiesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.citiesService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Cities successfully created"}, + }) +} + +// Update Cities +// @Summary Update Cities +// @Description API for update Cities +// @Tags Untags +// @Security Bearer +// @Body request.CitiesUpdateRequest +// @Param id path int true "Cities ID" +// @Param payload body request.CitiesUpdateRequest true "Required payload" +// @Accept json +// @Produce json +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /cities/{id} [put] +func (_i *citiesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.CitiesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.citiesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Cities successfully updated"}, + }) +} + +// Delete Cities +// @Summary Delete Cities +// @Description API for delete Cities +// @Tags Untags +// @Security Bearer +// @Param id path int true "Cities ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /cities/{id} [delete] +func (_i *citiesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.citiesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Cities successfully deleted"}, + }) +} diff --git a/app/module/cities/controller/controller.go b/app/module/cities/controller/controller.go new file mode 100644 index 0000000..cf824bb --- /dev/null +++ b/app/module/cities/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "web-qudo-be/app/module/cities/service" + +type Controller struct { + Cities CitiesController +} + +func NewController(CitiesService service.CitiesService) *Controller { + return &Controller{ + Cities: NewCitiesController(CitiesService), + } +} diff --git a/app/module/cities/mapper/cities.mapper.go b/app/module/cities/mapper/cities.mapper.go new file mode 100644 index 0000000..1969839 --- /dev/null +++ b/app/module/cities/mapper/cities.mapper.go @@ -0,0 +1,17 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/cities/response" +) + +func CitiesResponseMapper(citiesReq *entity.Cities) (citiesRes *res.CitiesResponse) { + if citiesReq != nil { + citiesRes = &res.CitiesResponse{ + ID: citiesReq.ID, + CityName: citiesReq.CityName, + ProvId: citiesReq.ProvId, + } + } + return citiesRes +} diff --git a/app/module/cities/repository/cities.repository.go b/app/module/cities/repository/cities.repository.go new file mode 100644 index 0000000..fab956f --- /dev/null +++ b/app/module/cities/repository/cities.repository.go @@ -0,0 +1,69 @@ +package repository + +import ( + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/cities/request" + "web-qudo-be/utils/paginator" +) + +type citiesRepository struct { + DB *database.Database +} + +// CitiesRepository define interface of ICitiesRepository +type CitiesRepository interface { + GetAll(req request.CitiesQueryRequest) (citiess []*entity.Cities, paging paginator.Pagination, err error) + FindOne(id uint) (cities *entity.Cities, err error) + Create(cities *entity.Cities) (err error) + Update(id uint, cities *entity.Cities) (err error) + Delete(id uint) (err error) +} + +func NewCitiesRepository(db *database.Database) CitiesRepository { + return &citiesRepository{ + DB: db, + } +} + +// implement interface of ICitiesRepository +func (_i *citiesRepository) GetAll(req request.CitiesQueryRequest) (citiess []*entity.Cities, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Cities{}) + query.Count(&count) + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&citiess).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *citiesRepository) FindOne(id uint) (cities *entity.Cities, err error) { + if err := _i.DB.DB.First(&cities, id).Error; err != nil { + return nil, err + } + + return cities, nil +} + +func (_i *citiesRepository) Create(cities *entity.Cities) (err error) { + return _i.DB.DB.Create(cities).Error +} + +func (_i *citiesRepository) Update(id uint, cities *entity.Cities) (err error) { + return _i.DB.DB.Model(&entity.Cities{}). + Where(&entity.Cities{ID: id}). + Updates(cities).Error +} + +func (_i *citiesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.Cities{}, id).Error +} diff --git a/app/module/cities/request/cities.request.go b/app/module/cities/request/cities.request.go new file mode 100644 index 0000000..cc848a6 --- /dev/null +++ b/app/module/cities/request/cities.request.go @@ -0,0 +1,42 @@ +package request + +import ( + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type CitiesGeneric interface { + ToEntity() +} + +type CitiesQueryRequest struct { + CityName string `json:"city_name" validate:"required"` + ProvId int `json:"prov_id" validate:"required"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type CitiesCreateRequest struct { + CityName string `json:"city_name" validate:"required"` + ProvId int `json:"prov_id" validate:"required"` +} + +func (req CitiesCreateRequest) ToEntity() *entity.Cities { + return &entity.Cities{ + CityName: req.CityName, + ProvId: req.ProvId, + } +} + +type CitiesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + CityName string `json:"city_name" validate:"required"` + ProvId int `json:"prov_id" validate:"required"` +} + +func (req CitiesUpdateRequest) ToEntity() *entity.Cities { + return &entity.Cities{ + ID: req.ID, + CityName: req.CityName, + ProvId: req.ProvId, + } +} diff --git a/app/module/cities/response/cities.response.go b/app/module/cities/response/cities.response.go new file mode 100644 index 0000000..8941551 --- /dev/null +++ b/app/module/cities/response/cities.response.go @@ -0,0 +1,7 @@ +package response + +type CitiesResponse struct { + ID uint `json:"id"` + CityName string `json:"city_name"` + ProvId int `json:"prov_id"` +} diff --git a/app/module/cities/service/cities.service.go b/app/module/cities/service/cities.service.go new file mode 100644 index 0000000..5f551d9 --- /dev/null +++ b/app/module/cities/service/cities.service.go @@ -0,0 +1,72 @@ +package service + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/cities/mapper" + "web-qudo-be/app/module/cities/repository" + "web-qudo-be/app/module/cities/request" + "web-qudo-be/app/module/cities/response" + "web-qudo-be/utils/paginator" +) + +// CitiesService +type citiesService struct { + Repo repository.CitiesRepository + Log zerolog.Logger +} + +// CitiesService define interface of ICitiesService +type CitiesService interface { + All(req request.CitiesQueryRequest) (cities []*response.CitiesResponse, paging paginator.Pagination, err error) + Show(id uint) (cities *response.CitiesResponse, err error) + Save(req request.CitiesCreateRequest) (err error) + Update(id uint, req request.CitiesUpdateRequest) (err error) + Delete(id uint) error +} + +// NewCitiesService init CitiesService +func NewCitiesService(repo repository.CitiesRepository, log zerolog.Logger) CitiesService { + + return &citiesService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of CitiesService +func (_i *citiesService) All(req request.CitiesQueryRequest) (citiess []*response.CitiesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + citiess = append(citiess, mapper.CitiesResponseMapper(result)) + } + + return +} + +func (_i *citiesService) Show(id uint) (cities *response.CitiesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.CitiesResponseMapper(result), nil +} + +func (_i *citiesService) Save(req request.CitiesCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + return _i.Repo.Create(req.ToEntity()) +} + +func (_i *citiesService) Update(id uint, req request.CitiesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *citiesService) Delete(id uint) error { + return _i.Repo.Delete(id) +} diff --git a/app/module/client_approval_settings/client_approval_settings.module.go b/app/module/client_approval_settings/client_approval_settings.module.go new file mode 100644 index 0000000..2a45063 --- /dev/null +++ b/app/module/client_approval_settings/client_approval_settings.module.go @@ -0,0 +1,65 @@ +package client_approval_settings + +import ( + "web-qudo-be/app/module/client_approval_settings/controller" + "web-qudo-be/app/module/client_approval_settings/repository" + "web-qudo-be/app/module/client_approval_settings/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// ClientApprovalSettingsRouter struct of ClientApprovalSettingsRouter +type ClientApprovalSettingsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// NewClientApprovalSettingsModule register bulky of ClientApprovalSettings module +var NewClientApprovalSettingsModule = fx.Options( + // register repository of ClientApprovalSettings module + fx.Provide(repository.NewClientApprovalSettingsRepository), + + // register service of ClientApprovalSettings module + fx.Provide(service.NewClientApprovalSettingsService), + + // register controller of ClientApprovalSettings module + fx.Provide(controller.NewController), + + // register router of ClientApprovalSettings module + fx.Provide(NewClientApprovalSettingsRouter), +) + +// NewClientApprovalSettingsRouter init ClientApprovalSettingsRouter +func NewClientApprovalSettingsRouter(fiber *fiber.App, controller *controller.Controller) *ClientApprovalSettingsRouter { + return &ClientApprovalSettingsRouter{ + App: fiber, + Controller: controller, + } +} + +// RegisterClientApprovalSettingsRoutes register routes of ClientApprovalSettings +func (_i *ClientApprovalSettingsRouter) RegisterClientApprovalSettingsRoutes() { + // define controllers + clientApprovalSettingsController := _i.Controller.ClientApprovalSettings + + // define routes + _i.App.Route("/client-approval-settings", func(router fiber.Router) { + // Basic CRUD routes + router.Post("/", clientApprovalSettingsController.CreateSettings) + router.Get("/", clientApprovalSettingsController.GetSettings) + router.Put("/", clientApprovalSettingsController.UpdateSettings) + router.Delete("/", clientApprovalSettingsController.DeleteSettings) + + // Approval management routes + router.Post("/toggle-approval", clientApprovalSettingsController.ToggleApproval) + router.Post("/enable-approval", clientApprovalSettingsController.EnableApproval) + router.Post("/disable-approval", clientApprovalSettingsController.DisableApproval) + router.Put("/default-workflow", clientApprovalSettingsController.SetDefaultWorkflow) + + // Exemption management routes + router.Post("/exempt-users", clientApprovalSettingsController.ManageExemptUsers) + router.Post("/exempt-roles", clientApprovalSettingsController.ManageExemptRoles) + router.Post("/exempt-categories", clientApprovalSettingsController.ManageExemptCategories) + }) +} diff --git a/app/module/client_approval_settings/controller/client_approval_settings.controller.go b/app/module/client_approval_settings/controller/client_approval_settings.controller.go new file mode 100644 index 0000000..5bccf49 --- /dev/null +++ b/app/module/client_approval_settings/controller/client_approval_settings.controller.go @@ -0,0 +1,430 @@ +package controller + +import ( + "fmt" + "strconv" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/client_approval_settings/request" + "web-qudo-be/app/module/client_approval_settings/service" + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" +) + +type clientApprovalSettingsController struct { + clientApprovalSettingsService service.ClientApprovalSettingsService + Log zerolog.Logger +} + +type ClientApprovalSettingsController interface { + CreateSettings(c *fiber.Ctx) error + GetSettings(c *fiber.Ctx) error + UpdateSettings(c *fiber.Ctx) error + DeleteSettings(c *fiber.Ctx) error + ToggleApproval(c *fiber.Ctx) error + EnableApproval(c *fiber.Ctx) error + DisableApproval(c *fiber.Ctx) error + SetDefaultWorkflow(c *fiber.Ctx) error + ManageExemptUsers(c *fiber.Ctx) error + ManageExemptRoles(c *fiber.Ctx) error + ManageExemptCategories(c *fiber.Ctx) error +} + +func NewClientApprovalSettingsController( + clientApprovalSettingsService service.ClientApprovalSettingsService, + log zerolog.Logger, +) ClientApprovalSettingsController { + return &clientApprovalSettingsController{ + clientApprovalSettingsService: clientApprovalSettingsService, + Log: log, + } +} + +// 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 +// @Summary Get Client Approval Settings +// @Description API for getting client approval settings +// @Tags ClientApprovalSettings +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /client-approval-settings [get] +func (_i *clientApprovalSettingsController) GetSettings(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + settings, err := _i.clientApprovalSettingsService.GetByClientId(clientId) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Client approval settings successfully retrieved"}, + Data: settings, + }) +} + +// UpdateSettings ClientApprovalSettings +// @Summary Update Client Approval Settings +// @Description API for updating client approval settings +// @Tags ClientApprovalSettings +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param payload body request.UpdateClientApprovalSettingsRequest 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 [put] +func (_i *clientApprovalSettingsController) UpdateSettings(c *fiber.Ctx) error { + req := new(request.UpdateClientApprovalSettingsRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + settings, err := _i.clientApprovalSettingsService.Update(clientId, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Client approval settings successfully updated"}, + Data: settings, + }) +} + +// DeleteSettings ClientApprovalSettings +// @Summary Delete Client Approval Settings +// @Description API for deleting client approval settings +// @Tags ClientApprovalSettings +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /client-approval-settings [delete] +func (_i *clientApprovalSettingsController) DeleteSettings(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err := _i.clientApprovalSettingsService.Delete(clientId) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Client approval settings successfully deleted"}, + }) +} + +// ToggleApproval ClientApprovalSettings +// @Summary Toggle Approval Requirement +// @Description API for toggling approval requirement on/off +// @Tags ClientApprovalSettings +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param payload body request.ToggleApprovalRequest 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/toggle [post] +func (_i *clientApprovalSettingsController) ToggleApproval(c *fiber.Ctx) error { + req := new(request.ToggleApprovalRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err := _i.clientApprovalSettingsService.ToggleApprovalRequirement(clientId, req.RequiresApproval) + if err != nil { + return err + } + + action := "enabled" + if !req.RequiresApproval { + action = "disabled with auto-publish" + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{fmt.Sprintf("Approval system successfully %s", action)}, + }) +} + +// EnableApproval ClientApprovalSettings +// @Summary Enable Approval System +// @Description API for enabling approval system with smooth transition +// @Tags ClientApprovalSettings +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param payload body request.EnableApprovalRequest 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/enable [post] +func (_i *clientApprovalSettingsController) EnableApproval(c *fiber.Ctx) error { + req := new(request.EnableApprovalRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err := _i.clientApprovalSettingsService.EnableApprovalWithTransition(clientId, req.DefaultWorkflowId) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Approval system successfully enabled with smooth transition"}, + }) +} + +// DisableApproval ClientApprovalSettings +// @Summary Disable Approval System +// @Description API for disabling approval system and auto-publish pending articles +// @Tags ClientApprovalSettings +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param payload body request.DisableApprovalRequest 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/disable [post] +func (_i *clientApprovalSettingsController) DisableApproval(c *fiber.Ctx) error { + req := new(request.DisableApprovalRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err := _i.clientApprovalSettingsService.DisableApprovalWithAutoPublish(clientId, req.Reason) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Approval system successfully disabled with auto-publish enabled"}, + }) +} + +// SetDefaultWorkflow ClientApprovalSettings +// @Summary Set Default Workflow +// @Description API for setting default workflow for client +// @Tags ClientApprovalSettings +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param payload body request.SetDefaultWorkflowRequest 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/default-workflow [post] +func (_i *clientApprovalSettingsController) SetDefaultWorkflow(c *fiber.Ctx) error { + req := new(request.SetDefaultWorkflowRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err := _i.clientApprovalSettingsService.SetDefaultWorkflow(clientId, req.WorkflowId) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Default workflow successfully set"}, + }) +} + +// ManageExemptUsers ClientApprovalSettings +// @Summary Manage Exempt Users +// @Description API for adding/removing users from approval exemption +// @Tags ClientApprovalSettings +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param action path string true "Action: add or remove" +// @Param user_id path int true "User ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /client-approval-settings/exempt-users/{action}/{user_id} [post] +func (_i *clientApprovalSettingsController) ManageExemptUsers(c *fiber.Ctx) error { + action := c.Params("action") + userIdStr := c.Params("user_id") + + if action != "add" && action != "remove" { + return utilRes.ErrorBadRequest(c, "Invalid action. Use 'add' or 'remove'") + } + + userId, err := strconv.Atoi(userIdStr) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid user ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + if action == "add" { + err = _i.clientApprovalSettingsService.AddExemptUser(clientId, uint(userId)) + } else { + err = _i.clientApprovalSettingsService.RemoveExemptUser(clientId, uint(userId)) + } + + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{fmt.Sprintf("User successfully %sd from approval exemption", action)}, + }) +} + +// ManageExemptRoles ClientApprovalSettings +// @Summary Manage Exempt Roles +// @Description API for adding/removing roles from approval exemption +// @Tags ClientApprovalSettings +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param action path string true "Action: add or remove" +// @Param role_id path int true "Role ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /client-approval-settings/exempt-roles/{action}/{role_id} [post] +func (_i *clientApprovalSettingsController) ManageExemptRoles(c *fiber.Ctx) error { + action := c.Params("action") + roleIdStr := c.Params("role_id") + + if action != "add" && action != "remove" { + return utilRes.ErrorBadRequest(c, "Invalid action. Use 'add' or 'remove'") + } + + roleId, err := strconv.Atoi(roleIdStr) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid role ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + if action == "add" { + err = _i.clientApprovalSettingsService.AddExemptRole(clientId, uint(roleId)) + } else { + err = _i.clientApprovalSettingsService.RemoveExemptRole(clientId, uint(roleId)) + } + + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{fmt.Sprintf("Role successfully %sd from approval exemption", action)}, + }) +} + +// ManageExemptCategories ClientApprovalSettings +// @Summary Manage Exempt Categories +// @Description API for adding/removing categories from approval exemption +// @Tags ClientApprovalSettings +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param action path string true "Action: add or remove" +// @Param category_id path int true "Category ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /client-approval-settings/exempt-categories/{action}/{category_id} [post] +func (_i *clientApprovalSettingsController) ManageExemptCategories(c *fiber.Ctx) error { + action := c.Params("action") + categoryIdStr := c.Params("category_id") + + if action != "add" && action != "remove" { + return utilRes.ErrorBadRequest(c, "Invalid action. Use 'add' or 'remove'") + } + + categoryId, err := strconv.Atoi(categoryIdStr) + if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid category ID format") + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + if action == "add" { + err = _i.clientApprovalSettingsService.AddExemptCategory(clientId, uint(categoryId)) + } else { + err = _i.clientApprovalSettingsService.RemoveExemptCategory(clientId, uint(categoryId)) + } + + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{fmt.Sprintf("Category successfully %sd from approval exemption", action)}, + }) +} diff --git a/app/module/client_approval_settings/controller/controller.go b/app/module/client_approval_settings/controller/controller.go new file mode 100644 index 0000000..53f7c4e --- /dev/null +++ b/app/module/client_approval_settings/controller/controller.go @@ -0,0 +1,17 @@ +package controller + +import ( + "web-qudo-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), + } +} diff --git a/app/module/client_approval_settings/mapper/client_approval_settings.mapper.go b/app/module/client_approval_settings/mapper/client_approval_settings.mapper.go new file mode 100644 index 0000000..f043c30 --- /dev/null +++ b/app/module/client_approval_settings/mapper/client_approval_settings.mapper.go @@ -0,0 +1,104 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/client_approval_settings/response" + + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +func ClientApprovalSettingsResponseMapper( + log zerolog.Logger, + clientId *uuid.UUID, + settings *entity.ClientApprovalSettings, +) *res.ClientApprovalSettingsResponse { + if settings == nil { + return nil + } + + return &res.ClientApprovalSettingsResponse{ + ID: settings.ID, + ClientId: settings.ClientId.String(), + RequiresApproval: *settings.RequiresApproval, + DefaultWorkflowId: settings.DefaultWorkflowId, + AutoPublishArticles: *settings.AutoPublishArticles, + ApprovalExemptUsers: settings.ApprovalExemptUsers, + ApprovalExemptRoles: settings.ApprovalExemptRoles, + ApprovalExemptCategories: settings.ApprovalExemptCategories, + RequireApprovalFor: settings.RequireApprovalFor, + SkipApprovalFor: settings.SkipApprovalFor, + IsActive: *settings.IsActive, + CreatedAt: settings.CreatedAt, + UpdatedAt: settings.UpdatedAt, + } +} + +func ClientApprovalSettingsDetailResponseMapper( + log zerolog.Logger, + clientId *uuid.UUID, + settings *entity.ClientApprovalSettings, +) *res.ClientApprovalSettingsDetailResponse { + if settings == nil { + return nil + } + + response := &res.ClientApprovalSettingsDetailResponse{ + ID: settings.ID, + ClientId: settings.ClientId.String(), + RequiresApproval: *settings.RequiresApproval, + DefaultWorkflowId: settings.DefaultWorkflowId, + AutoPublishArticles: *settings.AutoPublishArticles, + ApprovalExemptUsers: settings.ApprovalExemptUsers, + ApprovalExemptRoles: settings.ApprovalExemptRoles, + ApprovalExemptCategories: settings.ApprovalExemptCategories, + RequireApprovalFor: settings.RequireApprovalFor, + SkipApprovalFor: settings.SkipApprovalFor, + IsActive: *settings.IsActive, + CreatedAt: settings.CreatedAt, + UpdatedAt: settings.UpdatedAt, + } + + // Add client relation if available + if settings.Client.ID != uuid.Nil { + response.Client = &res.ClientResponse{ + ID: 1, // Placeholder - would need proper ID mapping + Name: settings.Client.Name, + IsActive: *settings.Client.IsActive, + } + } + + // Add workflow relation if available + if settings.Workflow != nil && settings.Workflow.ID != 0 { + response.Workflow = &res.WorkflowResponse{ + ID: settings.Workflow.ID, + Name: settings.Workflow.Name, + Description: settings.Workflow.Description, + IsActive: *settings.Workflow.IsActive, + } + } + + return response +} + +func ApprovalStatusResponseMapper( + log zerolog.Logger, + clientId *uuid.UUID, + settings *entity.ClientApprovalSettings, +) *res.ApprovalStatusResponse { + if settings == nil { + return &res.ApprovalStatusResponse{ + RequiresApproval: true, // Default to requiring approval + AutoPublishArticles: false, + IsActive: false, + Message: "No approval settings found", + } + } + + return &res.ApprovalStatusResponse{ + RequiresApproval: *settings.RequiresApproval, + AutoPublishArticles: *settings.AutoPublishArticles, + IsActive: *settings.IsActive, + Message: "Approval settings loaded successfully", + } +} diff --git a/app/module/client_approval_settings/repository/client_approval_settings.repository.go b/app/module/client_approval_settings/repository/client_approval_settings.repository.go new file mode 100644 index 0000000..1f422a1 --- /dev/null +++ b/app/module/client_approval_settings/repository/client_approval_settings.repository.go @@ -0,0 +1,33 @@ +package repository + +import ( + "web-qudo-be/app/database/entity" + + "github.com/google/uuid" +) + +type ClientApprovalSettingsRepository interface { + // Basic CRUD + Create(clientId *uuid.UUID, settings *entity.ClientApprovalSettings) (*entity.ClientApprovalSettings, error) + FindOne(clientId *uuid.UUID) (*entity.ClientApprovalSettings, error) + Update(clientId *uuid.UUID, settings *entity.ClientApprovalSettings) (*entity.ClientApprovalSettings, error) + Delete(clientId *uuid.UUID) error + + // Specific queries + FindByClientId(clientId uuid.UUID) (*entity.ClientApprovalSettings, error) + FindActiveSettings(clientId *uuid.UUID) (*entity.ClientApprovalSettings, error) + FindByWorkflowId(workflowId uint) ([]*entity.ClientApprovalSettings, error) + + // Exemption management + AddExemptUser(clientId *uuid.UUID, userId uint) error + RemoveExemptUser(clientId *uuid.UUID, userId uint) error + AddExemptRole(clientId *uuid.UUID, roleId uint) error + RemoveExemptRole(clientId *uuid.UUID, roleId uint) error + AddExemptCategory(clientId *uuid.UUID, categoryId uint) error + RemoveExemptCategory(clientId *uuid.UUID, categoryId uint) error + + // Bulk operations + BulkUpdateExemptUsers(clientId *uuid.UUID, userIds []uint) error + BulkUpdateExemptRoles(clientId *uuid.UUID, roleIds []uint) error + BulkUpdateExemptCategories(clientId *uuid.UUID, categoryIds []uint) error +} diff --git a/app/module/client_approval_settings/repository/client_approval_settings.repository.impl.go b/app/module/client_approval_settings/repository/client_approval_settings.repository.impl.go new file mode 100644 index 0000000..74db6ab --- /dev/null +++ b/app/module/client_approval_settings/repository/client_approval_settings.repository.impl.go @@ -0,0 +1,139 @@ +package repository + +import ( + "errors" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + + "github.com/google/uuid" + "github.com/rs/zerolog" + "gorm.io/gorm" +) + +type clientApprovalSettingsRepository struct { + DB *database.Database + Log zerolog.Logger +} + +func NewClientApprovalSettingsRepository(db *database.Database, log zerolog.Logger) ClientApprovalSettingsRepository { + return &clientApprovalSettingsRepository{ + DB: db, + Log: log, + } +} + +func (r *clientApprovalSettingsRepository) Create(clientId *uuid.UUID, settings *entity.ClientApprovalSettings) (*entity.ClientApprovalSettings, error) { + settings.ClientId = *clientId + if err := r.DB.DB.Create(settings).Error; err != nil { + return nil, err + } + return settings, nil +} + +func (r *clientApprovalSettingsRepository) FindOne(clientId *uuid.UUID) (*entity.ClientApprovalSettings, error) { + var settings entity.ClientApprovalSettings + err := r.DB.DB.Where("client_id = ?", clientId).First(&settings).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + return &settings, nil +} + +func (r *clientApprovalSettingsRepository) Update(clientId *uuid.UUID, settings *entity.ClientApprovalSettings) (*entity.ClientApprovalSettings, error) { + settings.ClientId = *clientId + if err := r.DB.DB.Where("client_id = ?", clientId).Save(settings).Error; err != nil { + return nil, err + } + return settings, nil +} + +func (r *clientApprovalSettingsRepository) Delete(clientId *uuid.UUID) error { + return r.DB.DB.Where("client_id = ?", clientId).Delete(&entity.ClientApprovalSettings{}).Error +} + +func (r *clientApprovalSettingsRepository) FindByClientId(clientId uuid.UUID) (*entity.ClientApprovalSettings, error) { + var settings entity.ClientApprovalSettings + err := r.DB.DB.Where("client_id = ?", clientId).First(&settings).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + return &settings, nil +} + +func (r *clientApprovalSettingsRepository) FindActiveSettings(clientId *uuid.UUID) (*entity.ClientApprovalSettings, error) { + var settings entity.ClientApprovalSettings + err := r.DB.DB.Where("client_id = ? AND is_active = ?", clientId, true).First(&settings).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + return &settings, nil +} + +func (r *clientApprovalSettingsRepository) FindByWorkflowId(workflowId uint) ([]*entity.ClientApprovalSettings, error) { + var settings []*entity.ClientApprovalSettings + err := r.DB.DB.Where("default_workflow_id = ?", workflowId).Find(&settings).Error + return settings, err +} + +func (r *clientApprovalSettingsRepository) AddExemptUser(clientId *uuid.UUID, userId uint) error { + return r.DB.DB.Model(&entity.ClientApprovalSettings{}). + Where("client_id = ?", clientId). + Update("approval_exempt_users", gorm.Expr("array_append(approval_exempt_users, ?)", userId)).Error +} + +func (r *clientApprovalSettingsRepository) RemoveExemptUser(clientId *uuid.UUID, userId uint) error { + return r.DB.DB.Model(&entity.ClientApprovalSettings{}). + Where("client_id = ?", clientId). + Update("approval_exempt_users", gorm.Expr("array_remove(approval_exempt_users, ?)", userId)).Error +} + +func (r *clientApprovalSettingsRepository) AddExemptRole(clientId *uuid.UUID, roleId uint) error { + return r.DB.DB.Model(&entity.ClientApprovalSettings{}). + Where("client_id = ?", clientId). + Update("approval_exempt_roles", gorm.Expr("array_append(approval_exempt_roles, ?)", roleId)).Error +} + +func (r *clientApprovalSettingsRepository) RemoveExemptRole(clientId *uuid.UUID, roleId uint) error { + return r.DB.DB.Model(&entity.ClientApprovalSettings{}). + Where("client_id = ?", clientId). + Update("approval_exempt_roles", gorm.Expr("array_remove(approval_exempt_roles, ?)", roleId)).Error +} + +func (r *clientApprovalSettingsRepository) AddExemptCategory(clientId *uuid.UUID, categoryId uint) error { + return r.DB.DB.Model(&entity.ClientApprovalSettings{}). + Where("client_id = ?", clientId). + Update("approval_exempt_categories", gorm.Expr("array_append(approval_exempt_categories, ?)", categoryId)).Error +} + +func (r *clientApprovalSettingsRepository) RemoveExemptCategory(clientId *uuid.UUID, categoryId uint) error { + return r.DB.DB.Model(&entity.ClientApprovalSettings{}). + Where("client_id = ?", clientId). + Update("approval_exempt_categories", gorm.Expr("array_remove(approval_exempt_categories, ?)", categoryId)).Error +} + +func (r *clientApprovalSettingsRepository) BulkUpdateExemptUsers(clientId *uuid.UUID, userIds []uint) error { + return r.DB.DB.Model(&entity.ClientApprovalSettings{}). + Where("client_id = ?", clientId). + Update("approval_exempt_users", userIds).Error +} + +func (r *clientApprovalSettingsRepository) BulkUpdateExemptRoles(clientId *uuid.UUID, roleIds []uint) error { + return r.DB.DB.Model(&entity.ClientApprovalSettings{}). + Where("client_id = ?", clientId). + Update("approval_exempt_roles", roleIds).Error +} + +func (r *clientApprovalSettingsRepository) BulkUpdateExemptCategories(clientId *uuid.UUID, categoryIds []uint) error { + return r.DB.DB.Model(&entity.ClientApprovalSettings{}). + Where("client_id = ?", clientId). + Update("approval_exempt_categories", categoryIds).Error +} diff --git a/app/module/client_approval_settings/request/client_approval_settings.request.go b/app/module/client_approval_settings/request/client_approval_settings.request.go new file mode 100644 index 0000000..3a9612f --- /dev/null +++ b/app/module/client_approval_settings/request/client_approval_settings.request.go @@ -0,0 +1,85 @@ +package request + +// CreateClientApprovalSettingsRequest represents request for creating client approval settings +type CreateClientApprovalSettingsRequest struct { + RequiresApproval bool `json:"requiresApproval"` + DefaultWorkflowId *uint `json:"defaultWorkflowId" validate:"omitempty,min=1"` + AutoPublishArticles bool `json:"autoPublishArticles"` + ApprovalExemptUsers []uint `json:"approvalExemptUsers" validate:"omitempty,dive,min=1"` + ApprovalExemptRoles []uint `json:"approvalExemptRoles" validate:"omitempty,dive,min=1"` + ApprovalExemptCategories []uint `json:"approvalExemptCategories" validate:"omitempty,dive,min=1"` + RequireApprovalFor []string `json:"requireApprovalFor" validate:"omitempty,dive,min=1"` + SkipApprovalFor []string `json:"skipApprovalFor" validate:"omitempty,dive,min=1"` + IsActive bool `json:"isActive"` +} + +// UpdateClientApprovalSettingsRequest represents request for updating client approval settings +type UpdateClientApprovalSettingsRequest struct { + RequiresApproval *bool `json:"requiresApproval"` + DefaultWorkflowId **uint `json:"defaultWorkflowId"` // double pointer to allow nil + AutoPublishArticles *bool `json:"autoPublishArticles"` + ApprovalExemptUsers []uint `json:"approvalExemptUsers" validate:"omitempty,dive,min=1"` + ApprovalExemptRoles []uint `json:"approvalExemptRoles" validate:"omitempty,dive,min=1"` + ApprovalExemptCategories []uint `json:"approvalExemptCategories" validate:"omitempty,dive,min=1"` + RequireApprovalFor []string `json:"requireApprovalFor" validate:"omitempty,dive,min=1"` + SkipApprovalFor []string `json:"skipApprovalFor" validate:"omitempty,dive,min=1"` + IsActive *bool `json:"isActive"` +} + +// ToggleApprovalRequest represents request for toggling approval requirement +type ToggleApprovalRequest struct { + RequiresApproval bool `json:"requiresApproval"` +} + +// EnableApprovalRequest represents request for enabling approval with smooth transition +type EnableApprovalRequest struct { + DefaultWorkflowId *uint `json:"defaultWorkflowId" validate:"omitempty,min=1"` + Reason string `json:"reason" validate:"omitempty,max=500"` +} + +// DisableApprovalRequest represents request for disabling approval system +type DisableApprovalRequest struct { + Reason string `json:"reason" validate:"required,max=500"` + HandleAction string `json:"handleAction" validate:"required,oneof=auto_approve keep_pending reset_to_draft"` // How to handle pending articles +} + +// SetDefaultWorkflowRequest represents request for setting default workflow +type SetDefaultWorkflowRequest struct { + WorkflowId *uint `json:"workflowId" validate:"omitempty,min=1"` +} + +// AddExemptUserRequest represents request for adding user to exemption +type AddExemptUserRequest struct { + UserId uint `json:"userId" validate:"required,min=1"` + Reason string `json:"reason" validate:"omitempty,max=500"` +} + +// RemoveExemptUserRequest represents request for removing user from exemption +type RemoveExemptUserRequest struct { + UserId uint `json:"userId" validate:"required,min=1"` + Reason string `json:"reason" validate:"omitempty,max=500"` +} + +// AddExemptRoleRequest represents request for adding role to exemption +type AddExemptRoleRequest struct { + RoleId uint `json:"roleId" validate:"required,min=1"` + Reason string `json:"reason" validate:"omitempty,max=500"` +} + +// RemoveExemptRoleRequest represents request for removing role from exemption +type RemoveExemptRoleRequest struct { + RoleId uint `json:"roleId" validate:"required,min=1"` + Reason string `json:"reason" validate:"omitempty,max=500"` +} + +// AddExemptCategoryRequest represents request for adding category to exemption +type AddExemptCategoryRequest struct { + CategoryId uint `json:"categoryId" validate:"required,min=1"` + Reason string `json:"reason" validate:"omitempty,max=500"` +} + +// RemoveExemptCategoryRequest represents request for removing category from exemption +type RemoveExemptCategoryRequest struct { + CategoryId uint `json:"categoryId" validate:"required,min=1"` + Reason string `json:"reason" validate:"omitempty,max=500"` +} diff --git a/app/module/client_approval_settings/response/client_approval_settings.response.go b/app/module/client_approval_settings/response/client_approval_settings.response.go new file mode 100644 index 0000000..ecbafd8 --- /dev/null +++ b/app/module/client_approval_settings/response/client_approval_settings.response.go @@ -0,0 +1,82 @@ +package response + +import ( + "time" +) + +type ClientApprovalSettingsResponse struct { + ID uint `json:"id"` + ClientId string `json:"clientId"` + RequiresApproval bool `json:"requiresApproval"` + DefaultWorkflowId *uint `json:"defaultWorkflowId,omitempty"` + AutoPublishArticles bool `json:"autoPublishArticles"` + ApprovalExemptUsers []uint `json:"approvalExemptUsers"` + ApprovalExemptRoles []uint `json:"approvalExemptRoles"` + ApprovalExemptCategories []uint `json:"approvalExemptCategories"` + RequireApprovalFor []string `json:"requireApprovalFor"` + SkipApprovalFor []string `json:"skipApprovalFor"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type ClientApprovalSettingsDetailResponse struct { + ID uint `json:"id"` + ClientId string `json:"clientId"` + RequiresApproval bool `json:"requiresApproval"` + DefaultWorkflowId *uint `json:"defaultWorkflowId,omitempty"` + AutoPublishArticles bool `json:"autoPublishArticles"` + ApprovalExemptUsers []uint `json:"approvalExemptUsers"` + ApprovalExemptRoles []uint `json:"approvalExemptRoles"` + ApprovalExemptCategories []uint `json:"approvalExemptCategories"` + RequireApprovalFor []string `json:"requireApprovalFor"` + SkipApprovalFor []string `json:"skipApprovalFor"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + // Relations + Client *ClientResponse `json:"client,omitempty"` + Workflow *WorkflowResponse `json:"workflow,omitempty"` +} + +type ClientResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + IsActive bool `json:"isActive"` +} + +type WorkflowResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + Description *string `json:"description,omitempty"` + IsActive bool `json:"isActive"` +} + +type ApprovalStatusResponse struct { + RequiresApproval bool `json:"requiresApproval"` + AutoPublishArticles bool `json:"autoPublishArticles"` + IsActive bool `json:"isActive"` + Message string `json:"message,omitempty"` +} + +type ExemptUserResponse struct { + UserId uint `json:"userId"` + Reason string `json:"reason,omitempty"` +} + +type ExemptRoleResponse struct { + RoleId uint `json:"roleId"` + Reason string `json:"reason,omitempty"` +} + +type ExemptCategoryResponse struct { + CategoryId uint `json:"categoryId"` + Reason string `json:"reason,omitempty"` +} + +type ApprovalTransitionResponse struct { + Success bool `json:"success"` + Message string `json:"message"` + AffectedArticles int `json:"affectedArticles,omitempty"` +} diff --git a/app/module/client_approval_settings/service/client_approval_settings.service.go b/app/module/client_approval_settings/service/client_approval_settings.service.go new file mode 100644 index 0000000..58eb547 --- /dev/null +++ b/app/module/client_approval_settings/service/client_approval_settings.service.go @@ -0,0 +1,326 @@ +package service + +import ( + "fmt" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/client_approval_settings/mapper" + "web-qudo-be/app/module/client_approval_settings/repository" + "web-qudo-be/app/module/client_approval_settings/request" + "web-qudo-be/app/module/client_approval_settings/response" + + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +type clientApprovalSettingsService struct { + clientApprovalSettingsRepo repository.ClientApprovalSettingsRepository + Log zerolog.Logger +} + +type ClientApprovalSettingsService interface { + GetByClientId(clientId *uuid.UUID) (*response.ClientApprovalSettingsResponse, error) + Create(clientId *uuid.UUID, req request.CreateClientApprovalSettingsRequest) (*response.ClientApprovalSettingsResponse, error) + Update(clientId *uuid.UUID, req request.UpdateClientApprovalSettingsRequest) (*response.ClientApprovalSettingsResponse, error) + Delete(clientId *uuid.UUID) error + ToggleApprovalRequirement(clientId *uuid.UUID, requiresApproval bool) error + SetDefaultWorkflow(clientId *uuid.UUID, workflowId *uint) error + AddExemptUser(clientId *uuid.UUID, userId uint) error + RemoveExemptUser(clientId *uuid.UUID, userId uint) error + AddExemptRole(clientId *uuid.UUID, roleId uint) error + RemoveExemptRole(clientId *uuid.UUID, roleId uint) error + AddExemptCategory(clientId *uuid.UUID, categoryId uint) error + RemoveExemptCategory(clientId *uuid.UUID, categoryId uint) error + CheckIfApprovalRequired(clientId *uuid.UUID, userId uint, userLevelId uint, categoryId uint, contentType string) (bool, error) + + // Enhanced methods for dynamic approval management + EnableApprovalWithTransition(clientId *uuid.UUID, defaultWorkflowId *uint) error + DisableApprovalWithAutoPublish(clientId *uuid.UUID, reason string) error + HandlePendingApprovalsOnDisable(clientId *uuid.UUID, action string) error // "auto_approve", "keep_pending", "reset_to_draft" +} + +func NewClientApprovalSettingsService( + clientApprovalSettingsRepo repository.ClientApprovalSettingsRepository, + log zerolog.Logger, +) ClientApprovalSettingsService { + return &clientApprovalSettingsService{ + clientApprovalSettingsRepo: clientApprovalSettingsRepo, + Log: log, + } +} + +func (_i *clientApprovalSettingsService) GetByClientId(clientId *uuid.UUID) (*response.ClientApprovalSettingsResponse, error) { + settings, err := _i.clientApprovalSettingsRepo.FindByClientId(*clientId) + if err != nil { + return nil, err + } + + if settings == nil { + // Return default settings if none found + return &response.ClientApprovalSettingsResponse{ + ClientId: clientId.String(), + RequiresApproval: true, // Default to requiring approval + AutoPublishArticles: false, + ApprovalExemptUsers: []uint{}, + ApprovalExemptRoles: []uint{}, + ApprovalExemptCategories: []uint{}, + RequireApprovalFor: []string{}, + SkipApprovalFor: []string{}, + IsActive: true, + }, nil + } + + return mapper.ClientApprovalSettingsResponseMapper(_i.Log, clientId, settings), nil +} + +func (_i *clientApprovalSettingsService) Create(clientId *uuid.UUID, req request.CreateClientApprovalSettingsRequest) (*response.ClientApprovalSettingsResponse, error) { + // Check if settings already exist + existing, err := _i.clientApprovalSettingsRepo.FindByClientId(*clientId) + if err != nil { + return nil, err + } + + if existing != nil { + return nil, fmt.Errorf("approval settings already exist for this client") + } + + // Create new settings + settings := &entity.ClientApprovalSettings{ + RequiresApproval: &req.RequiresApproval, + DefaultWorkflowId: req.DefaultWorkflowId, + AutoPublishArticles: &req.AutoPublishArticles, + ApprovalExemptUsers: req.ApprovalExemptUsers, + ApprovalExemptRoles: req.ApprovalExemptRoles, + ApprovalExemptCategories: req.ApprovalExemptCategories, + RequireApprovalFor: req.RequireApprovalFor, + SkipApprovalFor: req.SkipApprovalFor, + IsActive: &req.IsActive, + } + + createdSettings, err := _i.clientApprovalSettingsRepo.Create(clientId, settings) + if err != nil { + return nil, err + } + + return mapper.ClientApprovalSettingsResponseMapper(_i.Log, clientId, createdSettings), nil +} + +func (_i *clientApprovalSettingsService) Update(clientId *uuid.UUID, req request.UpdateClientApprovalSettingsRequest) (*response.ClientApprovalSettingsResponse, error) { + // Get existing settings + settings, err := _i.clientApprovalSettingsRepo.FindByClientId(*clientId) + if err != nil { + return nil, err + } + + if settings == nil { + return nil, fmt.Errorf("approval settings not found for this client") + } + + // Update fields if provided + if req.RequiresApproval != nil { + settings.RequiresApproval = req.RequiresApproval + } + if req.DefaultWorkflowId != nil { + settings.DefaultWorkflowId = *req.DefaultWorkflowId + } + if req.AutoPublishArticles != nil { + settings.AutoPublishArticles = req.AutoPublishArticles + } + if req.ApprovalExemptUsers != nil { + settings.ApprovalExemptUsers = req.ApprovalExemptUsers + } + if req.ApprovalExemptRoles != nil { + settings.ApprovalExemptRoles = req.ApprovalExemptRoles + } + if req.ApprovalExemptCategories != nil { + settings.ApprovalExemptCategories = req.ApprovalExemptCategories + } + if req.RequireApprovalFor != nil { + settings.RequireApprovalFor = req.RequireApprovalFor + } + if req.SkipApprovalFor != nil { + settings.SkipApprovalFor = req.SkipApprovalFor + } + if req.IsActive != nil { + settings.IsActive = req.IsActive + } + + updatedSettings, err := _i.clientApprovalSettingsRepo.Update(clientId, settings) + if err != nil { + return nil, err + } + + return mapper.ClientApprovalSettingsResponseMapper(_i.Log, clientId, updatedSettings), nil +} + +func (_i *clientApprovalSettingsService) Delete(clientId *uuid.UUID) error { + return _i.clientApprovalSettingsRepo.Delete(clientId) +} + +func (_i *clientApprovalSettingsService) ToggleApprovalRequirement(clientId *uuid.UUID, requiresApproval bool) error { + settings, err := _i.clientApprovalSettingsRepo.FindByClientId(*clientId) + if err != nil { + return err + } + + if settings == nil { + return fmt.Errorf("approval settings not found for this client") + } + + settings.RequiresApproval = &requiresApproval + _, err = _i.clientApprovalSettingsRepo.Update(clientId, settings) + return err +} + +func (_i *clientApprovalSettingsService) SetDefaultWorkflow(clientId *uuid.UUID, workflowId *uint) error { + settings, err := _i.clientApprovalSettingsRepo.FindByClientId(*clientId) + if err != nil { + return err + } + + if settings == nil { + return fmt.Errorf("approval settings not found for this client") + } + + settings.DefaultWorkflowId = workflowId + _, err = _i.clientApprovalSettingsRepo.Update(clientId, settings) + return err +} + +func (_i *clientApprovalSettingsService) AddExemptUser(clientId *uuid.UUID, userId uint) error { + return _i.clientApprovalSettingsRepo.AddExemptUser(clientId, userId) +} + +func (_i *clientApprovalSettingsService) RemoveExemptUser(clientId *uuid.UUID, userId uint) error { + return _i.clientApprovalSettingsRepo.RemoveExemptUser(clientId, userId) +} + +func (_i *clientApprovalSettingsService) AddExemptRole(clientId *uuid.UUID, roleId uint) error { + return _i.clientApprovalSettingsRepo.AddExemptRole(clientId, roleId) +} + +func (_i *clientApprovalSettingsService) RemoveExemptRole(clientId *uuid.UUID, roleId uint) error { + return _i.clientApprovalSettingsRepo.RemoveExemptRole(clientId, roleId) +} + +func (_i *clientApprovalSettingsService) AddExemptCategory(clientId *uuid.UUID, categoryId uint) error { + return _i.clientApprovalSettingsRepo.AddExemptCategory(clientId, categoryId) +} + +func (_i *clientApprovalSettingsService) RemoveExemptCategory(clientId *uuid.UUID, categoryId uint) error { + return _i.clientApprovalSettingsRepo.RemoveExemptCategory(clientId, categoryId) +} + +func (_i *clientApprovalSettingsService) CheckIfApprovalRequired(clientId *uuid.UUID, userId uint, userLevelId uint, categoryId uint, contentType string) (bool, error) { + settings, err := _i.clientApprovalSettingsRepo.FindActiveSettings(clientId) + if err != nil { + return true, err // Default to requiring approval on error + } + + if settings == nil { + return true, nil // Default to requiring approval if no settings + } + + // Check if approval is disabled + if settings.RequiresApproval != nil && !*settings.RequiresApproval { + return false, nil + } + + // Check user exemption + for _, exemptUserId := range settings.ApprovalExemptUsers { + if exemptUserId == userId { + return false, nil + } + } + + // Check role exemption + for _, exemptRoleId := range settings.ApprovalExemptRoles { + if exemptRoleId == userLevelId { + return false, nil + } + } + + // Check category exemption + for _, exemptCategoryId := range settings.ApprovalExemptCategories { + if exemptCategoryId == categoryId { + return false, nil + } + } + + // Check content type exemptions + for _, skipType := range settings.SkipApprovalFor { + if skipType == contentType { + return false, nil + } + } + + // Check if content type requires approval + for _, requireType := range settings.RequireApprovalFor { + if requireType == contentType { + return true, nil + } + } + + // Default to requiring approval + return true, nil +} + +func (_i *clientApprovalSettingsService) EnableApprovalWithTransition(clientId *uuid.UUID, defaultWorkflowId *uint) error { + settings, err := _i.clientApprovalSettingsRepo.FindByClientId(*clientId) + if err != nil { + return err + } + + if settings == nil { + // Create new settings + settings = &entity.ClientApprovalSettings{ + RequiresApproval: &[]bool{true}[0], + DefaultWorkflowId: defaultWorkflowId, + AutoPublishArticles: &[]bool{false}[0], + IsActive: &[]bool{true}[0], + } + _, err = _i.clientApprovalSettingsRepo.Create(clientId, settings) + } else { + // Update existing settings + settings.RequiresApproval = &[]bool{true}[0] + settings.DefaultWorkflowId = defaultWorkflowId + settings.AutoPublishArticles = &[]bool{false}[0] + settings.IsActive = &[]bool{true}[0] + _, err = _i.clientApprovalSettingsRepo.Update(clientId, settings) + } + + return err +} + +func (_i *clientApprovalSettingsService) DisableApprovalWithAutoPublish(clientId *uuid.UUID, reason string) error { + settings, err := _i.clientApprovalSettingsRepo.FindByClientId(*clientId) + if err != nil { + return err + } + + if settings == nil { + return fmt.Errorf("approval settings not found for this client") + } + + settings.RequiresApproval = &[]bool{false}[0] + settings.AutoPublishArticles = &[]bool{true}[0] + settings.IsActive = &[]bool{true}[0] + + _, err = _i.clientApprovalSettingsRepo.Update(clientId, settings) + return err +} + +func (_i *clientApprovalSettingsService) HandlePendingApprovalsOnDisable(clientId *uuid.UUID, action string) error { + // This would typically interact with article approval flows + // For now, just log the action + _i.Log.Info(). + Str("client_id", clientId.String()). + Str("action", action). + Msg("Handling pending approvals on disable") + + // TODO: Implement actual logic based on action: + // - "auto_approve": Auto approve all pending articles + // - "keep_pending": Keep articles in pending state + // - "reset_to_draft": Reset articles to draft state + + return nil +} diff --git a/app/module/clients/clients.module.go b/app/module/clients/clients.module.go new file mode 100644 index 0000000..020862d --- /dev/null +++ b/app/module/clients/clients.module.go @@ -0,0 +1,53 @@ +package clients + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/clients/controller" + "web-qudo-be/app/module/clients/repository" + "web-qudo-be/app/module/clients/service" +) + +// struct of ClientsRouter +type ClientsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of Clients module +var NewClientsModule = fx.Options( + // register repository of Clients module + fx.Provide(repository.NewClientsRepository), + + // register service of Clients module + fx.Provide(service.NewClientsService), + + // register controller of Clients module + fx.Provide(controller.NewController), + + // register router of Clients module + fx.Provide(NewClientsRouter), +) + +// init ClientsRouter +func NewClientsRouter(fiber *fiber.App, controller *controller.Controller) *ClientsRouter { + return &ClientsRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of Clients module +func (_i *ClientsRouter) RegisterClientsRoutes() { + // define controllers + clientsController := _i.Controller.Clients + + // define routes + _i.App.Route("/clients", func(router fiber.Router) { + router.Get("/", clientsController.All) + router.Get("/:id", clientsController.Show) + router.Post("/", clientsController.Save) + router.Put("/:id", clientsController.Update) + router.Delete("/:id", clientsController.Delete) + }) +} diff --git a/app/module/clients/controller/clients.controller.go b/app/module/clients/controller/clients.controller.go new file mode 100644 index 0000000..bcd283a --- /dev/null +++ b/app/module/clients/controller/clients.controller.go @@ -0,0 +1,196 @@ +package controller + +import ( + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" + "github.com/rs/zerolog" + "web-qudo-be/app/module/clients/request" + "web-qudo-be/app/module/clients/service" + "web-qudo-be/utils/paginator" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type clientsController struct { + clientsService service.ClientsService + Log zerolog.Logger +} + +type ClientsController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewClientsController(clientsService service.ClientsService, log zerolog.Logger) ClientsController { + return &clientsController{ + clientsService: clientsService, + Log: log, + } +} + +// All get all Clients +// @Summary Get all Clients +// @Description API for getting all Clients +// @Tags Clients +// @Security Bearer +// @Param req query request.ClientsQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /clients [get] +func (_i *clientsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.ClientsQueryRequestContext{ + Name: c.Query("name"), + CreatedById: c.Query("createdById"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + clientsData, paging, err := _i.clientsService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Clients list successfully retrieved"}, + Data: clientsData, + Meta: paging, + }) +} + +// Show get one Clients +// @Summary Get one Clients +// @Description API for getting one Clients +// @Tags Clients +// @Security Bearer +// @Param id path int true "Clients ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /clients/{id} [get] +func (_i *clientsController) Show(c *fiber.Ctx) error { + idStr := c.Params("id") + id, err := uuid.Parse(idStr) + if err != nil { + return err + } + + clientsData, err := _i.clientsService.Show(id) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Clients successfully retrieved"}, + Data: clientsData, + }) +} + +// Save create Clients +// @Summary Create Clients +// @Description API for create Clients +// @Tags Clients +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.ClientsCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /clients [post] +func (_i *clientsController) Save(c *fiber.Ctx) error { + req := new(request.ClientsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + dataResult, err := _i.clientsService.Save(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Clients successfully created"}, + Data: dataResult, + }) +} + +// Update update Clients +// @Summary update Clients +// @Description API for update Clients +// @Tags Clients +// @Security Bearer +// @Param payload body request.ClientsUpdateRequest true "Required payload" +// @Param id path string true "Clients ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /clients/{id} [put] +func (_i *clientsController) Update(c *fiber.Ctx) error { + idStr := c.Params("id") + id, err := uuid.Parse(idStr) + if err != nil { + return err + } + + req := new(request.ClientsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.clientsService.Update(id, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Clients successfully updated"}, + }) +} + +// Delete delete Clients +// @Summary delete Clients +// @Description API for delete Clients +// @Tags Clients +// @Security Bearer +// @Param id path string true "Clients ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /clients/{id} [delete] +func (_i *clientsController) Delete(c *fiber.Ctx) error { + idStr := c.Params("id") + id, err := uuid.Parse(idStr) + if err != nil { + return err + } + + err = _i.clientsService.Delete(id) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Clients successfully deleted"}, + }) +} diff --git a/app/module/clients/controller/controller.go b/app/module/clients/controller/controller.go new file mode 100644 index 0000000..cb5855e --- /dev/null +++ b/app/module/clients/controller/controller.go @@ -0,0 +1,16 @@ +package controller + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/clients/service" +) + +type Controller struct { + Clients ClientsController +} + +func NewController(ClientsService service.ClientsService, log zerolog.Logger) *Controller { + return &Controller{ + Clients: NewClientsController(ClientsService, log), + } +} diff --git a/app/module/clients/mapper/clients.mapper.go b/app/module/clients/mapper/clients.mapper.go new file mode 100644 index 0000000..0281f0b --- /dev/null +++ b/app/module/clients/mapper/clients.mapper.go @@ -0,0 +1,20 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/clients/response" +) + +func ClientsResponseMapper(clientsReq *entity.Clients) (clientsRes *res.ClientsResponse) { + if clientsReq != nil { + clientsRes = &res.ClientsResponse{ + ClientID: clientsReq.ID, + Name: clientsReq.Name, + CreatedById: *clientsReq.CreatedById, + IsActive: *clientsReq.IsActive, + CreatedAt: clientsReq.CreatedAt, + UpdatedAt: clientsReq.UpdatedAt, + } + } + return clientsRes +} diff --git a/app/module/clients/repository/clients.repository.go b/app/module/clients/repository/clients.repository.go new file mode 100644 index 0000000..e4b8f03 --- /dev/null +++ b/app/module/clients/repository/clients.repository.go @@ -0,0 +1,93 @@ +package repository + +import ( + "fmt" + "github.com/google/uuid" + "github.com/rs/zerolog" + "strings" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/clients/request" + "web-qudo-be/utils/paginator" +) + +type clientsRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ClientsRepository define interface of IClientsRepository +type ClientsRepository interface { + GetAll(req request.ClientsQueryRequest) (clientss []*entity.Clients, paging paginator.Pagination, err error) + FindOne(id uuid.UUID) (clients *entity.Clients, err error) + Create(clients *entity.Clients) (clientsReturn *entity.Clients, err error) + Update(id uuid.UUID, clients *entity.Clients) (err error) + Delete(id uuid.UUID) (err error) +} + +func NewClientsRepository(db *database.Database, logger zerolog.Logger) ClientsRepository { + return &clientsRepository{ + DB: db, + Log: logger, + } +} + +// implement interface of IClientsRepository +func (_i *clientsRepository) GetAll(req request.ClientsQueryRequest) (clientss []*entity.Clients, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Clients{}) + query = query.Where("is_active = ?", true) + + if req.Name != nil && *req.Name != "" { + name := strings.ToLower(*req.Name) + query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(name)+"%") + } + if req.CreatedById != nil { + query = query.Where("created_by_id = ?", req.CreatedById) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&clientss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *clientsRepository) FindOne(id uuid.UUID) (clients *entity.Clients, err error) { + if err := _i.DB.DB.First(&clients, id).Error; err != nil { + return nil, err + } + + return clients, nil +} + +func (_i *clientsRepository) Create(clients *entity.Clients) (clientsReturn *entity.Clients, err error) { + result := _i.DB.DB.Create(clients) + return clients, result.Error +} + +func (_i *clientsRepository) Update(id uuid.UUID, clients *entity.Clients) (err error) { + return _i.DB.DB.Model(&entity.Clients{}). + Where(&entity.Clients{ID: id}). + Updates(clients).Error +} + +func (_i *clientsRepository) Delete(id uuid.UUID) error { + return _i.DB.DB.Delete(&entity.Clients{}, id).Error +} diff --git a/app/module/clients/request/clients.request.go b/app/module/clients/request/clients.request.go new file mode 100644 index 0000000..00531cf --- /dev/null +++ b/app/module/clients/request/clients.request.go @@ -0,0 +1,66 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type ClientsGeneric interface { + ToEntity() +} + +type ClientsQueryRequest struct { + Name *string `json:"name"` + CreatedById *uint `json:"createdBy"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ClientsCreateRequest struct { + Name string `json:"name" validate:"required"` + CreatedById *uint `json:"createdById"` +} + +func (req ClientsCreateRequest) ToEntity() *entity.Clients { + return &entity.Clients{ + Name: req.Name, + CreatedById: req.CreatedById, + CreatedAt: time.Now(), + } +} + +type ClientsUpdateRequest struct { + Name string `json:"name" validate:"required"` + CreatedById *uint `json:"createdById"` +} + +func (req ClientsUpdateRequest) ToEntity() *entity.Clients { + return &entity.Clients{ + Name: req.Name, + CreatedById: req.CreatedById, + UpdatedAt: time.Now(), + } +} + +type ClientsQueryRequestContext struct { + Name string `json:"name"` + CreatedById string `json:"createdById"` +} + +func (req ClientsQueryRequestContext) ToParamRequest() ClientsQueryRequest { + var request ClientsQueryRequest + + if name := req.Name; name != "" { + request.Name = &name + } + if createdByStr := req.CreatedById; createdByStr != "" { + createdBy, err := strconv.Atoi(createdByStr) + if err == nil { + createdByIdUint := uint(createdBy) + request.CreatedById = &createdByIdUint + } + } + + return request +} diff --git a/app/module/clients/response/clients.response.go b/app/module/clients/response/clients.response.go new file mode 100644 index 0000000..e832b38 --- /dev/null +++ b/app/module/clients/response/clients.response.go @@ -0,0 +1,15 @@ +package response + +import ( + "github.com/google/uuid" + "time" +) + +type ClientsResponse struct { + ClientID uuid.UUID `json:"clientId"` + Name string `json:"name"` + CreatedById uint `json:"createdById"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/clients/service/clients.service.go b/app/module/clients/service/clients.service.go new file mode 100644 index 0000000..742b432 --- /dev/null +++ b/app/module/clients/service/clients.service.go @@ -0,0 +1,95 @@ +package service + +import ( + "github.com/google/uuid" + "github.com/rs/zerolog" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/clients/mapper" + "web-qudo-be/app/module/clients/repository" + "web-qudo-be/app/module/clients/request" + "web-qudo-be/app/module/clients/response" + usersRepository "web-qudo-be/app/module/users/repository" + "web-qudo-be/utils/paginator" + + utilSvc "web-qudo-be/utils/service" +) + +// ClientsService +type clientsService struct { + Repo repository.ClientsRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +// ClientsService define interface of IClientsService +type ClientsService interface { + All(req request.ClientsQueryRequest) (clients []*response.ClientsResponse, paging paginator.Pagination, err error) + Show(id uuid.UUID) (clients *response.ClientsResponse, err error) + Save(req request.ClientsCreateRequest, authToken string) (clients *entity.Clients, err error) + Update(id uuid.UUID, req request.ClientsUpdateRequest) (err error) + Delete(id uuid.UUID) error +} + +// NewClientsService init ClientsService +func NewClientsService(repo repository.ClientsRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository) ClientsService { + + return &clientsService{ + Repo: repo, + Log: log, + UsersRepo: usersRepo, + } +} + +// All implement interface of ClientsService +func (_i *clientsService) All(req request.ClientsQueryRequest) (clientss []*response.ClientsResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + clientss = append(clientss, mapper.ClientsResponseMapper(result)) + } + + return +} + +func (_i *clientsService) Show(id uuid.UUID) (clients *response.ClientsResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.ClientsResponseMapper(result), nil +} + +func (_i *clientsService) Save(req request.ClientsCreateRequest, authToken string) (clients *entity.Clients, err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + _i.Log.Info().Interface("token", authToken).Msg("") + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + _i.Log.Info().Interface("token", authToken).Msg("") + newReq.CreatedById = &createdBy.ID + + newReq.ID = uuid.New() + + _i.Log.Info().Interface("new data", newReq).Msg("") + + return _i.Repo.Create(newReq) +} + +func (_i *clientsService) Update(id uuid.UUID, req request.ClientsUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *clientsService) Delete(id uuid.UUID) error { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + isActive := false + result.IsActive = &isActive + return _i.Repo.Update(id, result) +} diff --git a/app/module/custom_static_pages/controller/controller.go b/app/module/custom_static_pages/controller/controller.go new file mode 100644 index 0000000..45abb5c --- /dev/null +++ b/app/module/custom_static_pages/controller/controller.go @@ -0,0 +1,16 @@ +package controller + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/custom_static_pages/service" +) + +type Controller struct { + CustomStaticPages CustomStaticPagesController +} + +func NewController(CustomStaticPagesService service.CustomStaticPagesService, log zerolog.Logger) *Controller { + return &Controller{ + CustomStaticPages: NewCustomStaticPagesController(CustomStaticPagesService, log), + } +} diff --git a/app/module/custom_static_pages/controller/custom_static_pages.controller.go b/app/module/custom_static_pages/controller/custom_static_pages.controller.go new file mode 100644 index 0000000..f05f620 --- /dev/null +++ b/app/module/custom_static_pages/controller/custom_static_pages.controller.go @@ -0,0 +1,250 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/custom_static_pages/request" + "web-qudo-be/app/module/custom_static_pages/service" + "web-qudo-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type customStaticPagesController struct { + customStaticPagesService service.CustomStaticPagesService + Log zerolog.Logger +} + +type CustomStaticPagesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + ShowBySlug(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewCustomStaticPagesController(customStaticPagesService service.CustomStaticPagesService, log zerolog.Logger) CustomStaticPagesController { + return &customStaticPagesController{ + customStaticPagesService: customStaticPagesService, + Log: log, + } +} + +// All get all CustomStaticPages +// @Summary Get all CustomStaticPages +// @Description API for getting all CustomStaticPages +// @Tags CustomStaticPages +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param req query request.CustomStaticPagesQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /custom-static-pages [get] +func (_i *customStaticPagesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.CustomStaticPagesQueryRequestContext{ + Title: c.Query("title"), + Description: c.Query("description"), + Slug: c.Query("slug"), + HtmlBody: c.Query("htmlBody"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + customStaticPagesData, paging, err := _i.customStaticPagesService.All(clientId, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CustomStaticPages list successfully retrieved"}, + Data: customStaticPagesData, + Meta: paging, + }) +} + +// Show get one CustomStaticPages +// @Summary Get one CustomStaticPages +// @Description API for getting one CustomStaticPages +// @Tags CustomStaticPages +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param id path int true "CustomStaticPages ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /custom-static-pages/{id} [get] +func (_i *customStaticPagesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + customStaticPagesData, err := _i.customStaticPagesService.Show(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CustomStaticPages successfully retrieved"}, + Data: customStaticPagesData, + }) +} + +// ShowBySlug get one CustomStaticPages +// @Summary Get one CustomStaticPages +// @Description API for getting one CustomStaticPages +// @Tags CustomStaticPages +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param slug path string true "CustomStaticPages Slug" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /custom-static-pages/slug/{slug} [get] +func (_i *customStaticPagesController) ShowBySlug(c *fiber.Ctx) error { + slug := c.Params("slug") + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + customStaticPagesData, err := _i.customStaticPagesService.ShowBySlug(clientId, slug) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CustomStaticPages successfully retrieved"}, + Data: customStaticPagesData, + }) +} + +// Save create CustomStaticPages +// @Summary Create CustomStaticPages +// @Description API for create CustomStaticPages +// @Tags CustomStaticPages +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.CustomStaticPagesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /custom-static-pages [post] +func (_i *customStaticPagesController) Save(c *fiber.Ctx) error { + req := new(request.CustomStaticPagesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + authToken := c.Get("Authorization") + dataResult, err := _i.customStaticPagesService.Save(clientId, *req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CustomStaticPages successfully created"}, + Data: dataResult, + }) +} + +// Update update CustomStaticPages +// @Summary update CustomStaticPages +// @Description API for update CustomStaticPages +// @Tags CustomStaticPages +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.CustomStaticPagesUpdateRequest true "Required payload" +// @Param id path int true "CustomStaticPages ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /custom-static-pages/{id} [put] +func (_i *customStaticPagesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.CustomStaticPagesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.customStaticPagesService.Update(clientId, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CustomStaticPages successfully updated"}, + }) +} + +// Delete delete CustomStaticPages +// @Summary delete CustomStaticPages +// @Description API for delete CustomStaticPages +// @Tags CustomStaticPages +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "CustomStaticPages ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /custom-static-pages/{id} [delete] +func (_i *customStaticPagesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.customStaticPagesService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CustomStaticPages successfully deleted"}, + }) +} diff --git a/app/module/custom_static_pages/custom_static_pages.module.go b/app/module/custom_static_pages/custom_static_pages.module.go new file mode 100644 index 0000000..85f7198 --- /dev/null +++ b/app/module/custom_static_pages/custom_static_pages.module.go @@ -0,0 +1,54 @@ +package custom_static_pages + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/custom_static_pages/controller" + "web-qudo-be/app/module/custom_static_pages/repository" + "web-qudo-be/app/module/custom_static_pages/service" +) + +// struct of CustomStaticPagesRouter +type CustomStaticPagesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of CustomStaticPages module +var NewCustomStaticPagesModule = fx.Options( + // register repository of CustomStaticPages module + fx.Provide(repository.NewCustomStaticPagesRepository), + + // register service of CustomStaticPages module + fx.Provide(service.NewCustomStaticPagesService), + + // register controller of CustomStaticPages module + fx.Provide(controller.NewController), + + // register router of CustomStaticPages module + fx.Provide(NewCustomStaticPagesRouter), +) + +// init CustomStaticPagesRouter +func NewCustomStaticPagesRouter(fiber *fiber.App, controller *controller.Controller) *CustomStaticPagesRouter { + return &CustomStaticPagesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of CustomStaticPages module +func (_i *CustomStaticPagesRouter) RegisterCustomStaticPagesRoutes() { + // define controllers + customStaticPagesController := _i.Controller.CustomStaticPages + + // define routes + _i.App.Route("/custom-static-pages", func(router fiber.Router) { + router.Get("/", customStaticPagesController.All) + router.Get("/:id", customStaticPagesController.Show) + router.Get("/slug/:slug", customStaticPagesController.ShowBySlug) + router.Post("/", customStaticPagesController.Save) + router.Put("/:id", customStaticPagesController.Update) + router.Delete("/:id", customStaticPagesController.Delete) + }) +} diff --git a/app/module/custom_static_pages/mapper/custom_static_pages.mapper.go b/app/module/custom_static_pages/mapper/custom_static_pages.mapper.go new file mode 100644 index 0000000..ad30791 --- /dev/null +++ b/app/module/custom_static_pages/mapper/custom_static_pages.mapper.go @@ -0,0 +1,22 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/custom_static_pages/response" +) + +func CustomStaticPagesResponseMapper(customStaticPagesReq *entity.CustomStaticPages) (customStaticPagesRes *res.CustomStaticPagesResponse) { + if customStaticPagesReq != nil { + customStaticPagesRes = &res.CustomStaticPagesResponse{ + ID: customStaticPagesReq.ID, + Title: customStaticPagesReq.Title, + Description: customStaticPagesReq.Description, + Slug: customStaticPagesReq.Slug, + HtmlBody: customStaticPagesReq.HtmlBody, + IsActive: customStaticPagesReq.IsActive, + CreatedAt: customStaticPagesReq.CreatedAt, + UpdatedAt: customStaticPagesReq.UpdatedAt, + } + } + return customStaticPagesRes +} diff --git a/app/module/custom_static_pages/repository/custom_static_pages.repository.go b/app/module/custom_static_pages/repository/custom_static_pages.repository.go new file mode 100644 index 0000000..d98cdc6 --- /dev/null +++ b/app/module/custom_static_pages/repository/custom_static_pages.repository.go @@ -0,0 +1,114 @@ +package repository + +import ( + "fmt" + "github.com/google/uuid" + "github.com/rs/zerolog" + "strings" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/custom_static_pages/request" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" +) + +type customStaticPagesRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// CustomStaticPagesRepository define interface of ICustomStaticPagesRepository +type CustomStaticPagesRepository interface { + GetAll(clientId *uuid.UUID, req request.CustomStaticPagesQueryRequest) (customStaticPagess []*entity.CustomStaticPages, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (customStaticPages *entity.CustomStaticPages, err error) + FindOneBySlug(clientId *uuid.UUID, slug string) (customStaticPages *entity.CustomStaticPages, err error) + Create(clientId *uuid.UUID, customStaticPages *entity.CustomStaticPages) (customStaticPagesReturn *entity.CustomStaticPages, err error) + Update(clientId *uuid.UUID, id uint, customStaticPages *entity.CustomStaticPages) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) +} + +func NewCustomStaticPagesRepository(db *database.Database, logger zerolog.Logger) CustomStaticPagesRepository { + return &customStaticPagesRepository{ + DB: db, + Log: logger, + } +} + +// implement interface of ICustomStaticPagesRepository +func (_i *customStaticPagesRepository) GetAll(clientId *uuid.UUID, req request.CustomStaticPagesQueryRequest) (customStaticPagess []*entity.CustomStaticPages, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.CustomStaticPages{}) + query = query.Where("is_active = ?", true) + query = query.Where("client_id = ?", clientId) + + if req.Title != nil && *req.Title != "" { + title := strings.ToLower(*req.Title) + query = query.Where("LOWER(title) LIKE ?", "%"+strings.ToLower(title)+"%") + } + if req.Description != nil && *req.Description != "" { + description := strings.ToLower(*req.Description) + query = query.Where("LOWER(description) LIKE ?", "%"+strings.ToLower(description)+"%") + } + if req.Slug != nil && *req.Slug != "" { + slug := strings.ToLower(*req.Slug) + query = query.Where("LOWER(slug) LIKE ?", "%"+strings.ToLower(slug)+"%") + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&customStaticPagess).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *customStaticPagesRepository) FindOne(clientId *uuid.UUID, id uint) (customStaticPages *entity.CustomStaticPages, err error) { + if err := _i.DB.DB.Where("client_id = ?", clientId).First(&customStaticPages, id).Error; err != nil { + return nil, err + } + + return customStaticPages, nil +} + +func (_i *customStaticPagesRepository) FindOneBySlug(clientId *uuid.UUID, slug string) (customStaticPages *entity.CustomStaticPages, err error) { + if err := _i.DB.DB.Where("client_id = ? AND slug = ?", clientId, slug).First(&customStaticPages).Error; err != nil { + return nil, err + } + + return customStaticPages, nil +} + +func (_i *customStaticPagesRepository) Create(clientId *uuid.UUID, customStaticPages *entity.CustomStaticPages) (customStaticPagesReturn *entity.CustomStaticPages, err error) { + customStaticPages.ClientId = clientId + result := _i.DB.DB.Create(customStaticPages) + return customStaticPages, result.Error +} + +func (_i *customStaticPagesRepository) Update(clientId *uuid.UUID, id uint, customStaticPages *entity.CustomStaticPages) (err error) { + customStaticPagesMap, err := utilSvc.StructToMap(customStaticPages) + if err != nil { + return err + } + return _i.DB.DB.Model(&entity.CustomStaticPages{}). + Where(&entity.CustomStaticPages{ID: id, ClientId: clientId}). + Updates(customStaticPagesMap).Error +} + +func (_i *customStaticPagesRepository) Delete(clientId *uuid.UUID, id uint) error { + return _i.DB.DB.Where("client_id = ?", clientId).Delete(&entity.CustomStaticPages{}, id).Error +} diff --git a/app/module/custom_static_pages/request/custom_static_pages.request.go b/app/module/custom_static_pages/request/custom_static_pages.request.go new file mode 100644 index 0000000..de9842c --- /dev/null +++ b/app/module/custom_static_pages/request/custom_static_pages.request.go @@ -0,0 +1,79 @@ +package request + +import ( + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type CustomStaticPagesGeneric interface { + ToEntity() +} + +type CustomStaticPagesQueryRequest struct { + Title *string `json:"title"` + Description *string `json:"description"` + Slug *string `json:"slug"` + HtmlBody *string `json:"htmlBody"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type CustomStaticPagesCreateRequest struct { + Title string `json:"title" validate:"required"` + Description string `json:"description"` + Slug string `json:"slug" validate:"required"` + HtmlBody string `json:"htmlBody" validate:"required"` +} + +func (req CustomStaticPagesCreateRequest) ToEntity() *entity.CustomStaticPages { + return &entity.CustomStaticPages{ + Title: req.Title, + Description: req.Description, + Slug: req.Slug, + HtmlBody: req.HtmlBody, + IsActive: true, + } +} + +type CustomStaticPagesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Title string `json:"title" validate:"required"` + Description string `json:"description"` + Slug string `json:"slug" validate:"required"` + HtmlBody string `json:"htmlBody" validate:"required"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (req CustomStaticPagesUpdateRequest) ToEntity() *entity.CustomStaticPages { + return &entity.CustomStaticPages{ + ID: req.ID, + Title: req.Title, + Description: req.Description, + Slug: req.Slug, + HtmlBody: req.HtmlBody, + UpdatedAt: req.UpdatedAt, + } +} + +type CustomStaticPagesQueryRequestContext struct { + Title string `json:"title"` + Description string `json:"description"` + Slug string `json:"slug"` + HtmlBody string `json:"htmlBody"` +} + +func (req CustomStaticPagesQueryRequestContext) ToParamRequest() CustomStaticPagesQueryRequest { + var request CustomStaticPagesQueryRequest + + if title := req.Title; title != "" { + request.Title = &title + } + if description := req.Description; description != "" { + request.Description = &description + } + if slug := req.Slug; slug != "" { + request.Slug = &slug + } + + return request +} diff --git a/app/module/custom_static_pages/response/custom_static_pages.response.go b/app/module/custom_static_pages/response/custom_static_pages.response.go new file mode 100644 index 0000000..89161f9 --- /dev/null +++ b/app/module/custom_static_pages/response/custom_static_pages.response.go @@ -0,0 +1,14 @@ +package response + +import "time" + +type CustomStaticPagesResponse struct { + ID uint `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Slug string `json:"slug"` + HtmlBody string `json:"htmlBody"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/custom_static_pages/service/custom_static_pages.service.go b/app/module/custom_static_pages/service/custom_static_pages.service.go new file mode 100644 index 0000000..cf156c2 --- /dev/null +++ b/app/module/custom_static_pages/service/custom_static_pages.service.go @@ -0,0 +1,98 @@ +package service + +import ( + "github.com/google/uuid" + "github.com/rs/zerolog" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/custom_static_pages/mapper" + "web-qudo-be/app/module/custom_static_pages/repository" + "web-qudo-be/app/module/custom_static_pages/request" + "web-qudo-be/app/module/custom_static_pages/response" + usersRepository "web-qudo-be/app/module/users/repository" + "web-qudo-be/utils/paginator" +) + +// CustomStaticPagesService +type customStaticPagesService struct { + Repo repository.CustomStaticPagesRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +// CustomStaticPagesService define interface of ICustomStaticPagesService +type CustomStaticPagesService interface { + All(clientId *uuid.UUID, req request.CustomStaticPagesQueryRequest) (customStaticPages []*response.CustomStaticPagesResponse, paging paginator.Pagination, err error) + Show(clientId *uuid.UUID, id uint) (customStaticPages *response.CustomStaticPagesResponse, err error) + ShowBySlug(clientId *uuid.UUID, slug string) (customStaticPages *response.CustomStaticPagesResponse, err error) + Save(clientId *uuid.UUID, req request.CustomStaticPagesCreateRequest, authToken string) (customStaticPages *entity.CustomStaticPages, err error) + Update(clientId *uuid.UUID, id uint, req request.CustomStaticPagesUpdateRequest) (err error) + Delete(clientId *uuid.UUID, id uint) error +} + +// NewCustomStaticPagesService init CustomStaticPagesService +func NewCustomStaticPagesService(repo repository.CustomStaticPagesRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository) CustomStaticPagesService { + + return &customStaticPagesService{ + Repo: repo, + Log: log, + UsersRepo: usersRepo, + } +} + +// All implement interface of CustomStaticPagesService +func (_i *customStaticPagesService) All(clientId *uuid.UUID, req request.CustomStaticPagesQueryRequest) (customStaticPagess []*response.CustomStaticPagesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(clientId, req) + if err != nil { + return + } + + for _, result := range results { + customStaticPagess = append(customStaticPagess, mapper.CustomStaticPagesResponseMapper(result)) + } + + return +} + +func (_i *customStaticPagesService) Show(clientId *uuid.UUID, id uint) (customStaticPages *response.CustomStaticPagesResponse, err error) { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return nil, err + } + + return mapper.CustomStaticPagesResponseMapper(result), nil +} + +func (_i *customStaticPagesService) ShowBySlug(clientId *uuid.UUID, slug string) (customStaticPages *response.CustomStaticPagesResponse, err error) { + result, err := _i.Repo.FindOneBySlug(clientId, slug) + if err != nil { + return nil, err + } + + return mapper.CustomStaticPagesResponseMapper(result), nil +} + +func (_i *customStaticPagesService) Save(clientId *uuid.UUID, req request.CustomStaticPagesCreateRequest, authToken string) (customStaticPages *entity.CustomStaticPages, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + newReq := req.ToEntity() + + //createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + //newReq.CreatedById = &createdBy.ID + + return _i.Repo.Create(clientId, newReq) +} + +func (_i *customStaticPagesService) Update(clientId *uuid.UUID, id uint, req request.CustomStaticPagesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(clientId, id, req.ToEntity()) +} + +func (_i *customStaticPagesService) Delete(clientId *uuid.UUID, id uint) error { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return err + } + + result.IsActive = false + return _i.Repo.Update(clientId, id, result) +} diff --git a/app/module/districts/controller/controller.go b/app/module/districts/controller/controller.go new file mode 100644 index 0000000..58f44d9 --- /dev/null +++ b/app/module/districts/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "web-qudo-be/app/module/districts/service" + +type Controller struct { + Districts DistrictsController +} + +func NewController(DistrictsService service.DistrictsService) *Controller { + return &Controller{ + Districts: NewDistrictsController(DistrictsService), + } +} diff --git a/app/module/districts/controller/districts.controller.go b/app/module/districts/controller/districts.controller.go new file mode 100644 index 0000000..4410fc0 --- /dev/null +++ b/app/module/districts/controller/districts.controller.go @@ -0,0 +1,181 @@ +package controller + +import ( + "github.com/gofiber/fiber/v2" + "strconv" + "web-qudo-be/app/module/districts/request" + "web-qudo-be/app/module/districts/service" + "web-qudo-be/utils/paginator" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type districtsController struct { + districtsService service.DistrictsService +} + +type DistrictsController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewDistrictsController(districtsService service.DistrictsService) DistrictsController { + return &districtsController{ + districtsService: districtsService, + } +} + +// All Districts +// @Summary Get all Districts +// @Description API for getting all Districts +// @Tags Untags +// @Security Bearer +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /districts [get] +func (_i *districtsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + var req request.DistrictsQueryRequest + req.Pagination = paginate + + districtsData, paging, err := _i.districtsService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Districts list successfully retrieved"}, + Data: districtsData, + Meta: paging, + }) +} + +// Show Districts +// @Summary Get one Districts +// @Description API for getting one Districts +// @Tags Untags +// @Security Bearer +// @Param id path int true "Districts ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /districts/{id} [get] +func (_i *districtsController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + districtsData, err := _i.districtsService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Districts successfully retrieved"}, + Data: districtsData, + }) +} + +// Save Districts +// @Summary Create Districts +// @Description API for create Districts +// @Tags Untags +// @Security Bearer +// @Body request.DistrictsCreateRequest +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /districts [post] +func (_i *districtsController) Save(c *fiber.Ctx) error { + req := new(request.DistrictsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.districtsService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Districts successfully created"}, + }) +} + +// Update Districts +// @Summary Update Districts +// @Description API for update Districts +// @Tags Untags +// @Security Bearer +// @Body request.DistrictsUpdateRequest +// @Param id path int true "Districts ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /districts/{id} [put] +func (_i *districtsController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.DistrictsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.districtsService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Districts successfully updated"}, + }) +} + +// Delete Districts +// @Summary Delete Districts +// @Description API for delete Districts +// @Tags Untags +// @Security Bearer +// @Param id path int true "Districts ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /districts/{id} [delete] +func (_i *districtsController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.districtsService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Districts successfully deleted"}, + }) +} diff --git a/app/module/districts/districts.module.go b/app/module/districts/districts.module.go new file mode 100644 index 0000000..ab1f2f2 --- /dev/null +++ b/app/module/districts/districts.module.go @@ -0,0 +1,53 @@ +package districts + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/districts/controller" + "web-qudo-be/app/module/districts/repository" + "web-qudo-be/app/module/districts/service" +) + +// DistrictsRouter struct of DistrictsRouter +type DistrictsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// NewDistrictsModule register bulky of Districts module +var NewDistrictsModule = fx.Options( + // register repository of Districts module + fx.Provide(repository.NewDistrictsRepository), + + // register service of Districts module + fx.Provide(service.NewDistrictsService), + + // register controller of Districts module + fx.Provide(controller.NewController), + + // register router of Districts module + fx.Provide(NewDistrictsRouter), +) + +// NewDistrictsRouter init DistrictsRouter +func NewDistrictsRouter(fiber *fiber.App, controller *controller.Controller) *DistrictsRouter { + return &DistrictsRouter{ + App: fiber, + Controller: controller, + } +} + +// RegisterDistrictsRoutes register routes of Districts module +func (_i *DistrictsRouter) RegisterDistrictsRoutes() { + // define controllers + districtsController := _i.Controller.Districts + + // define routes + _i.App.Route("/districts", func(router fiber.Router) { + router.Get("/", districtsController.All) + router.Get("/:id", districtsController.Show) + router.Post("/", districtsController.Save) + router.Put("/:id", districtsController.Update) + router.Delete("/:id", districtsController.Delete) + }) +} diff --git a/app/module/districts/mapper/districts.mapper.go b/app/module/districts/mapper/districts.mapper.go new file mode 100644 index 0000000..b08f7ac --- /dev/null +++ b/app/module/districts/mapper/districts.mapper.go @@ -0,0 +1,17 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/districts/response" +) + +func DistrictsResponseMapper(districtsReq *entity.Districts) (districtsRes *res.DistrictsResponse) { + if districtsReq != nil { + districtsRes = &res.DistrictsResponse{ + ID: districtsReq.ID, + DisNam: districtsReq.DisName, + CityId: districtsReq.CityId, + } + } + return districtsRes +} diff --git a/app/module/districts/repository/districts.repository.go b/app/module/districts/repository/districts.repository.go new file mode 100644 index 0000000..fd6996d --- /dev/null +++ b/app/module/districts/repository/districts.repository.go @@ -0,0 +1,69 @@ +package repository + +import ( + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/districts/request" + "web-qudo-be/utils/paginator" +) + +type districtsRepository struct { + DB *database.Database +} + +// DistrictsRepository define interface of IDistrictsRepository +type DistrictsRepository interface { + GetAll(req request.DistrictsQueryRequest) (districtss []*entity.Districts, paging paginator.Pagination, err error) + FindOne(id uint) (districts *entity.Districts, err error) + Create(districts *entity.Districts) (err error) + Update(id uint, districts *entity.Districts) (err error) + Delete(id uint) (err error) +} + +func NewDistrictsRepository(db *database.Database) DistrictsRepository { + return &districtsRepository{ + DB: db, + } +} + +// implement interface of IDistrictsRepository +func (_i *districtsRepository) GetAll(req request.DistrictsQueryRequest) (districtss []*entity.Districts, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Districts{}) + query.Count(&count) + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&districtss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *districtsRepository) FindOne(id uint) (districts *entity.Districts, err error) { + if err := _i.DB.DB.First(&districts, id).Error; err != nil { + return nil, err + } + + return districts, nil +} + +func (_i *districtsRepository) Create(districts *entity.Districts) (err error) { + return _i.DB.DB.Create(districts).Error +} + +func (_i *districtsRepository) Update(id uint, districts *entity.Districts) (err error) { + return _i.DB.DB.Model(&entity.Districts{}). + Where(&entity.Districts{ID: id}). + Updates(districts).Error +} + +func (_i *districtsRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.Districts{}, id).Error +} diff --git a/app/module/districts/request/districts.request.go b/app/module/districts/request/districts.request.go new file mode 100644 index 0000000..9f509fa --- /dev/null +++ b/app/module/districts/request/districts.request.go @@ -0,0 +1,42 @@ +package request + +import ( + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type DistrictsGeneric interface { + ToEntity() +} + +type DistrictsQueryRequest struct { + DisName string `json:"disName" validate:"required"` + CityId int `json:"cityId" validate:"required"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type DistrictsCreateRequest struct { + DisName string `json:"disName" validate:"required"` + CityId int `json:"cityId" validate:"required"` +} + +func (req DistrictsCreateRequest) ToEntity() *entity.Districts { + return &entity.Districts{ + DisName: req.DisName, + CityId: req.CityId, + } +} + +type DistrictsUpdateRequest struct { + ID uint `json:"id" validate:"required"` + DisName string `json:"disName" validate:"required"` + CityId int `json:"cityId" validate:"required"` +} + +func (req DistrictsUpdateRequest) ToEntity() *entity.Districts { + return &entity.Districts{ + ID: req.ID, + DisName: req.DisName, + CityId: req.CityId, + } +} diff --git a/app/module/districts/response/districts.response.go b/app/module/districts/response/districts.response.go new file mode 100644 index 0000000..1efe096 --- /dev/null +++ b/app/module/districts/response/districts.response.go @@ -0,0 +1,7 @@ +package response + +type DistrictsResponse struct { + ID uint `json:"id"` + DisNam string `json:"dis_nam"` + CityId int `json:"city_id"` +} diff --git a/app/module/districts/service/districts.service.go b/app/module/districts/service/districts.service.go new file mode 100644 index 0000000..55a3220 --- /dev/null +++ b/app/module/districts/service/districts.service.go @@ -0,0 +1,72 @@ +package service + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/districts/mapper" + "web-qudo-be/app/module/districts/repository" + "web-qudo-be/app/module/districts/request" + "web-qudo-be/app/module/districts/response" + "web-qudo-be/utils/paginator" +) + +// DistrictsService +type districtsService struct { + Repo repository.DistrictsRepository + Log zerolog.Logger +} + +// DistrictsService define interface of IDistrictsService +type DistrictsService interface { + All(req request.DistrictsQueryRequest) (districts []*response.DistrictsResponse, paging paginator.Pagination, err error) + Show(id uint) (districts *response.DistrictsResponse, err error) + Save(req request.DistrictsCreateRequest) (err error) + Update(id uint, req request.DistrictsUpdateRequest) (err error) + Delete(id uint) error +} + +// NewDistrictsService init DistrictsService +func NewDistrictsService(repo repository.DistrictsRepository, log zerolog.Logger) DistrictsService { + + return &districtsService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of DistrictsService +func (_i *districtsService) All(req request.DistrictsQueryRequest) (districtss []*response.DistrictsResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + districtss = append(districtss, mapper.DistrictsResponseMapper(result)) + } + + return +} + +func (_i *districtsService) Show(id uint) (districts *response.DistrictsResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.DistrictsResponseMapper(result), nil +} + +func (_i *districtsService) Save(req request.DistrictsCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + return _i.Repo.Create(req.ToEntity()) +} + +func (_i *districtsService) Update(id uint, req request.DistrictsUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *districtsService) Delete(id uint) error { + return _i.Repo.Delete(id) +} diff --git a/app/module/feedbacks/controller/controller.go b/app/module/feedbacks/controller/controller.go new file mode 100644 index 0000000..09ff807 --- /dev/null +++ b/app/module/feedbacks/controller/controller.go @@ -0,0 +1,16 @@ +package controller + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/feedbacks/service" +) + +type Controller struct { + Feedbacks FeedbacksController +} + +func NewController(FeedbacksService service.FeedbacksService, log zerolog.Logger) *Controller { + return &Controller{ + Feedbacks: NewFeedbacksController(FeedbacksService, log), + } +} diff --git a/app/module/feedbacks/controller/feedbacks.controller.go b/app/module/feedbacks/controller/feedbacks.controller.go new file mode 100644 index 0000000..047f88f --- /dev/null +++ b/app/module/feedbacks/controller/feedbacks.controller.go @@ -0,0 +1,257 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/feedbacks/request" + "web-qudo-be/app/module/feedbacks/service" + "web-qudo-be/utils/paginator" + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" +) + +type feedbacksController struct { + feedbacksService service.FeedbacksService + Log zerolog.Logger +} + +type FeedbacksController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + FeedbackMonthlyStats(c *fiber.Ctx) error +} + +func NewFeedbacksController(feedbacksService service.FeedbacksService, log zerolog.Logger) FeedbacksController { + return &feedbacksController{ + feedbacksService: feedbacksService, + Log: log, + } +} + +// All get all Feedbacks +// @Summary Get all Feedbacks +// @Description API for getting all Feedbacks +// @Tags Feedbacks +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param req query request.FeedbacksQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /feedbacks [get] +func (_i *feedbacksController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.FeedbacksQueryRequestContext{ + Message: c.Query("message"), + CommentFromName: c.Query("commentFromName"), + CommentFromEmail: c.Query("commentFromEmail"), + StatusId: c.Query("statusId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + feedbacksData, paging, err := _i.feedbacksService.All(clientId, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Feedbacks list successfully retrieved"}, + Data: feedbacksData, + Meta: paging, + }) +} + +// Show get one Feedbacks +// @Summary Get one Feedbacks +// @Description API for getting one Feedbacks +// @Tags Feedbacks +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param id path int true "Feedbacks ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /feedbacks/{id} [get] +func (_i *feedbacksController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + feedbacksData, err := _i.feedbacksService.Show(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Feedbacks successfully retrieved"}, + Data: feedbacksData, + }) +} + +// Save create Feedbacks +// @Summary Create Feedbacks +// @Description API for create Feedbacks +// @Tags Feedbacks +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.FeedbacksCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /feedbacks [post] +func (_i *feedbacksController) Save(c *fiber.Ctx) error { + req := new(request.FeedbacksCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + dataResult, err := _i.feedbacksService.Save(clientId, *req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Feedbacks successfully created"}, + Data: dataResult, + }) +} + +// Update update Feedbacks +// @Summary update Feedbacks +// @Description API for update Feedbacks +// @Tags Feedbacks +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.FeedbacksUpdateRequest true "Required payload" +// @Param id path int true "Feedbacks ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /feedbacks/{id} [put] +func (_i *feedbacksController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.FeedbacksUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.feedbacksService.Update(clientId, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Feedbacks successfully updated"}, + }) +} + +// Delete delete Feedbacks +// @Summary delete Feedbacks +// @Description API for delete Feedbacks +// @Tags Feedbacks +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "Feedbacks ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /feedbacks/{id} [delete] +func (_i *feedbacksController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.feedbacksService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Feedbacks successfully deleted"}, + }) +} + +// FeedbackMonthlyStats Feedbacks +// @Summary FeedbackMonthlyStats Feedbacks +// @Description API for FeedbackMonthlyStats of Feedbacks +// @Tags Feedbacks +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param year query int false "year" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /feedbacks/statistic/monthly [get] +func (_i *feedbacksController) FeedbackMonthlyStats(c *fiber.Ctx) error { + authToken := c.Get("Authorization") + year := c.Query("year") + yearInt, err := strconv.Atoi(year) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + response, err := _i.feedbacksService.FeedbackMonthlyStats(clientId, authToken, &yearInt) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"FeedbacksMonthlyStats of Feedbacks successfully retrieved"}, + Data: response, + }) +} diff --git a/app/module/feedbacks/feedbacks.module.go b/app/module/feedbacks/feedbacks.module.go new file mode 100644 index 0000000..06f9976 --- /dev/null +++ b/app/module/feedbacks/feedbacks.module.go @@ -0,0 +1,54 @@ +package feedbacks + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/feedbacks/controller" + "web-qudo-be/app/module/feedbacks/repository" + "web-qudo-be/app/module/feedbacks/service" +) + +// struct of FeedbacksRouter +type FeedbacksRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of Feedbacks module +var NewFeedbacksModule = fx.Options( + // register repository of Feedbacks module + fx.Provide(repository.NewFeedbacksRepository), + + // register service of Feedbacks module + fx.Provide(service.NewFeedbacksService), + + // register controller of Feedbacks module + fx.Provide(controller.NewController), + + // register router of Feedbacks module + fx.Provide(NewFeedbacksRouter), +) + +// init FeedbacksRouter +func NewFeedbacksRouter(fiber *fiber.App, controller *controller.Controller) *FeedbacksRouter { + return &FeedbacksRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of Feedbacks module +func (_i *FeedbacksRouter) RegisterFeedbacksRoutes() { + // define controllers + feedbacksController := _i.Controller.Feedbacks + + // define routes + _i.App.Route("/feedbacks", func(router fiber.Router) { + router.Get("/", feedbacksController.All) + router.Get("/:id", feedbacksController.Show) + router.Post("/", feedbacksController.Save) + router.Put("/:id", feedbacksController.Update) + router.Delete("/:id", feedbacksController.Delete) + router.Get("/statistic/monthly", feedbacksController.FeedbackMonthlyStats) + }) +} diff --git a/app/module/feedbacks/mapper/feedbacks.mapper.go b/app/module/feedbacks/mapper/feedbacks.mapper.go new file mode 100644 index 0000000..c7bcf83 --- /dev/null +++ b/app/module/feedbacks/mapper/feedbacks.mapper.go @@ -0,0 +1,24 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/feedbacks/response" +) + +func FeedbacksResponseMapper(feedbacksReq *entity.Feedbacks) (feedbacksRes *res.FeedbacksResponse) { + if feedbacksReq != nil { + feedbacksRes = &res.FeedbacksResponse{ + ID: feedbacksReq.ID, + Message: feedbacksReq.Message, + CommentFromName: feedbacksReq.CommentFromName, + CommentFromEmail: feedbacksReq.CommentFromEmail, + StatusId: feedbacksReq.StatusId, + ApprovedAt: feedbacksReq.ApprovedAt, + ReplyMessage: feedbacksReq.ReplyMessage, + IsActive: feedbacksReq.IsActive, + CreatedAt: feedbacksReq.CreatedAt, + UpdatedAt: feedbacksReq.UpdatedAt, + } + } + return feedbacksRes +} diff --git a/app/module/feedbacks/repository/feedbacks.repository.go b/app/module/feedbacks/repository/feedbacks.repository.go new file mode 100644 index 0000000..4b9db8b --- /dev/null +++ b/app/module/feedbacks/repository/feedbacks.repository.go @@ -0,0 +1,181 @@ +package repository + +import ( + "fmt" + "github.com/google/uuid" + "github.com/rs/zerolog" + "strings" + "time" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/feedbacks/request" + "web-qudo-be/app/module/feedbacks/response" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" +) + +type feedbacksRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// FeedbacksRepository define interface of IFeedbacksRepository +type FeedbacksRepository interface { + GetAll(clientId *uuid.UUID, req request.FeedbacksQueryRequest) (feedbackss []*entity.Feedbacks, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (feedbacks *entity.Feedbacks, err error) + Create(feedbacks *entity.Feedbacks) (feedbacksReturn *entity.Feedbacks, err error) + Update(clientId *uuid.UUID, id uint, feedbacks *entity.Feedbacks) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) + FeedbacksMonthlyStats(clientId *uuid.UUID, year int) (feedbacksMonthlyStats []*response.FeedbacksMonthlyStats, err error) +} + +func NewFeedbacksRepository(db *database.Database, logger zerolog.Logger) FeedbacksRepository { + return &feedbacksRepository{ + DB: db, + Log: logger, + } +} + +// implement interface of IFeedbacksRepository +func (_i *feedbacksRepository) GetAll(clientId *uuid.UUID, req request.FeedbacksQueryRequest) (feedbackss []*entity.Feedbacks, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Feedbacks{}) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("is_active = ?", true) + + if req.Message != nil && *req.Message != "" { + message := strings.ToLower(*req.Message) + query = query.Where("LOWER(message) LIKE ?", "%"+strings.ToLower(message)+"%") + } + if req.CommentFromName != nil && *req.CommentFromName != "" { + commentFromName := strings.ToLower(*req.CommentFromName) + query = query.Where("LOWER(comment_from_name) LIKE ?", "%"+strings.ToLower(commentFromName)+"%") + } + if req.CommentFromEmail != nil && *req.CommentFromEmail != "" { + commentFromEmail := strings.ToLower(*req.CommentFromEmail) + query = query.Where("LOWER(comment_from_email) LIKE ?", "%"+strings.ToLower(commentFromEmail)+"%") + } + if req.StatusId != nil { + query = query.Where("status_id = ?", req.StatusId) + } + + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&feedbackss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *feedbacksRepository) FindOne(clientId *uuid.UUID, id uint) (feedbacks *entity.Feedbacks, err error) { + query := _i.DB.DB.Where("id = ?", id) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err := query.First(&feedbacks).Error; err != nil { + return nil, err + } + + return feedbacks, nil +} + +func (_i *feedbacksRepository) Create(feedbacks *entity.Feedbacks) (feedbacksReturn *entity.Feedbacks, err error) { + result := _i.DB.DB.Create(feedbacks) + return feedbacks, result.Error +} + +func (_i *feedbacksRepository) Update(clientId *uuid.UUID, id uint, feedbacks *entity.Feedbacks) (err error) { + feedbacksMap, err := utilSvc.StructToMap(feedbacks) + if err != nil { + return err + } + query := _i.DB.DB.Model(&entity.Feedbacks{}).Where(&entity.Feedbacks{ID: id}) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + return query.Updates(feedbacksMap).Error +} + +func (_i *feedbacksRepository) Delete(clientId *uuid.UUID, id uint) error { + query := _i.DB.DB.Model(&entity.Feedbacks{}).Where("id = ?", id) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + return query.Delete(&entity.Feedbacks{}).Error +} + +func (_i *feedbacksRepository) FeedbacksMonthlyStats(clientId *uuid.UUID, year int) (feedbacksMonthlyStats []*response.FeedbacksMonthlyStats, err error) { + + if year < 1900 || year > 2100 { + return nil, fmt.Errorf("invalid year") + } + + var results []struct { + Month int + Day int + TotalFeedbacks int + } + + query := _i.DB.DB.Model(&entity.Feedbacks{}). + Select("EXTRACT(MONTH FROM created_at) as month, EXTRACT(DAY FROM created_at) as day, "+ + "count(id) as total_feedbacks"). + Where("EXTRACT(YEAR FROM created_at) = ?", year) + + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + err = query.Group("month, day").Scan(&results).Error + if err != nil { + return nil, err + } + + // Siapkan struktur untuk menyimpan data bulanan + feedbackStats := make([]*response.FeedbacksMonthlyStats, 12) + for i := 0; i < 12; i++ { + daysInMonth := time.Date(year, time.Month(i+1), 0, 0, 0, 0, 0, time.UTC).Day() + feedbackStats[i] = &response.FeedbacksMonthlyStats{ + Year: year, + Month: i + 1, + Suggestions: make([]int, daysInMonth), + } + } + + // Isi data dari hasil agregasi + for _, result := range results { + monthIndex := result.Month - 1 + dayIndex := result.Day - 1 + + if monthIndex >= 0 && monthIndex < 12 { + if dayIndex >= 0 && dayIndex < len(feedbackStats[monthIndex].Suggestions) { + feedbackStats[monthIndex].Suggestions[dayIndex] = result.TotalFeedbacks + } + } + } + + return feedbackStats, nil +} diff --git a/app/module/feedbacks/request/feedbacks.request.go b/app/module/feedbacks/request/feedbacks.request.go new file mode 100644 index 0000000..ac1ceb3 --- /dev/null +++ b/app/module/feedbacks/request/feedbacks.request.go @@ -0,0 +1,84 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type FeedbacksGeneric interface { + ToEntity() +} + +type FeedbacksQueryRequest struct { + Message *string `json:"message"` + CommentFromName *string `json:"commentFromName"` + CommentFromEmail *string `json:"commentFromEmail"` + StartDate *string `json:"startDate"` + EndDate *string `json:"endDate"` + StatusId *int `json:"statusId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type FeedbacksCreateRequest struct { + Message string `json:"message" validate:"required"` + CommentFromName string `json:"commentFromName" validate:"required"` + CommentFromEmail string `json:"commentFromEmail" validate:"required"` +} + +func (req FeedbacksCreateRequest) ToEntity() *entity.Feedbacks { + return &entity.Feedbacks{ + Message: req.Message, + CommentFromName: req.CommentFromName, + CommentFromEmail: req.CommentFromEmail, + StatusId: 0, + IsActive: true, + } +} + +type FeedbacksUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Message string `json:"message" validate:"required"` + CommentFromName string `json:"commentFromName" validate:"required"` + CommentFromEmail string `json:"commentFromEmail" validate:"required"` +} + +func (req FeedbacksUpdateRequest) ToEntity() *entity.Feedbacks { + return &entity.Feedbacks{ + ID: req.ID, + Message: req.Message, + CommentFromName: req.CommentFromName, + CommentFromEmail: req.CommentFromEmail, + UpdatedAt: time.Now(), + } +} + +type FeedbacksQueryRequestContext struct { + Message string `json:"message"` + CommentFromName string `json:"commentFromName"` + CommentFromEmail string `json:"commentFromEmail"` + StatusId string `json:"statusId"` +} + +func (req FeedbacksQueryRequestContext) ToParamRequest() FeedbacksQueryRequest { + var request FeedbacksQueryRequest + + if message := req.Message; message != "" { + request.Message = &message + } + if commentFromName := req.CommentFromName; commentFromName != "" { + request.CommentFromName = &commentFromName + } + if commentFromEmail := req.CommentFromEmail; commentFromEmail != "" { + request.CommentFromEmail = &commentFromEmail + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + + return request +} diff --git a/app/module/feedbacks/response/feedbacks.response.go b/app/module/feedbacks/response/feedbacks.response.go new file mode 100644 index 0000000..1c72ff4 --- /dev/null +++ b/app/module/feedbacks/response/feedbacks.response.go @@ -0,0 +1,22 @@ +package response + +import "time" + +type FeedbacksResponse struct { + ID uint `json:"id"` + Message string `json:"message"` + CommentFromName string `json:"commentFromName"` + CommentFromEmail string `json:"commentFromEmail"` + StatusId int `json:"statusId"` + ApprovedAt *time.Time `json:"approvedAt"` + ReplyMessage *string `json:"replyMessage"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type FeedbacksMonthlyStats struct { + Year int `json:"year"` + Month int `json:"month"` + Suggestions []int `json:"suggestions"` +} diff --git a/app/module/feedbacks/service/feedbacks.service.go b/app/module/feedbacks/service/feedbacks.service.go new file mode 100644 index 0000000..e8c44ce --- /dev/null +++ b/app/module/feedbacks/service/feedbacks.service.go @@ -0,0 +1,110 @@ +package service + +import ( + "github.com/google/uuid" + "github.com/rs/zerolog" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/feedbacks/mapper" + "web-qudo-be/app/module/feedbacks/repository" + "web-qudo-be/app/module/feedbacks/request" + "web-qudo-be/app/module/feedbacks/response" + usersRepository "web-qudo-be/app/module/users/repository" + "web-qudo-be/utils/paginator" +) + +// FeedbacksService +type feedbacksService struct { + Repo repository.FeedbacksRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +// FeedbacksService define interface of IFeedbacksService +type FeedbacksService interface { + All(clientId *uuid.UUID, req request.FeedbacksQueryRequest) (feedbacks []*response.FeedbacksResponse, paging paginator.Pagination, err error) + Show(clientId *uuid.UUID, id uint) (feedbacks *response.FeedbacksResponse, err error) + Save(clientId *uuid.UUID, req request.FeedbacksCreateRequest, authToken string) (feedbacks *entity.Feedbacks, err error) + Update(clientId *uuid.UUID, id uint, req request.FeedbacksUpdateRequest) (err error) + Delete(clientId *uuid.UUID, id uint) error + FeedbackMonthlyStats(clientId *uuid.UUID, authToken string, year *int) (articleMonthlyStats []*response.FeedbacksMonthlyStats, err error) +} + +// NewFeedbacksService init FeedbacksService +func NewFeedbacksService(repo repository.FeedbacksRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository) FeedbacksService { + + return &feedbacksService{ + Repo: repo, + Log: log, + UsersRepo: usersRepo, + } +} + +// All implement interface of FeedbacksService +func (_i *feedbacksService) All(clientId *uuid.UUID, req request.FeedbacksQueryRequest) (feedbackss []*response.FeedbacksResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(clientId, req) + if err != nil { + return + } + + for _, result := range results { + feedbackss = append(feedbackss, mapper.FeedbacksResponseMapper(result)) + } + + return +} + +func (_i *feedbacksService) Show(clientId *uuid.UUID, id uint) (feedbacks *response.FeedbacksResponse, err error) { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return nil, err + } + + return mapper.FeedbacksResponseMapper(result), nil +} + +func (_i *feedbacksService) Save(clientId *uuid.UUID, req request.FeedbacksCreateRequest, authToken string) (feedbacks *entity.Feedbacks, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + // Set ClientId on entity + entity := req.ToEntity() + entity.ClientId = clientId + + return _i.Repo.Create(entity) +} + +func (_i *feedbacksService) Update(clientId *uuid.UUID, id uint, req request.FeedbacksUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + // Set ClientId on entity + entity := req.ToEntity() + entity.ClientId = clientId + + return _i.Repo.Update(clientId, id, entity) +} + +func (_i *feedbacksService) Delete(clientId *uuid.UUID, id uint) error { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return err + } + result.IsActive = false + return _i.Repo.Update(clientId, id, result) +} + +func (_i *feedbacksService) FeedbackMonthlyStats(clientId *uuid.UUID, authToken string, year *int) (articleMonthlyStats []*response.FeedbacksMonthlyStats, err error) { + //user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + //var userLevelId *uint + //var userLevelNumber *int + // + //if user != nil { + // userLevelId = &user.UserLevelId + // userLevelNumber = &user.UserLevel.LevelNumber + //} + + result, err := _i.Repo.FeedbacksMonthlyStats(clientId, *year) + if err != nil { + return nil, err + } + return result, nil +} diff --git a/app/module/magazine_files/controller/controller.go b/app/module/magazine_files/controller/controller.go new file mode 100644 index 0000000..c14270e --- /dev/null +++ b/app/module/magazine_files/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "web-qudo-be/app/module/magazine_files/service" + +type Controller struct { + MagazineFiles MagazineFilesController +} + +func NewController(MagazineFilesService service.MagazineFilesService) *Controller { + return &Controller{ + MagazineFiles: NewMagazineFilesController(MagazineFilesService), + } +} diff --git a/app/module/magazine_files/controller/magazine_files.controller.go b/app/module/magazine_files/controller/magazine_files.controller.go new file mode 100644 index 0000000..cbffc86 --- /dev/null +++ b/app/module/magazine_files/controller/magazine_files.controller.go @@ -0,0 +1,209 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/module/magazine_files/request" + "web-qudo-be/app/module/magazine_files/service" + "web-qudo-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type magazineFilesController struct { + magazineFilesService service.MagazineFilesService +} + +type MagazineFilesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + Viewer(c *fiber.Ctx) error +} + +func NewMagazineFilesController(magazineFilesService service.MagazineFilesService) MagazineFilesController { + return &magazineFilesController{ + magazineFilesService: magazineFilesService, + } +} + +// All MagazineFiles +// @Summary Get all MagazineFiles +// @Description API for getting all MagazineFiles +// @Tags Magazine Files +// @Security Bearer +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazine-files [get] +func (_i *magazineFilesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.MagazineFilesQueryRequestContext{ + Title: c.Query("title"), + Description: c.Query("description"), + MagazineId: c.Query("magazineId"), + StatusId: c.Query("statusId"), + IsPublish: c.Query("isPublish"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + magazineFilesData, paging, err := _i.magazineFilesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MagazineFiles list successfully retrieved"}, + Data: magazineFilesData, + Meta: paging, + }) +} + +// Show MagazineFiles +// @Summary Get one MagazineFiles +// @Description API for getting one MagazineFiles +// @Tags Magazine Files +// @Security Bearer +// @Param id path int true "MagazineFiles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazine-files/{id} [get] +func (_i *magazineFilesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + magazineFilesData, err := _i.magazineFilesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MagazineFiles successfully retrieved"}, + Data: magazineFilesData, + }) +} + +// Save MagazineFiles +// @Summary Create MagazineFiles +// @Description API for create MagazineFiles +// @Tags Magazine Files +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param files formData file true "Upload file" multiple true +// @Param title formData string true "Magazine file title" +// @Param description formData string true "Magazine file description" +// @Param magazineId path int true "Magazine ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazine-files/{magazineId} [post] +func (_i *magazineFilesController) Save(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("magazineId"), 10, 0) + if err != nil { + return err + } + title := c.Params("title") + description := c.Params("description") + + err = _i.magazineFilesService.Save(c, uint(id), title, description) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MagazineFiles successfully created"}, + }) +} + +// Update MagazineFiles +// @Summary Update MagazineFiles +// @Description API for update MagazineFiles +// @Tags Magazine Files +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "MagazineFiles ID" +// @Body request.MagazineFilesUpdateRequest +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazine-files/{id} [put] +func (_i *magazineFilesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.MagazineFilesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.magazineFilesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MagazineFiles successfully updated"}, + }) +} + +// Delete MagazineFiles +// @Summary delete MagazineFiles +// @Description API for delete MagazineFiles +// @Tags Magazine Files +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "MagazineFiles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazine-files/{id} [delete] +func (_i *magazineFilesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.magazineFilesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MagazineFiles successfully deleted"}, + }) +} + +// Viewer MagazineFiles +// @Summary Create MagazineFiles +// @Description API for create MagazineFiles +// @Tags Magazine Files +// @Security Bearer +// @Param filename path string true "Magazine File Name" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazine-files/viewer/{filename} [get] +func (_i *magazineFilesController) Viewer(c *fiber.Ctx) error { + return _i.magazineFilesService.Viewer(c) +} diff --git a/app/module/magazine_files/magazine_files.module.go b/app/module/magazine_files/magazine_files.module.go new file mode 100644 index 0000000..a43bdaf --- /dev/null +++ b/app/module/magazine_files/magazine_files.module.go @@ -0,0 +1,54 @@ +package magazine_files + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/magazine_files/controller" + "web-qudo-be/app/module/magazine_files/repository" + "web-qudo-be/app/module/magazine_files/service" +) + +// struct of MagazineFilesRouter +type MagazineFilesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of MagazineFiles module +var NewMagazineFilesModule = fx.Options( + // register repository of MagazineFiles module + fx.Provide(repository.NewMagazineFilesRepository), + + // register service of MagazineFiles module + fx.Provide(service.NewMagazineFilesService), + + // register controller of MagazineFiles module + fx.Provide(controller.NewController), + + // register router of MagazineFiles module + fx.Provide(NewMagazineFilesRouter), +) + +// init MagazineFilesRouter +func NewMagazineFilesRouter(fiber *fiber.App, controller *controller.Controller) *MagazineFilesRouter { + return &MagazineFilesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of MagazineFiles module +func (_i *MagazineFilesRouter) RegisterMagazineFilesRoutes() { + // define controllers + magazineFilesController := _i.Controller.MagazineFiles + + // define routes + _i.App.Route("/magazine-files", func(router fiber.Router) { + router.Get("/", magazineFilesController.All) + router.Get("/:id", magazineFilesController.Show) + router.Post("/:magazineId", magazineFilesController.Save) + router.Put("/:id", magazineFilesController.Update) + router.Delete("/:id", magazineFilesController.Delete) + router.Get("/viewer/:filename", magazineFilesController.Viewer) + }) +} diff --git a/app/module/magazine_files/mapper/magazine_files.mapper.go b/app/module/magazine_files/mapper/magazine_files.mapper.go new file mode 100644 index 0000000..30a98f8 --- /dev/null +++ b/app/module/magazine_files/mapper/magazine_files.mapper.go @@ -0,0 +1,37 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/magazine_files/response" +) + +func MagazineFilesResponseMapper(magazineFilesReq *entity.MagazineFiles, host string) (magazineFilesRes *res.MagazineFilesResponse) { + fileUrl := host + "/magazine-files/viewer/" + if magazineFilesReq.FileName != nil { + fileUrl += *magazineFilesReq.FileName + } + + if magazineFilesReq != nil { + magazineFilesRes = &res.MagazineFilesResponse{ + ID: magazineFilesReq.ID, + Title: magazineFilesReq.Title, + Description: magazineFilesReq.Description, + MagazineId: magazineFilesReq.MagazineId, + DownloadCount: magazineFilesReq.DownloadCount, + FilePath: magazineFilesReq.FilePath, + FileUrl: &fileUrl, + FileName: magazineFilesReq.FileName, + FileAlt: magazineFilesReq.FileAlt, + WidthPixel: magazineFilesReq.WidthPixel, + HeightPixel: magazineFilesReq.HeightPixel, + Size: magazineFilesReq.Size, + StatusId: magazineFilesReq.StatusId, + IsPublish: magazineFilesReq.IsPublish, + PublishedAt: magazineFilesReq.PublishedAt, + IsActive: magazineFilesReq.IsActive, + CreatedAt: magazineFilesReq.CreatedAt, + UpdatedAt: magazineFilesReq.UpdatedAt, + } + } + return magazineFilesRes +} diff --git a/app/module/magazine_files/repository/magazine_files.repository.go b/app/module/magazine_files/repository/magazine_files.repository.go new file mode 100644 index 0000000..098ce90 --- /dev/null +++ b/app/module/magazine_files/repository/magazine_files.repository.go @@ -0,0 +1,121 @@ +package repository + +import ( + "fmt" + "strings" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/magazine_files/request" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" +) + +type magazineFilesRepository struct { + DB *database.Database +} + +// MagazineFilesRepository define interface of IMagazineFilesRepository +type MagazineFilesRepository interface { + GetAll(req request.MagazineFilesQueryRequest) (magazineFiless []*entity.MagazineFiles, paging paginator.Pagination, err error) + FindOne(id uint) (magazineFiles *entity.MagazineFiles, err error) + FindByMagazine(magazineId uint) (magazineFiles []*entity.MagazineFiles, err error) + FindByFilename(filename string) (magazineFiles *entity.MagazineFiles, err error) + Create(magazineFiles *entity.MagazineFiles) (err error) + Update(id uint, magazineFiles *entity.MagazineFiles) (err error) + Delete(id uint) (err error) +} + +func NewMagazineFilesRepository(db *database.Database) MagazineFilesRepository { + return &magazineFilesRepository{ + DB: db, + } +} + +// implement interface of IMagazineFilesRepository +func (_i *magazineFilesRepository) GetAll(req request.MagazineFilesQueryRequest) (magazineFiless []*entity.MagazineFiles, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.MagazineFiles{}) + query = query.Where("is_active = ?", true) + + if req.Title != nil && *req.Title != "" { + title := strings.ToLower(*req.Title) + query = query.Where("LOWER(title) LIKE ?", "%"+strings.ToLower(title)+"%") + } + if req.Description != nil && *req.Description != "" { + description := strings.ToLower(*req.Description) + query = query.Where("LOWER(description) LIKE ?", "%"+strings.ToLower(description)+"%") + } + if req.MagazineId != nil { + query = query.Where("magazine_id = ?", req.MagazineId) + } + if req.IsPublish != nil { + query = query.Where("is_publish = ?", req.IsPublish) + } + if req.StatusId != nil { + query = query.Where("status_id = ?", req.StatusId) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&magazineFiless).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *magazineFilesRepository) FindByMagazine(magazineId uint) (magazineFiles []*entity.MagazineFiles, err error) { + if err := _i.DB.DB.Where("magazine_id = ?", magazineId).Find(&magazineFiles).Error; err != nil { + return nil, err + } + + return magazineFiles, nil +} + +func (_i *magazineFilesRepository) FindByFilename(magazineFilename string) (magazineFiles *entity.MagazineFiles, err error) { + + if err := _i.DB.DB.Where("file_name = ?", magazineFilename).First(&magazineFiles).Error; err != nil { + return nil, err + } + + return magazineFiles, nil +} + +func (_i *magazineFilesRepository) FindOne(id uint) (magazineFiles *entity.MagazineFiles, err error) { + if err := _i.DB.DB.First(&magazineFiles, id).Error; err != nil { + return nil, err + } + + return magazineFiles, nil +} + +func (_i *magazineFilesRepository) Create(magazineFiles *entity.MagazineFiles) (err error) { + return _i.DB.DB.Create(magazineFiles).Error +} + +func (_i *magazineFilesRepository) Update(id uint, magazineFiles *entity.MagazineFiles) (err error) { + magazineFilesMap, err := utilSvc.StructToMap(magazineFiles) + if err != nil { + return err + } + return _i.DB.DB.Model(&entity.MagazineFiles{}). + Where(&entity.MagazineFiles{ID: id}). + Updates(magazineFilesMap).Error +} + +func (_i *magazineFilesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.MagazineFiles{}, id).Error +} diff --git a/app/module/magazine_files/request/magazine_files.request.go b/app/module/magazine_files/request/magazine_files.request.go new file mode 100644 index 0000000..ed6b2ec --- /dev/null +++ b/app/module/magazine_files/request/magazine_files.request.go @@ -0,0 +1,137 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type MagazineFilesGeneric interface { + ToEntity() +} + +type MagazineFilesQueryRequest struct { + Title *string `json:"title"` + Description *string `json:"description"` + MagazineId *int `json:"magazineId"` + DownloadCount *int `json:"downloadCount"` + StatusId *int `json:"statusId"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` + IsActive *bool `json:"isActive"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type MagazineFilesCreateRequest struct { + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + MagazineId uint `json:"magazineId" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + DownloadCount *int `json:"downloadCount"` + FilePath *string `json:"filePath"` + FileUrl *string `json:"fileUrl"` + FileName *string `json:"fileName"` + FileAlt *string `json:"fileAlt"` + WidthPixel *string `json:"widthPixel"` + HeightPixel *string `json:"heightPixel"` + Size *string `json:"size"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` +} + +func (req MagazineFilesCreateRequest) ToEntity() *entity.MagazineFiles { + return &entity.MagazineFiles{ + Title: req.Title, + Description: req.Description, + MagazineId: req.MagazineId, + DownloadCount: req.DownloadCount, + FilePath: req.FilePath, + FileUrl: req.FileUrl, + FileName: req.FileName, + FileAlt: req.FileAlt, + WidthPixel: req.WidthPixel, + HeightPixel: req.HeightPixel, + Size: req.Size, + StatusId: req.StatusId, + IsPublish: req.IsPublish, + PublishedAt: req.PublishedAt, + } +} + +type MagazineFilesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + MagazineId uint `json:"magazineId" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + DownloadCount *int `json:"downloadCount"` + FilePath *string `json:"filePath"` + FileUrl *string `json:"fileUrl"` + FileName *string `json:"fileName"` + FileAlt *string `json:"fileAlt"` + WidthPixel *string `json:"widthPixel"` + HeightPixel *string `json:"heightPixel"` + Size *string `json:"size"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` +} + +func (req MagazineFilesUpdateRequest) ToEntity() *entity.MagazineFiles { + return &entity.MagazineFiles{ + ID: req.ID, + Title: req.Title, + Description: req.Description, + MagazineId: req.MagazineId, + DownloadCount: req.DownloadCount, + FilePath: req.FilePath, + FileUrl: req.FileUrl, + FileName: req.FileName, + FileAlt: req.FileAlt, + WidthPixel: req.WidthPixel, + HeightPixel: req.HeightPixel, + Size: req.Size, + StatusId: req.StatusId, + IsPublish: req.IsPublish, + PublishedAt: req.PublishedAt, + } +} + +type MagazineFilesQueryRequestContext struct { + Title string `json:"title"` + Description string `json:"description"` + MagazineId string `json:"magazineId"` + IsPublish string `json:"isPublish"` + StatusId string `json:"statusId"` +} + +func (req MagazineFilesQueryRequestContext) ToParamRequest() MagazineFilesQueryRequest { + var request MagazineFilesQueryRequest + + if title := req.Title; title != "" { + request.Title = &title + } + if description := req.Description; description != "" { + request.Description = &description + } + if magazineIdStr := req.MagazineId; magazineIdStr != "" { + magazineId, err := strconv.Atoi(magazineIdStr) + if err == nil { + request.MagazineId = &magazineId + } + } + if isPublishStr := req.IsPublish; isPublishStr != "" { + isPublish, err := strconv.ParseBool(isPublishStr) + if err == nil { + request.IsPublish = &isPublish + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + + return request +} diff --git a/app/module/magazine_files/response/magazine_files.response.go b/app/module/magazine_files/response/magazine_files.response.go new file mode 100644 index 0000000..5b46c07 --- /dev/null +++ b/app/module/magazine_files/response/magazine_files.response.go @@ -0,0 +1,24 @@ +package response + +import "time" + +type MagazineFilesResponse struct { + ID uint `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + MagazineId uint `json:"magazineId"` + DownloadCount *int `json:"downloadCount"` + FilePath *string `json:"filePath"` + FileUrl *string `json:"fileUrl"` + FileName *string `json:"fileName"` + FileAlt *string `json:"fileAlt"` + WidthPixel *string `json:"widthPixel"` + HeightPixel *string `json:"heightPixel"` + Size *string `json:"size"` + StatusId int `json:"statusId"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/magazine_files/service/magazine_files.service.go b/app/module/magazine_files/service/magazine_files.service.go new file mode 100644 index 0000000..ff7a433 --- /dev/null +++ b/app/module/magazine_files/service/magazine_files.service.go @@ -0,0 +1,227 @@ +package service + +import ( + "context" + "fmt" + "github.com/gofiber/fiber/v2" + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" + "io" + "log" + "math/rand" + "mime" + "path/filepath" + "strconv" + "strings" + "time" + "web-qudo-be/app/module/magazine_files/mapper" + "web-qudo-be/app/module/magazine_files/repository" + "web-qudo-be/app/module/magazine_files/request" + "web-qudo-be/app/module/magazine_files/response" + config "web-qudo-be/config/config" + minioStorage "web-qudo-be/config/config" + "web-qudo-be/utils/paginator" +) + +// MagazineFilesService +type magazineFilesService struct { + Repo repository.MagazineFilesRepository + Log zerolog.Logger + Cfg *config.Config + MinioStorage *minioStorage.MinioStorage +} + +// MagazineFilesService define interface of IMagazineFilesService +type MagazineFilesService interface { + All(req request.MagazineFilesQueryRequest) (magazineFiles []*response.MagazineFilesResponse, paging paginator.Pagination, err error) + Show(id uint) (magazineFiles *response.MagazineFilesResponse, err error) + Save(c *fiber.Ctx, id uint, title string, description string) (err error) + Update(id uint, req request.MagazineFilesUpdateRequest) (err error) + Delete(id uint) error + Viewer(c *fiber.Ctx) error +} + +// NewMagazineFilesService init MagazineFilesService +func NewMagazineFilesService(repo repository.MagazineFilesRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) MagazineFilesService { + + return &magazineFilesService{ + Repo: repo, + Log: log, + Cfg: cfg, + MinioStorage: minioStorage, + } +} + +// All implement interface of MagazineFilesService +func (_i *magazineFilesService) All(req request.MagazineFilesQueryRequest) (magazineFiless []*response.MagazineFilesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + host := _i.Cfg.App.Domain + for _, result := range results { + magazineFiless = append(magazineFiless, mapper.MagazineFilesResponseMapper(result, host)) + } + return +} + +func (_i *magazineFilesService) Show(id uint) (magazineFiles *response.MagazineFilesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + host := _i.Cfg.App.Domain + return mapper.MagazineFilesResponseMapper(result, host), nil +} + +func (_i *magazineFilesService) Save(c *fiber.Ctx, id uint, title string, description string) (err error) { + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + + form, err := c.MultipartForm() + + if err != nil { + return err + } + //filess := form.File["files"] + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + for _, files := range form.File { + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: top"). + Interface("files", files).Msg("") + + for _, fileHeader := range files { + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop"). + Interface("data", fileHeader).Msg("") + + src, err := fileHeader.Open() + if err != nil { + return err + } + defer src.Close() + + filename := filepath.Base(fileHeader.Filename) + filenameAlt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + filename = strings.ReplaceAll(filename, " ", "") + filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + extension := filepath.Ext(fileHeader.Filename)[1:] + + now := time.Now() + rand.New(rand.NewSource(now.UnixNano())) + randUniqueId := rand.Intn(1000000) + + newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId) + newFilename := newFilenameWithoutExt + "." + extension + objectName := fmt.Sprintf("magazines/upload/%d/%d/%s", now.Year(), now.Month(), newFilename) + + fileSize := strconv.FormatInt(fileHeader.Size, 10) + + req := request.MagazineFilesCreateRequest{ + MagazineId: id, + Title: title, + Description: description, + FilePath: &objectName, + FileName: &newFilename, + FileAlt: &filenameAlt, + Size: &fileSize, + } + + err = _i.Repo.Create(req.ToEntity()) + if err != nil { + return err + } + + // Upload file ke MinIO + _, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{}) + if err != nil { + return err + } + } + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "User:All"). + Interface("data", "Successfully uploaded").Msg("") + + return +} + +func (_i *magazineFilesService) Viewer(c *fiber.Ctx) (err error) { + filename := c.Params("filename") + result, err := _i.Repo.FindByFilename(filename) + if err != nil { + return err + } + + ctx := context.Background() + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + objectName := *result.FilePath + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:Uploads"). + Interface("data", objectName).Msg("") + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{}) + if err != nil { + log.Fatalln(err) + } + defer fileContent.Close() + + // Tentukan Content-Type berdasarkan ekstensi file + contentType := mime.TypeByExtension("." + getFileExtension(objectName)) + if contentType == "" { + contentType = "application/octet-stream" // fallback jika tidak ada tipe MIME yang cocok + } + + c.Set("Content-Type", contentType) + + if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil { + return err + } + + return +} + +func getFileExtension(filename string) string { + // split file name + parts := strings.Split(filename, ".") + + // jika tidak ada ekstensi, kembalikan string kosong + if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") { + return "" + } + + // ambil ekstensi terakhir + return parts[len(parts)-1] +} + +func (_i *magazineFilesService) Update(id uint, req request.MagazineFilesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *magazineFilesService) Delete(id uint) error { + return _i.Repo.Delete(id) +} diff --git a/app/module/magazines/controller/controller.go b/app/module/magazines/controller/controller.go new file mode 100644 index 0000000..c5b3622 --- /dev/null +++ b/app/module/magazines/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "web-qudo-be/app/module/magazines/service" + +type Controller struct { + Magazines MagazinesController +} + +func NewController(MagazinesService service.MagazinesService) *Controller { + return &Controller{ + Magazines: NewMagazinesController(MagazinesService), + } +} diff --git a/app/module/magazines/controller/magazines.controller.go b/app/module/magazines/controller/magazines.controller.go new file mode 100644 index 0000000..7777327 --- /dev/null +++ b/app/module/magazines/controller/magazines.controller.go @@ -0,0 +1,266 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/magazines/request" + "web-qudo-be/app/module/magazines/service" + "web-qudo-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type magazinesController struct { + magazinesService service.MagazinesService +} + +type MagazinesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + SaveThumbnail(c *fiber.Ctx) error + Viewer(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewMagazinesController(magazinesService service.MagazinesService) MagazinesController { + return &magazinesController{ + magazinesService: magazinesService, + } +} + +// All Magazines +// @Summary Get all Magazines +// @Description API for getting all Magazines +// @Tags Magazines +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param req query request.MagazinesQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazines [get] +func (_i *magazinesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.MagazinesQueryRequestContext{ + Title: c.Query("title"), + Description: c.Query("description"), + CreatedById: c.Query("createdById"), + StatusId: c.Query("statusId"), + IsPublish: c.Query("isPublish"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + magazinesData, paging, err := _i.magazinesService.All(clientId, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Magazines list successfully retrieved"}, + Data: magazinesData, + Meta: paging, + }) +} + +// Show Magazines +// @Summary Get one Magazines +// @Description API for getting one Magazines +// @Tags Magazines +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param id path int true "Magazines ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazines/{id} [get] +func (_i *magazinesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + magazinesData, err := _i.magazinesService.Show(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Magazines successfully retrieved"}, + Data: magazinesData, + }) +} + +// Save Magazines +// @Summary Create Magazines +// @Description API for create Magazines +// @Tags Magazines +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.MagazinesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazines [post] +func (_i *magazinesController) Save(c *fiber.Ctx) error { + req := new(request.MagazinesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + dataResult, err := _i.magazinesService.Save(clientId, *req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Magazines successfully created"}, + Data: dataResult, + }) +} + +// Update Magazines +// @Summary Update Magazines +// @Description API for update Magazines +// @Tags Magazines +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "Magazines ID" +// @Param payload body request.MagazinesUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazines/{id} [put] +func (_i *magazinesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.MagazinesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.magazinesService.Update(clientId, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Magazines successfully updated"}, + }) +} + +// SaveThumbnail Magazines +// @Summary Save Thumbnail Magazines +// @Description API for Save Thumbnail of Magazines +// @Tags Magazines +// @Security Bearer +// @Produce json +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "Magazine ID" +// @Param files formData file true "Upload thumbnail" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazines/thumbnail/{id} [post] +func (_i *magazinesController) SaveThumbnail(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err := _i.magazinesService.SaveThumbnail(clientId, c) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Thumbnail of Magazines successfully created"}, + }) +} + +// Viewer Magazines +// @Summary Viewer Magazines Thumbnail +// @Description API for View Thumbnail of Magazines +// @Tags Magazines +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param thumbnailName path string true "Magazines Thumbnail Name" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazines/thumbnail/viewer/{thumbnailName} [get] +func (_i *magazinesController) Viewer(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + return _i.magazinesService.Viewer(clientId, c) +} + +// Delete Magazines +// @Summary Delete Magazines +// @Description API for delete Magazines +// @Tags Magazines +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "Magazines ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazines/{id} [delete] +func (_i *magazinesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.magazinesService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Magazines successfully deleted"}, + }) +} diff --git a/app/module/magazines/magazines.module.go b/app/module/magazines/magazines.module.go new file mode 100644 index 0000000..497901b --- /dev/null +++ b/app/module/magazines/magazines.module.go @@ -0,0 +1,55 @@ +package magazines + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/magazines/controller" + "web-qudo-be/app/module/magazines/repository" + "web-qudo-be/app/module/magazines/service" +) + +// struct of MagazinesRouter +type MagazinesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of Magazines module +var NewMagazinesModule = fx.Options( + // register repository of Magazines module + fx.Provide(repository.NewMagazinesRepository), + + // register service of Magazines module + fx.Provide(service.NewMagazinesService), + + // register controller of Magazines module + fx.Provide(controller.NewController), + + // register router of Magazines module + fx.Provide(NewMagazinesRouter), +) + +// init MagazinesRouter +func NewMagazinesRouter(fiber *fiber.App, controller *controller.Controller) *MagazinesRouter { + return &MagazinesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of Magazines module +func (_i *MagazinesRouter) RegisterMagazinesRoutes() { + // define controllers + magazinesController := _i.Controller.Magazines + + // define routes + _i.App.Route("/magazines", func(router fiber.Router) { + router.Get("/", magazinesController.All) + router.Get("/:id", magazinesController.Show) + router.Post("/", magazinesController.Save) + router.Put("/:id", magazinesController.Update) + router.Post("/thumbnail/:id", magazinesController.SaveThumbnail) + router.Get("/thumbnail/viewer/:thumbnailName", magazinesController.Viewer) + router.Delete("/:id", magazinesController.Delete) + }) +} diff --git a/app/module/magazines/mapper/magazines.mapper.go b/app/module/magazines/mapper/magazines.mapper.go new file mode 100644 index 0000000..4f2bbbd --- /dev/null +++ b/app/module/magazines/mapper/magazines.mapper.go @@ -0,0 +1,42 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + magazineFilesMapper "web-qudo-be/app/module/magazine_files/mapper" + magazineFilesRepository "web-qudo-be/app/module/magazine_files/repository" + magazineFilesResponse "web-qudo-be/app/module/magazine_files/response" + res "web-qudo-be/app/module/magazines/response" +) + +func MagazinesResponseMapper(magazinesReq *entity.Magazines, magazineFilesRepo magazineFilesRepository.MagazineFilesRepository, host string) (magazinesRes *res.MagazinesResponse) { + magazineFiles, _ := magazineFilesRepo.FindByMagazine(magazinesReq.ID) + var magazineFilesArr []*magazineFilesResponse.MagazineFilesResponse + if magazineFiles != nil && len(magazineFiles) > 0 { + for _, result := range magazineFiles { + magazineFilesArr = append(magazineFilesArr, magazineFilesMapper.MagazineFilesResponseMapper(result, host)) + } + } + + if magazinesReq != nil { + magazinesRes = &res.MagazinesResponse{ + ID: magazinesReq.ID, + Title: magazinesReq.Title, + Description: magazinesReq.Description, + ThumbnailPath: magazinesReq.ThumbnailPath, + PageUrl: magazinesReq.PageUrl, + CreatedById: magazinesReq.CreatedById, + StatusId: magazinesReq.StatusId, + IsPublish: magazinesReq.IsPublish, + PublishedAt: magazinesReq.PublishedAt, + IsActive: magazinesReq.IsActive, + CreatedAt: magazinesReq.CreatedAt, + UpdatedAt: magazinesReq.UpdatedAt, + MagazineFiles: magazineFilesArr, + } + + if magazinesReq.ThumbnailPath != nil { + magazinesRes.ThumbnailUrl = host + "/magazines/thumbnail/viewer/" + *magazinesReq.ThumbnailName + } + } + return magazinesRes +} diff --git a/app/module/magazines/repository/magazines.repository.go b/app/module/magazines/repository/magazines.repository.go new file mode 100644 index 0000000..77d543c --- /dev/null +++ b/app/module/magazines/repository/magazines.repository.go @@ -0,0 +1,140 @@ +package repository + +import ( + "fmt" + "github.com/google/uuid" + "strings" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/magazines/request" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" +) + +type magazinesRepository struct { + DB *database.Database +} + +// MagazinesRepository define interface of IMagazinesRepository +type MagazinesRepository interface { + GetAll(clientId *uuid.UUID, req request.MagazinesQueryRequest) (magaziness []*entity.Magazines, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (magazines *entity.Magazines, err error) + FindByFilename(clientId *uuid.UUID, thumbnailName string) (magazineReturn *entity.Magazines, err error) + Create(magazines *entity.Magazines) (magazineReturn *entity.Magazines, err error) + Update(clientId *uuid.UUID, id uint, magazines *entity.Magazines) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) +} + +func NewMagazinesRepository(db *database.Database) MagazinesRepository { + return &magazinesRepository{ + DB: db, + } +} + +// implement interface of IMagazinesRepository +func (_i *magazinesRepository) GetAll(clientId *uuid.UUID, req request.MagazinesQueryRequest) (magaziness []*entity.Magazines, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Magazines{}) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("is_active = ?", true) + + if req.Title != nil && *req.Title != "" { + title := strings.ToLower(*req.Title) + query = query.Where("LOWER(title) LIKE ?", "%"+strings.ToLower(title)+"%") + } + if req.Description != nil && *req.Description != "" { + description := strings.ToLower(*req.Description) + query = query.Where("LOWER(description) LIKE ?", "%"+strings.ToLower(description)+"%") + } + if req.CreatedById != nil { + query = query.Where("created_by_id = ?", req.CreatedById) + } + if req.IsPublish != nil { + query = query.Where("is_publish = ?", req.IsPublish) + } + if req.StatusId != nil { + query = query.Where("status_id = ?", req.StatusId) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&magaziness).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *magazinesRepository) FindOne(clientId *uuid.UUID, id uint) (magazines *entity.Magazines, err error) { + query := _i.DB.DB.Where("id = ?", id) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err := query.First(&magazines).Error; err != nil { + return nil, err + } + + return magazines, nil +} + +func (_i *magazinesRepository) FindByFilename(clientId *uuid.UUID, thumbnailName string) (magazines *entity.Magazines, err error) { + query := _i.DB.DB.Where("thumbnail_name = ?", thumbnailName) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err := query.First(&magazines).Error; err != nil { + return nil, err + } + + return magazines, nil +} + +func (_i *magazinesRepository) Create(magazines *entity.Magazines) (magazineReturn *entity.Magazines, err error) { + result := _i.DB.DB.Create(magazines) + return magazines, result.Error +} + +func (_i *magazinesRepository) Update(clientId *uuid.UUID, id uint, magazines *entity.Magazines) (err error) { + magazinesMap, err := utilSvc.StructToMap(magazines) + if err != nil { + return err + } + query := _i.DB.DB.Model(&entity.Magazines{}).Where(&entity.Magazines{ID: id}) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + return query.Updates(magazinesMap).Error +} + +func (_i *magazinesRepository) Delete(clientId *uuid.UUID, id uint) error { + query := _i.DB.DB.Model(&entity.Magazines{}).Where("id = ?", id) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + return query.Delete(&entity.Magazines{}).Error +} diff --git a/app/module/magazines/request/magazines.request.go b/app/module/magazines/request/magazines.request.go new file mode 100644 index 0000000..14ee645 --- /dev/null +++ b/app/module/magazines/request/magazines.request.go @@ -0,0 +1,119 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type MagazinesGeneric interface { + ToEntity() +} + +type MagazinesQueryRequest struct { + Title *string `json:"title"` + Description *string `json:"description"` + ThumbnailPath *string `json:"thumbnailPath"` + ThumbnailUrl *string `json:"thumbnailUrl"` + PageUrl *string `json:"pageUrl"` + CreatedById *int `json:"createdById"` + StatusId *int `json:"statusId"` + IsPublish *bool `json:"isPublish"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type MagazinesCreateRequest struct { + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + ThumbnailPath *string `json:"thumbnailPath"` + ThumbnailUrl *string `json:"thumbnailUrl"` + PageUrl *string `json:"pageUrl"` + CreatedById *uint `json:"createdById"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` +} + +func (req MagazinesCreateRequest) ToEntity() *entity.Magazines { + return &entity.Magazines{ + Title: req.Title, + Description: req.Description, + ThumbnailPath: req.ThumbnailPath, + ThumbnailUrl: req.ThumbnailUrl, + PageUrl: req.PageUrl, + CreatedById: req.CreatedById, + StatusId: req.StatusId, + IsPublish: req.IsPublish, + PublishedAt: req.PublishedAt, + IsActive: true, + } +} + +type MagazinesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + ThumbnailPath *string `json:"thumbnailPath"` + ThumbnailUrl *string `json:"thumbnailUrl"` + PageUrl *string `json:"pageUrl"` + CreatedById *uint `json:"createdById"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` +} + +func (req MagazinesUpdateRequest) ToEntity() *entity.Magazines { + return &entity.Magazines{ + ID: req.ID, + Title: req.Title, + Description: req.Description, + ThumbnailPath: req.ThumbnailPath, + ThumbnailUrl: req.ThumbnailUrl, + PageUrl: req.PageUrl, + CreatedById: req.CreatedById, + StatusId: req.StatusId, + IsPublish: req.IsPublish, + PublishedAt: req.PublishedAt, + UpdatedAt: time.Now(), + } +} + +type MagazinesQueryRequestContext struct { + Title string `json:"title"` + Description string `json:"description"` + CreatedById string `json:"createdById"` + IsPublish string `json:"isPublish"` + StatusId string `json:"statusId"` +} + +func (req MagazinesQueryRequestContext) ToParamRequest() MagazinesQueryRequest { + var request MagazinesQueryRequest + + if title := req.Title; title != "" { + request.Title = &title + } + if description := req.Description; description != "" { + request.Description = &description + } + if isPublishStr := req.IsPublish; isPublishStr != "" { + isPublish, err := strconv.ParseBool(isPublishStr) + if err == nil { + request.IsPublish = &isPublish + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + if createdByIdStr := req.CreatedById; createdByIdStr != "" { + createdById, err := strconv.Atoi(createdByIdStr) + if err == nil { + request.CreatedById = &createdById + } + } + + return request +} diff --git a/app/module/magazines/response/magazines.response.go b/app/module/magazines/response/magazines.response.go new file mode 100644 index 0000000..6fb0629 --- /dev/null +++ b/app/module/magazines/response/magazines.response.go @@ -0,0 +1,24 @@ +package response + +import ( + "time" + magazineFilesResponse "web-qudo-be/app/module/magazine_files/response" +) + +type MagazinesResponse struct { + ID uint `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + ThumbnailPath *string `json:"thumbnailPath"` + ThumbnailUrl string `json:"thumbnailUrl"` + PageUrl *string `json:"pageUrl"` + CreatedById *uint `json:"createdById"` + StatusId int `json:"statusId"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + MagazineFiles []*magazineFilesResponse.MagazineFilesResponse `json:"files"` +} diff --git a/app/module/magazines/service/magazines.service.go b/app/module/magazines/service/magazines.service.go new file mode 100644 index 0000000..c1fe7f7 --- /dev/null +++ b/app/module/magazines/service/magazines.service.go @@ -0,0 +1,262 @@ +package service + +import ( + "context" + "fmt" + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" + "io" + "log" + "math/rand" + "mime" + "path/filepath" + "strconv" + "strings" + "time" + "web-qudo-be/app/database/entity" + magazineFilesRepository "web-qudo-be/app/module/magazine_files/repository" + "web-qudo-be/app/module/magazines/mapper" + "web-qudo-be/app/module/magazines/repository" + "web-qudo-be/app/module/magazines/request" + "web-qudo-be/app/module/magazines/response" + usersRepository "web-qudo-be/app/module/users/repository" + config "web-qudo-be/config/config" + minioStorage "web-qudo-be/config/config" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" +) + +// MagazinesService +type magazinesService struct { + Repo repository.MagazinesRepository + UsersRepo usersRepository.UsersRepository + MagazineFilesRepo magazineFilesRepository.MagazineFilesRepository + MinioStorage *minioStorage.MinioStorage + Log zerolog.Logger + Cfg *config.Config +} + +// MagazinesService define interface of IMagazinesService +type MagazinesService interface { + All(clientId *uuid.UUID, req request.MagazinesQueryRequest) (magazines []*response.MagazinesResponse, paging paginator.Pagination, err error) + Show(clientId *uuid.UUID, id uint) (magazines *response.MagazinesResponse, err error) + Save(clientId *uuid.UUID, req request.MagazinesCreateRequest, authToken string) (magazines *entity.Magazines, err error) + Update(clientId *uuid.UUID, id uint, req request.MagazinesUpdateRequest) (err error) + SaveThumbnail(clientId *uuid.UUID, c *fiber.Ctx) (err error) + Viewer(clientId *uuid.UUID, c *fiber.Ctx) (err error) + Delete(clientId *uuid.UUID, id uint) error +} + +// NewMagazinesService init MagazinesService +func NewMagazinesService(repo repository.MagazinesRepository, magazineFilesRepo magazineFilesRepository.MagazineFilesRepository, usersRepo usersRepository.UsersRepository, minioStorage *minioStorage.MinioStorage, log zerolog.Logger, cfg *config.Config) MagazinesService { + + return &magazinesService{ + Repo: repo, + MagazineFilesRepo: magazineFilesRepo, + UsersRepo: usersRepo, + MinioStorage: minioStorage, + Log: log, + Cfg: cfg, + } +} + +// All implement interface of MagazinesService +func (_i *magazinesService) All(clientId *uuid.UUID, req request.MagazinesQueryRequest) (magaziness []*response.MagazinesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(clientId, req) + if err != nil { + return + } + + host := _i.Cfg.App.Domain + for _, result := range results { + magaziness = append(magaziness, mapper.MagazinesResponseMapper(result, _i.MagazineFilesRepo, host)) + } + + return +} + +func (_i *magazinesService) Show(clientId *uuid.UUID, id uint) (magazines *response.MagazinesResponse, err error) { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return nil, err + } + host := _i.Cfg.App.Domain + return mapper.MagazinesResponseMapper(result, _i.MagazineFilesRepo, host), nil +} + +func (_i *magazinesService) Save(clientId *uuid.UUID, req request.MagazinesCreateRequest, authToken string) (magazines *entity.Magazines, err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + newReq.CreatedById = &createdBy.ID + + // Set ClientId on entity + newReq.ClientId = clientId + + saveMagazineResponse, err := _i.Repo.Create(newReq) + if err != nil { + return nil, err + } + + return saveMagazineResponse, nil +} + +func (_i *magazinesService) SaveThumbnail(clientId *uuid.UUID, c *fiber.Ctx) (err error) { + + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:magazinesService", "Methods:SaveThumbnail"). + Interface("id", id).Msg("") + + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + + form, err := c.MultipartForm() + if err != nil { + return err + } + files := form.File["files"] + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + // Iterasi semua file yang diunggah + for _, file := range files { + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop1"). + Interface("data", file).Msg("") + + src, err := file.Open() + if err != nil { + return err + } + defer src.Close() + + filename := filepath.Base(file.Filename) + filename = strings.ReplaceAll(filename, " ", "") + filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + extension := filepath.Ext(file.Filename)[1:] + + now := time.Now() + rand.New(rand.NewSource(now.UnixNano())) + randUniqueId := rand.Intn(1000000) + + newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId) + newFilename := newFilenameWithoutExt + "." + extension + objectName := fmt.Sprintf("magazines/thumbnail/%d/%d/%s", now.Year(), now.Month(), newFilename) + + findCategory, err := _i.Repo.FindOne(clientId, uint(id)) + findCategory.ThumbnailName = &newFilename + findCategory.ThumbnailPath = &objectName + err = _i.Repo.Update(clientId, uint(id), findCategory) + if err != nil { + return err + } + + // Upload file ke MinIO + _, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, file.Size, minio.PutObjectOptions{}) + if err != nil { + return err + } + } + + return +} + +func (_i *magazinesService) Update(clientId *uuid.UUID, id uint, req request.MagazinesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + // Set ClientId on entity + entity := req.ToEntity() + entity.ClientId = clientId + + return _i.Repo.Update(clientId, id, entity) +} + +func (_i *magazinesService) Delete(clientId *uuid.UUID, id uint) error { + return _i.Repo.Delete(clientId, id) +} + +func (_i *magazinesService) Viewer(clientId *uuid.UUID, c *fiber.Ctx) (err error) { + thumbnailName := c.Params("thumbnailName") + + emptyImage := "empty-image.jpg" + searchThumbnail := emptyImage + if thumbnailName != emptyImage { + result, err := _i.Repo.FindByFilename(clientId, thumbnailName) + if err != nil { + return err + } + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "magazinesService:Viewer"). + Interface("resultThumbnail", result.ThumbnailPath).Msg("") + + if result.ThumbnailPath != nil { + searchThumbnail = *result.ThumbnailPath + } else { + searchThumbnail = "magazines/thumbnail/" + emptyImage + } + } else { + searchThumbnail = "magazines/thumbnail/" + emptyImage + } + + ctx := context.Background() + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + objectName := searchThumbnail + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{}) + if err != nil { + log.Fatalln(err) + } + defer fileContent.Close() + + contentType := mime.TypeByExtension("." + getFileExtension(objectName)) + if contentType == "" { + contentType = "application/octet-stream" + } + + c.Set("Content-Type", contentType) + + if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil { + return err + } + + return +} + +func getFileExtension(filename string) string { + // split file name + parts := strings.Split(filename, ".") + + // jika tidak ada ekstensi, kembalikan string kosong + if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") { + return "" + } + + // ambil ekstensi terakhir + return parts[len(parts)-1] +} diff --git a/app/module/master_menus/controller/controller.go b/app/module/master_menus/controller/controller.go new file mode 100644 index 0000000..cf58920 --- /dev/null +++ b/app/module/master_menus/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "web-qudo-be/app/module/master_menus/service" + +type Controller struct { + MasterMenus MasterMenusController +} + +func NewController(MasterMenusService service.MasterMenusService) *Controller { + return &Controller{ + MasterMenus: NewMasterMenusController(MasterMenusService), + } +} diff --git a/app/module/master_menus/controller/master_menus.controller.go b/app/module/master_menus/controller/master_menus.controller.go new file mode 100644 index 0000000..f41e607 --- /dev/null +++ b/app/module/master_menus/controller/master_menus.controller.go @@ -0,0 +1,195 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/module/master_menus/request" + "web-qudo-be/app/module/master_menus/service" + "web-qudo-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type masterMenusController struct { + masterMenusService service.MasterMenusService +} + +type MasterMenusController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewMasterMenusController(masterMenusService service.MasterMenusService) MasterMenusController { + return &masterMenusController{ + masterMenusService: masterMenusService, + } +} + +// All MasterMenus +// @Summary Get all MasterMenus +// @Description API for getting all MasterMenus +// @Tags MasterMenus +// @Security Bearer +// @Param req query request.MasterMenusQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /master-menus [get] +func (_i *masterMenusController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.MasterMenusQueryRequestContext{ + Name: c.Query("name"), + Description: c.Query("description"), + ParentMenuId: c.Query("parentMenuId"), + ModuleId: c.Query("moduleId"), + StatusId: c.Query("statusId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + masterMenusData, paging, err := _i.masterMenusService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterMenus list successfully retrieved"}, + Data: masterMenusData, + Meta: paging, + }) +} + +// Show get one MasterMenus +// @Summary Get one MasterMenus +// @Description API for getting one MasterMenus +// @Tags MasterMenus +// @Security Bearer +// @Param id path int true "MasterMenus ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /master-menus/{id} [get] +func (_i *masterMenusController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + masterMenusData, err := _i.masterMenusService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterMenus successfully retrieved"}, + Data: masterMenusData, + }) +} + +// Save create MasterMenus +// @Summary Create MasterMenus +// @Description API for create MasterMenus +// @Tags MasterMenus +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.MasterMenusCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /master-menus [post] +func (_i *masterMenusController) Save(c *fiber.Ctx) error { + req := new(request.MasterMenusCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.masterMenusService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterMenus successfully created"}, + }) +} + +// Update MasterMenus +// @Summary Update MasterMenus +// @Description API for update MasterMenus +// @Tags MasterMenus +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Body request.MasterMenusUpdateRequest +// @Param id path int true "MasterMenus ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /master-menus/{id} [put] +func (_i *masterMenusController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.MasterMenusUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.masterMenusService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterMenus successfully updated"}, + }) +} + +// Delete MasterMenus +// @Summary Delete MasterMenus +// @Description API for delete MasterMenus +// @Tags MasterMenus +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "MasterMenus ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /master-menus/{id} [delete] +func (_i *masterMenusController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.masterMenusService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterMenus successfully deleted"}, + }) +} diff --git a/app/module/master_menus/mapper/master_menus.mapper.go b/app/module/master_menus/mapper/master_menus.mapper.go new file mode 100644 index 0000000..34768de --- /dev/null +++ b/app/module/master_menus/mapper/master_menus.mapper.go @@ -0,0 +1,25 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/master_menus/response" +) + +func MasterMenusResponseMapper(masterMenusReq *entity.MasterMenus) (masterMenusRes *res.MasterMenusResponse) { + if masterMenusReq != nil { + masterMenusRes = &res.MasterMenusResponse{ + ID: masterMenusReq.ID, + Name: masterMenusReq.Name, + Description: masterMenusReq.Description, + ModuleId: masterMenusReq.ModuleId, + ParentMenuId: masterMenusReq.ParentMenuId, + Icon: masterMenusReq.Icon, + Position: masterMenusReq.Position, + StatusId: masterMenusReq.StatusId, + IsActive: masterMenusReq.IsActive, + CreatedAt: masterMenusReq.CreatedAt, + UpdatedAt: masterMenusReq.UpdatedAt, + } + } + return masterMenusRes +} diff --git a/app/module/master_menus/master_menus.module.go b/app/module/master_menus/master_menus.module.go new file mode 100644 index 0000000..f1ac9d3 --- /dev/null +++ b/app/module/master_menus/master_menus.module.go @@ -0,0 +1,53 @@ +package master_menus + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/master_menus/controller" + "web-qudo-be/app/module/master_menus/repository" + "web-qudo-be/app/module/master_menus/service" +) + +// struct of MasterMenusRouter +type MasterMenusRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of MasterMenus module +var NewMasterMenusModule = fx.Options( + // register repository of MasterMenus module + fx.Provide(repository.NewMasterMenusRepository), + + // register service of MasterMenus module + fx.Provide(service.NewMasterMenusService), + + // register controller of MasterMenus module + fx.Provide(controller.NewController), + + // register router of MasterMenus module + fx.Provide(NewMasterMenusRouter), +) + +// init MasterMenusRouter +func NewMasterMenusRouter(fiber *fiber.App, controller *controller.Controller) *MasterMenusRouter { + return &MasterMenusRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of MasterMenus module +func (_i *MasterMenusRouter) RegisterMasterMenusRoutes() { + // define controllers + masterMenusController := _i.Controller.MasterMenus + + // define routes + _i.App.Route("/master-menus", func(router fiber.Router) { + router.Get("/", masterMenusController.All) + router.Get("/:id", masterMenusController.Show) + router.Post("/", masterMenusController.Save) + router.Put("/:id", masterMenusController.Update) + router.Delete("/:id", masterMenusController.Delete) + }) +} diff --git a/app/module/master_menus/repository/master_menus.repository.go b/app/module/master_menus/repository/master_menus.repository.go new file mode 100644 index 0000000..f99049e --- /dev/null +++ b/app/module/master_menus/repository/master_menus.repository.go @@ -0,0 +1,108 @@ +package repository + +import ( + "fmt" + "strings" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/master_menus/request" + "web-qudo-be/utils/paginator" +) + +type masterMenusRepository struct { + DB *database.Database +} + +// MasterMenusRepository define interface of IMasterMenusRepository +type MasterMenusRepository interface { + GetAll(req request.MasterMenusQueryRequest) (masterMenuss []*entity.MasterMenus, paging paginator.Pagination, err error) + FindOne(id uint) (masterMenus *entity.MasterMenus, err error) + FindLastMenuPosition() (position *int, err error) + Create(masterMenus *entity.MasterMenus) (err error) + Update(id uint, masterMenus *entity.MasterMenus) (err error) + Delete(id uint) (err error) +} + +func NewMasterMenusRepository(db *database.Database) MasterMenusRepository { + return &masterMenusRepository{ + DB: db, + } +} + +// implement interface of IMasterMenusRepository +func (_i *masterMenusRepository) GetAll(req request.MasterMenusQueryRequest) (masterMenuss []*entity.MasterMenus, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.MasterMenus{}) + query = query.Where("is_active = ?", true) + + if req.Name != nil && *req.Name != "" { + name := strings.ToLower(*req.Name) + query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(name)+"%") + } + if req.Description != nil && *req.Description != "" { + description := strings.ToLower(*req.Description) + query = query.Where("LOWER(description) LIKE ?", "%"+strings.ToLower(description)+"%") + } + if req.ModuleId != nil { + query = query.Where("module_id = ?", req.ModuleId) + } + if req.ParentMenuId != nil { + query = query.Where("parent_menu_id = ?", req.ParentMenuId) + } + if req.StatusId != nil { + query = query.Where("status_id = ?", req.StatusId) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&masterMenuss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *masterMenusRepository) FindOne(id uint) (masterMenus *entity.MasterMenus, err error) { + if err := _i.DB.DB.First(&masterMenus, id).Error; err != nil { + return nil, err + } + + return masterMenus, nil +} + +func (_i *masterMenusRepository) FindLastMenuPosition() (position *int, err error) { + var masterMenus *entity.MasterMenus + if err := _i.DB.DB.Where("position IS NOT NULL").Order(fmt.Sprintf("%s %s", "position", "DESC")).First(&masterMenus).Error; err != nil { + return nil, err + } + + return masterMenus.Position, nil +} + +func (_i *masterMenusRepository) Create(masterMenus *entity.MasterMenus) (err error) { + return _i.DB.DB.Create(masterMenus).Error +} + +func (_i *masterMenusRepository) Update(id uint, masterMenus *entity.MasterMenus) (err error) { + return _i.DB.DB.Model(&entity.MasterMenus{}). + Where(&entity.MasterMenus{ID: id}). + Updates(masterMenus).Error +} + +func (_i *masterMenusRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.MasterMenus{}, id).Error +} diff --git a/app/module/master_menus/request/master_menus.request.go b/app/module/master_menus/request/master_menus.request.go new file mode 100644 index 0000000..de8c2eb --- /dev/null +++ b/app/module/master_menus/request/master_menus.request.go @@ -0,0 +1,108 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type MasterMenusGeneric interface { + ToEntity() +} + +type MasterMenusQueryRequest struct { + Name *string `json:"name"` + Description *string `json:"description"` + ModuleId *int `json:"moduleId"` + ParentMenuId *int `json:"parentMenuId"` + StatusId *int `json:"statusId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type MasterMenusCreateRequest struct { + Name string `json:"name" validate:"required"` + Description string `json:"description" validate:"required"` + ModuleId int `json:"moduleId" validate:"required"` + Group string `json:"group" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + ParentMenuId *int `json:"parentMenuId"` + Icon *string `json:"icon"` +} + +func (req MasterMenusCreateRequest) ToEntity() *entity.MasterMenus { + return &entity.MasterMenus{ + Name: req.Name, + Description: req.Description, + ModuleId: req.ModuleId, + ParentMenuId: req.ParentMenuId, + Icon: req.Icon, + Group: req.Group, + StatusId: req.StatusId, + } +} + +type MasterMenusUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + Description string `json:"description" validate:"required"` + ModuleId int `json:"moduleId" validate:"required"` + Group string `json:"group" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + ParentMenuId *int `json:"parentMenuId"` + Icon *string `json:"icon"` + UpdatedAt time.Time `json:"updatedAt"` +} + +func (req MasterMenusUpdateRequest) ToEntity() *entity.MasterMenus { + return &entity.MasterMenus{ + ID: req.ID, + Name: req.Name, + Description: req.Description, + ModuleId: req.ModuleId, + ParentMenuId: req.ParentMenuId, + Icon: req.Icon, + Group: req.Group, + StatusId: req.StatusId, + UpdatedAt: time.Now(), + } +} + +type MasterMenusQueryRequestContext struct { + Name string `json:"name"` + Description string `json:"description"` + ModuleId string `json:"moduleId"` + ParentMenuId string `json:"parentMenuId"` + StatusId string `json:"statusId"` +} + +func (req MasterMenusQueryRequestContext) ToParamRequest() MasterMenusQueryRequest { + var request MasterMenusQueryRequest + + if name := req.Name; name != "" { + request.Name = &name + } + if description := req.Description; description != "" { + request.Description = &description + } + if moduleIdStr := req.ModuleId; moduleIdStr != "" { + moduleId, err := strconv.Atoi(moduleIdStr) + if err == nil { + request.ModuleId = &moduleId + } + } + if parentMenuIdStr := req.ParentMenuId; parentMenuIdStr != "" { + parentMenuId, err := strconv.Atoi(parentMenuIdStr) + if err == nil { + request.ParentMenuId = &parentMenuId + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + + return request +} diff --git a/app/module/master_menus/response/master_menus.response.go b/app/module/master_menus/response/master_menus.response.go new file mode 100644 index 0000000..3f814df --- /dev/null +++ b/app/module/master_menus/response/master_menus.response.go @@ -0,0 +1,17 @@ +package response + +import "time" + +type MasterMenusResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + ModuleId int `json:"module_id"` + ParentMenuId *int `json:"parent_menu_id"` + Icon *string `json:"icon"` + Position *int `json:"position"` + StatusId int `json:"status_id"` + IsActive *bool `json:"is_active"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} diff --git a/app/module/master_menus/service/master_menus.service.go b/app/module/master_menus/service/master_menus.service.go new file mode 100644 index 0000000..7a0a664 --- /dev/null +++ b/app/module/master_menus/service/master_menus.service.go @@ -0,0 +1,90 @@ +package service + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/master_menus/mapper" + "web-qudo-be/app/module/master_menus/repository" + "web-qudo-be/app/module/master_menus/request" + "web-qudo-be/app/module/master_menus/response" + "web-qudo-be/utils/paginator" +) + +// MasterMenusService +type masterMenusService struct { + Repo repository.MasterMenusRepository + Log zerolog.Logger +} + +// MasterMenusService define interface of IMasterMenusService +type MasterMenusService interface { + All(req request.MasterMenusQueryRequest) (masterMenus []*response.MasterMenusResponse, paging paginator.Pagination, err error) + Show(id uint) (masterMenus *response.MasterMenusResponse, err error) + Save(req request.MasterMenusCreateRequest) (err error) + Update(id uint, req request.MasterMenusUpdateRequest) (err error) + Delete(id uint) error +} + +// NewMasterMenusService init MasterMenusService +func NewMasterMenusService(repo repository.MasterMenusRepository, log zerolog.Logger) MasterMenusService { + + return &masterMenusService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of MasterMenusService +func (_i *masterMenusService) All(req request.MasterMenusQueryRequest) (masterMenuss []*response.MasterMenusResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + masterMenuss = append(masterMenuss, mapper.MasterMenusResponseMapper(result)) + } + + return +} + +func (_i *masterMenusService) Show(id uint) (masterMenus *response.MasterMenusResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.MasterMenusResponseMapper(result), nil +} + +func (_i *masterMenusService) Save(req request.MasterMenusCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + newReq := req.ToEntity() + var latestPosition, _ = _i.Repo.FindLastMenuPosition() + if err != nil { + return err + } + *latestPosition = *latestPosition + 1 + + if latestPosition != nil { + newReq.Position = latestPosition + } + + return _i.Repo.Create(newReq) +} + +func (_i *masterMenusService) Update(id uint, req request.MasterMenusUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *masterMenusService) Delete(id uint) error { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + isActive := false + result.IsActive = &isActive + return _i.Repo.Update(id, result) +} diff --git a/app/module/master_modules/controller/controller.go b/app/module/master_modules/controller/controller.go new file mode 100644 index 0000000..27e309c --- /dev/null +++ b/app/module/master_modules/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "web-qudo-be/app/module/master_modules/service" + +type Controller struct { + MasterModules MasterModulesController +} + +func NewController(MasterModulesService service.MasterModulesService) *Controller { + return &Controller{ + MasterModules: NewMasterModulesController(MasterModulesService), + } +} diff --git a/app/module/master_modules/controller/master_modules.controller.go b/app/module/master_modules/controller/master_modules.controller.go new file mode 100644 index 0000000..1e98378 --- /dev/null +++ b/app/module/master_modules/controller/master_modules.controller.go @@ -0,0 +1,192 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/module/master_modules/request" + "web-qudo-be/app/module/master_modules/service" + "web-qudo-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type masterModulesController struct { + masterModulesService service.MasterModulesService +} + +type MasterModulesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewMasterModulesController(masterModulesService service.MasterModulesService) MasterModulesController { + return &masterModulesController{ + masterModulesService: masterModulesService, + } +} + +// All MasterModules +// @Summary Get all MasterModules +// @Description API for getting all MasterModules +// @Tags MasterModules +// @Security Bearer +// @Param req query request.MasterModulesQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /master-modules [get] +func (_i *masterModulesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.MasterModulesQueryRequestContext{ + Name: c.Query("name"), + Description: c.Query("description"), + StatusId: c.Query("statusId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + masterModulesData, paging, err := _i.masterModulesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterModules list successfully retrieved"}, + Data: masterModulesData, + Meta: paging, + }) +} + +// Show MasterModules +// @Summary Get one MasterModules +// @Description API for getting one MasterModules +// @Tags MasterModules +// @Security Bearer +// @Param id path int true "MasterModules ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /master-modules/{id} [get] +func (_i *masterModulesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + masterModulesData, err := _i.masterModulesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterModules successfully retrieved"}, + Data: masterModulesData, + }) +} + +// Save MasterModules +// @Summary Create MasterModules +// @Description API for create MasterModules +// @Tags MasterModules +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.MasterModulesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /master-modules [post] +func (_i *masterModulesController) Save(c *fiber.Ctx) error { + req := new(request.MasterModulesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.masterModulesService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterModules successfully created"}, + }) +} + +// Update MasterModules +// @Summary Update MasterModules +// @Description API for update MasterModules +// @Tags MasterModules +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "MasterModules ID" +// @Param payload body request.MasterModulesUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /master-modules/{id} [put] +func (_i *masterModulesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.MasterModulesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.masterModulesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterModules successfully updated"}, + }) +} + +// Delete MasterModules +// @Summary Delete MasterModules +// @Description API for delete MasterModules +// @Tags MasterModules +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "MasterModules ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /master-modules/{id} [delete] +func (_i *masterModulesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.masterModulesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterModules successfully deleted"}, + }) +} diff --git a/app/module/master_modules/mapper/master_modules.mapper.go b/app/module/master_modules/mapper/master_modules.mapper.go new file mode 100644 index 0000000..b52b040 --- /dev/null +++ b/app/module/master_modules/mapper/master_modules.mapper.go @@ -0,0 +1,22 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/master_modules/response" +) + +func MasterModulesResponseMapper(masterModulesReq *entity.MasterModules) (masterModulesRes *res.MasterModulesResponse) { + if masterModulesReq != nil { + masterModulesRes = &res.MasterModulesResponse{ + ID: masterModulesReq.ID, + Name: masterModulesReq.Name, + Description: masterModulesReq.Description, + PathUrl: masterModulesReq.PathUrl, + StatusId: masterModulesReq.StatusId, + IsActive: masterModulesReq.IsActive, + CreatedAt: masterModulesReq.CreatedAt, + UpdatedAt: masterModulesReq.UpdatedAt, + } + } + return masterModulesRes +} diff --git a/app/module/master_modules/master_modules.module.go b/app/module/master_modules/master_modules.module.go new file mode 100644 index 0000000..651165c --- /dev/null +++ b/app/module/master_modules/master_modules.module.go @@ -0,0 +1,53 @@ +package master_modules + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/master_modules/controller" + "web-qudo-be/app/module/master_modules/repository" + "web-qudo-be/app/module/master_modules/service" +) + +// struct of MasterModulesRouter +type MasterModulesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of MasterModules module +var NewMasterModulesModule = fx.Options( + // register repository of MasterModules module + fx.Provide(repository.NewMasterModulesRepository), + + // register service of MasterModules module + fx.Provide(service.NewMasterModulesService), + + // register controller of MasterModules module + fx.Provide(controller.NewController), + + // register router of MasterModules module + fx.Provide(NewMasterModulesRouter), +) + +// init MasterModulesRouter +func NewMasterModulesRouter(fiber *fiber.App, controller *controller.Controller) *MasterModulesRouter { + return &MasterModulesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of MasterModules module +func (_i *MasterModulesRouter) RegisterMasterModulesRoutes() { + // define controllers + masterModulesController := _i.Controller.MasterModules + + // define routes + _i.App.Route("/master-modules", func(router fiber.Router) { + router.Get("/", masterModulesController.All) + router.Get("/:id", masterModulesController.Show) + router.Post("/", masterModulesController.Save) + router.Put("/:id", masterModulesController.Update) + router.Delete("/:id", masterModulesController.Delete) + }) +} diff --git a/app/module/master_modules/repository/master_modules.repository.go b/app/module/master_modules/repository/master_modules.repository.go new file mode 100644 index 0000000..77e6a9d --- /dev/null +++ b/app/module/master_modules/repository/master_modules.repository.go @@ -0,0 +1,92 @@ +package repository + +import ( + "fmt" + "strings" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/master_modules/request" + "web-qudo-be/utils/paginator" +) + +type masterModulesRepository struct { + DB *database.Database +} + +// MasterModulesRepository define interface of IMasterModulesRepository +type MasterModulesRepository interface { + GetAll(req request.MasterModulesQueryRequest) (masterModuless []*entity.MasterModules, paging paginator.Pagination, err error) + FindOne(id uint) (masterModules *entity.MasterModules, err error) + Create(masterModules *entity.MasterModules) (err error) + Update(id uint, masterModules *entity.MasterModules) (err error) + Delete(id uint) (err error) +} + +func NewMasterModulesRepository(db *database.Database) MasterModulesRepository { + return &masterModulesRepository{ + DB: db, + } +} + +// implement interface of IMasterModulesRepository +func (_i *masterModulesRepository) GetAll(req request.MasterModulesQueryRequest) (masterModuless []*entity.MasterModules, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.MasterModules{}) + query = query.Where("is_active = ?", true) + + if req.Name != nil && *req.Name != "" { + name := strings.ToLower(*req.Name) + query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(name)+"%") + } + if req.Description != nil && *req.Description != "" { + description := strings.ToLower(*req.Description) + query = query.Where("LOWER(description) LIKE ?", "%"+strings.ToLower(description)+"%") + } + if req.StatusId != nil { + query = query.Where("status_id = ?", req.StatusId) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&masterModuless).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *masterModulesRepository) FindOne(id uint) (masterModules *entity.MasterModules, err error) { + if err := _i.DB.DB.First(&masterModules, id).Error; err != nil { + return nil, err + } + + return masterModules, nil +} + +func (_i *masterModulesRepository) Create(masterModules *entity.MasterModules) (err error) { + return _i.DB.DB.Create(masterModules).Error +} + +func (_i *masterModulesRepository) Update(id uint, masterModules *entity.MasterModules) (err error) { + return _i.DB.DB.Model(&entity.MasterModules{}). + Where(&entity.MasterModules{ID: id}). + Updates(masterModules).Error +} + +func (_i *masterModulesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.MasterModules{}, id).Error +} diff --git a/app/module/master_modules/request/master_modules.request.go b/app/module/master_modules/request/master_modules.request.go new file mode 100644 index 0000000..c908570 --- /dev/null +++ b/app/module/master_modules/request/master_modules.request.go @@ -0,0 +1,79 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type MasterModulesGeneric interface { + ToEntity() +} + +type MasterModulesQueryRequest struct { + Name *string `json:"name"` + Description *string `json:"description"` + StatusId *int `json:"statusId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type MasterModulesCreateRequest struct { + Name string `json:"name" validate:"required"` + Description string `json:"description" validate:"required"` + PathUrl string `json:"pathUrl" validate:"required"` + StatusId int `json:"statusId" validate:"required"` +} + +func (req MasterModulesCreateRequest) ToEntity() *entity.MasterModules { + return &entity.MasterModules{ + Name: req.Name, + Description: req.Description, + PathUrl: req.PathUrl, + StatusId: req.StatusId, + } +} + +type MasterModulesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + Description string `json:"description" validate:"required"` + PathUrl string `json:"pathUrl" validate:"required"` + StatusId int `json:"statusId" validate:"required"` +} + +func (req MasterModulesUpdateRequest) ToEntity() *entity.MasterModules { + return &entity.MasterModules{ + ID: req.ID, + Name: req.Name, + Description: req.Description, + PathUrl: req.PathUrl, + StatusId: req.StatusId, + UpdatedAt: time.Now(), + } +} + +type MasterModulesQueryRequestContext struct { + Name string `json:"name"` + Description string `json:"description"` + StatusId string `json:"statusId"` +} + +func (req MasterModulesQueryRequestContext) ToParamRequest() MasterModulesQueryRequest { + var request MasterModulesQueryRequest + + if name := req.Name; name != "" { + request.Name = &name + } + if description := req.Description; description != "" { + request.Description = &description + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + + return request +} diff --git a/app/module/master_modules/response/master_modules.response.go b/app/module/master_modules/response/master_modules.response.go new file mode 100644 index 0000000..9a80bf0 --- /dev/null +++ b/app/module/master_modules/response/master_modules.response.go @@ -0,0 +1,14 @@ +package response + +import "time" + +type MasterModulesResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + PathUrl string `json:"path_url"` + StatusId int `json:"status_id"` + IsActive *bool `json:"is_active"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} diff --git a/app/module/master_modules/service/master_modules.service.go b/app/module/master_modules/service/master_modules.service.go new file mode 100644 index 0000000..c60259a --- /dev/null +++ b/app/module/master_modules/service/master_modules.service.go @@ -0,0 +1,79 @@ +package service + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/master_modules/mapper" + "web-qudo-be/app/module/master_modules/repository" + "web-qudo-be/app/module/master_modules/request" + "web-qudo-be/app/module/master_modules/response" + "web-qudo-be/utils/paginator" +) + +// MasterModulesService +type masterModulesService struct { + Repo repository.MasterModulesRepository + Log zerolog.Logger +} + +// MasterModulesService define interface of IMasterModulesService +type MasterModulesService interface { + All(req request.MasterModulesQueryRequest) (masterModules []*response.MasterModulesResponse, paging paginator.Pagination, err error) + Show(id uint) (masterModules *response.MasterModulesResponse, err error) + Save(req request.MasterModulesCreateRequest) (err error) + Update(id uint, req request.MasterModulesUpdateRequest) (err error) + Delete(id uint) error +} + +// NewMasterModulesService init MasterModulesService +func NewMasterModulesService(repo repository.MasterModulesRepository, log zerolog.Logger) MasterModulesService { + + return &masterModulesService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of MasterModulesService +func (_i *masterModulesService) All(req request.MasterModulesQueryRequest) (masterModuless []*response.MasterModulesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + masterModuless = append(masterModuless, mapper.MasterModulesResponseMapper(result)) + } + + return +} + +func (_i *masterModulesService) Show(id uint) (masterModules *response.MasterModulesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.MasterModulesResponseMapper(result), nil +} + +func (_i *masterModulesService) Save(req request.MasterModulesCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + return _i.Repo.Create(req.ToEntity()) +} + +func (_i *masterModulesService) Update(id uint, req request.MasterModulesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *masterModulesService) Delete(id uint) error { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + isActive := false + result.IsActive = &isActive + return _i.Repo.Update(id, result) +} diff --git a/app/module/master_statuses/controller/controller.go b/app/module/master_statuses/controller/controller.go new file mode 100644 index 0000000..bc609dc --- /dev/null +++ b/app/module/master_statuses/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "web-qudo-be/app/module/master_statuses/service" + +type Controller struct { + MasterStatuses MasterStatusesController +} + +func NewController(MasterStatusesService service.MasterStatusesService) *Controller { + return &Controller{ + MasterStatuses: NewMasterStatusesController(MasterStatusesService), + } +} diff --git a/app/module/master_statuses/controller/master_statuses.controller.go b/app/module/master_statuses/controller/master_statuses.controller.go new file mode 100644 index 0000000..65c9860 --- /dev/null +++ b/app/module/master_statuses/controller/master_statuses.controller.go @@ -0,0 +1,181 @@ +package controller + +import ( + "github.com/gofiber/fiber/v2" + "strconv" + "web-qudo-be/app/module/master_statuses/request" + "web-qudo-be/app/module/master_statuses/service" + "web-qudo-be/utils/paginator" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type masterStatusesController struct { + masterStatusesService service.MasterStatusesService +} + +type MasterStatusesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewMasterStatusesController(masterStatusesService service.MasterStatusesService) MasterStatusesController { + return &masterStatusesController{ + masterStatusesService: masterStatusesService, + } +} + +// All MasterStatuses +// @Summary Get all MasterStatuses +// @Description API for getting all MasterStatuses +// @Tags Untags +// @Security Bearer +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /master-statuses [get] +func (_i *masterStatusesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + var req request.MasterStatusesQueryRequest + req.Pagination = paginate + + masterStatusesData, paging, err := _i.masterStatusesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MasterStatuses list successfully retrieved"}, + Data: masterStatusesData, + Meta: paging, + }) +} + +// Show MasterStatuses +// @Summary Get one MasterStatuses +// @Description API for getting one MasterStatuses +// @Tags Untags +// @Security Bearer +// @Param id path int true "MasterStatuses ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /master-statuses/{id} [get] +func (_i *masterStatusesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + masterStatusesData, err := _i.masterStatusesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MasterStatuses successfully retrieved"}, + Data: masterStatusesData, + }) +} + +// Save MasterStatuses +// @Summary Create MasterStatuses +// @Description API for create MasterStatuses +// @Tags Untags +// @Security Bearer +// @Body request.MasterStatusesCreateRequest +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /master-statuses [post] +func (_i *masterStatusesController) Save(c *fiber.Ctx) error { + req := new(request.MasterStatusesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.masterStatusesService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MasterStatuses successfully created"}, + }) +} + +// Update MasterStatuses +// @Summary Update MasterStatuses +// @Description API for update MasterStatuses +// @Tags Untags +// @Security Bearer +// @Body request.MasterStatusesUpdateRequest +// @Param id path int true "MasterStatuses ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /master-statuses/{id} [put] +func (_i *masterStatusesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.MasterStatusesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.masterStatusesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MasterStatuses successfully updated"}, + }) +} + +// Delete MasterStatuses +// @Summary Delete MasterStatuses +// @Description API for delete MasterStatuses +// @Tags Untags +// @Security Bearer +// @Param id path int true "MasterStatuses ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /master-statuses/{id} [delete] +func (_i *masterStatusesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.masterStatusesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MasterStatuses successfully deleted"}, + }) +} diff --git a/app/module/master_statuses/entity/master_statuses.entity.go b/app/module/master_statuses/entity/master_statuses.entity.go new file mode 100644 index 0000000..cf63c2f --- /dev/null +++ b/app/module/master_statuses/entity/master_statuses.entity.go @@ -0,0 +1,7 @@ +package entity + +type MasterStatuses struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Name string `json:"name" gorm:"type:varchar"` + IsActive bool `json:"is_active" gorm:"type:bool"` +} \ No newline at end of file diff --git a/app/module/master_statuses/mapper/master_statuses.mapper.go b/app/module/master_statuses/mapper/master_statuses.mapper.go new file mode 100644 index 0000000..9a28b73 --- /dev/null +++ b/app/module/master_statuses/mapper/master_statuses.mapper.go @@ -0,0 +1,17 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/master_statuses/response" +) + +func MasterStatusesResponseMapper(masterStatusesReq *entity.MasterStatuses) (masterStatusesRes *res.MasterStatusesResponse) { + if masterStatusesReq != nil { + masterStatusesRes = &res.MasterStatusesResponse{ + ID: masterStatusesReq.ID, + Name: masterStatusesReq.Name, + IsActive: masterStatusesReq.IsActive, + } + } + return masterStatusesRes +} diff --git a/app/module/master_statuses/master_statuses.module.go b/app/module/master_statuses/master_statuses.module.go new file mode 100644 index 0000000..e5e3095 --- /dev/null +++ b/app/module/master_statuses/master_statuses.module.go @@ -0,0 +1,53 @@ +package master_statuses + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/master_statuses/controller" + "web-qudo-be/app/module/master_statuses/repository" + "web-qudo-be/app/module/master_statuses/service" +) + +// struct of MasterStatusesRouter +type MasterStatusesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of MasterStatuses module +var NewMasterStatusesModule = fx.Options( + // register repository of MasterStatuses module + fx.Provide(repository.NewMasterStatusesRepository), + + // register service of MasterStatuses module + fx.Provide(service.NewMasterStatusesService), + + // register controller of MasterStatuses module + fx.Provide(controller.NewController), + + // register router of MasterStatuses module + fx.Provide(NewMasterStatusesRouter), +) + +// init MasterStatusesRouter +func NewMasterStatusesRouter(fiber *fiber.App, controller *controller.Controller) *MasterStatusesRouter { + return &MasterStatusesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of MasterStatuses module +func (_i *MasterStatusesRouter) RegisterMasterStatusesRoutes() { + // define controllers + masterStatusesController := _i.Controller.MasterStatuses + + // define routes + _i.App.Route("/master-statuses", func(router fiber.Router) { + router.Get("/", masterStatusesController.All) + router.Get("/:id", masterStatusesController.Show) + router.Post("/", masterStatusesController.Save) + router.Put("/:id", masterStatusesController.Update) + router.Delete("/:id", masterStatusesController.Delete) + }) +} diff --git a/app/module/master_statuses/repository/master_statuses.repository.go b/app/module/master_statuses/repository/master_statuses.repository.go new file mode 100644 index 0000000..43f6b57 --- /dev/null +++ b/app/module/master_statuses/repository/master_statuses.repository.go @@ -0,0 +1,69 @@ +package repository + +import ( + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/master_statuses/request" + "web-qudo-be/utils/paginator" +) + +type masterStatusesRepository struct { + DB *database.Database +} + +// MasterStatusesRepository define interface of IMasterStatusesRepository +type MasterStatusesRepository interface { + GetAll(req request.MasterStatusesQueryRequest) (masterStatusess []*entity.MasterStatuses, paging paginator.Pagination, err error) + FindOne(id uint) (masterStatuses *entity.MasterStatuses, err error) + Create(masterStatuses *entity.MasterStatuses) (err error) + Update(id uint, masterStatuses *entity.MasterStatuses) (err error) + Delete(id uint) (err error) +} + +func NewMasterStatusesRepository(db *database.Database) MasterStatusesRepository { + return &masterStatusesRepository{ + DB: db, + } +} + +// implement interface of IMasterStatusesRepository +func (_i *masterStatusesRepository) GetAll(req request.MasterStatusesQueryRequest) (masterStatusess []*entity.MasterStatuses, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.MasterStatuses{}) + query.Count(&count) + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&masterStatusess).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *masterStatusesRepository) FindOne(id uint) (masterStatuses *entity.MasterStatuses, err error) { + if err := _i.DB.DB.First(&masterStatuses, id).Error; err != nil { + return nil, err + } + + return masterStatuses, nil +} + +func (_i *masterStatusesRepository) Create(masterStatuses *entity.MasterStatuses) (err error) { + return _i.DB.DB.Create(masterStatuses).Error +} + +func (_i *masterStatusesRepository) Update(id uint, masterStatuses *entity.MasterStatuses) (err error) { + return _i.DB.DB.Model(&entity.MasterStatuses{}). + Where(&entity.MasterStatuses{ID: id}). + Updates(masterStatuses).Error +} + +func (_i *masterStatusesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.MasterStatuses{}, id).Error +} diff --git a/app/module/master_statuses/request/master_statuses.request.go b/app/module/master_statuses/request/master_statuses.request.go new file mode 100644 index 0000000..5b892f3 --- /dev/null +++ b/app/module/master_statuses/request/master_statuses.request.go @@ -0,0 +1,42 @@ +package request + +import ( + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type MasterStatusesGeneric interface { + ToEntity() +} + +type MasterStatusesQueryRequest struct { + Name string `json:"name" validate:"required"` + IsActive bool `json:"is_active" validate:"required"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type MasterStatusesCreateRequest struct { + Name string `json:"name" validate:"required"` + IsActive bool `json:"is_active" validate:"required"` +} + +func (req MasterStatusesCreateRequest) ToEntity() *entity.MasterStatuses { + return &entity.MasterStatuses{ + Name: req.Name, + IsActive: req.IsActive, + } +} + +type MasterStatusesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + IsActive bool `json:"is_active" validate:"required"` +} + +func (req MasterStatusesUpdateRequest) ToEntity() *entity.MasterStatuses { + return &entity.MasterStatuses{ + ID: req.ID, + Name: req.Name, + IsActive: req.IsActive, + } +} diff --git a/app/module/master_statuses/response/master_statuses.response.go b/app/module/master_statuses/response/master_statuses.response.go new file mode 100644 index 0000000..51f5765 --- /dev/null +++ b/app/module/master_statuses/response/master_statuses.response.go @@ -0,0 +1,7 @@ +package response + +type MasterStatusesResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + IsActive bool `json:"is_active"` +} \ No newline at end of file diff --git a/app/module/master_statuses/service/master_statuses.service.go b/app/module/master_statuses/service/master_statuses.service.go new file mode 100644 index 0000000..61ff6dd --- /dev/null +++ b/app/module/master_statuses/service/master_statuses.service.go @@ -0,0 +1,72 @@ +package service + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/master_statuses/mapper" + "web-qudo-be/app/module/master_statuses/repository" + "web-qudo-be/app/module/master_statuses/request" + "web-qudo-be/app/module/master_statuses/response" + "web-qudo-be/utils/paginator" +) + +// MasterStatusesService +type masterStatusesService struct { + Repo repository.MasterStatusesRepository + Log zerolog.Logger +} + +// MasterStatusesService define interface of IMasterStatusesService +type MasterStatusesService interface { + All(req request.MasterStatusesQueryRequest) (masterStatuses []*response.MasterStatusesResponse, paging paginator.Pagination, err error) + Show(id uint) (masterStatuses *response.MasterStatusesResponse, err error) + Save(req request.MasterStatusesCreateRequest) (err error) + Update(id uint, req request.MasterStatusesUpdateRequest) (err error) + Delete(id uint) error +} + +// NewMasterStatusesService init MasterStatusesService +func NewMasterStatusesService(repo repository.MasterStatusesRepository, log zerolog.Logger) MasterStatusesService { + + return &masterStatusesService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of MasterStatusesService +func (_i *masterStatusesService) All(req request.MasterStatusesQueryRequest) (masterStatusess []*response.MasterStatusesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + masterStatusess = append(masterStatusess, mapper.MasterStatusesResponseMapper(result)) + } + + return +} + +func (_i *masterStatusesService) Show(id uint) (masterStatuses *response.MasterStatusesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.MasterStatusesResponseMapper(result), nil +} + +func (_i *masterStatusesService) Save(req request.MasterStatusesCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + return _i.Repo.Create(req.ToEntity()) +} + +func (_i *masterStatusesService) Update(id uint, req request.MasterStatusesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *masterStatusesService) Delete(id uint) error { + return _i.Repo.Delete(id) +} diff --git a/app/module/provinces/controller/controller.go b/app/module/provinces/controller/controller.go new file mode 100644 index 0000000..95f5199 --- /dev/null +++ b/app/module/provinces/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "web-qudo-be/app/module/provinces/service" + +type Controller struct { + Provinces ProvincesController +} + +func NewController(ProvincesService service.ProvincesService) *Controller { + return &Controller{ + Provinces: NewProvincesController(ProvincesService), + } +} diff --git a/app/module/provinces/controller/provinces.controller.go b/app/module/provinces/controller/provinces.controller.go new file mode 100644 index 0000000..6856c7b --- /dev/null +++ b/app/module/provinces/controller/provinces.controller.go @@ -0,0 +1,181 @@ +package controller + +import ( + "github.com/gofiber/fiber/v2" + "strconv" + "web-qudo-be/app/module/provinces/request" + "web-qudo-be/app/module/provinces/service" + "web-qudo-be/utils/paginator" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type provincesController struct { + provincesService service.ProvincesService +} + +type ProvincesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewProvincesController(provincesService service.ProvincesService) ProvincesController { + return &provincesController{ + provincesService: provincesService, + } +} + +// All Provinces +// @Summary Get all Provinces +// @Description API for getting all Provinces +// @Tags Untags +// @Security Bearer +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /provinces [get] +func (_i *provincesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + var req request.ProvincesQueryRequest + req.Pagination = paginate + + provincesData, paging, err := _i.provincesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Provinces list successfully retrieved"}, + Data: provincesData, + Meta: paging, + }) +} + +// Show Provinces +// @Summary Get one Provinces +// @Description API for getting one Provinces +// @Tags Untags +// @Security Bearer +// @Param id path int true "Provinces ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /provinces/{id} [get] +func (_i *provincesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + provincesData, err := _i.provincesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Provinces successfully retrieved"}, + Data: provincesData, + }) +} + +// Save Provinces +// @Summary Create Provinces +// @Description API for create Provinces +// @Tags Untags +// @Security Bearer +// @Body request.ProvincesCreateRequest +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /provinces [post] +func (_i *provincesController) Save(c *fiber.Ctx) error { + req := new(request.ProvincesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.provincesService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Provinces successfully created"}, + }) +} + +// Update Provinces +// @Summary Update Provinces +// @Description API for update Provinces +// @Tags Untags +// @Security Bearer +// @Body request.ProvincesUpdateRequest +// @Param id path int true "Provinces ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /provinces/{id} [put] +func (_i *provincesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ProvincesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.provincesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Provinces successfully updated"}, + }) +} + +// Delete Provinces +// @Summary Delete Provinces +// @Description API for delete Provinces +// @Tags Untags +// @Security Bearer +// @Param id path int true "Provinces ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /provinces/{id} [delete] +func (_i *provincesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.provincesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Provinces successfully deleted"}, + }) +} diff --git a/app/module/provinces/mapper/provinces.mapper.go b/app/module/provinces/mapper/provinces.mapper.go new file mode 100644 index 0000000..233141e --- /dev/null +++ b/app/module/provinces/mapper/provinces.mapper.go @@ -0,0 +1,19 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/provinces/response" +) + +func ProvincesResponseMapper(provincesReq *entity.Provinces) (provincesRes *res.ProvincesResponse) { + if provincesReq != nil { + provincesRes = &res.ProvincesResponse{ + ID: provincesReq.ID, + ProvName: provincesReq.ProvName, + LocationId: provincesReq.LocationId, + Status: provincesReq.Status, + Timezone: provincesReq.Timezone, + } + } + return provincesRes +} diff --git a/app/module/provinces/provinces.module.go b/app/module/provinces/provinces.module.go new file mode 100644 index 0000000..5efb9c2 --- /dev/null +++ b/app/module/provinces/provinces.module.go @@ -0,0 +1,53 @@ +package provinces + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/provinces/controller" + "web-qudo-be/app/module/provinces/repository" + "web-qudo-be/app/module/provinces/service" +) + +// struct of ProvincesRouter +type ProvincesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of Provinces module +var NewProvincesModule = fx.Options( + // register repository of Provinces module + fx.Provide(repository.NewProvincesRepository), + + // register service of Provinces module + fx.Provide(service.NewProvincesService), + + // register controller of Provinces module + fx.Provide(controller.NewController), + + // register router of Provinces module + fx.Provide(NewProvincesRouter), +) + +// init ProvincesRouter +func NewProvincesRouter(fiber *fiber.App, controller *controller.Controller) *ProvincesRouter { + return &ProvincesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of Provinces module +func (_i *ProvincesRouter) RegisterProvincesRoutes() { + // define controllers + provincesController := _i.Controller.Provinces + + // define routes + _i.App.Route("/provinces", func(router fiber.Router) { + router.Get("/", provincesController.All) + router.Get("/:id", provincesController.Show) + router.Post("/", provincesController.Save) + router.Put("/:id", provincesController.Update) + router.Delete("/:id", provincesController.Delete) + }) +} diff --git a/app/module/provinces/repository/provinces.repository.go b/app/module/provinces/repository/provinces.repository.go new file mode 100644 index 0000000..2aba808 --- /dev/null +++ b/app/module/provinces/repository/provinces.repository.go @@ -0,0 +1,69 @@ +package repository + +import ( + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/provinces/request" + "web-qudo-be/utils/paginator" +) + +type provincesRepository struct { + DB *database.Database +} + +// ProvincesRepository define interface of IProvincesRepository +type ProvincesRepository interface { + GetAll(req request.ProvincesQueryRequest) (provincess []*entity.Provinces, paging paginator.Pagination, err error) + FindOne(id uint) (provinces *entity.Provinces, err error) + Create(provinces *entity.Provinces) (err error) + Update(id uint, provinces *entity.Provinces) (err error) + Delete(id uint) (err error) +} + +func NewProvincesRepository(db *database.Database) ProvincesRepository { + return &provincesRepository{ + DB: db, + } +} + +// implement interface of IProvincesRepository +func (_i *provincesRepository) GetAll(req request.ProvincesQueryRequest) (provincess []*entity.Provinces, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Provinces{}) + query.Count(&count) + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&provincess).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *provincesRepository) FindOne(id uint) (provinces *entity.Provinces, err error) { + if err := _i.DB.DB.First(&provinces, id).Error; err != nil { + return nil, err + } + + return provinces, nil +} + +func (_i *provincesRepository) Create(provinces *entity.Provinces) (err error) { + return _i.DB.DB.Create(provinces).Error +} + +func (_i *provincesRepository) Update(id uint, provinces *entity.Provinces) (err error) { + return _i.DB.DB.Model(&entity.Provinces{}). + Where(&entity.Provinces{ID: id}). + Updates(provinces).Error +} + +func (_i *provincesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.Provinces{}, id).Error +} diff --git a/app/module/provinces/request/provinces.request.go b/app/module/provinces/request/provinces.request.go new file mode 100644 index 0000000..4494fdd --- /dev/null +++ b/app/module/provinces/request/provinces.request.go @@ -0,0 +1,52 @@ +package request + +import ( + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type ProvincesGeneric interface { + ToEntity() +} + +type ProvincesQueryRequest struct { + ProvName string `json:"prov_name" validate:"required"` + LocationId int `json:"location_id" validate:"required"` + Status int `json:"status" validate:"required"` + Timezone string `json:"timezone" validate:"required"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ProvincesCreateRequest struct { + ProvName string `json:"prov_name" validate:"required"` + LocationId int `json:"location_id" validate:"required"` + Status int `json:"status" validate:"required"` + Timezone string `json:"timezone" validate:"required"` +} + +func (req ProvincesCreateRequest) ToEntity() *entity.Provinces { + return &entity.Provinces{ + ProvName: req.ProvName, + LocationId: req.LocationId, + Status: req.Status, + Timezone: req.Timezone, + } +} + +type ProvincesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + ProvName string `json:"prov_name" validate:"required"` + LocationId int `json:"location_id" validate:"required"` + Status int `json:"status" validate:"required"` + Timezone string `json:"timezone" validate:"required"` +} + +func (req ProvincesUpdateRequest) ToEntity() *entity.Provinces { + return &entity.Provinces{ + ID: req.ID, + ProvName: req.ProvName, + LocationId: req.LocationId, + Status: req.Status, + Timezone: req.Timezone, + } +} diff --git a/app/module/provinces/response/provinces.response.go b/app/module/provinces/response/provinces.response.go new file mode 100644 index 0000000..0bfb39a --- /dev/null +++ b/app/module/provinces/response/provinces.response.go @@ -0,0 +1,9 @@ +package response + +type ProvincesResponse struct { + ID uint `json:"id"` + ProvName string `json:"prov_name"` + LocationId int `json:"location_id"` + Status int `json:"status"` + Timezone string `json:"timezone"` +} diff --git a/app/module/provinces/service/provinces.service.go b/app/module/provinces/service/provinces.service.go new file mode 100644 index 0000000..8026dbb --- /dev/null +++ b/app/module/provinces/service/provinces.service.go @@ -0,0 +1,72 @@ +package service + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/provinces/mapper" + "web-qudo-be/app/module/provinces/repository" + "web-qudo-be/app/module/provinces/request" + "web-qudo-be/app/module/provinces/response" + "web-qudo-be/utils/paginator" +) + +// ProvincesService +type provincesService struct { + Repo repository.ProvincesRepository + Log zerolog.Logger +} + +// ProvincesService define interface of IProvincesService +type ProvincesService interface { + All(req request.ProvincesQueryRequest) (provinces []*response.ProvincesResponse, paging paginator.Pagination, err error) + Show(id uint) (provinces *response.ProvincesResponse, err error) + Save(req request.ProvincesCreateRequest) (err error) + Update(id uint, req request.ProvincesUpdateRequest) (err error) + Delete(id uint) error +} + +// NewProvincesService init ProvincesService +func NewProvincesService(repo repository.ProvincesRepository, log zerolog.Logger) ProvincesService { + + return &provincesService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of ProvincesService +func (_i *provincesService) All(req request.ProvincesQueryRequest) (provincess []*response.ProvincesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + provincess = append(provincess, mapper.ProvincesResponseMapper(result)) + } + + return +} + +func (_i *provincesService) Show(id uint) (provinces *response.ProvincesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.ProvincesResponseMapper(result), nil +} + +func (_i *provincesService) Save(req request.ProvincesCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + return _i.Repo.Create(req.ToEntity()) +} + +func (_i *provincesService) Update(id uint, req request.ProvincesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *provincesService) Delete(id uint) error { + return _i.Repo.Delete(id) +} diff --git a/app/module/schedules/controller/schedules.controller.go b/app/module/schedules/controller/schedules.controller.go new file mode 100644 index 0000000..9b43b43 --- /dev/null +++ b/app/module/schedules/controller/schedules.controller.go @@ -0,0 +1,235 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/schedules/request" + "web-qudo-be/app/module/schedules/service" + "web-qudo-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type schedulesController struct { + schedulesService service.SchedulesService + Log zerolog.Logger +} + +type SchedulesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewSchedulesController(schedulesService service.SchedulesService, log zerolog.Logger) SchedulesController { + return &schedulesController{ + schedulesService: schedulesService, + Log: log, + } +} + +// All Schedules +// @Summary Get all Schedules +// @Description API for getting all Schedules +// @Tags Schedules +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param req query request.SchedulesQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /schedules [get] +func (_i *schedulesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.SchedulesQueryRequestContext{ + Title: c.Query("title"), + Description: c.Query("description"), + Location: c.Query("location"), + TypeId: c.Query("typeId"), + StartDate: c.Query("startDate"), + EndDate: c.Query("endDate"), + IsLiveStreaming: c.Query("isLiveStreaming"), + Speakers: c.Query("speakers"), + StatusId: c.Query("statusId"), + CreatedById: c.Query("createdById"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Get Authorization token from header + authToken := c.Get("Authorization") + _i.Log.Info().Interface("clientId", clientId).Msg("") + _i.Log.Info().Str("authToken", authToken).Msg("") + + schedulesData, paging, err := _i.schedulesService.All(clientId, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Schedules list successfully retrieved"}, + Data: schedulesData, + Meta: paging, + }) +} + +// Show Schedule +// @Summary Get one Schedule +// @Description API for getting one Schedule +// @Tags Schedules +// @Security Bearer +// @Param id path int true "Schedule ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /schedules/{id} [get] +func (_i *schedulesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + scheduleData, err := _i.schedulesService.Show(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Schedule successfully retrieved"}, + Data: scheduleData, + }) +} + +// Save Schedule +// @Summary Create Schedule +// @Description API for create Schedule +// @Tags Schedules +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.SchedulesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /schedules [post] +func (_i *schedulesController) Save(c *fiber.Ctx) error { + req := new(request.SchedulesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + _i.Log.Info().Interface("clientId", clientId).Msg("") + _i.Log.Info().Interface("authToken", authToken).Msg("") + + dataResult, err := _i.schedulesService.Save(clientId, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Schedule successfully created"}, + Data: dataResult, + }) +} + +// Update Schedule +// @Summary Update Schedule +// @Description API for update Schedule +// @Tags Schedules +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.SchedulesUpdateRequest true "Required payload" +// @Param id path int true "Schedule ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /schedules/{id} [put] +func (_i *schedulesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.SchedulesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.schedulesService.Update(clientId, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Schedule successfully updated"}, + }) +} + +// Delete Schedule +// @Summary Delete Schedule +// @Description API for delete Schedule +// @Tags Schedules +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "Schedule ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /schedules/{id} [delete] +func (_i *schedulesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.schedulesService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Schedule successfully deleted"}, + }) +} diff --git a/app/module/schedules/mapper/schedules.mapper.go b/app/module/schedules/mapper/schedules.mapper.go new file mode 100644 index 0000000..0cbdeb5 --- /dev/null +++ b/app/module/schedules/mapper/schedules.mapper.go @@ -0,0 +1,37 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + schedulesResponse "web-qudo-be/app/module/schedules/response" +) + +func ToSchedulesResponse(schedule *entity.Schedules) *schedulesResponse.SchedulesResponse { + return &schedulesResponse.SchedulesResponse{ + ID: schedule.ID, + Title: schedule.Title, + Description: schedule.Description, + Location: schedule.Location, + IsLiveStreaming: schedule.IsLiveStreaming, + LiveStreamingUrl: schedule.LiveStreamingUrl, + TypeId: schedule.TypeId, + StartDate: schedule.StartDate, + EndDate: schedule.EndDate, + StartTime: schedule.StartTime, + EndTime: schedule.EndTime, + Speakers: schedule.Speakers, + PosterImagePath: schedule.PosterImagePath, + CreatedById: schedule.CreatedById, + StatusId: schedule.StatusId, + IsActive: schedule.IsActive, + CreatedAt: schedule.CreatedAt, + UpdatedAt: schedule.UpdatedAt, + } +} + +func ToSchedulesResponseList(schedules []*entity.Schedules) []*schedulesResponse.SchedulesResponse { + var responses []*schedulesResponse.SchedulesResponse + for _, schedule := range schedules { + responses = append(responses, ToSchedulesResponse(schedule)) + } + return responses +} diff --git a/app/module/schedules/repository/schedules.repository.go b/app/module/schedules/repository/schedules.repository.go new file mode 100644 index 0000000..ae5273a --- /dev/null +++ b/app/module/schedules/repository/schedules.repository.go @@ -0,0 +1,196 @@ +package repository + +import ( + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/schedules/request" + "web-qudo-be/utils/paginator" + + "github.com/google/uuid" +) + +type SchedulesRepository interface { + All(clientId *uuid.UUID, req request.SchedulesQueryRequest) ([]*entity.Schedules, *paginator.Pagination, error) + Show(clientId *uuid.UUID, id uint) (*entity.Schedules, error) + Save(clientId *uuid.UUID, schedule *entity.Schedules) (*entity.Schedules, error) + Update(clientId *uuid.UUID, id uint, schedule *entity.Schedules) error + Delete(clientId *uuid.UUID, id uint) error + Count(clientId *uuid.UUID, req request.SchedulesQueryRequest) (int64, error) +} + +type schedulesRepository struct { + db *database.Database +} + +func NewSchedulesRepository(db *database.Database) SchedulesRepository { + return &schedulesRepository{ + db: db, + } +} + +func (_i *schedulesRepository) All(clientId *uuid.UUID, req request.SchedulesQueryRequest) ([]*entity.Schedules, *paginator.Pagination, error) { + var schedules []*entity.Schedules + var total int64 + + query := _i.db.DB.Model(&entity.Schedules{}) + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", *clientId) + } + + // Apply filters + if req.Title != nil { + query = query.Where("title ILIKE ?", "%"+*req.Title+"%") + } + if req.Description != nil { + query = query.Where("description ILIKE ?", "%"+*req.Description+"%") + } + if req.Location != nil { + query = query.Where("location ILIKE ?", "%"+*req.Location+"%") + } + if req.TypeId != nil { + query = query.Where("type_id = ?", *req.TypeId) + } + if req.StartDate != nil { + query = query.Where("start_date >= ?", *req.StartDate) + } + if req.EndDate != nil { + query = query.Where("end_date <= ?", *req.EndDate) + } + if req.IsLiveStreaming != nil { + query = query.Where("is_live_streaming = ?", *req.IsLiveStreaming) + } + if req.Speakers != nil { + query = query.Where("speakers ILIKE ?", "%"+*req.Speakers+"%") + } + if req.StatusId != nil { + query = query.Where("status_id = ?", *req.StatusId) + } + if req.CreatedById != nil { + query = query.Where("created_by_id = ?", *req.CreatedById) + } + + // Count total records + if err := query.Count(&total).Error; err != nil { + return nil, nil, err + } + + // Apply pagination + if req.Pagination != nil { + offset := (req.Pagination.Page - 1) * req.Pagination.Limit + query = query.Offset(offset).Limit(req.Pagination.Limit) + } + + // Order by created_at desc + query = query.Order("created_at DESC") + + // Execute query + if err := query.Find(&schedules).Error; err != nil { + return nil, nil, err + } + + // Create pagination response + pagination := &paginator.Pagination{ + Page: req.Pagination.Page, + Limit: req.Pagination.Limit, + Count: total, + TotalPage: int((total + int64(req.Pagination.Limit) - 1) / int64(req.Pagination.Limit)), + } + + return schedules, pagination, nil +} + +func (_i *schedulesRepository) Show(clientId *uuid.UUID, id uint) (*entity.Schedules, error) { + var schedule entity.Schedules + + query := _i.db.DB.Model(&entity.Schedules{}) + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", *clientId) + } + + if err := query.Where("id = ?", id).First(&schedule).Error; err != nil { + return nil, err + } + + return &schedule, nil +} + +func (_i *schedulesRepository) Save(clientId *uuid.UUID, schedule *entity.Schedules) (*entity.Schedules, error) { + schedule.ClientId = clientId + + if err := _i.db.DB.Create(schedule).Error; err != nil { + return nil, err + } + + return schedule, nil +} + +func (_i *schedulesRepository) Update(clientId *uuid.UUID, id uint, schedule *entity.Schedules) error { + query := _i.db.DB.Model(&entity.Schedules{}) + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", *clientId) + } + + return query.Where("id = ?", id).Updates(schedule).Error +} + +func (_i *schedulesRepository) Delete(clientId *uuid.UUID, id uint) error { + query := _i.db.DB.Model(&entity.Schedules{}) + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", *clientId) + } + + return query.Where("id = ?", id).Delete(&entity.Schedules{}).Error +} + +func (_i *schedulesRepository) Count(clientId *uuid.UUID, req request.SchedulesQueryRequest) (int64, error) { + var total int64 + + query := _i.db.DB.Model(&entity.Schedules{}) + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", *clientId) + } + + // Apply filters + if req.Title != nil { + query = query.Where("title ILIKE ?", "%"+*req.Title+"%") + } + if req.Description != nil { + query = query.Where("description ILIKE ?", "%"+*req.Description+"%") + } + if req.Location != nil { + query = query.Where("location ILIKE ?", "%"+*req.Location+"%") + } + if req.TypeId != nil { + query = query.Where("type_id = ?", *req.TypeId) + } + if req.StartDate != nil { + query = query.Where("start_date >= ?", *req.StartDate) + } + if req.EndDate != nil { + query = query.Where("end_date <= ?", *req.EndDate) + } + if req.IsLiveStreaming != nil { + query = query.Where("is_live_streaming = ?", *req.IsLiveStreaming) + } + if req.Speakers != nil { + query = query.Where("speakers ILIKE ?", "%"+*req.Speakers+"%") + } + if req.StatusId != nil { + query = query.Where("status_id = ?", *req.StatusId) + } + if req.CreatedById != nil { + query = query.Where("created_by_id = ?", *req.CreatedById) + } + + return total, query.Count(&total).Error +} diff --git a/app/module/schedules/request/schedules.request.go b/app/module/schedules/request/schedules.request.go new file mode 100644 index 0000000..613c620 --- /dev/null +++ b/app/module/schedules/request/schedules.request.go @@ -0,0 +1,162 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type SchedulesGeneric interface { + ToEntity() +} + +type SchedulesQueryRequest struct { + Title *string `json:"title"` + Description *string `json:"description"` + Location *string `json:"location"` + TypeId *int `json:"typeId"` + StartDate *time.Time `json:"startDate"` + EndDate *time.Time `json:"endDate"` + IsLiveStreaming *bool `json:"isLiveStreaming"` + Speakers *string `json:"speakers"` + StatusId *int `json:"statusId"` + CreatedById *uint `json:"createdById"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type SchedulesCreateRequest struct { + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + Location string `json:"location" validate:"required"` + IsLiveStreaming *bool `json:"isLiveStreaming"` + LiveStreamingUrl *string `json:"liveStreamingUrl"` + TypeId int `json:"typeId" validate:"required"` + StartDate *time.Time `json:"startDate"` + EndDate *time.Time `json:"endDate"` + StartTime *string `json:"startTime"` + EndTime *string `json:"endTime"` + Speakers string `json:"speakers" validate:"required"` + PosterImagePath *string `json:"posterImagePath"` + CreatedById *uint `json:"createdById"` +} + +func (req SchedulesCreateRequest) ToEntity() *entity.Schedules { + return &entity.Schedules{ + Title: req.Title, + Description: req.Description, + Location: req.Location, + IsLiveStreaming: req.IsLiveStreaming, + LiveStreamingUrl: req.LiveStreamingUrl, + TypeId: req.TypeId, + StartDate: req.StartDate, + EndDate: req.EndDate, + StartTime: req.StartTime, + EndTime: req.EndTime, + Speakers: req.Speakers, + PosterImagePath: req.PosterImagePath, + CreatedById: req.CreatedById, + } +} + +type SchedulesUpdateRequest struct { + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + Location string `json:"location" validate:"required"` + IsLiveStreaming *bool `json:"isLiveStreaming"` + LiveStreamingUrl *string `json:"liveStreamingUrl"` + TypeId int `json:"typeId" validate:"required"` + StartDate *time.Time `json:"startDate"` + EndDate *time.Time `json:"endDate"` + StartTime *string `json:"startTime"` + EndTime *string `json:"endTime"` + Speakers string `json:"speakers" validate:"required"` + PosterImagePath *string `json:"posterImagePath"` + StatusId *int `json:"statusId"` +} + +func (req SchedulesUpdateRequest) ToEntity() *entity.Schedules { + return &entity.Schedules{ + Title: req.Title, + Description: req.Description, + Location: req.Location, + IsLiveStreaming: req.IsLiveStreaming, + LiveStreamingUrl: req.LiveStreamingUrl, + TypeId: req.TypeId, + StartDate: req.StartDate, + EndDate: req.EndDate, + StartTime: req.StartTime, + EndTime: req.EndTime, + Speakers: req.Speakers, + PosterImagePath: req.PosterImagePath, + StatusId: req.StatusId, + UpdatedAt: time.Now(), + } +} + +type SchedulesQueryRequestContext struct { + Title string `json:"title"` + Description string `json:"description"` + Location string `json:"location"` + TypeId string `json:"typeId"` + StartDate string `json:"startDate"` + EndDate string `json:"endDate"` + IsLiveStreaming string `json:"isLiveStreaming"` + Speakers string `json:"speakers"` + StatusId string `json:"statusId"` + CreatedById string `json:"createdById"` +} + +func (req SchedulesQueryRequestContext) ToParamRequest() SchedulesQueryRequest { + var request SchedulesQueryRequest + + if title := req.Title; title != "" { + request.Title = &title + } + if description := req.Description; description != "" { + request.Description = &description + } + if location := req.Location; location != "" { + request.Location = &location + } + if typeIdStr := req.TypeId; typeIdStr != "" { + typeId, err := strconv.Atoi(typeIdStr) + if err == nil { + request.TypeId = &typeId + } + } + if startDateStr := req.StartDate; startDateStr != "" { + if startDate, err := time.Parse("2006-01-02", startDateStr); err == nil { + request.StartDate = &startDate + } + } + if endDateStr := req.EndDate; endDateStr != "" { + if endDate, err := time.Parse("2006-01-02", endDateStr); err == nil { + request.EndDate = &endDate + } + } + if isLiveStreamingStr := req.IsLiveStreaming; isLiveStreamingStr != "" { + isLiveStreaming, err := strconv.ParseBool(isLiveStreamingStr) + if err == nil { + request.IsLiveStreaming = &isLiveStreaming + } + } + if speakers := req.Speakers; speakers != "" { + request.Speakers = &speakers + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + if createdByIdStr := req.CreatedById; createdByIdStr != "" { + createdById, err := strconv.Atoi(createdByIdStr) + if err == nil { + createdByIdUint := uint(createdById) + request.CreatedById = &createdByIdUint + } + } + + return request +} diff --git a/app/module/schedules/response/schedules.response.go b/app/module/schedules/response/schedules.response.go new file mode 100644 index 0000000..d0cfd80 --- /dev/null +++ b/app/module/schedules/response/schedules.response.go @@ -0,0 +1,37 @@ +package response + +import ( + "time" +) + +type SchedulesResponse struct { + ID uint `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Location string `json:"location"` + IsLiveStreaming *bool `json:"isLiveStreaming"` + LiveStreamingUrl *string `json:"liveStreamingUrl"` + TypeId int `json:"typeId"` + TypeName string `json:"typeName"` + StartDate *time.Time `json:"startDate"` + EndDate *time.Time `json:"endDate"` + StartTime *string `json:"startTime"` + EndTime *string `json:"endTime"` + Speakers string `json:"speakers"` + PosterImagePath *string `json:"posterImagePath"` + CreatedById *uint `json:"createdById"` + CreatedByName *string `json:"createdByName"` + StatusId *int `json:"statusId"` + StatusName *string `json:"statusName"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type SchedulesSummaryStats struct { + TotalToday int `json:"totalToday"` + TotalThisWeek int `json:"totalThisWeek"` + TotalAll int `json:"totalAll"` + TotalLive int `json:"totalLive"` + TotalUpcoming int `json:"totalUpcoming"` +} diff --git a/app/module/schedules/schedules.module.go b/app/module/schedules/schedules.module.go new file mode 100644 index 0000000..0338299 --- /dev/null +++ b/app/module/schedules/schedules.module.go @@ -0,0 +1,60 @@ +package schedules + +import ( + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/schedules/controller" + "web-qudo-be/app/module/schedules/repository" + "web-qudo-be/app/module/schedules/service" + usersRepo "web-qudo-be/app/module/users/repository" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// SchedulesRouter struct of SchedulesRouter +type SchedulesRouter struct { + App fiber.Router + Controller controller.SchedulesController + UsersRepo usersRepo.UsersRepository +} + +// NewSchedulesModule register bulky of Schedules module +var NewSchedulesModule = fx.Options( + // register repository of Schedules module + fx.Provide(repository.NewSchedulesRepository), + + // register service of Schedules module + fx.Provide(service.NewSchedulesService), + + // register controller of Schedules module + fx.Provide(controller.NewSchedulesController), + + // register router of Schedules module + fx.Provide(NewSchedulesRouter), +) + +// NewSchedulesRouter init SchedulesRouter +func NewSchedulesRouter(fiber *fiber.App, controller controller.SchedulesController, usersRepo usersRepo.UsersRepository) *SchedulesRouter { + return &SchedulesRouter{ + App: fiber, + Controller: controller, + UsersRepo: usersRepo, + } +} + +// RegisterSchedulesRoutes register routes of Schedules module +func (_i *SchedulesRouter) RegisterSchedulesRoutes() { + // define controllers + schedulesController := _i.Controller + + // define routes + _i.App.Route("/schedules", func(router fiber.Router) { + // Add user middleware to extract user level from JWT token + router.Use(middleware.UserMiddleware(_i.UsersRepo)) + router.Get("/", schedulesController.All) + router.Get("/:id", schedulesController.Show) + router.Post("/", schedulesController.Save) + router.Put("/:id", schedulesController.Update) + router.Delete("/:id", schedulesController.Delete) + }) +} diff --git a/app/module/schedules/service/schedules.service.go b/app/module/schedules/service/schedules.service.go new file mode 100644 index 0000000..cff4fc9 --- /dev/null +++ b/app/module/schedules/service/schedules.service.go @@ -0,0 +1,131 @@ +package service + +import ( + "web-qudo-be/app/module/schedules/mapper" + "web-qudo-be/app/module/schedules/repository" + "web-qudo-be/app/module/schedules/request" + schedulesResponse "web-qudo-be/app/module/schedules/response" + "web-qudo-be/utils/paginator" + + "github.com/google/uuid" +) + +type SchedulesService interface { + All(clientId *uuid.UUID, req request.SchedulesQueryRequest) ([]*schedulesResponse.SchedulesResponse, *paginator.Pagination, error) + Show(clientId *uuid.UUID, id uint) (*schedulesResponse.SchedulesResponse, error) + Save(clientId *uuid.UUID, req request.SchedulesCreateRequest) (*schedulesResponse.SchedulesResponse, error) + Update(clientId *uuid.UUID, id uint, req request.SchedulesUpdateRequest) error + Delete(clientId *uuid.UUID, id uint) error + SummaryStats(clientId *uuid.UUID) (*schedulesResponse.SchedulesSummaryStats, error) +} + +type schedulesService struct { + schedulesRepository repository.SchedulesRepository +} + +func NewSchedulesService(schedulesRepository repository.SchedulesRepository) SchedulesService { + return &schedulesService{ + schedulesRepository: schedulesRepository, + } +} + +func (_i *schedulesService) All(clientId *uuid.UUID, req request.SchedulesQueryRequest) ([]*schedulesResponse.SchedulesResponse, *paginator.Pagination, error) { + schedules, pagination, err := _i.schedulesRepository.All(clientId, req) + if err != nil { + return nil, nil, err + } + + responses := mapper.ToSchedulesResponseList(schedules) + return responses, pagination, nil +} + +func (_i *schedulesService) Show(clientId *uuid.UUID, id uint) (*schedulesResponse.SchedulesResponse, error) { + schedule, err := _i.schedulesRepository.Show(clientId, id) + if err != nil { + return nil, err + } + + response := mapper.ToSchedulesResponse(schedule) + return response, nil +} + +func (_i *schedulesService) Save(clientId *uuid.UUID, req request.SchedulesCreateRequest) (*schedulesResponse.SchedulesResponse, error) { + schedule := req.ToEntity() + + savedSchedule, err := _i.schedulesRepository.Save(clientId, schedule) + if err != nil { + return nil, err + } + + response := mapper.ToSchedulesResponse(savedSchedule) + return response, nil +} + +func (_i *schedulesService) Update(clientId *uuid.UUID, id uint, req request.SchedulesUpdateRequest) error { + schedule := req.ToEntity() + + err := _i.schedulesRepository.Update(clientId, id, schedule) + if err != nil { + return err + } + + return nil +} + +func (_i *schedulesService) Delete(clientId *uuid.UUID, id uint) error { + err := _i.schedulesRepository.Delete(clientId, id) + if err != nil { + return err + } + + return nil +} + +func (_i *schedulesService) SummaryStats(clientId *uuid.UUID) (*schedulesResponse.SchedulesSummaryStats, error) { + // Get today's count + todayReq := request.SchedulesQueryRequest{} + todayCount, err := _i.schedulesRepository.Count(clientId, todayReq) + if err != nil { + return nil, err + } + + // Get this week's count + weekReq := request.SchedulesQueryRequest{} + weekCount, err := _i.schedulesRepository.Count(clientId, weekReq) + if err != nil { + return nil, err + } + + // Get total count + totalReq := request.SchedulesQueryRequest{} + totalCount, err := _i.schedulesRepository.Count(clientId, totalReq) + if err != nil { + return nil, err + } + + // Get live streaming count + liveReq := request.SchedulesQueryRequest{ + IsLiveStreaming: &[]bool{true}[0], + } + liveCount, err := _i.schedulesRepository.Count(clientId, liveReq) + if err != nil { + return nil, err + } + + // Get upcoming count (start_date > today) + upcomingReq := request.SchedulesQueryRequest{} + upcomingCount, err := _i.schedulesRepository.Count(clientId, upcomingReq) + if err != nil { + return nil, err + } + + stats := &schedulesResponse.SchedulesSummaryStats{ + TotalToday: int(todayCount), + TotalThisWeek: int(weekCount), + TotalAll: int(totalCount), + TotalLive: int(liveCount), + TotalUpcoming: int(upcomingCount), + } + + return stats, nil +} diff --git a/app/module/subscription/controller/controller.go b/app/module/subscription/controller/controller.go new file mode 100644 index 0000000..d528a7a --- /dev/null +++ b/app/module/subscription/controller/controller.go @@ -0,0 +1,16 @@ +package controller + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/subscription/service" +) + +type Controller struct { + Subscription SubscriptionController +} + +func NewController(SubscriptionService service.SubscriptionService, log zerolog.Logger) *Controller { + return &Controller{ + Subscription: NewSubscriptionController(SubscriptionService, log), + } +} diff --git a/app/module/subscription/controller/subscription.controller.go b/app/module/subscription/controller/subscription.controller.go new file mode 100644 index 0000000..4c8cb27 --- /dev/null +++ b/app/module/subscription/controller/subscription.controller.go @@ -0,0 +1,214 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/subscription/request" + "web-qudo-be/app/module/subscription/service" + "web-qudo-be/utils/paginator" + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" +) + +type subscriptionController struct { + subscriptionService service.SubscriptionService + Log zerolog.Logger +} + +type SubscriptionController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewSubscriptionController(subscriptionService service.SubscriptionService, log zerolog.Logger) SubscriptionController { + return &subscriptionController{ + subscriptionService: subscriptionService, + Log: log, + } +} + +// All get all Subscription +// @Summary Get all Subscription +// @Description API for getting all Subscription +// @Tags Subscription +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param req query request.SubscriptionQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /subscription [get] +func (_i *subscriptionController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.SubscriptionQueryRequestContext{ + Email: c.Query("email"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + subscriptionData, paging, err := _i.subscriptionService.All(clientId, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Subscription list successfully retrieved"}, + Data: subscriptionData, + Meta: paging, + }) +} + +// Show get one Subscription +// @Summary Get one Subscription +// @Description API for getting one Subscription +// @Tags Subscription +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param id path int true "Subscription ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /subscription/{id} [get] +func (_i *subscriptionController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + subscriptionData, err := _i.subscriptionService.Show(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Subscription successfully retrieved"}, + Data: subscriptionData, + }) +} + +// Save create Subscription +// @Summary Create Subscription +// @Description API for create Subscription +// @Tags Subscription +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.SubscriptionCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /subscription [post] +func (_i *subscriptionController) Save(c *fiber.Ctx) error { + req := new(request.SubscriptionCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + dataResult, err := _i.subscriptionService.Save(clientId, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Subscription successfully created"}, + Data: dataResult, + }) +} + +// Update update Subscription +// @Summary update Subscription +// @Description API for update Subscription +// @Tags Subscription +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.SubscriptionUpdateRequest true "Required payload" +// @Param id path int true "Subscription ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /subscription/{id} [put] +func (_i *subscriptionController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.SubscriptionUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.subscriptionService.Update(clientId, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Subscription successfully updated"}, + }) +} + +// Delete delete Subscription +// @Summary delete Subscription +// @Description API for delete Subscription +// @Tags Subscription +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "Subscription ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /subscription/{id} [delete] +func (_i *subscriptionController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.subscriptionService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Subscription successfully deleted"}, + }) +} diff --git a/app/module/subscription/mapper/subscription.mapper.go b/app/module/subscription/mapper/subscription.mapper.go new file mode 100644 index 0000000..7c4c0d7 --- /dev/null +++ b/app/module/subscription/mapper/subscription.mapper.go @@ -0,0 +1,19 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/subscription/response" +) + +func SubscriptionResponseMapper(subscriptionReq *entity.Subscription) (subscriptionRes *res.SubscriptionResponse) { + if subscriptionReq != nil { + subscriptionRes = &res.SubscriptionResponse{ + ID: subscriptionReq.ID, + Email: subscriptionReq.Email, + IsActive: subscriptionReq.IsActive, + CreatedAt: subscriptionReq.CreatedAt, + UpdatedAt: subscriptionReq.UpdatedAt, + } + } + return subscriptionRes +} diff --git a/app/module/subscription/repository/subscription.repository.go b/app/module/subscription/repository/subscription.repository.go new file mode 100644 index 0000000..fe95bd6 --- /dev/null +++ b/app/module/subscription/repository/subscription.repository.go @@ -0,0 +1,109 @@ +package repository + +import ( + "fmt" + "github.com/google/uuid" + "github.com/rs/zerolog" + "strings" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/subscription/request" + "web-qudo-be/utils/paginator" +) + +type subscriptionRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// SubscriptionRepository define interface of ISubscriptionRepository +type SubscriptionRepository interface { + GetAll(clientId *uuid.UUID, req request.SubscriptionQueryRequest) (subscriptions []*entity.Subscription, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (subscription *entity.Subscription, err error) + Create(subscription *entity.Subscription) (subscriptionReturn *entity.Subscription, err error) + Update(clientId *uuid.UUID, id uint, subscription *entity.Subscription) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) +} + +func NewSubscriptionRepository(db *database.Database, logger zerolog.Logger) SubscriptionRepository { + return &subscriptionRepository{ + DB: db, + Log: logger, + } +} + +// implement interface of ISubscriptionRepository +func (_i *subscriptionRepository) GetAll(clientId *uuid.UUID, req request.SubscriptionQueryRequest) (subscriptions []*entity.Subscription, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Subscription{}) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("is_active = ?", true) + + if req.Email != nil && *req.Email != "" { + email := strings.ToLower(*req.Email) + query = query.Where("LOWER(email) LIKE ?", "%"+strings.ToLower(email)+"%") + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&subscriptions).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *subscriptionRepository) FindOne(clientId *uuid.UUID, id uint) (subscription *entity.Subscription, err error) { + query := _i.DB.DB.Where("id = ?", id) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err := query.First(&subscription).Error; err != nil { + return nil, err + } + + return subscription, nil +} + +func (_i *subscriptionRepository) Create(subscription *entity.Subscription) (subscriptionReturn *entity.Subscription, err error) { + result := _i.DB.DB.Create(subscription) + return subscription, result.Error +} + +func (_i *subscriptionRepository) Update(clientId *uuid.UUID, id uint, subscription *entity.Subscription) (err error) { + query := _i.DB.DB.Model(&entity.Subscription{}).Where(&entity.Subscription{ID: id}) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + return query.Updates(subscription).Error +} + +func (_i *subscriptionRepository) Delete(clientId *uuid.UUID, id uint) error { + query := _i.DB.DB.Model(&entity.Subscription{}).Where("id = ?", id) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + return query.Delete(&entity.Subscription{}).Error +} diff --git a/app/module/subscription/request/subscription.request.go b/app/module/subscription/request/subscription.request.go new file mode 100644 index 0000000..e6b71ce --- /dev/null +++ b/app/module/subscription/request/subscription.request.go @@ -0,0 +1,53 @@ +package request + +import ( + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type SubscriptionGeneric interface { + ToEntity() +} + +type SubscriptionQueryRequest struct { + Email *string `json:"email"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type SubscriptionCreateRequest struct { + Email string `json:"email" validate:"required,email"` +} + +func (req SubscriptionCreateRequest) ToEntity() *entity.Subscription { + return &entity.Subscription{ + Email: req.Email, + } +} + +type SubscriptionUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Email string `json:"email" validate:"required,email"` +} + +func (req SubscriptionUpdateRequest) ToEntity() *entity.Subscription { + return &entity.Subscription{ + ID: req.ID, + Email: req.Email, + UpdatedAt: time.Now(), + } +} + +type SubscriptionQueryRequestContext struct { + Email string `json:"email"` +} + +func (req SubscriptionQueryRequestContext) ToParamRequest() SubscriptionQueryRequest { + var request SubscriptionQueryRequest + + if email := req.Email; email != "" { + request.Email = &email + } + + return request +} diff --git a/app/module/subscription/response/subscription.response.go b/app/module/subscription/response/subscription.response.go new file mode 100644 index 0000000..b25d2d1 --- /dev/null +++ b/app/module/subscription/response/subscription.response.go @@ -0,0 +1,11 @@ +package response + +import "time" + +type SubscriptionResponse struct { + ID uint `json:"id"` + Email string `json:"email"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/subscription/service/subscription.service.go b/app/module/subscription/service/subscription.service.go new file mode 100644 index 0000000..ffa1815 --- /dev/null +++ b/app/module/subscription/service/subscription.service.go @@ -0,0 +1,92 @@ +package service + +import ( + "github.com/google/uuid" + "github.com/rs/zerolog" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/subscription/mapper" + "web-qudo-be/app/module/subscription/repository" + "web-qudo-be/app/module/subscription/request" + "web-qudo-be/app/module/subscription/response" + usersRepository "web-qudo-be/app/module/users/repository" + "web-qudo-be/utils/paginator" +) + +// SubscriptionService +type subscriptionService struct { + Repo repository.SubscriptionRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +// SubscriptionService define interface of ISubscriptionService +type SubscriptionService interface { + All(clientId *uuid.UUID, req request.SubscriptionQueryRequest) (subscription []*response.SubscriptionResponse, paging paginator.Pagination, err error) + Show(clientId *uuid.UUID, id uint) (subscription *response.SubscriptionResponse, err error) + Save(clientId *uuid.UUID, req request.SubscriptionCreateRequest) (subscription *entity.Subscription, err error) + Update(clientId *uuid.UUID, id uint, req request.SubscriptionUpdateRequest) (err error) + Delete(clientId *uuid.UUID, id uint) error +} + +// NewSubscriptionService init SubscriptionService +func NewSubscriptionService(repo repository.SubscriptionRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository) SubscriptionService { + + return &subscriptionService{ + Repo: repo, + Log: log, + UsersRepo: usersRepo, + } +} + +// All implement interface of SubscriptionService +func (_i *subscriptionService) All(clientId *uuid.UUID, req request.SubscriptionQueryRequest) (subscriptions []*response.SubscriptionResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(clientId, req) + if err != nil { + return + } + + for _, result := range results { + subscriptions = append(subscriptions, mapper.SubscriptionResponseMapper(result)) + } + + return +} + +func (_i *subscriptionService) Show(clientId *uuid.UUID, id uint) (subscription *response.SubscriptionResponse, err error) { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return nil, err + } + + return mapper.SubscriptionResponseMapper(result), nil +} + +func (_i *subscriptionService) Save(clientId *uuid.UUID, req request.SubscriptionCreateRequest) (subscription *entity.Subscription, err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + // Set ClientId on entity + newReq.ClientId = clientId + + return _i.Repo.Create(newReq) +} + +func (_i *subscriptionService) Update(clientId *uuid.UUID, id uint, req request.SubscriptionUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + // Set ClientId on entity + entity := req.ToEntity() + entity.ClientId = clientId + + return _i.Repo.Update(clientId, id, entity) +} + +func (_i *subscriptionService) Delete(clientId *uuid.UUID, id uint) error { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return err + } + + result.IsActive = false + return _i.Repo.Update(clientId, id, result) +} diff --git a/app/module/subscription/subscription.module.go b/app/module/subscription/subscription.module.go new file mode 100644 index 0000000..8f66427 --- /dev/null +++ b/app/module/subscription/subscription.module.go @@ -0,0 +1,53 @@ +package subscription + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/subscription/controller" + "web-qudo-be/app/module/subscription/repository" + "web-qudo-be/app/module/subscription/service" +) + +// struct of SubscriptionRouter +type SubscriptionRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of Subscription module +var NewSubscriptionModule = fx.Options( + // register repository of Subscription module + fx.Provide(repository.NewSubscriptionRepository), + + // register service of Subscription module + fx.Provide(service.NewSubscriptionService), + + // register controller of Subscription module + fx.Provide(controller.NewController), + + // register router of Subscription module + fx.Provide(NewSubscriptionRouter), +) + +// init SubscriptionRouter +func NewSubscriptionRouter(fiber *fiber.App, controller *controller.Controller) *SubscriptionRouter { + return &SubscriptionRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of Subscription module +func (_i *SubscriptionRouter) RegisterSubscriptionRoutes() { + // define controllers + subscriptionController := _i.Controller.Subscription + + // define routes + _i.App.Route("/subscription", func(router fiber.Router) { + router.Get("/", subscriptionController.All) + router.Get("/:id", subscriptionController.Show) + router.Post("/", subscriptionController.Save) + router.Put("/:id", subscriptionController.Update) + router.Delete("/:id", subscriptionController.Delete) + }) +} diff --git a/app/module/user_levels/controller/controller.go b/app/module/user_levels/controller/controller.go new file mode 100644 index 0000000..0702b52 --- /dev/null +++ b/app/module/user_levels/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "web-qudo-be/app/module/user_levels/service" + +type Controller struct { + UserLevels UserLevelsController +} + +func NewController(UserLevelsService service.UserLevelsService) *Controller { + return &Controller{ + UserLevels: NewUserLevelsController(UserLevelsService), + } +} diff --git a/app/module/user_levels/controller/user_levels.controller.go b/app/module/user_levels/controller/user_levels.controller.go new file mode 100644 index 0000000..5d14403 --- /dev/null +++ b/app/module/user_levels/controller/user_levels.controller.go @@ -0,0 +1,290 @@ +package controller + +import ( + "strconv" + "strings" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/user_levels/request" + "web-qudo-be/app/module/user_levels/service" + "web-qudo-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type userLevelsController struct { + userLevelsService service.UserLevelsService +} + +type UserLevelsController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + ShowByAlias(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + EnableApproval(c *fiber.Ctx) error +} + +func NewUserLevelsController(userLevelsService service.UserLevelsService) UserLevelsController { + return &userLevelsController{ + userLevelsService: userLevelsService, + } +} + +// All UserLevels +// @Summary Get all UserLevels +// @Description API for getting all UserLevels +// @Tags UserLevels +// @Security Bearer +// @Param X-Client-Key header string true "Client Key" +// @Param req query request.UserLevelsQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-levels [get] +func (_i *userLevelsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.UserLevelsQueryRequestContext{ + Name: c.Query("name"), + LevelNumber: c.Query("levelNumber"), + ParentLevelId: c.Query("parentLevelId"), + ProvinceId: c.Query("provinceId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + userLevelsData, paging, err := _i.userLevelsService.All(clientId, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserLevels list successfully retrieved"}, + Data: userLevelsData, + Meta: paging, + }) +} + +// Show UserLevels +// @Summary Get one UserLevels +// @Description API for getting one UserLevels +// @Tags UserLevels +// @Security Bearer +// @Param X-Client-Key header string true "Client Key" +// @Param id path int true "UserLevels ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-levels/{id} [get] +func (_i *userLevelsController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + userLevelsData, err := _i.userLevelsService.Show(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"UserLevels successfully retrieved"}, + Data: userLevelsData, + }) +} + +// ShowByAlias UserLevels +// @Summary Get one UserLevels +// @Description API for getting one UserLevels +// @Tags UserLevels +// @Security Bearer +// @Param X-Client-Key header string true "Client Key" +// @Param alias path string true "UserLevels Alias" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-levels/alias/{alias} [get] +func (_i *userLevelsController) ShowByAlias(c *fiber.Ctx) error { + alias := c.Params("alias") + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + userLevelsData, err := _i.userLevelsService.ShowByAlias(clientId, alias) + if err != nil { + return err + } + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"UserLevels successfully retrieved"}, + Data: userLevelsData, + }) +} + +// Save UserLevels +// @Summary Create UserLevels +// @Description API for create UserLevels +// @Tags UserLevels +// @Security Bearer +// @Param X-Client-Key header string true "Client Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.UserLevelsCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-levels [post] +func (_i *userLevelsController) Save(c *fiber.Ctx) error { + req := new(request.UserLevelsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + dataResult, err := _i.userLevelsService.Save(clientId, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserLevels successfully created"}, + Data: dataResult, + }) +} + +// Update UserLevels +// @Summary update UserLevels +// @Description API for update UserLevels +// @Tags UserLevels +// @Security Bearer +// @Param X-Client-Key header string true "Client Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.UserLevelsUpdateRequest true "Required payload" +// @Param id path int true "UserLevels ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-levels/{id} [put] +func (_i *userLevelsController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.UserLevelsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.userLevelsService.Update(clientId, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserLevels successfully updated"}, + }) +} + +// Delete UserLevels +// @Summary delete UserLevels +// @Description API for delete UserLevels +// @Tags UserLevels +// @Security Bearer +// @Param X-Client-Key header string true "Client Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "UserLevels ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /user-levels/{id} [delete] +func (_i *userLevelsController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.userLevelsService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserLevels successfully deleted"}, + }) +} + +// EnableApproval Articles +// @Summary EnableApproval Articles +// @Description API for Enable Approval of Article +// @Tags UserLevels +// @Security Bearer +// @Param X-Client-Key header string true "Client Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.UserLevelsApprovalRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-levels/enable-approval [post] +func (_i *userLevelsController) EnableApproval(c *fiber.Ctx) error { + req := new(request.UserLevelsApprovalRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + ids := strings.Split(req.Ids, ",") + for _, id := range ids { + idUint, err := strconv.ParseUint(id, 10, 64) + if err != nil { + return err + } + err = _i.userLevelsService.EnableApproval(clientId, uint(idUint), req.IsApprovalActive) + if err != nil { + return err + } + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"isApprovalActive of UserLevels successfully saved"}, + }) +} diff --git a/app/module/user_levels/mapper/user_levels.mapper.go b/app/module/user_levels/mapper/user_levels.mapper.go new file mode 100644 index 0000000..8923c7a --- /dev/null +++ b/app/module/user_levels/mapper/user_levels.mapper.go @@ -0,0 +1,25 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/user_levels/response" +) + +func UserLevelsResponseMapper(userLevelsReq *entity.UserLevels) (userLevelsRes *res.UserLevelsResponse) { + if userLevelsReq != nil { + userLevelsRes = &res.UserLevelsResponse{ + ID: userLevelsReq.ID, + Name: userLevelsReq.Name, + AliasName: userLevelsReq.AliasName, + LevelNumber: userLevelsReq.LevelNumber, + ParentLevelId: userLevelsReq.ParentLevelId, + ProvinceId: userLevelsReq.ProvinceId, + IsApprovalActive: userLevelsReq.IsApprovalActive, + Group: userLevelsReq.Group, + IsActive: userLevelsReq.IsActive, + CreatedAt: userLevelsReq.CreatedAt, + UpdatedAt: userLevelsReq.UpdatedAt, + } + } + return userLevelsRes +} diff --git a/app/module/user_levels/repository/user_levels.repository.go b/app/module/user_levels/repository/user_levels.repository.go new file mode 100644 index 0000000..8f43a91 --- /dev/null +++ b/app/module/user_levels/repository/user_levels.repository.go @@ -0,0 +1,151 @@ +package repository + +import ( + "fmt" + "strings" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/user_levels/request" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" + + "github.com/google/uuid" +) + +type userLevelsRepository struct { + DB *database.Database +} + +// UserLevelsRepository define interface of IUserLevelsRepository +type UserLevelsRepository interface { + GetAll(clientId *uuid.UUID, req request.UserLevelsQueryRequest) (userLevelss []*entity.UserLevels, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (userLevels *entity.UserLevels, err error) + FindOneByAlias(clientId *uuid.UUID, alias string) (userLevels *entity.UserLevels, err error) + Create(clientId *uuid.UUID, userLevels *entity.UserLevels) (userLevelsReturn *entity.UserLevels, err error) + Update(clientId *uuid.UUID, id uint, userLevels *entity.UserLevels) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) +} + +func NewUserLevelsRepository(db *database.Database) UserLevelsRepository { + return &userLevelsRepository{ + DB: db, + } +} + +// implement interface of IUserLevelsRepository +func (_i *userLevelsRepository) GetAll(clientId *uuid.UUID, req request.UserLevelsQueryRequest) (userLevelss []*entity.UserLevels, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.UserLevels{}) + 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 != "" { + name := strings.ToLower(*req.Name) + query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(name)+"%") + } + if req.LevelNumber != nil { + query = query.Where("level_number = ?", req.LevelNumber) + } + if req.ParentLevelId != nil { + query = query.Where("parent_level_id = ?", req.ParentLevelId) + } + if req.ProvinceId != nil { + query = query.Where("province_id = ?", req.ProvinceId) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&userLevelss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *userLevelsRepository) FindOne(clientId *uuid.UUID, id uint) (userLevels *entity.UserLevels, err error) { + 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 userLevels, nil +} + +func (_i *userLevelsRepository) FindOneByAlias(clientId *uuid.UUID, alias string) (userLevels *entity.UserLevels, err error) { + 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 userLevels, nil +} + +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) + return userLevels, result.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) + if err != nil { + return err + } + return _i.DB.DB.Model(&entity.UserLevels{}). + Where(&entity.UserLevels{ID: id}). + Updates(userLevelsMap).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 +} diff --git a/app/module/user_levels/request/user_levels.request.go b/app/module/user_levels/request/user_levels.request.go new file mode 100644 index 0000000..106845a --- /dev/null +++ b/app/module/user_levels/request/user_levels.request.go @@ -0,0 +1,107 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type UserLevelsGeneric interface { + ToEntity() +} + +type UserLevelsQueryRequest struct { + Name *string `json:"name"` + LevelNumber *int `json:"levelNumber"` + ParentLevelId *int `json:"parentLevelId"` + ProvinceId *int `json:"provinceId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type UserLevelsCreateRequest struct { + Name string `json:"name" validate:"required"` + AliasName string `json:"aliasName" validate:"required"` + LevelNumber int `json:"levelNumber" validate:"required"` + ParentLevelId *int `json:"parentLevelId"` + ProvinceId *int `json:"provinceId"` + Group *string `json:"group"` + IsApprovalActive *bool `json:"isApprovalActive"` + IsActive *bool `json:"isActive"` +} + +func (req UserLevelsCreateRequest) ToEntity() *entity.UserLevels { + return &entity.UserLevels{ + Name: req.Name, + AliasName: req.AliasName, + LevelNumber: req.LevelNumber, + ParentLevelId: req.ParentLevelId, + ProvinceId: req.ProvinceId, + IsApprovalActive: req.IsApprovalActive, + Group: req.Group, + IsActive: req.IsActive, + } +} + +type UserLevelsUpdateRequest struct { + Name string `json:"name" validate:"required"` + AliasName string `json:"aliasName" validate:"required"` + LevelNumber int `json:"levelNumber" validate:"required"` + ParentLevelId *int `json:"parentLevelId"` + IsApprovalActive *bool `json:"isApprovalActive"` + Group *string `json:"group"` + ProvinceId *int `json:"provinceId"` +} + +func (req UserLevelsUpdateRequest) ToEntity() *entity.UserLevels { + return &entity.UserLevels{ + Name: req.Name, + AliasName: req.AliasName, + LevelNumber: req.LevelNumber, + ParentLevelId: req.ParentLevelId, + ProvinceId: req.ProvinceId, + IsApprovalActive: req.IsApprovalActive, + Group: req.Group, + UpdatedAt: time.Now(), + } +} + +type UserLevelsApprovalRequest struct { + Ids string `json:"ids" validate:"required"` + IsApprovalActive bool `json:"isApprovalActive" validate:"required"` +} + +type UserLevelsQueryRequestContext struct { + Name string `json:"name"` + LevelNumber string `json:"levelNumber"` + ParentLevelId string `json:"parentLevelId"` + ProvinceId string `json:"provinceId"` +} + +func (req UserLevelsQueryRequestContext) ToParamRequest() UserLevelsQueryRequest { + var request UserLevelsQueryRequest + + if name := req.Name; name != "" { + request.Name = &name + } + if levelNumberStr := req.LevelNumber; levelNumberStr != "" { + LevelNumber, err := strconv.Atoi(levelNumberStr) + if err == nil { + request.LevelNumber = &LevelNumber + } + } + if parentLevelIdStr := req.ParentLevelId; parentLevelIdStr != "" { + parentLevelId, err := strconv.Atoi(parentLevelIdStr) + if err == nil { + request.ParentLevelId = &parentLevelId + } + } + if provinceIdStr := req.ProvinceId; provinceIdStr != "" { + provinceId, err := strconv.Atoi(provinceIdStr) + if err == nil { + request.ProvinceId = &provinceId + } + } + + return request +} diff --git a/app/module/user_levels/response/user_levels.response.go b/app/module/user_levels/response/user_levels.response.go new file mode 100644 index 0000000..cdc2956 --- /dev/null +++ b/app/module/user_levels/response/user_levels.response.go @@ -0,0 +1,17 @@ +package response + +import "time" + +type UserLevelsResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + AliasName string `json:"aliasName"` + LevelNumber int `json:"levelNumber"` + ParentLevelId *int `json:"parentLevelId"` + ProvinceId *int `json:"provinceId"` + IsApprovalActive *bool `json:"isApprovalActive"` + Group *string `json:"group"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/user_levels/service/user_levels.service.go b/app/module/user_levels/service/user_levels.service.go new file mode 100644 index 0000000..387de09 --- /dev/null +++ b/app/module/user_levels/service/user_levels.service.go @@ -0,0 +1,120 @@ +package service + +import ( + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/user_levels/mapper" + "web-qudo-be/app/module/user_levels/repository" + "web-qudo-be/app/module/user_levels/request" + "web-qudo-be/app/module/user_levels/response" + "web-qudo-be/utils/paginator" + + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +// UserLevelsService +type userLevelsService struct { + Repo repository.UserLevelsRepository + Log zerolog.Logger +} + +// UserLevelsService define interface of IUserLevelsService +type UserLevelsService interface { + All(clientId *uuid.UUID, req request.UserLevelsQueryRequest) (userLevels []*response.UserLevelsResponse, paging paginator.Pagination, err error) + Show(clientId *uuid.UUID, id uint) (userLevels *response.UserLevelsResponse, err error) + ShowByAlias(clientId *uuid.UUID, alias string) (userLevels *response.UserLevelsResponse, err error) + Save(clientId *uuid.UUID, req request.UserLevelsCreateRequest) (userLevels *entity.UserLevels, err error) + Update(clientId *uuid.UUID, id uint, req request.UserLevelsUpdateRequest) (err error) + Delete(clientId *uuid.UUID, id uint) error + EnableApproval(clientId *uuid.UUID, id uint, isApprovalActive bool) (err error) +} + +// NewUserLevelsService init UserLevelsService +func NewUserLevelsService(repo repository.UserLevelsRepository, log zerolog.Logger) UserLevelsService { + + return &userLevelsService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of UserLevelsService +func (_i *userLevelsService) All(clientId *uuid.UUID, req request.UserLevelsQueryRequest) (userLevelss []*response.UserLevelsResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(clientId, req) + if err != nil { + return + } + + for _, result := range results { + userLevelss = append(userLevelss, mapper.UserLevelsResponseMapper(result)) + } + + return +} + +func (_i *userLevelsService) Show(clientId *uuid.UUID, id uint) (userLevels *response.UserLevelsResponse, err error) { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return nil, err + } + + return mapper.UserLevelsResponseMapper(result), nil +} + +func (_i *userLevelsService) ShowByAlias(clientId *uuid.UUID, alias string) (userLevels *response.UserLevelsResponse, err error) { + result, err := _i.Repo.FindOneByAlias(clientId, alias) + if err != nil { + return nil, err + } + + return mapper.UserLevelsResponseMapper(result), nil +} + +func (_i *userLevelsService) Save(clientId *uuid.UUID, req request.UserLevelsCreateRequest) (userLevels *entity.UserLevels, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + entity := req.ToEntity() + // Set ClientId on entity + entity.ClientId = clientId + + saveUserLevelsRes, err := _i.Repo.Create(clientId, entity) + if err != nil { + return nil, err + } + + return saveUserLevelsRes, nil +} + +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.ToEntity()).Msg("") + + // Set ClientId on entity + entity := req.ToEntity() + entity.ClientId = clientId + + return _i.Repo.Update(clientId, id, entity) +} + +func (_i *userLevelsService) Delete(clientId *uuid.UUID, id uint) error { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return err + } + + isActive := false + result.IsActive = &isActive + return _i.Repo.Update(clientId, id, result) +} + +func (_i *userLevelsService) EnableApproval(clientId *uuid.UUID, id uint, isApprovalActive bool) (err error) { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return err + } + + *result.IsApprovalActive = isApprovalActive + + return _i.Repo.Update(clientId, id, result) +} diff --git a/app/module/user_levels/user_levels.module.go b/app/module/user_levels/user_levels.module.go new file mode 100644 index 0000000..654bd2f --- /dev/null +++ b/app/module/user_levels/user_levels.module.go @@ -0,0 +1,55 @@ +package user_levels + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/user_levels/controller" + "web-qudo-be/app/module/user_levels/repository" + "web-qudo-be/app/module/user_levels/service" +) + +// struct of UserLevelsRouter +type UserLevelsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of UserLevels module +var NewUserLevelsModule = fx.Options( + // register repository of UserLevels module + fx.Provide(repository.NewUserLevelsRepository), + + // register service of UserLevels module + fx.Provide(service.NewUserLevelsService), + + // register controller of UserLevels module + fx.Provide(controller.NewController), + + // register router of UserLevels module + fx.Provide(NewUserLevelsRouter), +) + +// init UserLevelsRouter +func NewUserLevelsRouter(fiber *fiber.App, controller *controller.Controller) *UserLevelsRouter { + return &UserLevelsRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of UserLevels module +func (_i *UserLevelsRouter) RegisterUserLevelsRoutes() { + // define controllers + userLevelsController := _i.Controller.UserLevels + + // define routes + _i.App.Route("/user-levels", func(router fiber.Router) { + router.Get("/", userLevelsController.All) + router.Get("/:id", userLevelsController.Show) + router.Get("/alias/:alias", userLevelsController.ShowByAlias) + router.Post("/", userLevelsController.Save) + router.Put("/:id", userLevelsController.Update) + router.Delete("/:id", userLevelsController.Delete) + router.Put("/approval/:id", userLevelsController.EnableApproval) + }) +} diff --git a/app/module/user_role_accesses/controller/controller.go b/app/module/user_role_accesses/controller/controller.go new file mode 100644 index 0000000..f13d228 --- /dev/null +++ b/app/module/user_role_accesses/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "web-qudo-be/app/module/user_role_accesses/service" + +type Controller struct { + UserRoleAccesses UserRoleAccessesController +} + +func NewController(UserRoleAccessesService service.UserRoleAccessesService) *Controller { + return &Controller{ + UserRoleAccesses: NewUserRoleAccessesController(UserRoleAccessesService), + } +} diff --git a/app/module/user_role_accesses/controller/user_role_accesses.controller.go b/app/module/user_role_accesses/controller/user_role_accesses.controller.go new file mode 100644 index 0000000..d79389d --- /dev/null +++ b/app/module/user_role_accesses/controller/user_role_accesses.controller.go @@ -0,0 +1,191 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/module/user_role_accesses/request" + "web-qudo-be/app/module/user_role_accesses/service" + "web-qudo-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type userRoleAccessesController struct { + userRoleAccessesService service.UserRoleAccessesService +} + +type UserRoleAccessesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewUserRoleAccessesController(userRoleAccessesService service.UserRoleAccessesService) UserRoleAccessesController { + return &userRoleAccessesController{ + userRoleAccessesService: userRoleAccessesService, + } +} + +// All UserRoleAccesses +// @Summary Get all UserRoleAccesses +// @Description API for getting all UserRoleAccesses +// @Tags UserRoleAccesses +// @Security Bearer +// @Param req query request.UserRoleAccessesQueryRequest false "query parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-role-accesses [get] +func (_i *userRoleAccessesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.UserRoleAccessesQueryRequestContext{ + MenuId: c.Query("menuId"), + UserRoleId: c.Query("userRoleId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + userRoleAccessesData, paging, err := _i.userRoleAccessesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoleAccesses list successfully retrieved"}, + Data: userRoleAccessesData, + Meta: paging, + }) +} + +// Show UserRoleAccesses +// @Summary Get one UserRoleAccesses +// @Description API for getting one UserRoleAccesses +// @Tags UserRoleAccesses +// @Security Bearer +// @Param id path int true "UserRoleAccesses ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-role-accesses/{id} [get] +func (_i *userRoleAccessesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + userRoleAccessesData, err := _i.userRoleAccessesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoleAccesses successfully retrieved"}, + Data: userRoleAccessesData, + }) +} + +// Save UserRoleAccesses +// @Summary Create UserRoleAccesses +// @Description API for create UserRoleAccesses +// @Tags UserRoleAccesses +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.UserRoleAccessesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-role-accesses [post] +func (_i *userRoleAccessesController) Save(c *fiber.Ctx) error { + req := new(request.UserRoleAccessesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.userRoleAccessesService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoleAccesses successfully created"}, + }) +} + +// Update UserRoleAccesses +// @Summary update UserRoleAccesses +// @Description API for update UserRoleAccesses +// @Tags UserRoleAccesses +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.UserRoleAccessesUpdateRequest true "Required payload" +// @Param id path int true "UserRoleAccesses ID" +// @Success 200 {object} response.Response +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-role-accesses/{id} [put] +func (_i *userRoleAccessesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.UserRoleAccessesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.userRoleAccessesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoleAccesses successfully updated"}, + }) +} + +// Delete UserRoleAccesses +// @Summary delete UserRoleAccesses +// @Description API for delete UserRoleAccesses +// @Tags UserRoleAccesses +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "UserRoleAccesses ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-role-accesses/{id} [delete] +func (_i *userRoleAccessesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.userRoleAccessesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoleAccesses successfully deleted"}, + }) +} diff --git a/app/module/user_role_accesses/entity/user_role_accesses.entity.go b/app/module/user_role_accesses/entity/user_role_accesses.entity.go new file mode 100644 index 0000000..3d1c58d --- /dev/null +++ b/app/module/user_role_accesses/entity/user_role_accesses.entity.go @@ -0,0 +1,18 @@ +package entity + +import "time" + +type UserRoleAccesses struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + UserRoleId int `json:"user_role_id" gorm:"type:int4"` + MenuId int `json:"menu_id" gorm:"type:int4"` + IsViewEnabled bool `json:"is_view_enabled" gorm:"type:bool"` + IsInsertEnabled bool `json:"is_insert_enabled" gorm:"type:bool"` + IsUpdateEnabled bool `json:"is_update_enabled" gorm:"type:bool"` + IsDeleteEnabled bool `json:"is_delete_enabled" gorm:"type:bool"` + IsApprovalEnabled bool `json:"is_approval_enabled" gorm:"type:bool"` + IsAdminEnabled bool `json:"is_admin_enabled" gorm:"type:bool"` + IsActive bool `json:"is_active" gorm:"type:bool"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} \ No newline at end of file diff --git a/app/module/user_role_accesses/mapper/user_role_accesses.mapper.go b/app/module/user_role_accesses/mapper/user_role_accesses.mapper.go new file mode 100644 index 0000000..7aa1f61 --- /dev/null +++ b/app/module/user_role_accesses/mapper/user_role_accesses.mapper.go @@ -0,0 +1,26 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/user_role_accesses/response" +) + +func UserRoleAccessesResponseMapper(userRoleAccessesReq *entity.UserRoleAccesses) (userRoleAccessesRes *res.UserRoleAccessesResponse) { + if userRoleAccessesReq != nil { + userRoleAccessesRes = &res.UserRoleAccessesResponse{ + ID: userRoleAccessesReq.ID, + UserRoleId: userRoleAccessesReq.UserRoleId, + MenuId: userRoleAccessesReq.MenuId, + IsViewEnabled: userRoleAccessesReq.IsViewEnabled, + IsInsertEnabled: userRoleAccessesReq.IsInsertEnabled, + IsUpdateEnabled: userRoleAccessesReq.IsUpdateEnabled, + IsDeleteEnabled: userRoleAccessesReq.IsDeleteEnabled, + IsApprovalEnabled: userRoleAccessesReq.IsApprovalEnabled, + IsAdminEnabled: userRoleAccessesReq.IsAdminEnabled, + IsActive: userRoleAccessesReq.IsActive, + CreatedAt: userRoleAccessesReq.CreatedAt, + UpdatedAt: userRoleAccessesReq.UpdatedAt, + } + } + return userRoleAccessesRes +} diff --git a/app/module/user_role_accesses/repository/user_role_accesses.repository.go b/app/module/user_role_accesses/repository/user_role_accesses.repository.go new file mode 100644 index 0000000..3b96db2 --- /dev/null +++ b/app/module/user_role_accesses/repository/user_role_accesses.repository.go @@ -0,0 +1,82 @@ +package repository + +import ( + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/user_role_accesses/request" + "web-qudo-be/utils/paginator" +) + +type userRoleAccessesRepository struct { + DB *database.Database +} + +// UserRoleAccessesRepository define interface of IUserRoleAccessesRepository +type UserRoleAccessesRepository interface { + GetAll(req request.UserRoleAccessesQueryRequest) (userRoleAccessess []*entity.UserRoleAccesses, paging paginator.Pagination, err error) + FindOne(id uint) (userRoleAccesses *entity.UserRoleAccesses, err error) + Create(userRoleAccesses *entity.UserRoleAccesses) (err error) + CreateAll(userRoleAccesses *[]entity.UserRoleAccesses) (err error) + Update(id uint, userRoleAccesses *entity.UserRoleAccesses) (err error) + Delete(id uint) (err error) +} + +func NewUserRoleAccessesRepository(db *database.Database) UserRoleAccessesRepository { + return &userRoleAccessesRepository{ + DB: db, + } +} + +// implement interface of IUserRoleAccessesRepository +func (_i *userRoleAccessesRepository) GetAll(req request.UserRoleAccessesQueryRequest) (userRoleAccessess []*entity.UserRoleAccesses, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.UserRoleAccesses{}) + query = query.Where("is_active = ?", true) + + if req.UserRoleId != nil { + query = query.Where("user_role_id = ?", req.UserRoleId) + } + if req.MenuId != nil { + query = query.Where("menu_id = ?", req.MenuId) + } + query.Count(&count) + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&userRoleAccessess).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *userRoleAccessesRepository) FindOne(id uint) (userRoleAccesses *entity.UserRoleAccesses, err error) { + if err := _i.DB.DB.First(&userRoleAccesses, id).Error; err != nil { + return nil, err + } + + return userRoleAccesses, nil +} + +func (_i *userRoleAccessesRepository) Create(userRoleAccesses *entity.UserRoleAccesses) (err error) { + return _i.DB.DB.Create(userRoleAccesses).Error +} + +func (_i *userRoleAccessesRepository) CreateAll(userRoleAccesses *[]entity.UserRoleAccesses) (err error) { + return _i.DB.DB.Create(userRoleAccesses).Error +} + +func (_i *userRoleAccessesRepository) Update(id uint, userRoleAccesses *entity.UserRoleAccesses) (err error) { + return _i.DB.DB.Model(&entity.UserRoleAccesses{}). + Where(&entity.UserRoleAccesses{ID: id}). + Updates(userRoleAccesses).Error +} + +func (_i *userRoleAccessesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.UserRoleAccesses{}, id).Error +} diff --git a/app/module/user_role_accesses/request/user_role_accesses.request.go b/app/module/user_role_accesses/request/user_role_accesses.request.go new file mode 100644 index 0000000..bac7ee9 --- /dev/null +++ b/app/module/user_role_accesses/request/user_role_accesses.request.go @@ -0,0 +1,93 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type UserRoleAccessesGeneric interface { + ToEntity() +} + +type UserRoleAccessesQueryRequest struct { + UserRoleId *int `json:"userRoleId" validate:"required"` + MenuId *int `json:"menuId" validate:"required"` + IsActive *bool `json:"isActive" validate:"required"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type UserRoleAccessesCreateRequest struct { + MenuId int `json:"menuId" validate:"required"` + IsViewEnabled bool `json:"isViewEnabled" validate:"required"` + IsInsertEnabled bool `json:"isInsertEnabled" validate:"required"` + IsUpdateEnabled bool `json:"isUpdateEnabled" validate:"required"` + IsDeleteEnabled bool `json:"isDeleteEnabled" validate:"required"` + IsApprovalEnabled bool `json:"isApprovalEnabled" validate:"required"` + IsAdminEnabled bool `json:"isAdminEnabled" validate:"required"` +} + +func (req UserRoleAccessesCreateRequest) ToEntity() *entity.UserRoleAccesses { + return &entity.UserRoleAccesses{ + MenuId: req.MenuId, + IsViewEnabled: req.IsViewEnabled, + IsInsertEnabled: req.IsInsertEnabled, + IsUpdateEnabled: req.IsUpdateEnabled, + IsDeleteEnabled: req.IsDeleteEnabled, + IsApprovalEnabled: req.IsApprovalEnabled, + IsAdminEnabled: req.IsAdminEnabled, + } +} + +type UserRoleAccessesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + UserRoleId uint `json:"user_role_id" validate:"required"` + MenuId int `json:"menu_id" validate:"required"` + IsViewEnabled bool `json:"is_view_enabled" validate:"required"` + IsInsertEnabled bool `json:"is_insert_enabled" validate:"required"` + IsUpdateEnabled bool `json:"is_update_enabled" validate:"required"` + IsDeleteEnabled bool `json:"is_delete_enabled" validate:"required"` + IsApprovalEnabled bool `json:"is_approval_enabled" validate:"required"` + IsAdminEnabled bool `json:"is_admin_enabled" validate:"required"` +} + +func (req UserRoleAccessesUpdateRequest) ToEntity() *entity.UserRoleAccesses { + return &entity.UserRoleAccesses{ + ID: req.ID, + UserRoleId: req.UserRoleId, + MenuId: req.MenuId, + IsViewEnabled: req.IsViewEnabled, + IsInsertEnabled: req.IsInsertEnabled, + IsUpdateEnabled: req.IsUpdateEnabled, + IsDeleteEnabled: req.IsDeleteEnabled, + IsApprovalEnabled: req.IsApprovalEnabled, + IsAdminEnabled: req.IsAdminEnabled, + UpdatedAt: time.Now(), + } +} + +type UserRoleAccessesQueryRequestContext struct { + UserRoleId string `json:"userRoleId"` + MenuId string `json:"menuId"` +} + +func (req UserRoleAccessesQueryRequestContext) ToParamRequest() UserRoleAccessesQueryRequest { + var request UserRoleAccessesQueryRequest + + if userRoleIdStr := req.UserRoleId; userRoleIdStr != "" { + userRoleId, err := strconv.Atoi(userRoleIdStr) + if err == nil { + request.UserRoleId = &userRoleId + } + } + + if menuIdStr := req.MenuId; menuIdStr != "" { + menuId, err := strconv.Atoi(menuIdStr) + if err == nil { + request.MenuId = &menuId + } + } + + return request +} diff --git a/app/module/user_role_accesses/response/user_role_accesses.response.go b/app/module/user_role_accesses/response/user_role_accesses.response.go new file mode 100644 index 0000000..c9d0f70 --- /dev/null +++ b/app/module/user_role_accesses/response/user_role_accesses.response.go @@ -0,0 +1,18 @@ +package response + +import "time" + +type UserRoleAccessesResponse struct { + ID uint `json:"id"` + UserRoleId uint `json:"user_role_id"` + MenuId int `json:"menu_id"` + IsViewEnabled bool `json:"is_view_enabled"` + IsInsertEnabled bool `json:"is_insert_enabled"` + IsUpdateEnabled bool `json:"is_update_enabled"` + IsDeleteEnabled bool `json:"is_delete_enabled"` + IsApprovalEnabled bool `json:"is_approval_enabled"` + IsAdminEnabled bool `json:"is_admin_enabled"` + IsActive *bool `json:"is_active"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} diff --git a/app/module/user_role_accesses/service/user_role_accesses.service.go b/app/module/user_role_accesses/service/user_role_accesses.service.go new file mode 100644 index 0000000..c776b7a --- /dev/null +++ b/app/module/user_role_accesses/service/user_role_accesses.service.go @@ -0,0 +1,79 @@ +package service + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/user_role_accesses/mapper" + "web-qudo-be/app/module/user_role_accesses/repository" + "web-qudo-be/app/module/user_role_accesses/request" + "web-qudo-be/app/module/user_role_accesses/response" + "web-qudo-be/utils/paginator" +) + +// UserRoleAccessesService +type userRoleAccessesService struct { + Repo repository.UserRoleAccessesRepository + Log zerolog.Logger +} + +// UserRoleAccessesService define interface of IUserRoleAccessesService +type UserRoleAccessesService interface { + All(req request.UserRoleAccessesQueryRequest) (userRoleAccesses []*response.UserRoleAccessesResponse, paging paginator.Pagination, err error) + Show(id uint) (userRoleAccesses *response.UserRoleAccessesResponse, err error) + Save(req request.UserRoleAccessesCreateRequest) (err error) + Update(id uint, req request.UserRoleAccessesUpdateRequest) (err error) + Delete(id uint) error +} + +// NewUserRoleAccessesService init UserRoleAccessesService +func NewUserRoleAccessesService(repo repository.UserRoleAccessesRepository, log zerolog.Logger) UserRoleAccessesService { + + return &userRoleAccessesService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of UserRoleAccessesService +func (_i *userRoleAccessesService) All(req request.UserRoleAccessesQueryRequest) (userRoleAccessess []*response.UserRoleAccessesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + userRoleAccessess = append(userRoleAccessess, mapper.UserRoleAccessesResponseMapper(result)) + } + + return +} + +func (_i *userRoleAccessesService) Show(id uint) (userRoleAccesses *response.UserRoleAccessesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.UserRoleAccessesResponseMapper(result), nil +} + +func (_i *userRoleAccessesService) Save(req request.UserRoleAccessesCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + return _i.Repo.Create(req.ToEntity()) +} + +func (_i *userRoleAccessesService) Update(id uint, req request.UserRoleAccessesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *userRoleAccessesService) Delete(id uint) error { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + isActive := false + result.IsActive = &isActive + return _i.Repo.Update(id, result) +} diff --git a/app/module/user_role_accesses/user_role_accesses.module.go b/app/module/user_role_accesses/user_role_accesses.module.go new file mode 100644 index 0000000..128dbef --- /dev/null +++ b/app/module/user_role_accesses/user_role_accesses.module.go @@ -0,0 +1,53 @@ +package user_role_accesses + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/user_role_accesses/controller" + "web-qudo-be/app/module/user_role_accesses/repository" + "web-qudo-be/app/module/user_role_accesses/service" +) + +// struct of UserRoleAccessesRouter +type UserRoleAccessesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of UserRoleAccesses module +var NewUserRoleAccessesModule = fx.Options( + // register repository of UserRoleAccesses module + fx.Provide(repository.NewUserRoleAccessesRepository), + + // register service of UserRoleAccesses module + fx.Provide(service.NewUserRoleAccessesService), + + // register controller of UserRoleAccesses module + fx.Provide(controller.NewController), + + // register router of UserRoleAccesses module + fx.Provide(NewUserRoleAccessesRouter), +) + +// init UserRoleAccessesRouter +func NewUserRoleAccessesRouter(fiber *fiber.App, controller *controller.Controller) *UserRoleAccessesRouter { + return &UserRoleAccessesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of UserRoleAccesses module +func (_i *UserRoleAccessesRouter) RegisterUserRoleAccessesRoutes() { + // define controllers + userRoleAccessesController := _i.Controller.UserRoleAccesses + + // define routes + _i.App.Route("/user-role-accesses", func(router fiber.Router) { + router.Get("/", userRoleAccessesController.All) + router.Get("/:id", userRoleAccessesController.Show) + router.Post("/", userRoleAccessesController.Save) + router.Put("/:id", userRoleAccessesController.Update) + router.Delete("/:id", userRoleAccessesController.Delete) + }) +} diff --git a/app/module/user_role_level_details/controller/controller.go b/app/module/user_role_level_details/controller/controller.go new file mode 100644 index 0000000..ed78673 --- /dev/null +++ b/app/module/user_role_level_details/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "web-qudo-be/app/module/user_role_level_details/service" + +type Controller struct { + UserRoleLevelDetails UserRoleLevelDetailsController +} + +func NewController(UserRoleLevelDetailsService service.UserRoleLevelDetailsService) *Controller { + return &Controller{ + UserRoleLevelDetails: NewUserRoleLevelDetailsController(UserRoleLevelDetailsService), + } +} diff --git a/app/module/user_role_level_details/controller/user_role_level_details.controller.go b/app/module/user_role_level_details/controller/user_role_level_details.controller.go new file mode 100644 index 0000000..ac91a21 --- /dev/null +++ b/app/module/user_role_level_details/controller/user_role_level_details.controller.go @@ -0,0 +1,181 @@ +package controller + +import ( + "github.com/gofiber/fiber/v2" + "strconv" + "web-qudo-be/app/module/user_role_level_details/request" + "web-qudo-be/app/module/user_role_level_details/service" + "web-qudo-be/utils/paginator" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type userRoleLevelDetailsController struct { + userRoleLevelDetailsService service.UserRoleLevelDetailsService +} + +type UserRoleLevelDetailsController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewUserRoleLevelDetailsController(userRoleLevelDetailsService service.UserRoleLevelDetailsService) UserRoleLevelDetailsController { + return &userRoleLevelDetailsController{ + userRoleLevelDetailsService: userRoleLevelDetailsService, + } +} + +// All get all UserRoleLevelDetails +// @Summary Get all UserRoleLevelDetails +// @Description API for getting all UserRoleLevelDetails +// @Tags Task +// @Security Bearer +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /user-role-level-details [get] +func (_i *userRoleLevelDetailsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + var req request.UserRoleLevelDetailsQueryRequest + req.Pagination = paginate + + userRoleLevelDetailsData, paging, err := _i.userRoleLevelDetailsService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"UserRoleLevelDetails list successfully retrieved"}, + Data: userRoleLevelDetailsData, + Meta: paging, + }) +} + +// Show get one UserRoleLevelDetails +// @Summary Get one UserRoleLevelDetails +// @Description API for getting one UserRoleLevelDetails +// @Tags Task +// @Security Bearer +// @Param id path int true "UserRoleLevelDetails ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /user-role-level-details/{id} [get] +func (_i *userRoleLevelDetailsController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + userRoleLevelDetailsData, err := _i.userRoleLevelDetailsService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"UserRoleLevelDetails successfully retrieved"}, + Data: userRoleLevelDetailsData, + }) +} + +// Save create UserRoleLevelDetails +// @Summary Create UserRoleLevelDetails +// @Description API for create UserRoleLevelDetails +// @Tags Task +// @Security Bearer +// @Body request.UserRoleLevelDetailsCreateRequest +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /user-role-level-details [post] +func (_i *userRoleLevelDetailsController) Save(c *fiber.Ctx) error { + req := new(request.UserRoleLevelDetailsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.userRoleLevelDetailsService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"UserRoleLevelDetails successfully created"}, + }) +} + +// Update update UserRoleLevelDetails +// @Summary update UserRoleLevelDetails +// @Description API for update UserRoleLevelDetails +// @Tags Task +// @Security Bearer +// @Body request.UserRoleLevelDetailsUpdateRequest +// @Param id path int true "UserRoleLevelDetails ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /user-role-level-details/{id} [put] +func (_i *userRoleLevelDetailsController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.UserRoleLevelDetailsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.userRoleLevelDetailsService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"UserRoleLevelDetails successfully updated"}, + }) +} + +// Delete delete UserRoleLevelDetails +// @Summary delete UserRoleLevelDetails +// @Description API for delete UserRoleLevelDetails +// @Tags Task +// @Security Bearer +// @Param id path int true "UserRoleLevelDetails ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /user-role-level-details/{id} [delete] +func (_i *userRoleLevelDetailsController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.userRoleLevelDetailsService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"UserRoleLevelDetails successfully deleted"}, + }) +} diff --git a/app/module/user_role_level_details/mapper/user_role_level_details.mapper.go b/app/module/user_role_level_details/mapper/user_role_level_details.mapper.go new file mode 100644 index 0000000..6009a92 --- /dev/null +++ b/app/module/user_role_level_details/mapper/user_role_level_details.mapper.go @@ -0,0 +1,20 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/user_role_level_details/response" +) + +func UserRoleLevelDetailsResponseMapper(userRoleLevelDetailsReq *entity.UserRoleLevelDetails) (userRoleLevelDetailsRes *res.UserRoleLevelDetailsResponse) { + if userRoleLevelDetailsReq != nil { + userRoleLevelDetailsRes = &res.UserRoleLevelDetailsResponse{ + ID: userRoleLevelDetailsReq.ID, + UserRoleId: userRoleLevelDetailsReq.UserRoleId, + UserLevelId: userRoleLevelDetailsReq.UserLevelId, + IsActive: userRoleLevelDetailsReq.IsActive, + CreatedAt: userRoleLevelDetailsReq.CreatedAt, + UpdatedAt: userRoleLevelDetailsReq.UpdatedAt, + } + } + return userRoleLevelDetailsRes +} diff --git a/app/module/user_role_level_details/repository/user_role_level_details.repository.go b/app/module/user_role_level_details/repository/user_role_level_details.repository.go new file mode 100644 index 0000000..0d5565d --- /dev/null +++ b/app/module/user_role_level_details/repository/user_role_level_details.repository.go @@ -0,0 +1,83 @@ +package repository + +import ( + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/user_role_level_details/request" + "web-qudo-be/utils/paginator" +) + +type userRoleLevelDetailsRepository struct { + DB *database.Database +} + +// UserRoleLevelDetailsRepository define interface of IUserRoleLevelDetailsRepository +type UserRoleLevelDetailsRepository interface { + GetAll(req request.UserRoleLevelDetailsQueryRequest) (userRoleLevelDetailss []*entity.UserRoleLevelDetails, paging paginator.Pagination, err error) + FindOne(id uint) (userRoleLevelDetails *entity.UserRoleLevelDetails, err error) + FindByUserLevels(userLevelId uint) (userRoleLevelDetails []*entity.UserRoleLevelDetails, err error) + Create(userRoleLevelDetails *entity.UserRoleLevelDetails) (err error) + CreateAll(userRoleLevelDetails *[]entity.UserRoleLevelDetails) (err error) + Update(id uint, userRoleLevelDetails *entity.UserRoleLevelDetails) (err error) + Delete(id uint) (err error) +} + +func NewUserRoleLevelDetailsRepository(db *database.Database) UserRoleLevelDetailsRepository { + return &userRoleLevelDetailsRepository{ + DB: db, + } +} + +// implement interface of IUserRoleLevelDetailsRepository +func (_i *userRoleLevelDetailsRepository) GetAll(req request.UserRoleLevelDetailsQueryRequest) (userRoleLevelDetailss []*entity.UserRoleLevelDetails, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.UserRoleLevelDetails{}) + query.Count(&count) + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&userRoleLevelDetailss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *userRoleLevelDetailsRepository) FindOne(id uint) (userRoleLevelDetails *entity.UserRoleLevelDetails, err error) { + if err := _i.DB.DB.First(&userRoleLevelDetails, id).Error; err != nil { + return nil, err + } + + return userRoleLevelDetails, nil +} + +func (_i *userRoleLevelDetailsRepository) FindByUserLevels(userLevelId uint) (userRoleLevelDetails []*entity.UserRoleLevelDetails, err error) { + if err := _i.DB.DB.Where(&entity.UserRoleLevelDetails{UserLevelId: userLevelId}).Find(&userRoleLevelDetails).Error; err != nil { + return nil, err + } + + return userRoleLevelDetails, nil +} + +func (_i *userRoleLevelDetailsRepository) Create(userRoleLevelDetails *entity.UserRoleLevelDetails) (err error) { + return _i.DB.DB.Create(userRoleLevelDetails).Error +} + +func (_i *userRoleLevelDetailsRepository) CreateAll(userRoleLevelDetails *[]entity.UserRoleLevelDetails) (err error) { + return _i.DB.DB.Create(userRoleLevelDetails).Error +} + +func (_i *userRoleLevelDetailsRepository) Update(id uint, userRoleLevelDetails *entity.UserRoleLevelDetails) (err error) { + return _i.DB.DB.Model(&entity.UserRoleLevelDetails{}). + Where(&entity.UserRoleLevelDetails{ID: id}). + Updates(userRoleLevelDetails).Error +} + +func (_i *userRoleLevelDetailsRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.UserRoleLevelDetails{}, id).Error +} diff --git a/app/module/user_role_level_details/request/user_role_level_details.request.go b/app/module/user_role_level_details/request/user_role_level_details.request.go new file mode 100644 index 0000000..c165def --- /dev/null +++ b/app/module/user_role_level_details/request/user_role_level_details.request.go @@ -0,0 +1,51 @@ +package request + +import ( + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/utils/paginator" +) + +type UserRoleLevelDetailsGeneric interface { + ToEntity() +} + +type UserRoleLevelDetailsQueryRequest struct { + UserRoleId uint `json:"user_role_id" validate:"required"` + UserLevelId uint `json:"user_level_id" validate:"required"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type UserRoleLevelDetailsCreateRequest struct { + UserRoleId uint `json:"user_role_id" validate:"required"` + UserLevelId uint `json:"user_level_id" validate:"required"` + IsActive *bool `json:"is_active" validate:"required"` +} + +func (req UserRoleLevelDetailsCreateRequest) ToEntity() *entity.UserRoleLevelDetails { + return &entity.UserRoleLevelDetails{ + UserRoleId: req.UserRoleId, + UserLevelId: req.UserLevelId, + IsActive: req.IsActive, + } +} + +type UserRoleLevelDetailsUpdateRequest struct { + ID uint `json:"id" validate:"required"` + UserRoleId uint `json:"user_role_id" validate:"required"` + UserLevelId uint `json:"user_level_id" validate:"required"` + IsActive *bool `json:"is_active" validate:"required"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (req UserRoleLevelDetailsUpdateRequest) ToEntity() *entity.UserRoleLevelDetails { + return &entity.UserRoleLevelDetails{ + ID: req.ID, + UserRoleId: req.UserRoleId, + UserLevelId: req.UserLevelId, + IsActive: req.IsActive, + CreatedAt: req.CreatedAt, + UpdatedAt: req.UpdatedAt, + } +} diff --git a/app/module/user_role_level_details/response/user_role_level_details.response.go b/app/module/user_role_level_details/response/user_role_level_details.response.go new file mode 100644 index 0000000..58ed186 --- /dev/null +++ b/app/module/user_role_level_details/response/user_role_level_details.response.go @@ -0,0 +1,12 @@ +package response + +import "time" + +type UserRoleLevelDetailsResponse struct { + ID uint `json:"id"` + UserRoleId uint `json:"user_role_id"` + UserLevelId uint `json:"user_level_id"` + IsActive *bool `json:"is_active"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} diff --git a/app/module/user_role_level_details/service/user_role_level_details.service.go b/app/module/user_role_level_details/service/user_role_level_details.service.go new file mode 100644 index 0000000..fadea9a --- /dev/null +++ b/app/module/user_role_level_details/service/user_role_level_details.service.go @@ -0,0 +1,72 @@ +package service + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/user_role_level_details/mapper" + "web-qudo-be/app/module/user_role_level_details/repository" + "web-qudo-be/app/module/user_role_level_details/request" + "web-qudo-be/app/module/user_role_level_details/response" + "web-qudo-be/utils/paginator" +) + +// UserRoleLevelDetailsService +type userRoleLevelDetailsService struct { + Repo repository.UserRoleLevelDetailsRepository + Log zerolog.Logger +} + +// UserRoleLevelDetailsService define interface of IUserRoleLevelDetailsService +type UserRoleLevelDetailsService interface { + All(req request.UserRoleLevelDetailsQueryRequest) (userRoleLevelDetails []*response.UserRoleLevelDetailsResponse, paging paginator.Pagination, err error) + Show(id uint) (userRoleLevelDetails *response.UserRoleLevelDetailsResponse, err error) + Save(req request.UserRoleLevelDetailsCreateRequest) (err error) + Update(id uint, req request.UserRoleLevelDetailsUpdateRequest) (err error) + Delete(id uint) error +} + +// NewUserRoleLevelDetailsService init UserRoleLevelDetailsService +func NewUserRoleLevelDetailsService(repo repository.UserRoleLevelDetailsRepository, log zerolog.Logger) UserRoleLevelDetailsService { + + return &userRoleLevelDetailsService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of UserRoleLevelDetailsService +func (_i *userRoleLevelDetailsService) All(req request.UserRoleLevelDetailsQueryRequest) (userRoleLevelDetailss []*response.UserRoleLevelDetailsResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + userRoleLevelDetailss = append(userRoleLevelDetailss, mapper.UserRoleLevelDetailsResponseMapper(result)) + } + + return +} + +func (_i *userRoleLevelDetailsService) Show(id uint) (userRoleLevelDetails *response.UserRoleLevelDetailsResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.UserRoleLevelDetailsResponseMapper(result), nil +} + +func (_i *userRoleLevelDetailsService) Save(req request.UserRoleLevelDetailsCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + return _i.Repo.Create(req.ToEntity()) +} + +func (_i *userRoleLevelDetailsService) Update(id uint, req request.UserRoleLevelDetailsUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *userRoleLevelDetailsService) Delete(id uint) error { + return _i.Repo.Delete(id) +} diff --git a/app/module/user_role_level_details/user_role_level_details.module.go b/app/module/user_role_level_details/user_role_level_details.module.go new file mode 100644 index 0000000..e82ca79 --- /dev/null +++ b/app/module/user_role_level_details/user_role_level_details.module.go @@ -0,0 +1,53 @@ +package user_role_level_details + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/user_role_level_details/controller" + "web-qudo-be/app/module/user_role_level_details/repository" + "web-qudo-be/app/module/user_role_level_details/service" +) + +// struct of UserRoleLevelDetailsRouter +type UserRoleLevelDetailsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of UserRoleLevelDetails module +var NewUserRoleLevelDetailsModule = fx.Options( + // register repository of UserRoleLevelDetails module + fx.Provide(repository.NewUserRoleLevelDetailsRepository), + + // register service of UserRoleLevelDetails module + fx.Provide(service.NewUserRoleLevelDetailsService), + + // register controller of UserRoleLevelDetails module + fx.Provide(controller.NewController), + + // register router of UserRoleLevelDetails module + fx.Provide(NewUserRoleLevelDetailsRouter), +) + +// init UserRoleLevelDetailsRouter +func NewUserRoleLevelDetailsRouter(fiber *fiber.App, controller *controller.Controller) *UserRoleLevelDetailsRouter { + return &UserRoleLevelDetailsRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of UserRoleLevelDetails module +func (_i *UserRoleLevelDetailsRouter) RegisterUserRoleLevelDetailsRoutes() { + // define controllers + userRoleLevelDetailsController := _i.Controller.UserRoleLevelDetails + + // define routes + _i.App.Route("/user-role-level-details", func(router fiber.Router) { + router.Get("/", userRoleLevelDetailsController.All) + router.Get("/:id", userRoleLevelDetailsController.Show) + router.Post("/", userRoleLevelDetailsController.Save) + router.Put("/:id", userRoleLevelDetailsController.Update) + router.Delete("/:id", userRoleLevelDetailsController.Delete) + }) +} diff --git a/app/module/user_roles/controller/controller.go b/app/module/user_roles/controller/controller.go new file mode 100644 index 0000000..c02d219 --- /dev/null +++ b/app/module/user_roles/controller/controller.go @@ -0,0 +1,16 @@ +package controller + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/module/user_roles/service" +) + +type Controller struct { + UserRoles UserRolesController +} + +func NewController(UserRolesService service.UserRolesService, log zerolog.Logger) *Controller { + return &Controller{ + UserRoles: NewUserRolesController(UserRolesService, log), + } +} diff --git a/app/module/user_roles/controller/user_roles.controller.go b/app/module/user_roles/controller/user_roles.controller.go new file mode 100644 index 0000000..88e6248 --- /dev/null +++ b/app/module/user_roles/controller/user_roles.controller.go @@ -0,0 +1,199 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/module/user_roles/request" + "web-qudo-be/app/module/user_roles/service" + "web-qudo-be/utils/paginator" + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" +) + +type userRolesController struct { + userRolesService service.UserRolesService + Log zerolog.Logger +} + +type UserRolesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewUserRolesController(userRolesService service.UserRolesService, log zerolog.Logger) UserRolesController { + return &userRolesController{ + userRolesService: userRolesService, + Log: log, + } +} + +// All UserRoles +// @Summary Get all UserRoles +// @Description API for getting all UserRoles +// @Tags UserRoles +// @Security Bearer +// @Param req query request.UserRolesQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-roles [get] +func (_i *userRolesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.UserRolesQueryRequestContext{ + Name: c.Query("name"), + Description: c.Query("description"), + Code: c.Query("code"), + UserLevelId: c.Query("userLevelId"), + StatusId: c.Query("statusId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + userRolesData, paging, err := _i.userRolesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoles list successfully retrieved"}, + Data: userRolesData, + Meta: paging, + }) +} + +// Show UserRoles +// @Summary Get one UserRoles +// @Description API for getting one UserRoles +// @Tags UserRoles +// @Security Bearer +// @Param id path int true "UserRoles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-roles/{id} [get] +func (_i *userRolesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + userRolesData, err := _i.userRolesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoles successfully retrieved"}, + Data: userRolesData, + }) +} + +// Save UserRoles +// @Summary Create UserRoles +// @Description API for create UserRoles +// @Tags UserRoles +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.UserRolesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-roles [post] +func (_i *userRolesController) Save(c *fiber.Ctx) error { + req := new(request.UserRolesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + err := _i.userRolesService.Save(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoles successfully created"}, + }) +} + +// Update UserRoles +// @Summary update UserRoles +// @Description API for update UserRoles +// @Tags UserRoles +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.UserRolesUpdateRequest true "Required payload" +// @Param id path int true "UserRoles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-roles/{id} [put] +func (_i *userRolesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.UserRolesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.userRolesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoles successfully updated"}, + }) +} + +// Delete UserRoles +// @Summary delete UserRoles +// @Description API for delete UserRoles +// @Tags UserRoles +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "UserRoles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-roles/{id} [delete] +func (_i *userRolesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.userRolesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoles successfully deleted"}, + }) +} diff --git a/app/module/user_roles/mapper/user_roles.mapper.go b/app/module/user_roles/mapper/user_roles.mapper.go new file mode 100644 index 0000000..c254948 --- /dev/null +++ b/app/module/user_roles/mapper/user_roles.mapper.go @@ -0,0 +1,24 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity" + res "web-qudo-be/app/module/user_roles/response" +) + +func UserRolesResponseMapper(userRolesReq *entity.UserRoles) (userRolesRes *res.UserRolesResponse) { + if userRolesReq != nil { + userRolesRes = &res.UserRolesResponse{ + ID: userRolesReq.ID, + Name: userRolesReq.Name, + Description: userRolesReq.Description, + Code: userRolesReq.Code, + UserLevelId: userRolesReq.UserLevelId, + StatusId: userRolesReq.StatusId, + CreatedById: userRolesReq.CreatedById, + IsActive: userRolesReq.IsActive, + CreatedAt: userRolesReq.CreatedAt, + UpdatedAt: userRolesReq.UpdatedAt, + } + } + return userRolesRes +} diff --git a/app/module/user_roles/repository/user_roles.repository.go b/app/module/user_roles/repository/user_roles.repository.go new file mode 100644 index 0000000..bebea89 --- /dev/null +++ b/app/module/user_roles/repository/user_roles.repository.go @@ -0,0 +1,105 @@ +package repository + +import ( + "fmt" + "github.com/rs/zerolog" + "strings" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/user_roles/request" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" +) + +type userRolesRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// UserRolesRepository define interface of IUserRolesRepository +type UserRolesRepository interface { + GetAll(req request.UserRolesQueryRequest) (userRoles []*entity.UserRoles, paging paginator.Pagination, err error) + FindOne(id uint) (userRoles *entity.UserRoles, err error) + Create(userRoles *entity.UserRoles) (userRolesReturn *entity.UserRoles, err error) + Update(id uint, userRoles *entity.UserRoles) (err error) + Delete(id uint) (err error) +} + +func NewUserRolesRepository(db *database.Database, log zerolog.Logger) UserRolesRepository { + return &userRolesRepository{ + DB: db, + Log: log, + } +} + +// implement interface of IUserRolesRepository +func (_i *userRolesRepository) GetAll(req request.UserRolesQueryRequest) (userRoles []*entity.UserRoles, paging paginator.Pagination, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + var count int64 + + query := _i.DB.DB.Model(&entity.UserRoles{}) + query = query.Where("is_active = ?", true) + + if req.Name != nil && *req.Name != "" { + name := strings.ToLower(*req.Name) + query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(name)+"%") + } + if req.Code != nil { + query = query.Where("code = ?", req.Code) + } + if req.UserLevelId != nil { + query = query.Where("user_level_id = ?", req.UserLevelId) + } + if req.StatusId != nil { + query = query.Where("status_id = ?", req.StatusId) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&userRoles).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *userRolesRepository) FindOne(id uint) (userRoles *entity.UserRoles, err error) { + if err := _i.DB.DB.First(&userRoles, id).Error; err != nil { + return nil, err + } + + return userRoles, nil +} + +func (_i *userRolesRepository) Create(userRoles *entity.UserRoles) (userRolesReturn *entity.UserRoles, err error) { + result := _i.DB.DB.Create(userRoles) + return userRoles, result.Error +} + +func (_i *userRolesRepository) Update(id uint, userRoles *entity.UserRoles) (err error) { + userRolesMap, err := utilSvc.StructToMap(userRoles) + if err != nil { + return err + } + return _i.DB.DB.Model(&entity.UserRoles{}). + Where(&entity.UserRoles{ID: id}). + Updates(userRolesMap).Error +} + +func (_i *userRolesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.UserRoles{}, id).Error +} diff --git a/app/module/user_roles/request/user_roles.request.go b/app/module/user_roles/request/user_roles.request.go new file mode 100644 index 0000000..c7ae8fd --- /dev/null +++ b/app/module/user_roles/request/user_roles.request.go @@ -0,0 +1,92 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity" + userRoleAccessReq "web-qudo-be/app/module/user_role_accesses/request" + "web-qudo-be/utils/paginator" +) + +type UserRolesGeneric interface { + ToEntity() +} + +type UserRolesQueryRequest struct { + Name *string `json:"name"` + Description *string `json:"description"` + Code *string `json:"code"` + UserLevelId *int `json:"userLevelId"` + StatusId *int `json:"statusId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type UserRolesCreateRequest struct { + Name string `json:"name" validate:"required"` + Description string `json:"description" validate:"required"` + Code string `json:"code" validate:"required"` + UserLevelIds []uint `json:"userLevelIds" validate:"required"` + UserRoleAccess []userRoleAccessReq.UserRoleAccessesCreateRequest `json:"userRoleAccess" validate:"required"` + StatusId int `json:"statusId" validate:"required"` +} + +func (req UserRolesCreateRequest) ToEntity() *entity.UserRoles { + return &entity.UserRoles{ + Name: req.Name, + Description: req.Description, + Code: req.Code, + StatusId: req.StatusId, + } +} + +type UserRolesUpdateRequest struct { + Name string `json:"name" validate:"required"` + Description string `json:"description" validate:"required"` + Code string `json:"code" validate:"required"` + LevelNumber int `json:"level_number" validate:"required"` + UserLevelIds []uint `json:"userLevelIds" validate:"required"` + StatusId int `json:"status_id" validate:"required"` +} + +func (req UserRolesUpdateRequest) ToEntity() *entity.UserRoles { + return &entity.UserRoles{ + Name: req.Name, + Description: req.Description, + Code: req.Code, + StatusId: req.StatusId, + UpdatedAt: time.Now(), + } +} + +type UserRolesQueryRequestContext struct { + Name string `json:"name"` + Description string `json:"description"` + Code string `json:"code"` + UserLevelId string `json:"userLevelId"` + StatusId string `json:"statusId"` +} + +func (req UserRolesQueryRequestContext) ToParamRequest() UserRolesQueryRequest { + var request UserRolesQueryRequest + + if name := req.Name; name != "" { + request.Name = &name + } + if code := req.Code; code != "" { + request.Code = &code + } + if userLevelIdStr := req.UserLevelId; userLevelIdStr != "" { + userLevelId, err := strconv.Atoi(userLevelIdStr) + if err == nil { + request.UserLevelId = &userLevelId + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + + return request +} diff --git a/app/module/user_roles/response/user_roles.response.go b/app/module/user_roles/response/user_roles.response.go new file mode 100644 index 0000000..7747582 --- /dev/null +++ b/app/module/user_roles/response/user_roles.response.go @@ -0,0 +1,16 @@ +package response + +import "time" + +type UserRolesResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Code string `json:"code"` + UserLevelId uint `json:"user_level_id"` + StatusId int `json:"status_id"` + CreatedById *uint `json:"created_by_id"` + IsActive *bool `json:"is_active"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} diff --git a/app/module/user_roles/service/user_roles.service.go b/app/module/user_roles/service/user_roles.service.go new file mode 100644 index 0000000..0e0d0c1 --- /dev/null +++ b/app/module/user_roles/service/user_roles.service.go @@ -0,0 +1,142 @@ +package service + +import ( + "github.com/rs/zerolog" + "web-qudo-be/app/database/entity" + userLevelsRepository "web-qudo-be/app/module/user_levels/repository" + userRoleAccessRepository "web-qudo-be/app/module/user_role_accesses/repository" + userRoleLevelDetailsRepository "web-qudo-be/app/module/user_role_level_details/repository" + "web-qudo-be/app/module/user_roles/mapper" + "web-qudo-be/app/module/user_roles/repository" + "web-qudo-be/app/module/user_roles/request" + "web-qudo-be/app/module/user_roles/response" + usersRepository "web-qudo-be/app/module/users/repository" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" +) + +// UserRolesService +type userRolesService struct { + Repo repository.UserRolesRepository + UsersRepo usersRepository.UsersRepository + UserLevelsRepo userLevelsRepository.UserLevelsRepository + UserRoleLevelDetailsRepo userRoleLevelDetailsRepository.UserRoleLevelDetailsRepository + UserRoleAccessRepo userRoleAccessRepository.UserRoleAccessesRepository + Log zerolog.Logger +} + +// UserRolesService define interface of IUserRolesService +type UserRolesService interface { + All(req request.UserRolesQueryRequest) (userRoles []*response.UserRolesResponse, paging paginator.Pagination, err error) + Show(id uint) (userRoles *response.UserRolesResponse, err error) + Save(req request.UserRolesCreateRequest, authToken string) (err error) + Update(id uint, req request.UserRolesUpdateRequest) (err error) + Delete(id uint) error +} + +// NewUserRolesService init UserRolesService +func NewUserRolesService( + repo repository.UserRolesRepository, + usersRepo usersRepository.UsersRepository, + userLevelsRepo userLevelsRepository.UserLevelsRepository, + userRoleLevelDetailsRepo userRoleLevelDetailsRepository.UserRoleLevelDetailsRepository, + userRoleAccessRepo userRoleAccessRepository.UserRoleAccessesRepository, + log zerolog.Logger, +) UserRolesService { + + return &userRolesService{ + Repo: repo, + UsersRepo: usersRepo, + UserLevelsRepo: userLevelsRepo, + UserRoleLevelDetailsRepo: userRoleLevelDetailsRepo, + UserRoleAccessRepo: userRoleAccessRepo, + Log: log, + } +} + +// All implement interface of UserRolesService +func (_i *userRolesService) All(req request.UserRolesQueryRequest) (userRoless []*response.UserRolesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + + if err != nil { + return + } + + for _, result := range results { + userRoless = append(userRoless, mapper.UserRolesResponseMapper(result)) + } + + return +} + +func (_i *userRolesService) Show(id uint) (userRoles *response.UserRolesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.UserRolesResponseMapper(result), nil +} + +func (_i *userRolesService) Save(req request.UserRolesCreateRequest, authToken string) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + newReq.CreatedById = &createdBy.ID + + userRolesReturn, err := _i.Repo.Create(newReq) + if err == nil { + var userRoleAccessList []entity.UserRoleAccesses + for _, item := range req.UserRoleAccess { + userRoleAccess := entity.UserRoleAccesses{ + UserRoleId: userRolesReturn.ID, + MenuId: item.MenuId, + IsViewEnabled: item.IsViewEnabled, + IsInsertEnabled: item.IsInsertEnabled, + IsUpdateEnabled: item.IsUpdateEnabled, + IsDeleteEnabled: item.IsDeleteEnabled, + IsApprovalEnabled: item.IsApprovalEnabled, + IsAdminEnabled: item.IsAdminEnabled, + } + userRoleAccessList = append(userRoleAccessList, userRoleAccess) + } + err := _i.UserRoleAccessRepo.CreateAll(&userRoleAccessList) + if err != nil { + return err + } + + var UserRoleLevelDetailList []entity.UserRoleLevelDetails + isActive := true + for _, id := range req.UserLevelIds { + userRoleLevelDetail := entity.UserRoleLevelDetails{ + UserRoleId: userRolesReturn.ID, + UserLevelId: id, + IsActive: &isActive, + } + UserRoleLevelDetailList = append(UserRoleLevelDetailList, userRoleLevelDetail) + } + err = _i.UserRoleLevelDetailsRepo.CreateAll(&UserRoleLevelDetailList) + if err != nil { + return err + } + } + + return err +} + +func (_i *userRolesService) Update(id uint, req request.UserRolesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *userRolesService) Delete(id uint) (err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + isActive := false + result.IsActive = &isActive + return _i.Repo.Update(id, result) +} diff --git a/app/module/user_roles/user_roles.module.go b/app/module/user_roles/user_roles.module.go new file mode 100644 index 0000000..d75810a --- /dev/null +++ b/app/module/user_roles/user_roles.module.go @@ -0,0 +1,53 @@ +package user_roles + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/user_roles/controller" + "web-qudo-be/app/module/user_roles/repository" + "web-qudo-be/app/module/user_roles/service" +) + +// struct of UserRolesRouter +type UserRolesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of UserRoles module +var NewUserRolesModule = fx.Options( + // register repository of UserRoles module + fx.Provide(repository.NewUserRolesRepository), + + // register service of UserRoles module + fx.Provide(service.NewUserRolesService), + + // register controller of UserRoles module + fx.Provide(controller.NewController), + + // register router of UserRoles module + fx.Provide(NewUserRolesRouter), +) + +// init UserRolesRouter +func NewUserRolesRouter(fiber *fiber.App, controller *controller.Controller) *UserRolesRouter { + return &UserRolesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of UserRoles module +func (_i *UserRolesRouter) RegisterUserRolesRoutes() { + // define controllers + userRolesController := _i.Controller.UserRoles + + // define routes + _i.App.Route("/user-roles", func(router fiber.Router) { + router.Get("/", userRolesController.All) + router.Get("/:id", userRolesController.Show) + router.Post("/", userRolesController.Save) + router.Put("/:id", userRolesController.Update) + router.Delete("/:id", userRolesController.Delete) + }) +} diff --git a/app/module/users/controller/controller.go b/app/module/users/controller/controller.go new file mode 100644 index 0000000..3902438 --- /dev/null +++ b/app/module/users/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "web-qudo-be/app/module/users/service" + +type Controller struct { + Users UsersController +} + +func NewController(UsersService service.UsersService) *Controller { + return &Controller{ + Users: NewUsersController(UsersService), + } +} diff --git a/app/module/users/controller/users.controller.go b/app/module/users/controller/users.controller.go new file mode 100644 index 0000000..56766ab --- /dev/null +++ b/app/module/users/controller/users.controller.go @@ -0,0 +1,588 @@ +package controller + +import ( + "strconv" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/users/request" + "web-qudo-be/app/module/users/service" + "web-qudo-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + _ "github.com/gofiber/fiber/v2" + + utilRes "web-qudo-be/utils/response" + utilVal "web-qudo-be/utils/validator" +) + +type usersController struct { + usersService service.UsersService +} + +type UsersController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + ShowByUsername(c *fiber.Ctx) error + ShowInfo(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Login(c *fiber.Ctx) error + ParetoLogin(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + SavePassword(c *fiber.Ctx) error + ResetPassword(c *fiber.Ctx) error + ForgotPassword(c *fiber.Ctx) error + OtpRequest(c *fiber.Ctx) error + OtpValidation(c *fiber.Ctx) error + EmailValidation(c *fiber.Ctx) error + SetupEmail(c *fiber.Ctx) error +} + +func NewUsersController(usersService service.UsersService) UsersController { + return &usersController{ + usersService: usersService, + } +} + +// All Users +// @Summary Get all Users +// @Description API for getting all Users +// @Tags Users +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param req query request.UsersQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users [get] +func (_i *usersController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.UsersQueryRequestContext{ + Username: c.Query("username"), + Email: c.Query("email"), + Fullname: c.Query("fullname"), + PhoneNumber: c.Query("phoneNumber"), + WorkType: c.Query("workType"), + GenderType: c.Query("genderType"), + IdentityType: c.Query("identityType"), + IdentityGroup: c.Query("identityGroup"), + IdentityGroupNumber: c.Query("identityGroupNumber"), + IdentityNumber: c.Query("identityNumber"), + UserRoleId: c.Query("userRoleId"), + StatusId: c.Query("statusId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + usersData, paging, err := _i.usersService.All(clientId, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users list successfully retrieved"}, + Data: usersData, + Meta: paging, + }) +} + +// Show Users +// @Summary Get one Users +// @Description API for getting one Users +// @Tags Users +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param id path int true "Users ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/detail/{id} [get] +func (_i *usersController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + usersData, err := _i.usersService.Show(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users successfully retrieved"}, + Data: usersData, + }) +} + +// ShowByUsername Users +// @Summary Get one Users +// @Description API for getting one Users +// @Tags Users +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param username path string true "Username" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/username/{username} [get] +func (_i *usersController) ShowByUsername(c *fiber.Ctx) error { + username := c.Params("username") + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + usersData, err := _i.usersService.ShowByUsername(clientId, username) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users successfully retrieved"}, + Data: usersData, + }) +} + +// ShowInfo Users +// @Summary ShowInfo Users +// @Description API for ShowUserInfo +// @Tags Users +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/info [get] +func (_i *usersController) ShowInfo(c *fiber.Ctx) error { + authToken := c.Get("Authorization") + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + dataResult, err := _i.usersService.ShowUserInfo(clientId, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users successfully retrieve"}, + Data: dataResult, + }) +} + +// Save Users +// @Summary Create Users +// @Description API for create Users +// @Tags Users +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.UsersCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users [post] +func (_i *usersController) Save(c *fiber.Ctx) error { + req := new(request.UsersCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + dataResult, err := _i.usersService.Save(clientId, *req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users successfully created"}, + Data: dataResult, + }) +} + +// Update Users +// @Summary update Users +// @Description API for update Users +// @Tags Users +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "Users ID" +// @Param payload body request.UsersUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/{id} [put] +func (_i *usersController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.UsersUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.usersService.Update(clientId, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users successfully updated"}, + }) +} + +// Login Users +// @Summary Login Users +// @Description API for Login Users +// @Tags Users +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.UserLogin true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/login [post] +func (_i *usersController) Login(c *fiber.Ctx) error { + req := new(request.UserLogin) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + loginResponse, err := _i.usersService.Login(*req) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Code: 401, + Messages: utilRes.Messages{err.Error()}, + }) + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users successfully login"}, + Data: loginResponse, + }) +} + +// ParetoLogin Users +// @Summary ParetoLogin Users +// @Description API for ParetoLogin Users +// @Tags Users +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.UserLogin true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/pareto-login [post] +func (_i *usersController) ParetoLogin(c *fiber.Ctx) error { + req := new(request.UserLogin) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + loginResponse, err := _i.usersService.ParetoLogin(*req) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Code: 401, + Messages: utilRes.Messages{err.Error()}, + }) + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users successfully login"}, + Data: loginResponse, + }) +} + +// Delete Users +// @Summary delete Users +// @Description API for delete Users +// @Tags Users +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "Users ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/{id} [delete] +func (_i *usersController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err = _i.usersService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users successfully deleted"}, + }) +} + +// SavePassword Users +// @Summary SavePassword Users +// @Description API for SavePassword Users +// @Tags Users +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.UserSavePassword true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/save-password [post] +func (_i *usersController) SavePassword(c *fiber.Ctx) error { + req := new(request.UserSavePassword) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err := _i.usersService.SavePassword(clientId, *req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users password successfully update"}, + }) +} + +// ResetPassword Users +// @Summary ResetPassword Users +// @Description API for ResetPassword Users +// @Tags Users +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.UserResetPassword true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/reset-password [post] +func (_i *usersController) ResetPassword(c *fiber.Ctx) error { + req := new(request.UserResetPassword) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.usersService.ResetPassword(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users password successfully reset"}, + }) +} + +// ForgotPassword Users +// @Summary ForgotPassword Users +// @Description API for ForgotPassword Users +// @Tags Users +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.UserForgotPassword true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/forgot-password [post] +func (_i *usersController) ForgotPassword(c *fiber.Ctx) error { + req := new(request.UserForgotPassword) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + err := _i.usersService.ForgotPassword(clientId, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users forgot password has sent"}, + }) +} + +// OtpRequest Users +// @Summary OtpRequest Users +// @Description API for OtpRequest Users +// @Tags Users +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.UserOtpRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/otp-request [post] +func (_i *usersController) OtpRequest(c *fiber.Ctx) error { + req := new(request.UserOtpRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.usersService.OtpRequest(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Otp has sent"}, + }) +} + +// OtpValidation Users +// @Summary OtpValidation Users +// @Description API for OtpValidation Users +// @Tags Users +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.UserOtpValidation true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/otp-validation [post] +func (_i *usersController) OtpValidation(c *fiber.Ctx) error { + req := new(request.UserOtpValidation) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.usersService.OtpValidation(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"OTP is valid"}, + }) +} + +// EmailValidation Users +// @Summary EmailValidation Users +// @Description API for Email Validation Users +// @Tags Users +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.UserEmailValidationRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/email-validation [post] +func (_i *usersController) EmailValidation(c *fiber.Ctx) error { + req := new(request.UserEmailValidationRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + messageResponse, err := _i.usersService.EmailValidationPreLogin(clientId, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{messageResponse}, + }) +} + +// SetupEmail Users +// @Summary SetupEmail Users +// @Description API for Setup Email Users +// @Tags Users +// @Security Bearer +// @Param X-Client-Key header string false "Insert the X-Client-Key" +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param payload body request.UserEmailValidationRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/setup-email [post] +func (_i *usersController) SetupEmail(c *fiber.Ctx) error { + req := new(request.UserEmailValidationRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + messageResponse, err := _i.usersService.SetupEmail(clientId, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{messageResponse}, + }) +} diff --git a/app/module/users/mapper/users.mapper.go b/app/module/users/mapper/users.mapper.go new file mode 100644 index 0000000..9df22e2 --- /dev/null +++ b/app/module/users/mapper/users.mapper.go @@ -0,0 +1,45 @@ +package mapper + +import ( + "web-qudo-be/app/database/entity/users" + userLevelsRepository "web-qudo-be/app/module/user_levels/repository" + res "web-qudo-be/app/module/users/response" + + "github.com/google/uuid" +) + +func UsersResponseMapper(usersReq *users.Users, userLevelsRepo userLevelsRepository.UserLevelsRepository, clientId *uuid.UUID) (usersRes *res.UsersResponse) { + if usersReq != nil { + findUserLevel, _ := userLevelsRepo.FindOne(clientId, usersReq.UserLevelId) + userLevelGroup := "" + if findUserLevel != nil { + userLevelGroup = findUserLevel.AliasName + } + + usersRes = &res.UsersResponse{ + ID: usersReq.ID, + Username: usersReq.Username, + Email: usersReq.Email, + Fullname: usersReq.Fullname, + Address: usersReq.Address, + PhoneNumber: usersReq.PhoneNumber, + WorkType: usersReq.WorkType, + GenderType: usersReq.GenderType, + IdentityType: usersReq.IdentityType, + IdentityNumber: usersReq.IdentityNumber, + DateOfBirth: usersReq.DateOfBirth, + LastEducation: usersReq.LastEducation, + KeycloakId: usersReq.KeycloakId, + UserRoleId: usersReq.UserRoleId, + UserLevelGroup: userLevelGroup, + StatusId: usersReq.StatusId, + UserLevelId: usersReq.UserLevelId, + CreatedById: usersReq.CreatedById, + ProfilePicturePath: usersReq.ProfilePicturePath, + IsActive: usersReq.IsActive, + CreatedAt: usersReq.CreatedAt, + UpdatedAt: usersReq.UpdatedAt, + } + } + return usersRes +} diff --git a/app/module/users/repository/users.repository.go b/app/module/users/repository/users.repository.go new file mode 100644 index 0000000..dd3b8c6 --- /dev/null +++ b/app/module/users/repository/users.repository.go @@ -0,0 +1,241 @@ +package repository + +import ( + "encoding/json" + "fmt" + "github.com/google/uuid" + "github.com/rs/zerolog" + "strings" + "web-qudo-be/app/database" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/database/entity/users" + "web-qudo-be/app/module/users/request" + "web-qudo-be/utils/paginator" +) + +type usersRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// UsersRepository define interface of IUsersRepository +type UsersRepository interface { + GetAll(clientId *uuid.UUID, req request.UsersQueryRequest) (userss []*users.Users, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (users *users.Users, err error) + FindByKeycloakId(keycloakId string) (users *users.Users, err error) + FindByUsername(clientId *uuid.UUID, username string) (users *users.Users, err error) + Create(users *users.Users) (userReturn *users.Users, err error) + Update(clientId *uuid.UUID, id uint, users *users.Users) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) + CreateForgotPassword(forgotPasswords *entity.ForgotPasswords) (err error) + UpdateForgotPassword(id uint, forgotPasswords *entity.ForgotPasswords) (err error) + FindForgotPassword(keycloakId string, code string) (forgotPasswords *entity.ForgotPasswords, err error) + CreateOtp(otp *entity.OneTimePasswords) (err error) + FindOtpByEmail(email string, code string) (otp *entity.OneTimePasswords, err error) + FindOtpByIdentity(identity string, code string) (otp *entity.OneTimePasswords, err error) +} + +func NewUsersRepository(db *database.Database, log zerolog.Logger) UsersRepository { + return &usersRepository{ + DB: db, + Log: log, + } +} + +// implement interface of IUsersRepository +func (_i *usersRepository) GetAll(clientId *uuid.UUID, req request.UsersQueryRequest) (userss []*users.Users, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&users.Users{}) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + query = query.Where("is_active = ?", true) + + if req.Username != nil && *req.Username != "" { + username := strings.ToLower(*req.Username) + query = query.Where("LOWER(username) LIKE ?", "%"+strings.ToLower(username)+"%") + } + if req.Fullname != nil && *req.Fullname != "" { + fullname := strings.ToLower(*req.Fullname) + query = query.Where("LOWER(fullname) LIKE ?", "%"+strings.ToLower(fullname)+"%") + } + if req.Email != nil && *req.Email != "" { + query = query.Where("email = ?", req.Email) + } + if req.PhoneNumber != nil && *req.PhoneNumber != "" { + query = query.Where("phone_number = ?", req.PhoneNumber) + } + if req.WorkType != nil && *req.WorkType != "" { + query = query.Where("work_type = ?", req.WorkType) + } + if req.GenderType != nil && *req.GenderType != "" { + query = query.Where("gender_type = ?", req.GenderType) + } + if req.IdentityType != nil && *req.IdentityType != "" { + query = query.Where("identity_type = ?", req.IdentityType) + } + if req.IdentityGroup != nil && *req.IdentityGroup != "" { + query = query.Where("identity_group = ?", req.IdentityGroup) + } + if req.IdentityGroupNumber != nil && *req.IdentityGroupNumber != "" { + query = query.Where("identity_group_number = ?", req.IdentityGroupNumber) + } + if req.IdentityNumber != nil && *req.IdentityNumber != "" { + query = query.Where("identity_number = ?", req.IdentityNumber) + } + if req.UserRoleId != nil { + query = query.Where("user_role_id = ?", req.UserRoleId) + } + if req.StatusId != nil { + query = query.Where("status_id = ?", req.StatusId) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&userss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *usersRepository) FindOne(clientId *uuid.UUID, id uint) (users *users.Users, err error) { + query := _i.DB.DB.Where("id = ?", id) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err := query.Preload("UserLevel").First(&users).Error; err != nil { + return nil, err + } + + return users, nil +} + +func (_i *usersRepository) FindByKeycloakId(keycloakId string) (users *users.Users, err error) { + query := _i.DB.DB.Where("keycloak_id = ?", keycloakId) + if err := query.Preload("UserLevel").First(&users).Error; err != nil { + return nil, err + } + + return users, nil +} + +func (_i *usersRepository) FindByUsername(clientId *uuid.UUID, username string) (users *users.Users, err error) { + query := _i.DB.DB.Where("username = ?", username) + + // Add ClientId filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err := query.Preload("UserLevel").First(&users).Error; err != nil { + return nil, err + } + + return users, nil +} + +func (_i *usersRepository) Create(users *users.Users) (userReturn *users.Users, err error) { + result := _i.DB.DB.Create(users) + return users, result.Error +} + +func (_i *usersRepository) Update(clientId *uuid.UUID, id uint, userReturn *users.Users) (err error) { + userReturnMap, err := StructToMap(userReturn) + delete(userReturnMap, "user_levels") + _i.Log.Info().Interface("Update", userReturnMap).Msg("") + if err != nil { + return err + } + query := _i.DB.DB.Model(&users.Users{}).Where(&users.Users{ID: id}) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + return query.Updates(userReturnMap).Error +} + +func (_i *usersRepository) Delete(clientId *uuid.UUID, id uint) error { + query := _i.DB.DB.Model(&users.Users{}).Where("id = ?", id) + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + return query.Delete(&users.Users{}).Error +} + +func (_i *usersRepository) CreateForgotPassword(forgotPasswords *entity.ForgotPasswords) (err error) { + result := _i.DB.DB.Create(forgotPasswords) + return result.Error +} + +func (_i *usersRepository) UpdateForgotPassword(id uint, forgotPasswords *entity.ForgotPasswords) (err error) { + _i.Log.Info().Interface("forgotPasswords", id).Msg("") + _i.Log.Info().Interface("forgotPasswords", forgotPasswords).Msg("") + + return _i.DB.DB.Model(&entity.ForgotPasswords{}). + Where(&entity.ForgotPasswords{ID: id}). + Updates(forgotPasswords).Error +} + +func (_i *usersRepository) FindForgotPassword(keycloakId string, code string) (forgotPasswords *entity.ForgotPasswords, err error) { + if err := _i.DB.DB.Where("keycloak_id = ?", keycloakId).Where("code_request = ?", code).Where("is_active = ?", true).First(&forgotPasswords).Error; err != nil { + return nil, err + } + + return forgotPasswords, nil +} + +func (_i *usersRepository) CreateOtp(otp *entity.OneTimePasswords) (err error) { + result := _i.DB.DB.Create(otp) + return result.Error +} + +func (_i *usersRepository) FindOtpByEmail(email string, code string) (otp *entity.OneTimePasswords, err error) { + if err := _i.DB.DB.Where("email = ?", email).Where("otp_code = ?", code).First(&otp).Error; err != nil { + return nil, err + } + + return otp, nil +} + +func (_i *usersRepository) FindOtpByIdentity(identity string, code string) (otp *entity.OneTimePasswords, err error) { + if err := _i.DB.DB.Where("identity = ?", identity).Where("otp_code = ?", code).First(&otp).Error; err != nil { + return nil, err + } + + return otp, nil +} + +func StructToMap(obj interface{}) (map[string]interface{}, error) { + var result map[string]interface{} + jsonData, err := json.Marshal(obj) + if err != nil { + return nil, err + } + + err = json.Unmarshal(jsonData, &result) + if err != nil { + return nil, err + } + + return result, nil +} diff --git a/app/module/users/request/users.request.go b/app/module/users/request/users.request.go new file mode 100644 index 0000000..50ce499 --- /dev/null +++ b/app/module/users/request/users.request.go @@ -0,0 +1,212 @@ +package request + +import ( + "strconv" + "time" + "web-qudo-be/app/database/entity/users" + "web-qudo-be/utils/paginator" +) + +type UsersGeneric interface { + ToEntity() +} + +type UsersQueryRequest struct { + Username *string `json:"username"` + Email *string `json:"email"` + Fullname *string `json:"fullname"` + PhoneNumber *string `json:"phoneNumber"` + WorkType *string `json:"workType"` + GenderType *string `json:"genderType"` + IdentityType *string `json:"identityType"` + IdentityGroup *string `json:"identityGroup"` + IdentityGroupNumber *string `json:"identityGroupNumber"` + IdentityNumber *string `json:"identityNumber"` + UserRoleId *int `json:"userRoleId"` + StatusId *int `json:"statusId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type UsersCreateRequest struct { + Username string `json:"username" validate:"required,lowercase"` + Email string `json:"email" validate:"required,email"` + Fullname string `json:"fullname" validate:"required"` + UserLevelId uint `json:"userLevelId" validate:"required"` + UserRoleId uint `json:"userRoleId" validate:"required"` + Password string `json:"password" validate:"required"` + PhoneNumber *string `json:"phoneNumber"` + Address *string `json:"address"` + WorkType *string `json:"workType"` + GenderType *string `json:"genderType"` + IdentityType *string `json:"identityType"` + IdentityGroup *string `json:"identityGroup"` + IdentityGroupNumber *string `json:"identityGroupNumber"` + IdentityNumber *string `json:"identityNumber"` + DateOfBirth *string `json:"dateOfBirth"` + LastEducation *string `json:"lastEducation"` +} + +func (req UsersCreateRequest) ToEntity() *users.Users { + return &users.Users{ + Username: req.Username, + Email: req.Email, + Fullname: req.Fullname, + Address: req.Address, + PhoneNumber: req.PhoneNumber, + WorkType: req.WorkType, + GenderType: req.GenderType, + IdentityType: req.IdentityType, + IdentityGroup: req.IdentityGroup, + IdentityGroupNumber: req.IdentityGroupNumber, + IdentityNumber: req.IdentityNumber, + DateOfBirth: req.DateOfBirth, + LastEducation: req.LastEducation, + UserRoleId: req.UserRoleId, + UserLevelId: req.UserLevelId, + } +} + +type UsersUpdateRequest struct { + Username string `json:"username" validate:"required,lowercase"` + Email string `json:"email" validate:"required,email"` + Fullname string `json:"fullname" validate:"required"` + UserLevelId uint `json:"userLevelId" validate:"required"` + UserRoleId uint `json:"userRoleId" validate:"required"` + PhoneNumber *string `json:"phoneNumber"` + Address *string `json:"address"` + WorkType *string `json:"workType"` + GenderType *string `json:"genderType"` + IdentityType *string `json:"identityType"` + IdentityGroup *string `json:"identityGroup"` + IdentityGroupNumber *string `json:"identityGroupNumber"` + IdentityNumber *string `json:"identityNumber"` + DateOfBirth *string `json:"dateOfBirth"` + LastEducation *string `json:"lastEducation"` + StatusId *int `json:"statusId"` +} + +func (req UsersUpdateRequest) ToEntity() *users.Users { + return &users.Users{ + Username: req.Username, + Email: req.Email, + Fullname: req.Fullname, + Address: req.Address, + PhoneNumber: req.PhoneNumber, + WorkType: req.WorkType, + GenderType: req.GenderType, + IdentityType: req.IdentityType, + IdentityGroup: req.IdentityGroup, + IdentityGroupNumber: req.IdentityGroupNumber, + IdentityNumber: req.IdentityNumber, + DateOfBirth: req.DateOfBirth, + LastEducation: req.LastEducation, + UserRoleId: req.UserRoleId, + StatusId: req.StatusId, + UserLevelId: req.UserLevelId, + UpdatedAt: time.Now(), + } +} + +type UserLogin struct { + Username *string `json:"username"` + Password *string `json:"password"` + RefreshToken *string `json:"refreshToken"` +} + +type UserForgotPassword struct { + Username string `json:"username"` +} + +type UserSavePassword struct { + Password string `json:"password"` + ConfirmPassword string `json:"confirmPassword"` +} + +type UserResetPassword struct { + CodeRequest string `json:"codeRequest"` + UserId string `json:"userId"` + Password string `json:"password"` + ConfirmPassword string `json:"confirmPassword"` +} + +type UserEmailValidationRequest struct { + Username *string `json:"username"` + Password *string `json:"password"` + OldEmail *string `json:"oldEmail"` + NewEmail *string `json:"newEmail"` +} + +type UserOtpRequest struct { + Email string `json:"email" validate:"required,email"` + Name *string `json:"name"` +} + +type UserOtpValidation struct { + Email *string `json:"email"` + Username *string `json:"username"` + OtpCode string `json:"otpCode"` +} + +type UsersQueryRequestContext struct { + Username string `json:"username"` + Email string `json:"email"` + Fullname string `json:"fullname"` + PhoneNumber string `json:"phoneNumber"` + WorkType string `json:"workType"` + GenderType string `json:"genderType"` + IdentityType string `json:"identityType"` + IdentityGroup string `json:"identityGroup"` + IdentityGroupNumber string `json:"identityGroupNumber"` + IdentityNumber string `json:"identityNumber"` + UserRoleId string `json:"userRoleId"` + StatusId string `json:"statusId"` +} + +func (req UsersQueryRequestContext) ToParamRequest() UsersQueryRequest { + var request UsersQueryRequest + + if username := req.Username; username != "" { + request.Username = &username + } + if email := req.Email; email != "" { + request.Email = &email + } + if fullname := req.Fullname; fullname != "" { + request.Fullname = &fullname + } + if phoneNumber := req.PhoneNumber; phoneNumber != "" { + request.PhoneNumber = &phoneNumber + } + if workType := req.WorkType; workType != "" { + request.WorkType = &workType + } + if genderType := req.GenderType; genderType != "" { + request.GenderType = &genderType + } + if identityType := req.IdentityType; identityType != "" { + request.IdentityType = &identityType + } + if identityGroup := req.IdentityGroup; identityGroup != "" { + request.IdentityGroup = &identityGroup + } + if identityGroupNumber := req.IdentityGroupNumber; identityGroupNumber != "" { + request.IdentityGroupNumber = &identityGroupNumber + } + if identityNumber := req.IdentityNumber; identityNumber != "" { + request.IdentityNumber = &identityNumber + } + if userRoleIdStr := req.UserRoleId; userRoleIdStr != "" { + userRoleId, err := strconv.Atoi(userRoleIdStr) + if err == nil { + request.UserRoleId = &userRoleId + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + + return request +} diff --git a/app/module/users/response/users.response.go b/app/module/users/response/users.response.go new file mode 100644 index 0000000..2b4c309 --- /dev/null +++ b/app/module/users/response/users.response.go @@ -0,0 +1,36 @@ +package response + +import "time" + +type UsersResponse struct { + ID uint `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + Fullname string `json:"fullname"` + Address *string `json:"address"` + PhoneNumber *string `json:"phoneNumber"` + WorkType *string `json:"workType"` + GenderType *string `json:"genderType"` + IdentityType *string `json:"identityType"` + IdentityNumber *string `json:"identityNumber"` + DateOfBirth *string `json:"dateOfBirth"` + LastEducation *string `json:"lastEducation"` + KeycloakId *string `json:"keycloakId"` + UserRoleId uint `json:"userRoleId"` + UserLevelId uint `json:"userLevelId"` + UserLevelGroup string `json:"userLevelGroup"` + StatusId *int `json:"statusId"` + CreatedById *uint `json:"createdById"` + ProfilePicturePath *string `json:"profilePicturePath"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type ParetoLoginResponse struct { + AccessToken string `json:"accessToken"` +} + +type VisitorStatistic struct { + TotalVisitor string `json:"accessToken"` +} diff --git a/app/module/users/service/users.service.go b/app/module/users/service/users.service.go new file mode 100644 index 0000000..73e8b7d --- /dev/null +++ b/app/module/users/service/users.service.go @@ -0,0 +1,561 @@ +package service + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "strings" + "time" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/database/entity/users" + userLevelsRepository "web-qudo-be/app/module/user_levels/repository" + "web-qudo-be/app/module/users/mapper" + "web-qudo-be/app/module/users/repository" + "web-qudo-be/app/module/users/request" + "web-qudo-be/app/module/users/response" + "web-qudo-be/config/config" + "web-qudo-be/utils/paginator" + utilSvc "web-qudo-be/utils/service" + + paseto "aidanwoods.dev/go-paseto" + "github.com/Nerzal/gocloak/v13" + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +// UsersService +type usersService struct { + Repo repository.UsersRepository + UserLevelsRepo userLevelsRepository.UserLevelsRepository + Log zerolog.Logger + Keycloak *config.KeycloakConfig + Smtp *config.SmtpConfig +} + +// UsersService define interface of IUsersService +type UsersService interface { + All(clientId *uuid.UUID, req request.UsersQueryRequest) (users []*response.UsersResponse, paging paginator.Pagination, err error) + Show(clientId *uuid.UUID, id uint) (users *response.UsersResponse, err error) + ShowByUsername(clientId *uuid.UUID, username string) (users *response.UsersResponse, err error) + ShowUserInfo(clientId *uuid.UUID, authToken string) (users *response.UsersResponse, err error) + Save(clientId *uuid.UUID, req request.UsersCreateRequest, authToken string) (userReturn *users.Users, err error) + Login(req request.UserLogin) (res *gocloak.JWT, err error) + ParetoLogin(req request.UserLogin) (res *response.ParetoLoginResponse, err error) + Update(clientId *uuid.UUID, id uint, req request.UsersUpdateRequest) (err error) + Delete(clientId *uuid.UUID, id uint) error + SavePassword(clientId *uuid.UUID, req request.UserSavePassword, authToken string) (err error) + ResetPassword(req request.UserResetPassword) (err error) + ForgotPassword(clientId *uuid.UUID, req request.UserForgotPassword) (err error) + EmailValidationPreLogin(clientId *uuid.UUID, req request.UserEmailValidationRequest) (msgResponse *string, err error) + SetupEmail(clientId *uuid.UUID, req request.UserEmailValidationRequest) (msgResponse *string, err error) + OtpRequest(req request.UserOtpRequest) (err error) + OtpValidation(req request.UserOtpValidation) (err error) + SendLoginOtp(name string, email string, otp string) error + SendRegistrationOtp(name string, email string, otp string) error +} + +// NewUsersService init UsersService +func NewUsersService(repo repository.UsersRepository, userLevelsRepo userLevelsRepository.UserLevelsRepository, log zerolog.Logger, keycloak *config.KeycloakConfig, smtp *config.SmtpConfig) UsersService { + + return &usersService{ + Repo: repo, + UserLevelsRepo: userLevelsRepo, + Log: log, + Keycloak: keycloak, + Smtp: smtp, + } +} + +// All implement interface of UsersService +func (_i *usersService) All(clientId *uuid.UUID, req request.UsersQueryRequest) (users []*response.UsersResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(clientId, req) + if err != nil { + return + } + + for _, result := range results { + users = append(users, mapper.UsersResponseMapper(result, _i.UserLevelsRepo, clientId)) + } + + return +} + +func (_i *usersService) Show(clientId *uuid.UUID, id uint) (users *response.UsersResponse, err error) { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return nil, err + } + + return mapper.UsersResponseMapper(result, _i.UserLevelsRepo, clientId), nil +} + +func (_i *usersService) ShowByUsername(clientId *uuid.UUID, username string) (users *response.UsersResponse, err error) { + result, err := _i.Repo.FindByUsername(clientId, username) + if err != nil { + return nil, err + } + + return mapper.UsersResponseMapper(result, _i.UserLevelsRepo, clientId), nil +} + +func (_i *usersService) ShowUserInfo(clientId *uuid.UUID, authToken string) (users *response.UsersResponse, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken) + + 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) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + _i.Log.Info().Interface("AUTH TOKEN", authToken).Msg("") + + if authToken != "" { + createdBy := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken) + newReq.CreatedById = &createdBy.ID + } + + keycloakId, err := _i.Keycloak.CreateUser(req.Fullname, req.Email, req.Username, req.Password) + if err != nil { + return nil, err + } + + newReq.KeycloakId = &keycloakId + newReq.TempPassword = &req.Password + + // Set ClientId on entity + newReq.ClientId = clientId + + return _i.Repo.Create(newReq) +} + +func (_i *usersService) Login(req request.UserLogin) (res *gocloak.JWT, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + var loginResponse *gocloak.JWT + if req.RefreshToken == nil { + loginResponse, err = _i.Keycloak.Login(*req.Username, *req.Password) + } else { + loginResponse, err = _i.Keycloak.RefreshToken(*req.RefreshToken) + } + if err != nil { + return nil, err + } + return loginResponse, nil +} + +func (_i *usersService) ParetoLogin(req request.UserLogin) (res *response.ParetoLoginResponse, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + var loginResponse *gocloak.JWT + token := paseto.NewToken() + secretKeyHex := "bdc42b1a0ba2bac3e27ba84241f9de06dee71b70f838af8d1beb0417f03d1d00" + secretKey, _ := paseto.V4SymmetricKeyFromHex(secretKeyHex) + // secretKey := paseto.NewV4SymmetricKey() // to change the secretKey periodically + + if req.RefreshToken == nil { + loginResponse, err = _i.Keycloak.Login(*req.Username, *req.Password) + } else { + // Retrieve Refresh Token + parser := paseto.NewParser() + verifiedToken, err := parser.ParseV4Local(secretKey, *req.RefreshToken, nil) + if err != nil { + panic(err) + } + + refreshToken, _ := verifiedToken.GetString("refresh_token") + _i.Log.Info().Interface("Pareto parse refresh token", refreshToken).Msg("") + + loginResponse, err = _i.Keycloak.RefreshToken(refreshToken) + } + + _i.Log.Info().Interface("loginResponse", loginResponse).Msg("") + + if err != nil { + return nil, err + } + + parseToken, err := ParseJWTToken(loginResponse.AccessToken) + if err != nil { + return nil, err + } + + issuedAt := parseToken["iat"].(float64) + expirationTime := parseToken["exp"].(float64) + issuer := parseToken["iss"].(string) + jti := parseToken["jti"].(string) + subject := parseToken["sub"].(string) + + token.SetIssuedAt(time.Unix(int64(issuedAt), 0)) + token.SetNotBefore(time.Unix(int64(issuedAt), 0)) + token.SetExpiration(time.Unix(int64(expirationTime), 0)) + token.SetIssuer(issuer) + token.SetJti(jti) + token.SetSubject(subject) + token.SetString("access_token", loginResponse.AccessToken) + token.SetString("refresh_token", loginResponse.RefreshToken) + + _i.Log.Info().Interface("Pareto Generated Key", secretKey.ExportHex()).Msg("") + + tokenEncrypted := token.V4Encrypt(secretKey, nil) + + parser := paseto.NewParser() + verifiedToken, err := parser.ParseV4Local(secretKey, tokenEncrypted, nil) + if err != nil { + panic(err) + } + + tokenParsing, _ := verifiedToken.GetString("access_token") + _i.Log.Info().Interface("Pareto parse token", tokenParsing).Msg("") + + resLogin := &response.ParetoLoginResponse{ + AccessToken: tokenEncrypted, + } + + if err != nil { + return nil, err + } + + return resLogin, nil +} + +func (_i *usersService) Update(clientId *uuid.UUID, id uint, req request.UsersUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + findUser, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return err + } + + err = _i.Keycloak.UpdateUser(findUser.KeycloakId, req.Fullname, req.Email) + if err != nil { + return err + } + + // Set ClientId on entity + newReq.ClientId = clientId + + return _i.Repo.Update(clientId, id, newReq) +} + +func (_i *usersService) Delete(clientId *uuid.UUID, id uint) error { + result, err := _i.Repo.FindOne(clientId, id) + if err != nil { + return err + } + + isActive := false + result.IsActive = &isActive + return _i.Repo.Update(clientId, id, result) +} + +func (_i *usersService) SavePassword(clientId *uuid.UUID, req request.UserSavePassword, authToken string) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + _i.Log.Info().Interface("AUTH TOKEN", authToken).Msg("") + + if authToken != "" { + createdBy := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken) + + tokenString := strings.TrimPrefix(authToken, "Bearer ") + + err := _i.Keycloak.SetPassword(tokenString, *createdBy.KeycloakId, req.Password) + if err != nil { + return err + } + + return nil + } else { + return fmt.Errorf("Invalid token") + } +} + +func (_i *usersService) ResetPassword(req request.UserResetPassword) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + if req.Password != req.ConfirmPassword { + return fmt.Errorf("Invalid Password") + } + + user, err := _i.Repo.FindByKeycloakId(req.UserId) + if err != nil { + return fmt.Errorf("User Id Not Found") + } + + forgotPassword, err := _i.Repo.FindForgotPassword(req.UserId, req.CodeRequest) + if err != nil { + return fmt.Errorf("Invalid Request") + } + + _i.Log.Info().Interface("data", forgotPassword).Msg("") + + _i.Log.Info().Interface("dataForgotPassword", forgotPassword).Msg("") + + if user != nil { + err := _i.Keycloak.SetPasswordWithoutToken(req.UserId, req.Password) + if err != nil { + return err + } + + forgotPassword.IsActive = false + forgotPassword.UpdatedAt = time.Now() + err = _i.Repo.UpdateForgotPassword(forgotPassword.ID, forgotPassword) + if err != nil { + return err + } + + return nil + } else { + return fmt.Errorf("User not found") + } +} + +func (_i *usersService) ForgotPassword(clientId *uuid.UUID, req request.UserForgotPassword) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + user, err := _i.Repo.FindByUsername(clientId, req.Username) + if err != nil { + return err + } + + if user != nil { + codeRequest, err := utilSvc.GenerateNumericCode(8) + if err != nil { + return nil + } + forgotPasswordReq := entity.ForgotPasswords{ + KeycloakID: *user.KeycloakId, + CodeRequest: codeRequest, + IsActive: true, + } + + err = _i.Repo.CreateForgotPassword(&forgotPasswordReq) + if err != nil { + return err + } + + // send email forgot password + url := fmt.Sprintf("https://kontenhumas.com/setup-password?userId=%s&code=%s", *user.KeycloakId, codeRequest) + + subject := "[HUMAS POLRI] Forgot Password" + htmlBody := "

Anda telah mengirimkan permintaan untuk melakukan reset password.

" + htmlBody += "

Silahkan buat password akun anda dengan menekan tombol di bawah ini, untuk membuat password baru

" + htmlBody += fmt.Sprintf("Reset Password", url) + htmlBody += "

Terimakasih.

" + err = _i.Smtp.SendEmail(subject, user.Email, user.Fullname, htmlBody) + + return nil + } else { + return fmt.Errorf("User not found") + } +} + +func (_i *usersService) OtpRequest(req request.UserOtpRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + codeRequest, err := utilSvc.GenerateNumericCode(6) + if req.Name == nil { + req.Name = &req.Email + } + if err != nil { + return err + } + otpReq := entity.OneTimePasswords{ + Email: req.Email, + Name: req.Name, + OtpCode: codeRequest, + IsActive: true, + } + + err = _i.Repo.CreateOtp(&otpReq) + if err != nil { + return err + } + + err = _i.SendRegistrationOtp(*req.Name, req.Email, codeRequest) + if err != nil { + return err + } + + return nil +} + +func (_i *usersService) OtpValidation(req request.UserOtpValidation) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + var otp *entity.OneTimePasswords + if req.Email == nil { + otp, err = _i.Repo.FindOtpByIdentity(*req.Username, req.OtpCode) + if err != nil { + return fmt.Errorf("OTP is not valid") + } + } else { + otp, err = _i.Repo.FindOtpByEmail(*req.Email, req.OtpCode) + if err != nil { + return fmt.Errorf("OTP is not valid") + } + } + + if otp != nil { + if otp.ValidUntil.Before(time.Now()) { + return fmt.Errorf("OTP has expired") + } + + return nil + } else { + return fmt.Errorf("OTP is not valid") + } +} + +func (_i *usersService) EmailValidationPreLogin(clientId *uuid.UUID, req request.UserEmailValidationRequest) (msgResponse *string, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + var loginResponse *gocloak.JWT + loginResponse, err = _i.Keycloak.Login(*req.Username, *req.Password) + + if loginResponse == nil || err != nil { + return nil, fmt.Errorf("username / password incorrect") + } + + findUser, err := _i.Repo.FindByUsername(clientId, *req.Username) + if findUser == nil || err != nil { + return nil, fmt.Errorf("username / password incorrect") + } + + _i.Log.Info().Interface("data user", findUser).Msg("") + + if *findUser.IsEmailUpdated != true { + message := "Continue to setup email" + msgResponse = &message + } else { + codeRequest, err := utilSvc.GenerateNumericCode(6) + if err != nil { + return nil, err + } + otpReq := entity.OneTimePasswords{ + Email: findUser.Email, + Identity: &findUser.Username, + OtpCode: codeRequest, + IsActive: true, + } + + err = _i.Repo.CreateOtp(&otpReq) + if err != nil { + return nil, err + } + + err = _i.SendLoginOtp(findUser.Fullname, findUser.Email, codeRequest) + if err != nil { + return nil, err + } else { + msg := "Email is valid and OTP has been sent" + msgResponse = &msg + } + } + + return msgResponse, nil +} + +func (_i *usersService) SetupEmail(clientId *uuid.UUID, req request.UserEmailValidationRequest) (msgResponse *string, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + var loginResponse *gocloak.JWT + loginResponse, err = _i.Keycloak.Login(*req.Username, *req.Password) + + if loginResponse == nil || err != nil { + return nil, fmt.Errorf("username / password incorrect") + } + + _i.Log.Info().Interface("findUser", "").Msg("") + findUser, err := _i.Repo.FindByUsername(clientId, *req.Username) + + _i.Log.Info().Interface("findUser", findUser).Msg("") + if findUser == nil || err != nil { + return nil, fmt.Errorf("username / password incorrect") + } + + isTrue := true + if findUser.Email == *req.OldEmail { + findUser.Email = *req.NewEmail + findUser.IsEmailUpdated = &isTrue + _i.Log.Info().Interface("Update", "").Msg("") + err = _i.Repo.Update(clientId, findUser.ID, findUser) + _i.Log.Info().Interface("Update", err).Msg("") + if err != nil { + return nil, err + } + + codeRequest, err := utilSvc.GenerateNumericCode(6) + if err != nil { + return nil, err + } + otpReq := entity.OneTimePasswords{ + Email: findUser.Email, + Identity: &findUser.Username, + OtpCode: codeRequest, + IsActive: true, + } + + err = _i.Repo.CreateOtp(&otpReq) + if err != nil { + return nil, err + } + + err = _i.SendLoginOtp(findUser.Fullname, findUser.Email, codeRequest) + if err != nil { + return nil, err + } else { + msg := "Email is valid and OTP has been sent" + msgResponse = &msg + } + } else { + return nil, fmt.Errorf("the old email is not same") + } + + return msgResponse, nil +} + +func ParseJWTToken(token string) (map[string]interface{}, error) { + // Pisahkan JWT menjadi 3 bagian: header, payload, dan signature + parts := strings.Split(token, ".") + if len(parts) != 3 { + return nil, fmt.Errorf("Invalid JWT token") + } + + // Decode bagian payload (index ke-1) + payloadData, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return nil, fmt.Errorf("Failed to decode payload: %v", err) + } + + // Ubah payload menjadi map[string]interface{} + var payload map[string]interface{} + if err := json.Unmarshal(payloadData, &payload); err != nil { + return nil, fmt.Errorf("Failed to parse payload JSON: %v", err) + } + + return payload, nil +} + +func (_i *usersService) SendLoginOtp(name string, email string, otp string) error { + subject := "[HUMAS POLRI] Permintaan OTP" + htmlBody := fmt.Sprintf("

Hai %s !

Berikut kode OTP yang digunakan untuk Login.

", name) + htmlBody += fmt.Sprintf("

%s

", otp) + htmlBody += "

Kode diatas hanya berlaku selama 10 menit. Harap segera masukkan kode tersebut pada aplikasi HUMAS POLRI.

" + htmlBody += "

Demi menjaga kerahasiaan data kamu, mohon jangan membagikan kode OTP ke siapapun.

" + err := _i.Smtp.SendEmail(subject, email, name, htmlBody) + + return err +} + +func (_i *usersService) SendRegistrationOtp(name string, email string, otp string) error { + subject := "[HUMAS POLRI] Permintaan OTP" + htmlBody := fmt.Sprintf("

Hai %s !

Berikut kode OTP yang digunakan untuk Verifikasi Registrasi.

", name) + htmlBody += fmt.Sprintf("

%s

", otp) + htmlBody += "

Kode diatas hanya berlaku selama 10 menit. Harap segera masukkan kode tersebut pada aplikasi HUMAS POLRI.

" + htmlBody += "

Demi menjaga kerahasiaan data kamu, mohon jangan membagikan kode OTP ke siapapun.

" + err := _i.Smtp.SendEmail(subject, email, name, htmlBody) + + return err +} diff --git a/app/module/users/users.module.go b/app/module/users/users.module.go new file mode 100644 index 0000000..2e9aa59 --- /dev/null +++ b/app/module/users/users.module.go @@ -0,0 +1,64 @@ +package users + +import ( + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" + "web-qudo-be/app/module/users/controller" + "web-qudo-be/app/module/users/repository" + "web-qudo-be/app/module/users/service" +) + +// struct of UsersRouter +type UsersRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of Users module +var NewUsersModule = fx.Options( + // register repository of Users module + fx.Provide(repository.NewUsersRepository), + + // register service of Users module + fx.Provide(service.NewUsersService), + + // register controller of Users module + fx.Provide(controller.NewController), + + // register router of Users module + fx.Provide(NewUsersRouter), +) + +// init UsersRouter +func NewUsersRouter(fiber *fiber.App, controller *controller.Controller) *UsersRouter { + return &UsersRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of Users module +func (_i *UsersRouter) RegisterUsersRoutes() { + // define controllers + usersController := _i.Controller.Users + + // define routes + _i.App.Route("/users", func(router fiber.Router) { + router.Get("/", usersController.All) + router.Get("/detail/:id", usersController.Show) + router.Get("/username/:username", usersController.ShowByUsername) + router.Get("/info", usersController.ShowInfo) + router.Post("/", usersController.Save) + router.Put("/:id", usersController.Update) + router.Post("/login", usersController.Login) + router.Post("/pareto-login", usersController.ParetoLogin) + router.Delete("/:id", usersController.Delete) + router.Post("/save-password", usersController.SavePassword) + router.Post("/reset-password", usersController.ResetPassword) + router.Post("/forgot-password", usersController.ForgotPassword) + router.Post("/otp-request", usersController.OtpRequest) + router.Post("/otp-validation", usersController.OtpValidation) + router.Post("/email-validation", usersController.EmailValidation) + router.Post("/setup-email", usersController.SetupEmail) + }) +} diff --git a/app/router/api.go b/app/router/api.go new file mode 100644 index 0000000..ef61b48 --- /dev/null +++ b/app/router/api.go @@ -0,0 +1,194 @@ +package router + +import ( + "web-qudo-be/app/module/activity_logs" + "web-qudo-be/app/module/advertisement" + "web-qudo-be/app/module/approval_workflow_steps" + "web-qudo-be/app/module/approval_workflows" + "web-qudo-be/app/module/article_approval_flows" + "web-qudo-be/app/module/article_approval_step_logs" + "web-qudo-be/app/module/article_approvals" + "web-qudo-be/app/module/article_categories" + "web-qudo-be/app/module/article_category_details" + "web-qudo-be/app/module/article_comments" + "web-qudo-be/app/module/article_files" + "web-qudo-be/app/module/article_nulis_ai" + "web-qudo-be/app/module/articles" + "web-qudo-be/app/module/bookmarks" + "web-qudo-be/app/module/cities" + "web-qudo-be/app/module/client_approval_settings" + "web-qudo-be/app/module/clients" + "web-qudo-be/app/module/custom_static_pages" + "web-qudo-be/app/module/districts" + "web-qudo-be/app/module/feedbacks" + "web-qudo-be/app/module/magazine_files" + "web-qudo-be/app/module/magazines" + "web-qudo-be/app/module/master_menus" + "web-qudo-be/app/module/master_modules" + "web-qudo-be/app/module/provinces" + "web-qudo-be/app/module/schedules" + "web-qudo-be/app/module/subscription" + "web-qudo-be/app/module/user_levels" + "web-qudo-be/app/module/user_role_accesses" + "web-qudo-be/app/module/user_roles" + "web-qudo-be/app/module/users" + "web-qudo-be/config/config" + _ "web-qudo-be/docs/swagger" + + swagger "github.com/arsmn/fiber-swagger/v2" + "github.com/gofiber/fiber/v2" +) + +type Router struct { + App fiber.Router + Cfg *config.Config + + ActivityLogsRouter *activity_logs.ActivityLogsRouter + AdvertisementRouter *advertisement.AdvertisementRouter + ApprovalWorkflowsRouter *approval_workflows.ApprovalWorkflowsRouter + ApprovalWorkflowStepsRouter *approval_workflow_steps.ApprovalWorkflowStepsRouter + ArticleApprovalFlowsRouter *article_approval_flows.ArticleApprovalFlowsRouter + ArticleApprovalStepLogsRouter *article_approval_step_logs.ArticleApprovalStepLogsRouter + ArticleCategoriesRouter *article_categories.ArticleCategoriesRouter + ArticleCategoryDetailsRouter *article_category_details.ArticleCategoryDetailsRouter + ArticleFilesRouter *article_files.ArticleFilesRouter + ArticleCommentsRouter *article_comments.ArticleCommentsRouter + ArticleApprovalsRouter *article_approvals.ArticleApprovalsRouter + ArticlesRouter *articles.ArticlesRouter + ArticleNulisAIRouter *article_nulis_ai.ArticleNulisAIRouter + BookmarksRouter *bookmarks.BookmarksRouter + CitiesRouter *cities.CitiesRouter + ClientApprovalSettingsRouter *client_approval_settings.ClientApprovalSettingsRouter + ClientsRouter *clients.ClientsRouter + CustomStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter + DistrictsRouter *districts.DistrictsRouter + FeedbacksRouter *feedbacks.FeedbacksRouter + MagazineFilesRouter *magazine_files.MagazineFilesRouter + MagazinesRouter *magazines.MagazinesRouter + MasterMenusRouter *master_menus.MasterMenusRouter + MasterModulesRouter *master_modules.MasterModulesRouter + ProvincesRouter *provinces.ProvincesRouter + SchedulesRouter *schedules.SchedulesRouter + SubscriptionRouter *subscription.SubscriptionRouter + UserLevelsRouter *user_levels.UserLevelsRouter + UserRoleAccessesRouter *user_role_accesses.UserRoleAccessesRouter + UserRolesRouter *user_roles.UserRolesRouter + UsersRouter *users.UsersRouter +} + +func NewRouter( + fiber *fiber.App, + cfg *config.Config, + + activityLogsRouter *activity_logs.ActivityLogsRouter, + advertisementRouter *advertisement.AdvertisementRouter, + approvalWorkflowsRouter *approval_workflows.ApprovalWorkflowsRouter, + approvalWorkflowStepsRouter *approval_workflow_steps.ApprovalWorkflowStepsRouter, + articleApprovalFlowsRouter *article_approval_flows.ArticleApprovalFlowsRouter, + articleApprovalStepLogsRouter *article_approval_step_logs.ArticleApprovalStepLogsRouter, + articleCategoriesRouter *article_categories.ArticleCategoriesRouter, + articleCategoryDetailsRouter *article_category_details.ArticleCategoryDetailsRouter, + articleFilesRouter *article_files.ArticleFilesRouter, + articleCommentsRouter *article_comments.ArticleCommentsRouter, + articleApprovalsRouter *article_approvals.ArticleApprovalsRouter, + articlesRouter *articles.ArticlesRouter, + articleNulisRouter *article_nulis_ai.ArticleNulisAIRouter, + bookmarksRouter *bookmarks.BookmarksRouter, + citiesRouter *cities.CitiesRouter, + clientApprovalSettingsRouter *client_approval_settings.ClientApprovalSettingsRouter, + clientsRouter *clients.ClientsRouter, + customStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter, + districtsRouter *districts.DistrictsRouter, + feedbacksRouter *feedbacks.FeedbacksRouter, + magazineFilesRouter *magazine_files.MagazineFilesRouter, + magazinesRouter *magazines.MagazinesRouter, + masterMenuRouter *master_menus.MasterMenusRouter, + masterModuleRouter *master_modules.MasterModulesRouter, + provincesRouter *provinces.ProvincesRouter, + schedulesRouter *schedules.SchedulesRouter, + subscriptionRouter *subscription.SubscriptionRouter, + userLevelsRouter *user_levels.UserLevelsRouter, + userRoleAccessesRouter *user_role_accesses.UserRoleAccessesRouter, + userRolesRouter *user_roles.UserRolesRouter, + usersRouter *users.UsersRouter, +) *Router { + return &Router{ + App: fiber, + Cfg: cfg, + ActivityLogsRouter: activityLogsRouter, + AdvertisementRouter: advertisementRouter, + ApprovalWorkflowsRouter: approvalWorkflowsRouter, + ApprovalWorkflowStepsRouter: approvalWorkflowStepsRouter, + ArticleApprovalFlowsRouter: articleApprovalFlowsRouter, + ArticleApprovalStepLogsRouter: articleApprovalStepLogsRouter, + ArticleCategoriesRouter: articleCategoriesRouter, + ArticleCategoryDetailsRouter: articleCategoryDetailsRouter, + ArticleFilesRouter: articleFilesRouter, + ArticleCommentsRouter: articleCommentsRouter, + ArticleApprovalsRouter: articleApprovalsRouter, + ArticlesRouter: articlesRouter, + ArticleNulisAIRouter: articleNulisRouter, + BookmarksRouter: bookmarksRouter, + CitiesRouter: citiesRouter, + ClientApprovalSettingsRouter: clientApprovalSettingsRouter, + ClientsRouter: clientsRouter, + CustomStaticPagesRouter: customStaticPagesRouter, + DistrictsRouter: districtsRouter, + FeedbacksRouter: feedbacksRouter, + MagazineFilesRouter: magazineFilesRouter, + MagazinesRouter: magazinesRouter, + MasterMenusRouter: masterMenuRouter, + MasterModulesRouter: masterModuleRouter, + ProvincesRouter: provincesRouter, + SchedulesRouter: schedulesRouter, + SubscriptionRouter: subscriptionRouter, + UserLevelsRouter: userLevelsRouter, + UserRoleAccessesRouter: userRoleAccessesRouter, + UserRolesRouter: userRolesRouter, + UsersRouter: usersRouter, + } +} + +// Register routes +func (r *Router) Register() { + // Test Routes + r.App.Get("/ping", func(c *fiber.Ctx) error { + return c.SendString("Pong! ๐Ÿ‘‹") + }) + + //Swagger Documentation + r.App.Get("/swagger/*", swagger.HandlerDefault) + + // Register routes of modules + r.ActivityLogsRouter.RegisterActivityLogsRoutes() + r.AdvertisementRouter.RegisterAdvertisementRoutes() + r.ApprovalWorkflowsRouter.RegisterApprovalWorkflowsRoutes() + r.ApprovalWorkflowStepsRouter.RegisterApprovalWorkflowStepsRoutes() + r.ArticleApprovalFlowsRouter.RegisterArticleApprovalFlowsRoutes() + r.ArticleApprovalStepLogsRouter.RegisterArticleApprovalStepLogsRoutes() + r.ArticleCategoriesRouter.RegisterArticleCategoriesRoutes() + r.ArticleCategoryDetailsRouter.RegisterArticleCategoryDetailsRoutes() + r.ArticleFilesRouter.RegisterArticleFilesRoutes() + r.ArticleApprovalsRouter.RegisterArticleApprovalsRoutes() + r.ArticlesRouter.RegisterArticlesRoutes() + r.ArticleCommentsRouter.RegisterArticleCommentsRoutes() + r.ArticleNulisAIRouter.RegisterArticleNulisAIRoutes() + r.BookmarksRouter.RegisterBookmarksRoutes() + r.CitiesRouter.RegisterCitiesRoutes() + r.ClientApprovalSettingsRouter.RegisterClientApprovalSettingsRoutes() + r.ClientsRouter.RegisterClientsRoutes() + r.CustomStaticPagesRouter.RegisterCustomStaticPagesRoutes() + r.DistrictsRouter.RegisterDistrictsRoutes() + r.FeedbacksRouter.RegisterFeedbacksRoutes() + r.MagazinesRouter.RegisterMagazinesRoutes() + r.MagazineFilesRouter.RegisterMagazineFilesRoutes() + r.MasterMenusRouter.RegisterMasterMenusRoutes() + r.MasterModulesRouter.RegisterMasterModulesRoutes() + r.ProvincesRouter.RegisterProvincesRoutes() + r.SchedulesRouter.RegisterSchedulesRoutes() + r.SubscriptionRouter.RegisterSubscriptionRoutes() + r.UserLevelsRouter.RegisterUserLevelsRoutes() + r.UserRoleAccessesRouter.RegisterUserRoleAccessesRoutes() + r.UsersRouter.RegisterUsersRoutes() + r.UserRolesRouter.RegisterUserRolesRoutes() +} diff --git a/config/config/index.config.go b/config/config/index.config.go new file mode 100644 index 0000000..5f2d2e4 --- /dev/null +++ b/config/config/index.config.go @@ -0,0 +1,175 @@ +package config + +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/compress" + "github.com/pelletier/go-toml/v2" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "os" + "path/filepath" + "runtime" + "strings" + "time" +) + +type app = struct { + Name string `toml:"name"` + Host string `toml:"host"` + Domain string `toml:"domain"` + Port string `toml:"port"` + ExternalPort string `toml:"external-port"` + PrintRoutes bool `toml:"print-routes"` + Prefork bool `toml:"prefork"` + Production bool `toml:"production"` + IdleTimeout time.Duration `toml:"idle-timeout"` + BodyLimit int `toml:"body-limit"` +} + +// db struct config +type db = struct { + Postgres struct { + DSN string `toml:"dsn"` + LogMode string `toml:"log-mode"` + Migrate bool `toml:"migrate"` + Seed bool `toml:"seed"` + } +} + +// log struct config +type logger = struct { + TimeFormat string `toml:"time-format"` + Level zerolog.Level `toml:"level"` + Prettier bool `toml:"prettier"` +} + +// middleware +type middleware = struct { + Compress struct { + Enable bool + Level compress.Level + } + + Recover struct { + Enable bool + } + + Monitor struct { + Enable bool + Path string + } + + Pprof struct { + Enable bool + } + + Cors struct { + Enable bool + } + + Limiter struct { + Enable bool + Max int + Expiration time.Duration `toml:"expiration_seconds"` + } + + Csrf struct { + Enable bool + CookieName string `toml:"cookie-name"` + CookieSameSite string `toml:"cookie-same-site"` + CookieSecure bool `toml:"cookie-secure"` + CookieSessionOnly bool `toml:"cookie-session-only"` + CookieHttpOnly bool `toml:"cookie-http-only"` + } + + AuditTrails struct { + Enable bool + Retention int + } +} + +// minio struct config +type objectStorage = struct { + MinioStorage struct { + Endpoint string `toml:"endpoint"` + AccessKeyID string `toml:"access-key-id"` + SecretAccessKey string `toml:"secret-access-key"` + UseSSL bool `toml:"use-ssl"` + BucketName string `toml:"bucket-name"` + Location string `toml:"location"` + } +} + +type keycloak = struct { + Endpoint string `toml:"endpoint"` + Realm string `toml:"realm"` + ClientId string `toml:"client-id"` + ClientSecret string `toml:"client-secret"` + AdminUsername string `toml:"admin-username"` + AdminPassword string `toml:"admin-password"` +} + +type smtp = struct { + Host string `toml:"host"` + Port int `toml:"port"` + Username string `toml:"username"` + Password string `toml:"password"` + FromAddress string `toml:"from-address"` + FromName string `toml:"from-name"` +} + +type Config struct { + App app + DB db + Logger logger + Middleware middleware + ObjectStorage objectStorage + Keycloak keycloak + Smtp smtp +} + +// NewConfig : initialize config +func NewConfig() *Config { + config, err := ParseConfig("config") + if err != nil && !fiber.IsChild() { + // panic if config is not found + log.Panic().Err(err).Msg("config not found") + } + + return config +} + +// ParseConfig : func to parse config +func ParseConfig(name string, debug ...bool) (*Config, error) { + var ( + contents *Config + file []byte + err error + ) + + if len(debug) > 0 { + file, err = os.ReadFile(name) + } else { + _, b, _, _ := runtime.Caller(0) + // get base path + path := filepath.Dir(filepath.Dir(filepath.Dir(b))) + file, err = os.ReadFile(filepath.Join(path, "./config/toml/", name+".toml")) + } + + if err != nil { + return &Config{}, err + } + + err = toml.Unmarshal(file, &contents) + + return contents, err +} + +// ParseAddress : func to parse address +func ParseAddress(raw string) (host, port string) { + if i := strings.LastIndex(raw, ":"); i > 0 { + return raw[:i], raw[i+1:] + } + + return raw, "" +} diff --git a/config/config/keycloak.config.go b/config/config/keycloak.config.go new file mode 100644 index 0000000..1d5c38f --- /dev/null +++ b/config/config/keycloak.config.go @@ -0,0 +1,189 @@ +package config + +import ( + "context" + "errors" + + "github.com/Nerzal/gocloak/v13" +) + +// MinioSetup struct +type KeycloakConfig struct { + Cfg *Config +} + +func NewKeycloakConfig(cfg *Config) *KeycloakConfig { + keycloakSetup := &KeycloakConfig{ + Cfg: cfg, + } + + return keycloakSetup +} + +func (_keycloak *KeycloakConfig) Login(username string, password string) (*gocloak.JWT, error) { + ctx := context.Background() + client := gocloak.NewClient(_keycloak.Cfg.Keycloak.Endpoint) + loginResponse, err := client.Login( + ctx, + _keycloak.Cfg.Keycloak.ClientId, + _keycloak.Cfg.Keycloak.ClientSecret, + _keycloak.Cfg.Keycloak.Realm, + username, + password, + ) + if err != nil { + return nil, errors.New("Invalid User Credentials") + } + + return loginResponse, nil +} + +func (_keycloak *KeycloakConfig) RefreshToken(refreshToken string) (*gocloak.JWT, error) { + ctx := context.Background() + client := gocloak.NewClient(_keycloak.Cfg.Keycloak.Endpoint) + loginResponse, err := client.RefreshToken( + ctx, + refreshToken, + _keycloak.Cfg.Keycloak.ClientId, + _keycloak.Cfg.Keycloak.ClientSecret, + _keycloak.Cfg.Keycloak.Realm, + ) + if err != nil { + return nil, errors.New("Invalid User Credentials") + } + + return loginResponse, nil +} + +func (_keycloak *KeycloakConfig) CreateUser(fullname string, email string, username string, password string) (string, error) { + ctx := context.Background() + client := gocloak.NewClient(_keycloak.Cfg.Keycloak.Endpoint) + token, err := client.Login( + ctx, + _keycloak.Cfg.Keycloak.ClientId, + _keycloak.Cfg.Keycloak.ClientSecret, + _keycloak.Cfg.Keycloak.Realm, + _keycloak.Cfg.Keycloak.AdminUsername, + _keycloak.Cfg.Keycloak.AdminPassword, + ) + if err != nil { + panic("Something wrong with the credentials or url") + } + + var group []string + group = append(group, "medols") + user := gocloak.User{ + FirstName: gocloak.StringP(fullname), + LastName: gocloak.StringP(fullname), + Email: gocloak.StringP(email), + Enabled: gocloak.BoolP(true), + EmailVerified: gocloak.BoolP(true), + Username: gocloak.StringP(username), + Groups: &group, + } + + keycloakId, err := client.CreateUser(ctx, token.AccessToken, _keycloak.Cfg.Keycloak.Realm, user) + if err != nil { + panic("Oh no!, failed to create user :(") + } + + err = _keycloak.SetPassword(token.AccessToken, keycloakId, password) + if err != nil { + return "", err + } + + return keycloakId, nil +} + +func (_keycloak *KeycloakConfig) UpdateUser(keycloakId *string, fullname string, email string) error { + ctx := context.Background() + client := gocloak.NewClient(_keycloak.Cfg.Keycloak.Endpoint) + token, err := client.Login( + ctx, + _keycloak.Cfg.Keycloak.ClientId, + _keycloak.Cfg.Keycloak.ClientSecret, + _keycloak.Cfg.Keycloak.Realm, + _keycloak.Cfg.Keycloak.AdminUsername, + _keycloak.Cfg.Keycloak.AdminPassword, + ) + if err != nil { + panic("Something wrong with the credentials or url") + } + + var group []string + group = append(group, "humas") + user := gocloak.User{ + ID: keycloakId, + FirstName: gocloak.StringP(fullname), + LastName: gocloak.StringP(fullname), + Email: gocloak.StringP(email), + Groups: &group, + } + + err = client.UpdateUser(ctx, token.AccessToken, _keycloak.Cfg.Keycloak.Realm, user) + if err != nil { + panic(err) + } + + return err +} + +func (_keycloak *KeycloakConfig) SetPassword(token string, keycloakId string, password string) error { + ctx := context.Background() + client := gocloak.NewClient(_keycloak.Cfg.Keycloak.Endpoint) + + err := client.SetPassword(ctx, token, keycloakId, _keycloak.Cfg.Keycloak.Realm, password, false) + if err != nil { + panic("Oh no!, failed to set password :(") + } + + return nil +} + +func (_keycloak *KeycloakConfig) SetPasswordWithoutToken(keycloakId string, password string) error { + ctx := context.Background() + client := gocloak.NewClient(_keycloak.Cfg.Keycloak.Endpoint) + + token, err := client.Login( + ctx, + _keycloak.Cfg.Keycloak.ClientId, + _keycloak.Cfg.Keycloak.ClientSecret, + _keycloak.Cfg.Keycloak.Realm, + _keycloak.Cfg.Keycloak.AdminUsername, + _keycloak.Cfg.Keycloak.AdminPassword, + ) + if err != nil { + panic("Something wrong with the credentials or url") + } + + err = client.SetPassword(ctx, token.AccessToken, keycloakId, _keycloak.Cfg.Keycloak.Realm, password, false) + if err != nil { + panic("Oh no!, failed to set password :(") + } + + return nil +} + +func (_keycloak *KeycloakConfig) GetUserSessions() ([]*gocloak.UserSessionRepresentation, error) { + ctx := context.Background() + client := gocloak.NewClient(_keycloak.Cfg.Keycloak.Endpoint) + + token, err := client.Login( + ctx, + _keycloak.Cfg.Keycloak.ClientId, + _keycloak.Cfg.Keycloak.ClientSecret, + _keycloak.Cfg.Keycloak.Realm, + _keycloak.Cfg.Keycloak.AdminUsername, + _keycloak.Cfg.Keycloak.AdminPassword, + ) + if err != nil { + panic("Something wrong with the credentials or url") + } + + sessionData, err := client.GetClientUserSessions(ctx, token.AccessToken, _keycloak.Cfg.Keycloak.Realm, _keycloak.Cfg.Keycloak.ClientId) + if err != nil { + panic("Oh no!, failed to set password :(") + } + + return sessionData, nil +} diff --git a/config/config/minio.config.go b/config/config/minio.config.go new file mode 100644 index 0000000..211db47 --- /dev/null +++ b/config/config/minio.config.go @@ -0,0 +1,71 @@ +package config + +import ( + "context" + "log" + + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" +) + +// MinioSetup struct +type MinioStorage struct { + Cfg *Config +} + +func NewMinio(cfg *Config) *MinioStorage { + minioSetup := &MinioStorage{ + Cfg: cfg, + } + + return minioSetup +} + +func (_minio *MinioStorage) ConnectMinio() (*minio.Client, error) { + ctx := context.Background() + endpoint := _minio.Cfg.ObjectStorage.MinioStorage.Endpoint + accessKeyID := _minio.Cfg.ObjectStorage.MinioStorage.AccessKeyID + secretAccessKey := _minio.Cfg.ObjectStorage.MinioStorage.SecretAccessKey + useSSL := _minio.Cfg.ObjectStorage.MinioStorage.UseSSL + + bucketName := _minio.Cfg.ObjectStorage.MinioStorage.BucketName + location := _minio.Cfg.ObjectStorage.MinioStorage.Location + + // Log connection attempt + log.Printf("[MinIO] Attempting to connect to MinIO at endpoint: %s (SSL: %v)", endpoint, useSSL) + + // Initialize minio client object. + minioClient, errInit := minio.New(endpoint, &minio.Options{ + Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), + Secure: useSSL, + }) + if errInit != nil { + log.Printf("[MinIO] ERROR: Failed to initialize MinIO client. Endpoint: %s, SSL: %v, Error: %v", endpoint, useSSL, errInit) + return nil, errInit + } + + log.Printf("[MinIO] MinIO client initialized successfully. Checking bucket: %s", bucketName) + + // Check if bucket exists first + exists, errBucketExists := minioClient.BucketExists(ctx, bucketName) + if errBucketExists != nil { + log.Printf("[MinIO] ERROR: Failed to check if bucket exists. Bucket: %s, Error: %v", bucketName, errBucketExists) + return nil, errBucketExists + } + + if exists { + log.Printf("[MinIO] Bucket '%s' already exists and is accessible", bucketName) + } else { + // Try to create the bucket + log.Printf("[MinIO] Bucket '%s' does not exist. Attempting to create it in location: %s", bucketName, location) + err := minioClient.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: location}) + if err != nil { + log.Printf("[MinIO] ERROR: Failed to create bucket. Bucket: %s, Location: %s, Error: %v", bucketName, location, err) + return nil, err + } + log.Printf("[MinIO] Successfully created bucket '%s' in location '%s'", bucketName, location) + } + + log.Printf("[MinIO] Successfully connected to MinIO and bucket '%s' is ready", bucketName) + return minioClient, nil +} diff --git a/config/config/smtp.config.go b/config/config/smtp.config.go new file mode 100644 index 0000000..a376074 --- /dev/null +++ b/config/config/smtp.config.go @@ -0,0 +1,64 @@ +package config + +import ( + "fmt" + "github.com/wneessen/go-mail" + ht "html/template" +) + +// StmpConfig struct +type SmtpConfig struct { + Cfg *Config +} + +func NewSmtpConfig(cfg *Config) *SmtpConfig { + smtpSetup := &SmtpConfig{ + Cfg: cfg, + } + + return smtpSetup +} + +func (_smtp *SmtpConfig) SendEmail(subject string, toAddress string, toName string, htmlBody string) (err error) { + println(_smtp.Cfg.Smtp.Host) + println(_smtp.Cfg.Smtp.Port) + println(_smtp.Cfg.Smtp.Username) + println(_smtp.Cfg.Smtp.Password) + println(subject) + println(toAddress) + println(toName) + println(htmlBody) + + client, err := mail.NewClient(_smtp.Cfg.Smtp.Host, mail.WithPort(_smtp.Cfg.Smtp.Port), mail.WithSSL(), + mail.WithSMTPAuth(mail.SMTPAuthPlain), mail.WithUsername(_smtp.Cfg.Smtp.Username), mail.WithPassword(_smtp.Cfg.Smtp.Password)) + if err != nil { + return fmt.Errorf("failed to create mail client: %s\n", err) + } + + message := mail.NewMsg() + + if err := message.EnvelopeFrom(_smtp.Cfg.Smtp.FromAddress); err != nil { + return fmt.Errorf("failed to set ENVELOPE FROM address: %s", err) + } + if err := message.FromFormat(_smtp.Cfg.Smtp.FromName, _smtp.Cfg.Smtp.FromAddress); err != nil { + return fmt.Errorf("failed to set formatted FROM address: %s", err) + } + if err := message.AddToFormat(toName, toAddress); err != nil { + return fmt.Errorf("failed to set formatted TO address: %s", err) + } + + message.SetMessageID() + message.SetDate() + message.Subject(subject) + + htmlTpl, err := ht.New("htmltpl").Parse(htmlBody) + if err := message.SetBodyHTMLTemplate(htmlTpl, toAddress); err != nil { + return fmt.Errorf("failed to add HTML template to mail body: %s", err) + } + + if err = client.DialAndSend(message); err != nil { + return fmt.Errorf("failed to send mail: %s\n", err) + } + + return nil +} diff --git a/config/logger/index.logger.go b/config/logger/index.logger.go new file mode 100644 index 0000000..c4b8751 --- /dev/null +++ b/config/logger/index.logger.go @@ -0,0 +1,45 @@ +package logger + +import ( + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "os" + "web-qudo-be/config/config" +) + +// NewLogger : initialize logger +func NewLogger(cfg *config.Config) zerolog.Logger { + zerolog.TimeFieldFormat = cfg.Logger.TimeFormat + + if cfg.Logger.Prettier { + //log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) + + logFile, _ := os.OpenFile( + "debug.log", + os.O_APPEND|os.O_CREATE|os.O_WRONLY, + 0664, + ) + + multi := zerolog.MultiLevelWriter(os.Stdout, logFile) + log.Logger = zerolog.New(multi).With().Timestamp().Logger() + } + + zerolog.SetGlobalLevel(cfg.Logger.Level) + + return log.Hook(PreforkHook{}) +} + +// PreforkHook : prefer hook for zerologger +type PreforkHook struct{} + +func (h PreforkHook) Run(e *zerolog.Event, level zerolog.Level, msg string) { + //if fiber.IsChild() { + // e.Discard() + //} +} + +type StringerFunc func() string + +func (f StringerFunc) String() string { + return f() +} diff --git a/config/toml/config.toml b/config/toml/config.toml new file mode 100644 index 0000000..73a2e73 --- /dev/null +++ b/config/toml/config.toml @@ -0,0 +1,82 @@ +# Configuration vars for cmd/app +[app] +name = "Fiber starter" +host = "http://38.47.185.86" +port = ":8800" +domain = "https://qudo.id/api" +external-port = ":8812" +idle-timeout = 5 # As seconds +print-routes = false +prefork = false +production = false +body-limit = 1048576000 # "100 * 1024 * 1024" + +[db.postgres] +dsn = "postgresql://medols_user:MedolsDB@2025@38.47.185.79:5432/medols_db" # ://:@:/ +log-mode = "ERROR" +migrate = true +seed = false + +[logger] +log-dir = "debug.log" +time-format = "" # https://pkg.go.dev/time#pkg-constants, https://github.com/rs/zerolog/blob/master/api.go#L10 +level = 0 # panic -> 5, fatal -> 4, error -> 3, warn -> 2, info -> 1, debug -> 0, trace -> -1 +prettier = true + +[objectstorage.miniostorage] +endpoint = "is3.cloudhost.id" +access-key-id = "YRP1RM617986USRU6NN8" +secret-access-key = "vfbwQDYb1m7nfzo4LVEz90BIyOWfBMZ6bfGQbqDO" +use-ssl = true +bucket-name = "mikulnews" +location = "us-east-1" + +[middleware.compress] +enable = true +level = 1 + +[middleware.recover] +enable = true + +[middleware.monitor] +enable = true +path = "/monitor" + +[middleware.pprof] +enable = true + +[middleware.cors] +enable = true + +[middleware.limiter] +enable = false +max = 500 +expiration_seconds = 60 + +[middleware.csrf] +enable = false +cookie-name = "csrf_" +cookie-same-site = "Lax" +cookie-secure = false +cookie-session-only = true +cookie-http-only = true + +[middleware.audittrails] +enable = true +retention = 30 + +[keycloak] +endpoint = "http://38.47.185.86:8008" +realm = "medols" +client-id = "medols-app" +client-secret = "iyonEpZbAUs20quwaNFLMwRX7MUgPRlS" +admin-username = "developer-admin" +admin-password = "P@ssw0rd.1" + +[smtp] +host = "mail.polri.go.id" +port = 465 +username = "webhumas.divhumas@polri.go.id" +password = "8vm1nxxTsaB6" +from-address = "webhumas.divhumas@polri.go.id" +from-name = "APLIKASI WEB HUMAS DIVHUMAS POLRI" \ No newline at end of file diff --git a/config/webserver/webserver.config.go b/config/webserver/webserver.config.go new file mode 100644 index 0000000..0fc83ac --- /dev/null +++ b/config/webserver/webserver.config.go @@ -0,0 +1,184 @@ +package webserver + +import ( + "context" + "flag" + "fmt" + "github.com/go-co-op/gocron" + "github.com/gofiber/fiber/v2" + futils "github.com/gofiber/fiber/v2/utils" + "github.com/rs/zerolog" + "go.uber.org/fx" + "os" + "runtime" + "strings" + "time" + "web-qudo-be/app/database" + "web-qudo-be/app/database/seeds" + md "web-qudo-be/app/middleware" + articlesService "web-qudo-be/app/module/articles/service" + "web-qudo-be/app/router" + "web-qudo-be/config/config" + "web-qudo-be/utils/response" +) + +// NewFiber : initialize the webserver +func NewFiber(cfg *config.Config) *fiber.App { + // setup + app := fiber.New(fiber.Config{ + ServerHeader: cfg.App.Name, + AppName: cfg.App.Name, + Prefork: cfg.App.Prefork, + ErrorHandler: response.ErrorHandler, + IdleTimeout: cfg.App.IdleTimeout * time.Second, + EnablePrintRoutes: cfg.App.PrintRoutes, + BodyLimit: cfg.App.BodyLimit, + DisableStartupMessage: true, + ReadBufferSize: 8192, + }) + + // pass production config to check it + response.IsProduction = cfg.App.Production + + return app +} + +// Start : function to start webserver +func Start(lifecycle fx.Lifecycle, cfg *config.Config, fiber *fiber.App, router *router.Router, middlewares *md.Middleware, db *database.Database, log zerolog.Logger) { + lifecycle.Append( + fx.Hook{ + OnStart: func(ctx context.Context) error { + + // Connect database + db.ConnectDatabase() + + // Register middlewares + middlewares.Register(db) + + // Register routes + router.Register() + + // Custom Startup Messages + host, port := config.ParseAddress(cfg.App.Port) + if host == "" { + if fiber.Config().Network == "tcp6" { + host = "[::1]" + } else { + host = "0.0.0.0" + } + } + + // ASCII Art + ascii, err := os.ReadFile("./storage/ascii_art.txt") + if err != nil { + log.Debug().Err(err).Msg("An unknown error occurred when to print ASCII art!") + } + + for _, line := range strings.Split(futils.UnsafeString(ascii), "\n") { + log.Info().Msg(line) + } + + // Information message + log.Info().Msg(fiber.Config().AppName + " is running at the moment!") + + // Debug informations + if !cfg.App.Production { + prefork := "Enabled" + procs := runtime.GOMAXPROCS(0) + if !cfg.App.Prefork { + procs = 1 + prefork = "Disabled" + } + + log.Debug().Msgf("Version: %s", "-") + log.Debug().Msgf("Host: %s", host) + log.Debug().Msgf("Port: %s", port) + log.Debug().Msgf("Prefork: %s", prefork) + log.Debug().Msgf("Handlers: %d", fiber.HandlersCount()) + log.Debug().Msgf("Processes: %d", procs) + log.Debug().Msgf("PID: %d", os.Getpid()) + } + + // Listen the app (with TLS Support) + //if cfg.App.TLS.Enable { + // log.Debug().Msg("TLS support was enabled.") + // + // if err := fiber.ListenTLS(cfg.App.Port, cfg.App.TLS.CertFile, cfg.App.TLS.KeyFile); err != nil { + // log.Error().Err(err).Msg("An unknown error occurred when to run server!") + // } + //} + + go func() { + if err := fiber.Listen(cfg.App.Port); err != nil { + log.Error().Err(err).Msg("An unknown error occurred when to run server!") + } + }() + + migrateFlag := flag.Bool("migrate", db.Cfg.DB.Postgres.Migrate, "migrate the database") + seedFlag := flag.Bool("seed", db.Cfg.DB.Postgres.Seed, "seed the database") + flag.Parse() + + // read flag -migrate to migrate the database + if *migrateFlag { + db.MigrateModels() + } + // read flag -seed to seed the database + if *seedFlag { + + // init seed models + masterStatusSeeder := seeds.MasterStatusesSeeder{} + masterApprovalStatusSeeder := seeds.MasterApprovalStatusesSeeder{} + activityLogsSeeder := seeds.ActivityLogsSeeder{} + approvalWorkflowsSeeder := seeds.ApprovalWorkflowsSeeder{} + allSeeders := []database.Seeder{masterStatusSeeder, masterApprovalStatusSeeder, activityLogsSeeder, approvalWorkflowsSeeder} + db.SeedModels(allSeeders) + } + + return nil + }, + OnStop: func(ctx context.Context) error { + log.Info().Msg("Shutting down the app...") + if err := fiber.Shutdown(); err != nil { + log.Panic().Err(err).Msg("") + } + + log.Info().Msg("Running cleanup tasks...") + log.Info().Msg("1- Shutdown the database") + db.ShutdownDatabase() + log.Info().Msgf("%s was successful shutdown.", cfg.App.Name) + log.Info().Msg("\u001b[96msee you again๐Ÿ‘‹\u001b[0m") + + return nil + }, + }, + ) +} + +func RunScheduling(lifecycle fx.Lifecycle, articleService articlesService.ArticlesService) *gocron.Scheduler { + scheduler := gocron.NewScheduler(time.UTC) + + // Tambahkan tugas yang berjalan setiap 10 detik + scheduler.Every(1).Minutes().SingletonMode().Do(func() { + fmt.Println("Run Scheduler", time.Now()) + err := articleService.ExecuteScheduling() + if err != nil { + fmt.Println("Scheduler Got An Error", time.Now()) + } + }) + + // Menambahkan lifecycle hooks di Fx + lifecycle.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + fmt.Println("Memulai scheduler...") + scheduler.StartAsync() // Mulai scheduler saat aplikasi dimulai + return nil + }, + OnStop: func(ctx context.Context) error { + fmt.Println("Menghentikan scheduler...") + scheduler.Stop() // Hentikan scheduler saat aplikasi dihentikan + return nil + }, + }) + + return scheduler +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0e2d97a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +version: "3.8" + +services: + web-qudo-be: + image: registry.gitlab.com/hanifsalafi/web-qudo-be:dev + build: + context: . + dockerfile: Dockerfile + volumes: + - ./data/web-qudo-be/logs:/app + ports: + - "8800:8800" diff --git a/docs/notes/api-endpoints-documentation.md b/docs/notes/api-endpoints-documentation.md new file mode 100644 index 0000000..5fbb087 --- /dev/null +++ b/docs/notes/api-endpoints-documentation.md @@ -0,0 +1,497 @@ +# API Endpoints Documentation + +## Overview +Dokumentasi lengkap untuk semua API endpoint yang tersedia di sistem dynamic article approval. Dokumen ini mencakup endpoint untuk approval workflows, article approval flows, approval workflow steps, article approval step logs, dan client approval settings. + +--- + +## ๐Ÿ“‹ Table of Contents +1. [Approval Workflows](#approval-workflows) +2. [Article Approval Flows](#article-approval-flows) +3. [Approval Workflow Steps](#approval-workflow-steps) +4. [Article Approval Step Logs](#article-approval-step-logs) +5. [Client Approval Settings](#client-approval-settings) +6. [Articles (Enhanced)](#articles-enhanced) + +--- + +## ๐Ÿ”„ Approval Workflows + +**Base Path:** `/approval-workflows` + +### Endpoint List + +| Endpoint | Method | Purpose | Description | +|----------|--------|---------|-------------| +| `/approval-workflows` | GET | Get All Workflows | Retrieve all approval workflows for the client | +| `/approval-workflows` | POST | Create Workflow | Create a new approval workflow | +| `/approval-workflows/:id` | GET | Get Workflow by ID | Get specific workflow details | +| `/approval-workflows/:id` | PUT | Update Workflow | Update existing workflow | +| `/approval-workflows/:id` | DELETE | Delete Workflow | Delete workflow (soft delete) | +| `/approval-workflows/default` | GET | Get Default Workflow | Get the default workflow for the client | +| `/approval-workflows/:id/with-steps` | GET | Get Workflow with Steps | Get workflow including all its approval steps | +| `/approval-workflows/:id/activate` | POST | Activate Workflow | Activate a workflow | +| `/approval-workflows/:id/deactivate` | POST | Deactivate Workflow | Deactivate a workflow | + +### Request/Response Examples + +#### Create Workflow +```json +POST /approval-workflows +{ + "name": "3-Level Editorial Review", + "description": "Standard editorial workflow with 3 approval levels", + "is_default": true, + "is_active": true, + "requires_approval": true, + "auto_publish": false, + "steps": [ + { + "step_order": 1, + "step_name": "Editor Review", + "required_user_level_id": 2, + "estimated_days": 1 + }, + { + "step_order": 2, + "step_name": "Senior Editor Review", + "required_user_level_id": 3, + "estimated_days": 2 + }, + { + "step_order": 3, + "step_name": "Editor in Chief", + "required_user_level_id": 4, + "estimated_days": 1 + } + ] +} +``` + +--- + +## ๐Ÿ“ Article Approval Flows + +**Base Path:** `/article-approval-flows` + +### Endpoint List + +| Endpoint | Method | Purpose | Description | +|----------|--------|---------|-------------| +| `/article-approval-flows` | GET | Get All Flows | Retrieve all article approval flows | +| `/article-approval-flows` | POST | Create Flow | Create new approval flow for article | +| `/article-approval-flows/:id` | GET | Get Flow by ID | Get specific approval flow details | +| `/article-approval-flows/:id` | PUT | Update Flow | Update approval flow | +| `/article-approval-flows/:id` | DELETE | Delete Flow | Delete approval flow | +| `/article-approval-flows/my-queue` | GET | Get My Queue | Get articles pending approval for current user | +| `/article-approval-flows/submit` | POST | Submit for Approval | Submit article for approval process | +| `/article-approval-flows/:id/approve` | POST | Approve Step | Approve current approval step | +| `/article-approval-flows/:id/reject` | POST | Reject Article | Reject article and return to author | +| `/article-approval-flows/:id/request-revision` | POST | Request Revision | Request revision from author | +| `/article-approval-flows/:id/resubmit` | POST | Resubmit After Revision | Resubmit article after revision | +| `/article-approval-flows/:id/status` | GET | Get Flow Status | Get current status of approval flow | + +### Request/Response Examples + +#### Submit for Approval +```json +POST /article-approval-flows/submit +{ + "article_id": 123, + "workflow_id": 1, + "message": "Ready for editorial review" +} +``` + +#### Approve Step +```json +POST /article-approval-flows/456/approve +{ + "message": "Content approved, moving to next level", + "comments": "Good quality content, minor formatting suggestions" +} +``` + +#### Reject Article +```json +POST /article-approval-flows/456/reject +{ + "reason": "Content does not meet editorial standards", + "feedback": "Please revise the introduction and add more supporting evidence", + "return_to_step": 1 +} +``` + +--- + +## โš™๏ธ Approval Workflow Steps + +**Base Path:** `/approval-workflow-steps` + +### Endpoint List + +| Endpoint | Method | Purpose | Description | +|----------|--------|---------|-------------| +| `/approval-workflow-steps` | GET | Get All Steps | Retrieve all workflow steps | +| `/approval-workflow-steps` | POST | Create Step | Create new workflow step | +| `/approval-workflow-steps/:id` | GET | Get Step by ID | Get specific step details | +| `/approval-workflow-steps/:id` | PUT | Update Step | Update existing step | +| `/approval-workflow-steps/:id` | DELETE | Delete Step | Delete workflow step | +| `/approval-workflow-steps/workflow/:workflow_id` | GET | Get Steps by Workflow | Get all steps for specific workflow | +| `/approval-workflow-steps/:id/reorder` | PUT | Reorder Step | Change step order in workflow | +| `/approval-workflow-steps/:id/activate` | POST | Activate Step | Activate specific step | +| `/approval-workflow-steps/:id/deactivate` | POST | Deactivate Step | Deactivate specific step | + +### Request/Response Examples + +#### Create Workflow Step +```json +POST /approval-workflow-steps +{ + "workflow_id": 1, + "step_order": 1, + "step_name": "Initial Review", + "step_description": "First level content review", + "required_user_level_id": 2, + "estimated_days": 1, + "is_required": true, + "can_skip": false, + "auto_approve_after_days": null +} +``` + +#### Reorder Step +```json +PUT /approval-workflow-steps/5/reorder +{ + "new_order": 2 +} +``` + +--- + +## ๐Ÿ“Š Article Approval Step Logs + +**Base Path:** `/article-approval-step-logs` + +### Endpoint List + +| Endpoint | Method | Purpose | Description | +|----------|--------|---------|-------------| +| `/article-approval-step-logs` | GET | Get All Logs | Retrieve all approval step logs | +| `/article-approval-step-logs` | POST | Create Log | Create new approval step log | +| `/article-approval-step-logs/:id` | GET | Get Log by ID | Get specific log details | +| `/article-approval-step-logs/:id` | PUT | Update Log | Update existing log | +| `/article-approval-step-logs/:id` | DELETE | Delete Log | Delete approval step log | +| `/article-approval-step-logs/flow/:flow_id` | GET | Get Logs by Flow | Get all logs for specific approval flow | +| `/article-approval-step-logs/article/:article_id` | GET | Get Logs by Article | Get all logs for specific article | +| `/article-approval-step-logs/user/:user_id` | GET | Get Logs by User | Get all logs created by specific user | +| `/article-approval-step-logs/step/:step_id` | GET | Get Logs by Step | Get all logs for specific workflow step | + +### Request/Response Examples + +#### Create Approval Step Log +```json +POST /article-approval-step-logs +{ + "approval_flow_id": 123, + "workflow_step_id": 5, + "action": "approved", + "action_by_user_id": 45, + "action_date": "2024-01-15T10:30:00Z", + "comments": "Content meets quality standards", + "metadata": { + "review_time_minutes": 15, + "revision_count": 0 + } +} +``` + +#### Get Logs by Flow +```json +GET /article-approval-step-logs/flow/123?page=1&limit=10 +``` + +--- + +## โš™๏ธ Client Approval Settings + +**Base Path:** `/client-approval-settings` + +### Endpoint List + +| Endpoint | Method | Purpose | Description | +|----------|--------|---------|-------------| +| `/client-approval-settings` | GET | Get Settings | Get current client approval settings | +| `/client-approval-settings` | PUT | Update Settings | Update client approval settings | +| `/client-approval-settings/toggle` | POST | Toggle Approval | Quick toggle approval on/off | +| `/client-approval-settings/enable` | POST | Enable Approval | Enable approval system with transition | +| `/client-approval-settings/disable` | POST | Disable Approval | Disable approval with auto-publish | +| `/client-approval-settings/default-workflow` | POST | Set Default Workflow | Set default workflow for client | +| `/client-approval-settings/exempt-users/add/{user_id}` | POST | Add Exempt User | Add user to approval exemption | +| `/client-approval-settings/exempt-users/remove/{user_id}` | POST | Remove Exempt User | Remove user from exemption | +| `/client-approval-settings/exempt-roles/add/{role_id}` | POST | Add Exempt Role | Add role to approval exemption | +| `/client-approval-settings/exempt-roles/remove/{role_id}` | POST | Remove Exempt Role | Remove role from exemption | +| `/client-approval-settings/exempt-categories/add/{category_id}` | POST | Add Exempt Category | Add category to exemption | +| `/client-approval-settings/exempt-categories/remove/{category_id}` | POST | Remove Exempt Category | Remove category from exemption | + +### Request/Response Examples + +#### Toggle Approval System +```json +POST /client-approval-settings/toggle +{ + "requires_approval": false +} +``` + +#### Enable Approval with Transition +```json +POST /client-approval-settings/enable +{ + "default_workflow_id": 1, + "reason": "Implementing content quality control" +} +``` + +#### Disable Approval with Auto-Publish +```json +POST /client-approval-settings/disable +{ + "reason": "Streamlining content publishing for breaking news", + "handle_action": "auto_approve" +} +``` + +#### Update Settings +```json +PUT /client-approval-settings +{ + "requires_approval": true, + "auto_publish_articles": false, + "default_workflow_id": 1, + "approval_exempt_users": [10, 15, 20], + "approval_exempt_roles": [2, 3], + "approval_exempt_categories": [5], + "require_approval_for": ["news", "opinion"], + "skip_approval_for": ["announcement", "update"] +} +``` + +--- + +## ๐Ÿ“ฐ Articles (Enhanced) + +**Base Path:** `/articles` + +### New Dynamic Approval Endpoints + +| Endpoint | Method | Purpose | Description | +|----------|--------|---------|-------------| +| `/articles/:id/submit-approval` | POST | Submit for Approval | Submit article for approval process | +| `/articles/:id/approval-status` | GET | Get Approval Status | Get current approval status and progress | +| `/articles/pending-approval` | GET | Get Pending Approvals | Get articles pending approval for user level | + +### Request/Response Examples + +#### Submit Article for Approval +```json +POST /articles/123/submit-approval +{ + "workflow_id": 1, + "message": "Ready for editorial review" +} +``` + +#### Get Approval Status +```json +GET /articles/123/approval-status +``` + +**Response:** +```json +{ + "success": true, + "data": { + "article_id": 123, + "current_status": "pending_approval", + "current_step": 1, + "total_steps": 3, + "workflow_name": "3-Level Editorial Review", + "current_step_name": "Editor Review", + "next_step_name": "Senior Editor Review", + "estimated_completion": "2024-01-18T10:30:00Z", + "approval_progress": { + "completed_steps": 0, + "total_steps": 3, + "percentage": 0 + }, + "pending_approver": { + "user_level_id": 2, + "user_level_name": "Editor", + "estimated_days": 1 + } + } +} +``` + +#### Get Pending Approvals +```json +GET /articles/pending-approval?page=1&limit=10 +``` + +**Response:** +```json +{ + "success": true, + "data": [ + { + "article_id": 123, + "title": "Breaking News Article", + "category_name": "News", + "author_name": "John Doe", + "submitted_at": "2024-01-15T09:00:00Z", + "current_step": 1, + "workflow_name": "3-Level Editorial Review", + "priority": "high", + "estimated_time": "2 days", + "article_thumbnail": "https://example.com/thumb.jpg" + } + ], + "pagination": { + "page": 1, + "limit": 10, + "total": 25, + "total_pages": 3 + } +} +``` + +--- + +## ๐Ÿ” Authentication & Authorization + +### Headers Required +``` +X-Client-Key: {client_key} +Authorization: Bearer {jwt_token} +``` + +### Client ID Context +Semua endpoint menggunakan `X-Client-Key` header untuk mengidentifikasi client dan memastikan data isolation. + +--- + +## ๐Ÿ“Š Response Format + +### Success Response +```json +{ + "success": true, + "messages": ["Operation completed successfully"], + "data": { ... }, + "pagination": { ... } // if applicable +} +``` + +### Error Response +```json +{ + "success": false, + "messages": ["Error message"], + "error": "BAD_REQUEST", + "data": null +} +``` + +--- + +## ๐Ÿš€ Usage Examples + +### Complete Workflow Creation +```bash +# 1. Create approval workflow +POST /approval-workflows +{ + "name": "Editorial Review", + "description": "Standard editorial workflow", + "is_default": true, + "requires_approval": true, + "auto_publish": false +} + +# 2. Create workflow steps +POST /approval-workflow-steps +{ + "workflow_id": 1, + "step_order": 1, + "step_name": "Editor Review", + "required_user_level_id": 2 +} + +# 3. Submit article for approval +POST /articles/123/submit-approval +{ + "workflow_id": 1, + "message": "Ready for review" +} + +# 4. Approve first step +POST /article-approval-flows/456/approve +{ + "message": "Content approved" +} +``` + +### Dynamic Approval Toggle +```bash +# Disable approval temporarily +POST /client-approval-settings/disable +{ + "reason": "Breaking news mode", + "handle_action": "auto_approve" +} + +# Re-enable approval +POST /client-approval-settings/enable +{ + "default_workflow_id": 1, + "reason": "Returning to normal process" +} +``` + +--- + +## ๐Ÿ“ Notes + +### Important Considerations +1. **Client Isolation**: Semua endpoint menggunakan client ID untuk data isolation +2. **Workflow Flexibility**: Workflow dapat dikonfigurasi dengan unlimited steps +3. **Dynamic Toggle**: Approval system dapat di-enable/disable secara real-time +4. **Audit Trail**: Semua approval actions dicatat untuk compliance +5. **Auto-Publish**: Artikel dapat di-auto-publish ketika approval di-disable + +### Performance Notes +- Pagination tersedia untuk endpoint yang return banyak data +- Database queries dioptimasi dengan proper indexing +- Caching dapat diimplementasikan untuk frequently accessed data + +### Security Features +- JWT token authentication +- Client-level data isolation +- Role-based access control +- Audit logging untuk semua actions + +--- + +## ๐Ÿ”„ Version History + +| Version | Date | Changes | +|---------|------|---------| +| 1.0.0 | 2024-01-15 | Initial API documentation | +| 1.1.0 | 2024-01-15 | Added dynamic approval toggle endpoints | +| 1.2.0 | 2024-01-15 | Enhanced with client approval settings | + +--- + +*Dokumentasi ini akan diupdate secara berkala sesuai dengan perkembangan sistem.* diff --git a/docs/notes/end-to-end-test-scenarios.md b/docs/notes/end-to-end-test-scenarios.md new file mode 100644 index 0000000..9125c69 --- /dev/null +++ b/docs/notes/end-to-end-test-scenarios.md @@ -0,0 +1,1098 @@ +# End-to-End Test Scenarios + +## Overview +Dokumentasi ini berisi skenario test end-to-end untuk sistem dynamic article approval. Setiap skenario mencakup langkah-langkah lengkap dari setup hingga completion, termasuk API calls, expected responses, dan business logic yang terjadi. + +--- + +## ๐Ÿ“‹ Table of Contents +1. [Skenario 1: Setup Workflow Standard 3-Level](#skenario-1-setup-workflow-standard-3-level) +2. [Skenario 2: Artikel Approval Process Normal](#skenario-2-artikel-approval-process-normal) +3. [Skenario 3: Dynamic Approval Toggle](#skenario-3-dynamic-approval-toggle) +4. [Skenario 4: Artikel Rejection & Revision](#skenario-4-artikel-rejection--revision) +5. [Skenario 5: Multi-Client dengan Approval Berbeda](#skenario-5-multi-client-dengan-approval-berbeda) +6. [Skenario 6: Breaking News Mode](#skenario-6-breaking-news-mode) +7. [Skenario 7: User Exemption Management](#skenario-7-user-exemption-management) +8. [Skenario 8: Workflow Modification](#skenario-8-workflow-modification) +9. [Skenario 9: Audit Trail & Reporting](#skenario-9-audit-trail--reporting) +10. [Skenario 10: Performance & Load Testing](#skenario-10-performance--load-testing) + +--- + +## ๐ŸŽฏ Skenario 1: Setup Workflow Standard 3-Level + +### **Business Context** +Setup workflow approval standard untuk editorial process dengan 3 level: Editor โ†’ Senior Editor โ†’ Editor in Chief. + +### **Test Steps** + +#### **Step 1: Create Approval Workflow** +```bash +POST /approval-workflows +Headers: + X-Client-Key: client_123 + Authorization: Bearer {jwt_token} + +Body: +{ + "name": "3-Level Editorial Review", + "description": "Standard editorial workflow for quality content", + "is_default": true, + "is_active": true, + "requires_approval": true, + "auto_publish": false +} +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "id": 1, + "name": "3-Level Editorial Review", + "description": "Standard editorial workflow for quality content", + "is_default": true, + "is_active": true, + "requires_approval": true, + "auto_publish": false, + "created_at": "2024-01-15T10:00:00Z" + } +} +``` + +#### **Step 2: Create Workflow Steps** +```bash +# Step 1: Editor Review +POST /approval-workflow-steps +{ + "workflow_id": 1, + "step_order": 1, + "step_name": "Editor Review", + "step_description": "Initial content review by editor", + "required_user_level_id": 2, + "estimated_days": 1, + "is_required": true, + "can_skip": false +} + +# Step 2: Senior Editor Review +POST /approval-workflow-steps +{ + "workflow_id": 1, + "step_order": 2, + "step_name": "Senior Editor Review", + "step_description": "Secondary review by senior editor", + "required_user_level_id": 3, + "estimated_days": 2, + "is_required": true, + "can_skip": false +} + +# Step 3: Editor in Chief +POST /approval-workflow-steps +{ + "workflow_id": 1, + "step_order": 3, + "step_name": "Editor in Chief", + "step_description": "Final approval by editor in chief", + "required_user_level_id": 4, + "estimated_days": 1, + "is_required": true, + "can_skip": false +} +``` + +#### **Step 3: Verify Workflow Setup** +```bash +GET /approval-workflows/1/with-steps +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "id": 1, + "name": "3-Level Editorial Review", + "steps": [ + { + "id": 1, + "step_order": 1, + "step_name": "Editor Review", + "required_user_level_id": 2, + "estimated_days": 1 + }, + { + "id": 2, + "step_order": 2, + "step_name": "Senior Editor Review", + "required_user_level_id": 3, + "estimated_days": 2 + }, + { + "id": 3, + "step_order": 3, + "step_name": "Editor in Chief", + "required_user_level_id": 4, + "estimated_days": 1 + } + ] + } +} +``` + +### **Validation Points** +- โœ… Workflow created dengan 3 steps +- โœ… Step order sequential (1, 2, 3) +- โœ… User level requirements set correctly +- โœ… Estimated days reasonable + +--- + +## ๐Ÿ“ Skenario 2: Artikel Approval Process Normal + +### **Business Context** +Artikel "Breaking News: Tech Conference 2024" perlu melalui approval process normal. + +### **Test Steps** + +#### **Step 1: Submit Article for Approval** +```bash +POST /articles/123/submit-approval +{ + "workflow_id": 1, + "message": "Ready for editorial review - Tech conference coverage" +} +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "approval_flow_id": 456, + "article_id": 123, + "current_step": 1, + "status": "pending_approval", + "workflow_name": "3-Level Editorial Review" + } +} +``` + +#### **Step 2: Check Approval Status** +```bash +GET /articles/123/approval-status +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "article_id": 123, + "current_status": "pending_approval", + "current_step": 1, + "total_steps": 3, + "workflow_name": "3-Level Editorial Review", + "current_step_name": "Editor Review", + "next_step_name": "Senior Editor Review", + "pending_approver": { + "user_level_id": 2, + "user_level_name": "Editor", + "estimated_days": 1 + } + } +} +``` + +#### **Step 3: Editor Approves (Step 1)** +```bash +POST /article-approval-flows/456/approve +{ + "message": "Content quality meets standards", + "comments": "Good coverage, minor grammar fixes suggested" +} +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "approval_flow_id": 456, + "current_step": 2, + "status": "pending_approval", + "next_approver": "Senior Editor" + } +} +``` + +#### **Step 4: Senior Editor Approves (Step 2)** +```bash +POST /article-approval-flows/456/approve +{ + "message": "Content approved for final review", + "comments": "Excellent coverage, ready for final approval" +} +``` + +#### **Step 5: Editor in Chief Approves (Step 3)** +```bash +POST /article-approval-flows/456/approve +{ + "message": "Final approval granted", + "comments": "Publish immediately - breaking news" +} +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "approval_flow_id": 456, + "status": "approved", + "article_status": "published", + "completion_date": "2024-01-15T15:30:00Z" + } +} +``` + +### **Validation Points** +- โœ… Artikel berhasil submit ke approval +- โœ… Progress melalui 3 steps berurutan +- โœ… Status update real-time +- โœ… Artikel otomatis publish setelah approval lengkap + +--- + +## โšก Skenario 3: Dynamic Approval Toggle + +### **Business Context** +Breaking news terjadi, perlu disable approval system sementara untuk immediate publishing. + +### **Test Steps** + +#### **Step 1: Check Current Settings** +```bash +GET /client-approval-settings +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "requires_approval": true, + "auto_publish_articles": false, + "default_workflow_id": 1 + } +} +``` + +#### **Step 2: Disable Approval System** +```bash +POST /client-approval-settings/disable +{ + "reason": "Breaking news mode - immediate publishing required", + "handle_action": "auto_approve" +} +``` + +**Expected Response:** +```json +{ + "success": true, + "messages": ["Approval system successfully disabled with auto-publish enabled"] +} +``` + +#### **Step 3: Create Article (Should Auto-Publish)** +```bash +POST /articles +{ + "title": "BREAKING: Major Tech Acquisition", + "content": "Breaking news content...", + "category_id": 1 +} +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "id": 124, + "status": "published", + "is_publish": true, + "published_at": "2024-01-15T16:00:00Z", + "approval_bypassed": true + } +} +``` + +#### **Step 4: Re-enable Approval System** +```bash +POST /client-approval-settings/enable +{ + "default_workflow_id": 1, + "reason": "Returning to normal approval process" +} +``` + +**Expected Response:** +```json +{ + "success": true, + "messages": ["Approval system successfully enabled with smooth transition"] +} +``` + +#### **Step 5: Verify Settings Restored** +```bash +GET /client-approval-settings +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "requires_approval": true, + "auto_publish_articles": false, + "default_workflow_id": 1 + } +} +``` + +### **Validation Points** +- โœ… Approval system berhasil di-disable +- โœ… Artikel baru auto-publish tanpa approval +- โœ… Settings berhasil di-restore +- โœ… Approval system kembali normal + +--- + +## โŒ Skenario 4: Artikel Rejection & Revision + +### **Business Context** +Artikel "Product Review: Smartphone X" ditolak di step 2, perlu revision. + +### **Test Steps** + +#### **Step 1: Submit Article for Approval** +```bash +POST /articles/125/submit-approval +{ + "workflow_id": 1, + "message": "Product review ready for approval" +} +``` + +#### **Step 2: Editor Approves (Step 1)** +```bash +POST /article-approval-flows/457/approve +{ + "message": "Initial review passed", + "comments": "Good structure, ready for senior review" +} +``` + +#### **Step 3: Senior Editor Rejects (Step 2)** +```bash +POST /article-approval-flows/457/reject +{ + "reason": "Insufficient technical details", + "feedback": "Please add more technical specifications and benchmark comparisons", + "return_to_step": 1 +} +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "approval_flow_id": 457, + "status": "revision_requested", + "article_status": "draft", + "returned_to_step": 1, + "rejection_reason": "Insufficient technical details" + } +} +``` + +#### **Step 4: Check Article Status** +```bash +GET /articles/125/approval-status +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "status": "revision_requested", + "current_step": 1, + "rejection_feedback": "Please add more technical specifications and benchmark comparisons", + "returned_to_step": 1 + } +} +``` + +#### **Step 5: Resubmit After Revision** +```bash +POST /article-approval-flows/457/resubmit +{ + "message": "Article revised with additional technical details", + "revision_summary": "Added benchmark comparisons and technical specifications" +} +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "approval_flow_id": 457, + "status": "pending_approval", + "current_step": 1, + "revision_count": 1 + } +} +``` + +### **Validation Points** +- โœ… Artikel berhasil di-reject dengan feedback +- โœ… Status berubah ke revision_requested +- โœ… Artikel kembali ke step 1 +- โœ… Revision tracking berfungsi +- โœ… Resubmission berhasil + +--- + +## ๐Ÿข Skenario 5: Multi-Client dengan Approval Berbeda + +### **Business Context** +3 client berbeda dengan kebutuhan approval yang berbeda: +- Client A: Always requires approval +- Client B: Never requires approval +- Client C: Conditional approval + +### **Test Steps** + +#### **Step 1: Setup Client A (Always Approval)** +```bash +# Set client context +X-Client-Key: client_a + +POST /client-approval-settings +{ + "requires_approval": true, + "auto_publish_articles": false, + "default_workflow_id": 1 +} +``` + +#### **Step 2: Setup Client B (No Approval)** +```bash +# Set client context +X-Client-Key: client_b + +POST /client-approval-settings +{ + "requires_approval": false, + "auto_publish_articles": true +} +``` + +#### **Step 3: Setup Client C (Conditional)** +```bash +# Set client context +X-Client-Key: client_c + +POST /client-approval-settings +{ + "requires_approval": true, + "auto_publish_articles": false, + "default_workflow_id": 1, + "approval_exempt_users": [10, 15], + "approval_exempt_categories": [5], + "skip_approval_for": ["announcement", "update"] +} +``` + +#### **Step 4: Test Client A (Always Approval)** +```bash +X-Client-Key: client_a + +POST /articles +{ + "title": "Client A Article", + "content": "Content from client A" +} +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "status": "draft", + "approval_required": true + } +} +``` + +#### **Step 5: Test Client B (No Approval)** +```bash +X-Client-Key: client_b + +POST /articles +{ + "title": "Client B Article", + "content": "Content from client B" +} +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "status": "published", + "is_publish": true, + "approval_bypassed": true + } +} +``` + +#### **Step 6: Test Client C (Conditional)** +```bash +X-Client-Key: client_c + +# Test exempt user +POST /articles +{ + "title": "Exempt User Article", + "content": "Content from exempt user", + "author_id": 10 +} +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "status": "published", + "approval_bypassed": true, + "exemption_reason": "user_exempt" + } +} + +# Test exempt category +POST /articles +{ + "title": "Announcement", + "content": "Company announcement", + "category_id": 5 +} +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "status": "published", + "approval_bypassed": true, + "exemption_reason": "category_exempt" + } +} +``` + +### **Validation Points** +- โœ… Client A selalu require approval +- โœ… Client B auto-publish tanpa approval +- โœ… Client C conditional approval berfungsi +- โœ… Exemption rules diterapkan dengan benar +- โœ… Multi-client isolation berfungsi + +--- + +## ๐Ÿšจ Skenario 6: Breaking News Mode + +### **Business Context** +Emergency situation memerlukan immediate publishing tanpa approval process. + +### **Test Steps** + +#### **Step 1: Activate Breaking News Mode** +```bash +POST /client-approval-settings/disable +{ + "reason": "EMERGENCY: Breaking news situation", + "handle_action": "auto_approve" +} +``` + +#### **Step 2: Handle Pending Articles** +```bash +# Check pending articles +GET /article-approval-flows?status=pending_approval + +# Expected: All pending articles auto-approved +``` + +#### **Step 3: Create Emergency Articles** +```bash +POST /articles +{ + "title": "EMERGENCY ALERT: System Maintenance", + "content": "Emergency maintenance required...", + "priority": "critical" +} +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "status": "published", + "is_publish": true, + "published_at": "2024-01-15T18:00:00Z", + "emergency_mode": true + } +} +``` + +#### **Step 4: Monitor Auto-Publishing** +```bash +# Check recent articles +GET /articles?status=published&limit=10 + +# Expected: All articles published immediately +``` + +#### **Step 5: Return to Normal Mode** +```bash +POST /client-approval-settings/enable +{ + "default_workflow_id": 1, + "reason": "Emergency situation resolved" +} +``` + +### **Validation Points** +- โœ… Breaking news mode aktif +- โœ… Pending articles auto-approved +- โœ… New articles auto-publish +- โœ… Normal mode restored +- โœ… Emergency tracking berfungsi + +--- + +## ๐Ÿ‘ฅ Skenario 7: User Exemption Management + +### **Business Context** +Manage user exemptions untuk approval system. + +### **Test Steps** + +#### **Step 1: Add Exempt User** +```bash +POST /client-approval-settings/exempt-users/add/25 +``` + +**Expected Response:** +```json +{ + "success": true, + "messages": ["User successfully added to approval exemption"] +} +``` + +#### **Step 2: Add Exempt Role** +```bash +POST /client-approval-settings/exempt-roles/add/3 +``` + +#### **Step 3: Add Exempt Category** +```bash +POST /client-approval-settings/exempt-categories/add/7 +``` + +#### **Step 4: Test Exempt User** +```bash +POST /articles +{ + "title": "Exempt User Article", + "content": "Content from exempt user", + "author_id": 25 +} +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "status": "published", + "approval_bypassed": true, + "exemption_reason": "user_exempt" + } +} +``` + +#### **Step 5: Remove Exemption** +```bash +POST /client-approval-settings/exempt-users/remove/25 +``` + +#### **Step 6: Verify Exemption Removed** +```bash +POST /articles +{ + "title": "Non-Exempt Article", + "content": "Content from non-exempt user", + "author_id": 25 +} +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "status": "draft", + "approval_required": true + } +} +``` + +### **Validation Points** +- โœ… User exemption berhasil ditambahkan +- โœ… Exempt user bypass approval +- โœ… Exemption berhasil di-remove +- โœ… Non-exempt user require approval + +--- + +## ๐Ÿ”ง Skenario 8: Workflow Modification + +### **Business Context** +Modify existing workflow untuk menambah step baru. + +### **Test Steps** + +#### **Step 1: Get Current Workflow** +```bash +GET /approval-workflows/1/with-steps +``` + +#### **Step 2: Add New Step** +```bash +POST /approval-workflow-steps +{ + "workflow_id": 1, + "step_order": 2, + "step_name": "Legal Review", + "step_description": "Legal compliance check", + "required_user_level_id": 5, + "estimated_days": 1 +} +``` + +#### **Step 3: Reorder Steps** +```bash +# Move Legal Review to position 2 +PUT /approval-workflow-steps/4/reorder +{ + "new_order": 2 +} + +# Update other steps +PUT /approval-workflow-steps/2/reorder +{ + "new_order": 3 +} + +PUT /approval-workflow-steps/3/reorder +{ + "new_order": 4 +} +``` + +#### **Step 4: Verify New Order** +```bash +GET /approval-workflows/1/with-steps +``` + +**Expected Response:** +```json +{ + "success": true, + "data": { + "steps": [ + {"step_order": 1, "step_name": "Editor Review"}, + {"step_order": 2, "step_name": "Legal Review"}, + {"step_order": 3, "step_name": "Senior Editor Review"}, + {"step_order": 4, "step_name": "Editor in Chief"} + ] + } +} +``` + +#### **Step 5: Test Modified Workflow** +```bash +POST /articles/126/submit-approval +{ + "workflow_id": 1, + "message": "Test modified workflow" +} +``` + +### **Validation Points** +- โœ… New step berhasil ditambahkan +- โœ… Step order berhasil di-reorder +- โœ… Workflow modification tidak impact existing flows +- โœ… New workflow berfungsi dengan benar + +--- + +## ๐Ÿ“Š Skenario 9: Audit Trail & Reporting + +### **Business Context** +Generate comprehensive audit trail dan reporting untuk compliance. + +### **Test Steps** + +#### **Step 1: Create Multiple Approval Actions** +```bash +# Submit article +POST /articles/127/submit-approval +# Approve step 1 +POST /article-approval-flows/458/approve +# Approve step 2 +POST /article-approval-flows/458/approve +# Approve step 3 +POST /article-approval-flows/458/approve +``` + +#### **Step 2: Get Approval Step Logs** +```bash +GET /article-approval-step-logs/flow/458 +``` + +**Expected Response:** +```json +{ + "success": true, + "data": [ + { + "id": 1, + "action": "submitted", + "action_by_user_id": 20, + "action_date": "2024-01-15T10:00:00Z", + "comments": "Ready for review" + }, + { + "id": 2, + "action": "approved", + "action_by_user_id": 25, + "action_date": "2024-01-15T11:00:00Z", + "comments": "Step 1 approved" + }, + { + "id": 3, + "action": "approved", + "action_by_user_id": 30, + "action_date": "2024-01-15T12:00:00Z", + "comments": "Step 2 approved" + }, + { + "id": 4, + "action": "approved", + "action_by_user_id": 35, + "action_date": "2024-01-15T13:00:00Z", + "comments": "Final approval" + } + ] +} +``` + +#### **Step 3: Get User Activity Logs** +```bash +GET /article-approval-step-logs/user/25 +``` + +#### **Step 4: Get Workflow Performance** +```bash +GET /approval-workflows/1/performance +``` + +### **Validation Points** +- โœ… All actions logged dengan detail +- โœ… User activity tracking berfungsi +- โœ… Timestamp dan metadata lengkap +- โœ… Audit trail searchable dan filterable + +--- + +## โšก Skenario 10: Performance & Load Testing + +### **Business Context** +Test system performance dengan multiple concurrent requests. + +### **Test Steps** + +#### **Step 1: Concurrent Article Submissions** +```bash +# Simulate 10 concurrent submissions +for i in {1..10}; do + POST /articles/submit-approval & +done +wait +``` + +#### **Step 2: Concurrent Approvals** +```bash +# Simulate multiple approvers working simultaneously +for i in {1..5}; do + POST /article-approval-flows/$i/approve & +done +wait +``` + +#### **Step 3: Load Test Approval Status Checks** +```bash +# Simulate 50 concurrent status checks +for i in {1..50}; do + GET /articles/$i/approval-status & +done +wait +``` + +#### **Step 4: Database Query Performance** +```bash +# Test pagination performance +GET /article-approval-flows?page=1&limit=100 +GET /article-approval-flows?page=1&limit=1000 +``` + +### **Validation Points** +- โœ… System handle concurrent requests +- โœ… Response time acceptable (< 500ms) +- โœ… Database queries optimized +- โœ… No deadlocks atau race conditions + +--- + +## ๐Ÿงช Test Data Setup + +### **Required Test Data** +```sql +-- User Levels +INSERT INTO user_levels (id, name) VALUES +(1, 'Author'), +(2, 'Editor'), +(3, 'Senior Editor'), +(4, 'Editor in Chief'), +(5, 'Legal Reviewer'); + +-- Test Users +INSERT INTO users (id, name, user_level_id) VALUES +(20, 'John Author', 1), +(25, 'Alice Editor', 2), +(30, 'Bob Senior Editor', 3), +(35, 'Carol Editor in Chief', 4), +(40, 'David Legal', 5); + +-- Test Categories +INSERT INTO article_categories (id, name) VALUES +(1, 'News'), +(2, 'Opinion'), +(3, 'Review'), +(4, 'Tutorial'), +(5, 'Announcement'), +(6, 'Update'), +(7, 'Press Release'); +``` + +--- + +## ๐Ÿ“‹ Test Checklist + +### **Functional Testing** +- [ ] Workflow creation dan modification +- [ ] Article submission dan approval flow +- [ ] Dynamic approval toggle +- [ ] User exemption management +- [ ] Multi-client isolation +- [ ] Audit trail generation + +### **Performance Testing** +- [ ] Response time < 500ms +- [ ] Concurrent request handling +- [ ] Database query optimization +- [ ] Memory usage monitoring + +### **Security Testing** +- [ ] Client isolation +- [ ] User authorization +- [ ] Data validation +- [ ] SQL injection prevention + +### **Integration Testing** +- [ ] API endpoint connectivity +- [ ] Database consistency +- [ ] Error handling +- [ ] Logging functionality + +--- + +## ๐Ÿš€ Running the Tests + +### **Environment Setup** +```bash +# Set environment variables +export CLIENT_KEY="test_client_123" +export JWT_TOKEN="your_test_jwt_token" +export API_BASE_URL="http://localhost:8080" + +# Run test scenarios +./run-test-scenarios.sh +``` + +### **Test Execution Order** +1. Setup workflows dan basic configuration +2. Test normal approval flow +3. Test dynamic toggle functionality +4. Test edge cases dan error scenarios +5. Test performance dan load +6. Test multi-client scenarios + +--- + +## ๐Ÿ“ Notes + +### **Important Considerations** +- Semua test menggunakan test client ID +- JWT token harus valid untuk test duration +- Database state harus clean sebelum test +- Monitor system resources selama performance test + +### **Test Data Cleanup** +```bash +# Cleanup test data after testing +DELETE FROM article_approval_step_logs WHERE test_flag = true; +DELETE FROM article_approval_flows WHERE test_flag = true; +DELETE FROM articles WHERE test_flag = true; +``` + +--- + +*Dokumentasi ini akan diupdate sesuai dengan perkembangan sistem dan feedback dari testing.* diff --git a/docs/notes/troubleshooting-guide.md b/docs/notes/troubleshooting-guide.md new file mode 100644 index 0000000..f9a3ee1 --- /dev/null +++ b/docs/notes/troubleshooting-guide.md @@ -0,0 +1,398 @@ +# Troubleshooting Guide + +## Overview + +Dokumentasi ini berisi panduan troubleshooting untuk masalah-masalah umum yang mungkin terjadi dalam sistem dynamic article approval. + +--- + +## ๐Ÿšจ Common Errors & Solutions + +### **1. Swagger Documentation Errors** + +#### **Error: `cannot find type definition: request.ApprovalWorkflowStepsQueryRequest`** + +**Problem:** + +- Controller menggunakan type yang tidak ada di request struct +- Swagger documentation reference ke type yang salah + +**Solution:** + +```go +// โŒ WRONG - Type tidak ada +@Param req query request.ApprovalWorkflowStepsQueryRequest false "query parameters" + +// โœ… CORRECT - Gunakan type yang ada +@Param workflowId query int false "Workflow ID filter" +@Param stepOrder query int false "Step order filter" +@Param stepName query string false "Step name filter" +``` + +**Fix Steps:** + +1. Periksa file `request/` untuk melihat struct yang tersedia +2. Update Swagger documentation dengan type yang benar +3. Update controller code untuk menggunakan struct yang ada + +#### **Error: `undefined: request.ApprovalWorkflowStepsQueryRequestContext`** + +**Problem:** + +- Controller menggunakan struct yang tidak didefinisikan + +**Solution:** + +```go +// โŒ WRONG - Struct tidak ada +reqContext := request.ApprovalWorkflowStepsQueryRequestContext{...} + +// โœ… CORRECT - Gunakan struct yang ada +req := request.GetApprovalWorkflowStepsRequest{ + WorkflowID: parseUintPointer(c.Query("workflowId")), + StepOrder: parseIntPointer(c.Query("stepOrder")), + // ... other fields +} +``` + +### **2. Missing Method Errors** + +#### **Error: `req.ToEntity()` undefined** + +**Problem:** + +- Request struct tidak memiliki method `ToEntity()` + +**Solution:** + +```go +// โŒ WRONG - Method tidak ada +step := req.ToEntity() + +// โœ… CORRECT - Manual conversion +step := &entity.ApprovalWorkflowSteps{ + WorkflowId: req.WorkflowID, + StepOrder: req.StepOrder, + StepName: req.StepName, + // ... map other fields +} +``` + +**Fix Steps:** + +1. Periksa request struct yang tersedia +2. Buat manual conversion dari request ke entity +3. Pastikan field mapping sesuai dengan entity structure + +### **3. Import Errors** + +#### **Error: `undefined: paginator.Paginate`** + +**Problem:** + +- Import path salah atau package tidak ada + +**Solution:** + +```go +// โŒ WRONG - Import path salah +import "web-qudo-be/utils/paginator" + +// โœ… CORRECT - Import path yang benar +import "web-qudo-be/utils/paginator" +``` + +**Fix Steps:** + +1. Periksa struktur folder `utils/` +2. Pastikan package name sesuai dengan folder +3. Update import path jika diperlukan + +--- + +## ๐Ÿ”ง Code Fixes Examples + +### **Example 1: Fix Controller Query Parameter Parsing** + +**Before (Broken):** + +```go +reqContext := request.ApprovalWorkflowStepsQueryRequestContext{ + WorkflowId: c.Query("workflowId"), + StepOrder: c.Query("stepOrder"), + // ... other fields +} +req := reqContext.ToParamRequest() +``` + +**After (Fixed):** + +```go +req := request.GetApprovalWorkflowStepsRequest{ + WorkflowID: parseUintPointer(c.Query("workflowId")), + StepOrder: parseIntPointer(c.Query("stepOrder")), + StepName: parseStringPointer(c.Query("stepName")), + UserLevelID: parseUintPointer(c.Query("userLevelId")), + IsOptional: parseBoolPointer(c.Query("isOptional")), + IsActive: parseBoolPointer(c.Query("isActive")), + Page: 1, + Limit: 10, +} +``` + +**Helper Functions:** + +```go +func parseUintPointer(s string) *uint { + if s == "" { + return nil + } + if val, err := strconv.ParseUint(s, 10, 32); err == nil { + uval := uint(val) + return &uval + } + return nil +} + +func parseIntPointer(s string) *int { + if s == "" { + return nil + } + if val, err := strconv.Atoi(s); err == nil { + return &val + } + return nil +} + +func parseStringPointer(s string) *string { + if s == "" { + return nil + } + return &s +} + +func parseBoolPointer(s string) *bool { + if s == "" { + return nil + } + if val, err := strconv.ParseBool(s); err == nil { + return &val + } + return nil +} +``` + +### **Example 2: Fix Request to Entity Conversion** + +**Before (Broken):** + +```go +step := req.ToEntity() +``` + +**After (Fixed):** + +```go +step := &entity.ApprovalWorkflowSteps{ + WorkflowId: req.WorkflowID, + StepOrder: req.StepOrder, + StepName: req.StepName, + StepDescription: req.Description, + RequiredUserLevelId: req.ApproverRoleID, + CanSkip: req.IsOptional, + RequiresComment: req.RequiresComment, + AutoApprove: req.AutoApprove, + TimeoutHours: req.TimeoutHours, +} +``` + +### **Example 3: Fix Update Method Conversion** + +**Before (Broken):** + +```go +step := req.ToEntity() +``` + +**After (Fixed):** + +```go +step := &entity.ApprovalWorkflowSteps{} +if req.StepOrder != nil { + step.StepOrder = *req.StepOrder +} +if req.StepName != nil { + step.StepName = *req.StepName +} +if req.Description != nil { + step.StepDescription = req.Description +} +if req.ApproverRoleID != nil { + step.RequiredUserLevelId = req.ApproverRoleID +} +if req.IsOptional != nil { + step.CanSkip = *req.IsOptional +} +if req.RequiresComment != nil { + step.RequiresComment = *req.RequiresComment +} +if req.AutoApprove != nil { + step.AutoApprove = *req.AutoApprove +} +if req.TimeoutHours != nil { + step.TimeoutHours = req.TimeoutHours +} +``` + +--- + +## ๐Ÿ“‹ Debugging Checklist + +### **When Controller Has Errors:** + +- [ ] Periksa import statements +- [ ] Periksa struct names di request package +- [ ] Periksa method calls yang tidak ada +- [ ] Periksa Swagger documentation references +- [ ] Periksa type conversions + +### **When Build Fails:** + +- [ ] Run `go build -o web-qudo-be.exe .` +- [ ] Periksa error messages dengan teliti +- [ ] Periksa file yang disebutkan dalam error +- [ ] Periksa line number yang error +- [ ] Periksa dependencies dan imports + +### **When API Returns Errors:** + +- [ ] Periksa request payload format +- [ ] Periksa validation rules +- [ ] Periksa database connections +- [ ] Periksa middleware configuration +- [ ] Periksa client authentication + +--- + +## ๐Ÿš€ Prevention Tips + +### **1. Code Structure Best Practices** + +- **Consistent Naming**: Gunakan naming convention yang konsisten +- **Type Safety**: Selalu gunakan type yang sudah didefinisikan +- **Documentation**: Update Swagger docs setiap kali ada perubahan +- **Testing**: Test build setelah setiap perubahan besar + +### **2. Common Patterns to Follow** + +```go +// โœ… GOOD - Consistent struct usage +type CreateRequest struct { + Field1 string `json:"field1" validate:"required"` + Field2 *int `json:"field2" validate:"omitempty,min=1"` +} + +// โœ… GOOD - Proper conversion +func (r *CreateRequest) ToEntity() *Entity { + return &Entity{ + Field1: r.Field1, + Field2: r.Field2, + } +} + +// โœ… GOOD - Proper Swagger docs +// @Param payload body request.CreateRequest true "Required payload" +``` + +### **3. Error Handling Best Practices** + +```go +// โœ… GOOD - Proper error handling +if err != nil { + return utilRes.ErrorBadRequest(c, "Invalid ID format") +} + +// โœ… GOOD - Validation before processing +if req.Field1 == "" { + return utilRes.ErrorBadRequest(c, "Field1 is required") +} +``` + +--- + +## ๐Ÿ” Debugging Commands + +### **Build Commands:** + +```bash +# Basic build +go build -o web-qudo-be.exe . + +# Build with verbose output +go build -v -o web-qudo-be.exe . + +# Build specific package +go build ./app/module/approval_workflow_steps/controller +``` + +### **Go Tools:** + +```bash +# Format code +go fmt ./... + +# Vet code for common mistakes +go vet ./... + +# Run tests +go test ./... + +# Check dependencies +go mod tidy +``` + +### **IDE Tools:** + +- **GoLand**: Built-in error detection +- **VS Code**: Go extension with error highlighting +- **Vim**: Go plugins for syntax checking + +--- + +## ๐Ÿ“š Reference Materials + +### **Useful Go Documentation:** + +- [Go Language Specification](https://golang.org/ref/spec) +- [Go Modules](https://golang.org/doc/modules) +- [Go Testing](https://golang.org/doc/testing) + +### **Project-Specific:** + +- `docs/notes/api-endpoints-documentation.md` - API documentation +- `docs/notes/end-to-end-test-scenarios.md` - Test scenarios +- `app/module/*/request/*.go` - Request structs +- `app/module/*/entity/*.go` - Database entities + +--- + +## ๐Ÿ†˜ Getting Help + +### **When to Ask for Help:** + +- Error tidak bisa di-resolve setelah 30 menit +- Error message tidak jelas atau misleading +- Build berhasil tapi runtime error +- Performance issues yang tidak expected + +### **Information to Provide:** + +- Error message lengkap +- File dan line number yang error +- Steps to reproduce +- Environment details (OS, Go version, etc.) +- Code snippet yang bermasalah + +--- + +_Dokumentasi ini akan diupdate sesuai dengan masalah-masalah baru yang ditemukan._ diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go new file mode 100644 index 0000000..1c7ffd8 --- /dev/null +++ b/docs/swagger/docs.go @@ -0,0 +1,18287 @@ +// Package swagger Code generated by swaggo/swag. DO NOT EDIT +package swagger + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/activity-logs": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "Get all ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "name": "activityTypeId", + "in": "query" + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "string", + "name": "url", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "Create ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ActivityLogsCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/activity-logs/detail/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "Get one ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "ActivityLogs ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/activity-logs/statistics": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for get activity stats ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "Get activity stats ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + } + ], + "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" + } + } + } + } + }, + "/activity-logs/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "update ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ActivityLogsUpdateRequest" + } + }, + { + "type": "integer", + "description": "ActivityLogs ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "delete ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "ActivityLogs ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/advertisement": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "Get all Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "string", + "name": "placement", + "in": "query" + }, + { + "type": "string", + "name": "redirectLink", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "Create Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AdvertisementCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/advertisement/publish/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Update Publish Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "Update Publish Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "boolean", + "description": "Advertisement Publish Status", + "name": "isPublish", + "in": "query", + "required": true + } + ], + "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" + } + } + } + } + }, + "/advertisement/upload/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Upload File Advertisement", + "produces": [ + "application/json" + ], + "tags": [ + "Advertisement" + ], + "summary": "Upload Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "file", + "description": "Upload file", + "name": "file", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/advertisement/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Viewer Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "Viewer Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Content File Name", + "name": "filename", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/advertisement/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "Get one Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "update Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AdvertisementUpdateRequest" + } + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "delete Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflow-steps": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ApprovalWorkflowSteps", + "tags": [ + "ApprovalWorkflowSteps" + ], + "summary": "Get all ApprovalWorkflowSteps", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Workflow ID filter", + "name": "workflowId", + "in": "query" + }, + { + "type": "integer", + "description": "Step order filter", + "name": "stepOrder", + "in": "query" + }, + { + "type": "string", + "description": "Step name filter", + "name": "stepName", + "in": "query" + }, + { + "type": "integer", + "description": "User level ID filter", + "name": "userLevelId", + "in": "query" + }, + { + "type": "boolean", + "description": "Is optional filter", + "name": "isOptional", + "in": "query" + }, + { + "type": "boolean", + "description": "Is active filter", + "name": "isActive", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for saving ApprovalWorkflowSteps", + "tags": [ + "ApprovalWorkflowSteps" + ], + "summary": "Save ApprovalWorkflowSteps", + "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.CreateApprovalWorkflowStepsRequest" + } + } + ], + "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" + } + } + } + } + }, + "/approval-workflow-steps/bulk": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for bulk creating ApprovalWorkflowSteps", + "tags": [ + "ApprovalWorkflowSteps" + ], + "summary": "Bulk create ApprovalWorkflowSteps", + "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.BulkCreateApprovalWorkflowStepsRequest" + } + } + ], + "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" + } + } + } + } + }, + "/approval-workflow-steps/role/{roleId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ApprovalWorkflowSteps by Role ID", + "tags": [ + "ApprovalWorkflowSteps" + ], + "summary": "Get ApprovalWorkflowSteps by Role ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Role ID", + "name": "roleId", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflow-steps/workflow/{workflowId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ApprovalWorkflowSteps by Workflow ID", + "tags": [ + "ApprovalWorkflowSteps" + ], + "summary": "Get ApprovalWorkflowSteps by Workflow ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Workflow ID", + "name": "workflowId", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflow-steps/workflow/{workflowId}/reorder": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for reordering ApprovalWorkflowSteps", + "tags": [ + "ApprovalWorkflowSteps" + ], + "summary": "Reorder ApprovalWorkflowSteps", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Workflow ID", + "name": "workflowId", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ReorderApprovalWorkflowStepsRequest" + } + } + ], + "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" + } + } + } + } + }, + "/approval-workflow-steps/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ApprovalWorkflowSteps", + "tags": [ + "ApprovalWorkflowSteps" + ], + "summary": "Get one ApprovalWorkflowSteps", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflowSteps ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating ApprovalWorkflowSteps", + "tags": [ + "ApprovalWorkflowSteps" + ], + "summary": "Update ApprovalWorkflowSteps", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflowSteps ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UpdateApprovalWorkflowStepsRequest" + } + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting ApprovalWorkflowSteps", + "tags": [ + "ApprovalWorkflowSteps" + ], + "summary": "Delete ApprovalWorkflowSteps", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflowSteps ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflows": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ApprovalWorkflows", + "tags": [ + "ApprovalWorkflows" + ], + "summary": "Get all ApprovalWorkflows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isActive", + "in": "query" + }, + { + "type": "boolean", + "name": "isDefault", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for saving ApprovalWorkflows", + "tags": [ + "ApprovalWorkflows" + ], + "summary": "Save ApprovalWorkflows", + "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.ApprovalWorkflowsCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/approval-workflows/default": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting default ApprovalWorkflows", + "tags": [ + "ApprovalWorkflows" + ], + "summary": "Get default ApprovalWorkflows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflows/with-steps": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating ApprovalWorkflows with steps", + "tags": [ + "ApprovalWorkflows" + ], + "summary": "Create ApprovalWorkflows with steps", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "description": "ApprovalWorkflows with steps data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ApprovalWorkflowsWithStepsCreateRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "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" + } + } + } + } + }, + "/approval-workflows/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ApprovalWorkflows", + "tags": [ + "ApprovalWorkflows" + ], + "summary": "Get one ApprovalWorkflows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflows ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating ApprovalWorkflows", + "tags": [ + "ApprovalWorkflows" + ], + "summary": "Update ApprovalWorkflows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflows ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ApprovalWorkflowsUpdateRequest" + } + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting ApprovalWorkflows", + "tags": [ + "ApprovalWorkflows" + ], + "summary": "Delete ApprovalWorkflows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflows ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflows/{id}/activate": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for activating ApprovalWorkflows", + "tags": [ + "ApprovalWorkflows" + ], + "summary": "Activate ApprovalWorkflows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflows ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflows/{id}/deactivate": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deactivating ApprovalWorkflows", + "tags": [ + "ApprovalWorkflows" + ], + "summary": "Deactivate ApprovalWorkflows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflows ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflows/{id}/set-default": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for setting default ApprovalWorkflows", + "tags": [ + "ApprovalWorkflows" + ], + "summary": "Set default ApprovalWorkflows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflows ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflows/{id}/with-steps": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ApprovalWorkflows with steps", + "tags": [ + "ApprovalWorkflows" + ], + "summary": "Get ApprovalWorkflows with steps", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflows ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating ApprovalWorkflows with steps", + "tags": [ + "ApprovalWorkflows" + ], + "summary": "Update ApprovalWorkflows with steps", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflows ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "ApprovalWorkflows with steps data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ApprovalWorkflowsWithStepsUpdateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approval-flows": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleApprovalFlows", + "tags": [ + "ArticleApprovalFlows" + ], + "summary": "Get all ArticleApprovalFlows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "currentStep", + "in": "query" + }, + { + "type": "string", + "name": "dateFrom", + "in": "query" + }, + { + "type": "string", + "name": "dateTo", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "submittedBy", + "in": "query" + }, + { + "type": "integer", + "name": "workflowId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + } + }, + "/article-approval-flows/analytics": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting approval analytics", + "tags": [ + "ArticleApprovalFlows" + ], + "summary": "Get approval analytics", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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", + "description": "Period filter (daily, weekly, monthly)", + "name": "period", + "in": "query" + }, + { + "type": "string", + "description": "Start date filter (YYYY-MM-DD)", + "name": "startDate", + "in": "query" + }, + { + "type": "string", + "description": "End date filter (YYYY-MM-DD)", + "name": "endDate", + "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" + } + } + } + } + }, + "/article-approval-flows/dashboard-stats": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting dashboard statistics", + "tags": [ + "ArticleApprovalFlows" + ], + "summary": "Get dashboard statistics", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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" + } + ], + "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" + } + } + } + } + }, + "/article-approval-flows/history": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting approval history", + "tags": [ + "ArticleApprovalFlows" + ], + "summary": "Get approval history", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Article ID filter", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "description": "User ID filter", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + } + }, + "/article-approval-flows/my-queue": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting my approval queue", + "tags": [ + "ArticleApprovalFlows" + ], + "summary": "Get my approval queue", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "boolean", + "description": "Include article preview", + "name": "includePreview", + "in": "query" + }, + { + "type": "boolean", + "description": "Show only urgent articles", + "name": "urgentOnly", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + } + }, + "/article-approval-flows/pending": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting pending approvals", + "tags": [ + "ArticleApprovalFlows" + ], + "summary": "Get pending approvals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + } + }, + "/article-approval-flows/submit": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for submitting article for approval", + "tags": [ + "ArticleApprovalFlows" + ], + "summary": "Submit article for approval", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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" + }, + { + "description": "Submit for approval data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web-qudo-be_app_module_article_approval_flows_request.SubmitForApprovalRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "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" + } + } + } + } + }, + "/article-approval-flows/workload-stats": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting workload statistics", + "tags": [ + "ArticleApprovalFlows" + ], + "summary": "Get workload statistics", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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" + } + ], + "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" + } + } + } + } + }, + "/article-approval-flows/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleApprovalFlows", + "tags": [ + "ArticleApprovalFlows" + ], + "summary": "Get one ArticleApprovalFlows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleApprovalFlows ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-approval-flows/{id}/approve": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for approving article", + "tags": [ + "ArticleApprovalFlows" + ], + "summary": "Approve article", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "description": "ArticleApprovalFlows ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Approval action data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ApprovalActionRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approval-flows/{id}/reject": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for rejecting article", + "tags": [ + "ArticleApprovalFlows" + ], + "summary": "Reject article", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "description": "ArticleApprovalFlows ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Rejection data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.RejectionRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approval-flows/{id}/request-revision": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for requesting revision for article", + "tags": [ + "ArticleApprovalFlows" + ], + "summary": "Request revision for article", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "description": "ArticleApprovalFlows ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Revision request data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.RevisionRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approval-flows/{id}/resubmit": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for resubmitting article after revision", + "tags": [ + "ArticleApprovalFlows" + ], + "summary": "Resubmit article after revision", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "description": "ArticleApprovalFlows ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Resubmit data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ResubmitRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approval-step-logs": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleApprovalStepLogs", + "tags": [ + "ArticleApprovalStepLogs" + ], + "summary": "Get all ArticleApprovalStepLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "approvalStatusId", + "in": "query" + }, + { + "type": "integer", + "name": "approverUserId", + "in": "query" + }, + { + "type": "integer", + "name": "articleApprovalFlowId", + "in": "query" + }, + { + "type": "string", + "name": "dateFrom", + "in": "query" + }, + { + "type": "string", + "name": "dateTo", + "in": "query" + }, + { + "type": "boolean", + "name": "isAutoApproved", + "in": "query" + }, + { + "type": "integer", + "name": "workflowStepId", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for saving ArticleApprovalStepLogs", + "tags": [ + "ArticleApprovalStepLogs" + ], + "summary": "Save ArticleApprovalStepLogs", + "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.ArticleApprovalStepLogsCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approval-step-logs/approver/{user_id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ArticleApprovalStepLogs by Approver User ID", + "tags": [ + "ArticleApprovalStepLogs" + ], + "summary": "Get ArticleApprovalStepLogs by Approver User ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Approver User ID", + "name": "user_id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-approval-step-logs/bulk-process": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for bulk processing approval steps", + "tags": [ + "ArticleApprovalStepLogs" + ], + "summary": "Bulk Process Approvals", + "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.BulkProcessApprovalRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approval-step-logs/flow/{flow_id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ArticleApprovalStepLogs by Approval Flow ID", + "tags": [ + "ArticleApprovalStepLogs" + ], + "summary": "Get ArticleApprovalStepLogs by Approval Flow ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Approval Flow ID", + "name": "flow_id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-approval-step-logs/history": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting approval history", + "tags": [ + "ArticleApprovalStepLogs" + ], + "summary": "Get Approval History", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Filter by article ID", + "name": "article_id", + "in": "query" + }, + { + "type": "integer", + "description": "Filter by user ID", + "name": "user_id", + "in": "query" + }, + { + "type": "string", + "description": "Filter from date (YYYY-MM-DD)", + "name": "from_date", + "in": "query" + }, + { + "type": "string", + "description": "Filter to date (YYYY-MM-DD)", + "name": "to_date", + "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" + } + } + } + } + }, + "/article-approval-step-logs/overdue": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting overdue approvals", + "tags": [ + "ArticleApprovalStepLogs" + ], + "summary": "Get Overdue Approvals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Filter by user ID", + "name": "user_id", + "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" + } + } + } + } + }, + "/article-approval-step-logs/pending": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting pending approvals", + "tags": [ + "ArticleApprovalStepLogs" + ], + "summary": "Get Pending Approvals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Filter by user ID", + "name": "user_id", + "in": "query" + }, + { + "type": "integer", + "description": "Filter by role ID", + "name": "role_id", + "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" + } + } + } + } + }, + "/article-approval-step-logs/stats": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting approval statistics", + "tags": [ + "ArticleApprovalStepLogs" + ], + "summary": "Get Approval Statistics", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Filter from date (YYYY-MM-DD)", + "name": "from_date", + "in": "query" + }, + { + "type": "string", + "description": "Filter to date (YYYY-MM-DD)", + "name": "to_date", + "in": "query" + }, + { + "type": "integer", + "description": "Filter by workflow ID", + "name": "workflow_id", + "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" + } + } + } + } + }, + "/article-approval-step-logs/step/{step_id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ArticleApprovalStepLogs by Workflow Step ID", + "tags": [ + "ArticleApprovalStepLogs" + ], + "summary": "Get ArticleApprovalStepLogs by Workflow Step ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Workflow Step ID", + "name": "step_id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-approval-step-logs/user/{user_id}/workload": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting user workload statistics", + "tags": [ + "ArticleApprovalStepLogs" + ], + "summary": "Get User Workload", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "user_id", + "in": "path", + "required": true + }, + { + "type": "boolean", + "description": "Include detailed statistics", + "name": "include_stats", + "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" + } + } + } + } + }, + "/article-approval-step-logs/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleApprovalStepLogs", + "tags": [ + "ArticleApprovalStepLogs" + ], + "summary": "Get one ArticleApprovalStepLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleApprovalStepLogs ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating ArticleApprovalStepLogs", + "tags": [ + "ArticleApprovalStepLogs" + ], + "summary": "Update ArticleApprovalStepLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleApprovalStepLogs ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleApprovalStepLogsUpdateRequest" + } + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting ArticleApprovalStepLogs", + "tags": [ + "ArticleApprovalStepLogs" + ], + "summary": "Delete ArticleApprovalStepLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleApprovalStepLogs ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-approval-step-logs/{id}/auto-approve": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for automatically approving a step", + "tags": [ + "ArticleApprovalStepLogs" + ], + "summary": "Auto Approve Step", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Step Log ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Auto approval reason", + "name": "reason", + "in": "query", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-approval-step-logs/{id}/process": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for processing approval step", + "tags": [ + "ArticleApprovalStepLogs" + ], + "summary": "Process Approval", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Step Log ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ProcessApprovalRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approvals": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleApprovals", + "tags": [ + "ArticleApprovals" + ], + "summary": "Get all ArticleApprovals", + "parameters": [ + { + "type": "integer", + "name": "approvalAtLevel", + "in": "query" + }, + { + "type": "integer", + "name": "approvalBy", + "in": "query" + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "string", + "name": "message", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleApprovals", + "tags": [ + "ArticleApprovals" + ], + "summary": "Create ArticleApprovals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleApprovalsCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approvals/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleApprovals", + "tags": [ + "ArticleApprovals" + ], + "summary": "Get one ArticleApprovals", + "parameters": [ + { + "type": "integer", + "description": "ArticleApprovals ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleApprovals", + "tags": [ + "ArticleApprovals" + ], + "summary": "update ArticleApprovals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleApprovalsUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleApprovals ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleApprovals", + "tags": [ + "ArticleApprovals" + ], + "summary": "delete ArticleApprovals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleApprovals ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-categories": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Get all ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "name": "UserLevelId", + "in": "query" + }, + { + "type": "integer", + "name": "UserLevelNumber", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "integer", + "name": "parentId", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Create ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCategoriesCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-categories/old/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Get one ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleCategories Old ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-categories/slug/{slug}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Get one ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "ArticleCategories Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-categories/thumbnail/viewer/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for View Thumbnail of ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Viewer ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-categories/thumbnail/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Upload ArticleCategories Thumbnail", + "produces": [ + "application/json" + ], + "tags": [ + "Article Categories" + ], + "summary": "Upload ArticleCategories Thumbnail", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "file", + "description": "Upload thumbnail", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-categories/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Get one ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "update ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCategoriesUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "delete ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-category-details": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleCategoryDetails", + "tags": [ + "Untags" + ], + "summary": "Get all ArticleCategoryDetails", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleCategoryDetails", + "tags": [ + "Untags" + ], + "summary": "Create ArticleCategoryDetails", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/article-category-details/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleCategoryDetails", + "tags": [ + "Untags" + ], + "summary": "Get one ArticleCategoryDetails", + "parameters": [ + { + "type": "integer", + "description": "ArticleCategoryDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleCategoryDetails", + "tags": [ + "Untags" + ], + "summary": "update ArticleCategoryDetails", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleCategoryDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleCategoryDetails", + "tags": [ + "Untags" + ], + "summary": "delete ArticleCategoryDetails", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleCategoryDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/article-comments": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Get all ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "commentFrom", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublic", + "in": "query" + }, + { + "type": "string", + "name": "message", + "in": "query" + }, + { + "type": "integer", + "name": "parentId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Create ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCommentsCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-comments/approval": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Approval ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Approval ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCommentsApprovalRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-comments/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Get one ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "update ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCommentsUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "delete ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-files": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "Get all ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "string", + "name": "fileName", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + } + }, + "/article-files/upload-status/{uploadId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for GetUploadStatus ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "GetUploadStatus ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Upload ID of ArticleFiles", + "name": "uploadId", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-files/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Viewer ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "Viewer ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Article File Name", + "name": "filename", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-files/{articleId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleFiles", + "produces": [ + "application/json" + ], + "tags": [ + "Article Files" + ], + "summary": "Upload ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "file", + "description": "Upload file", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Article ID", + "name": "articleId", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-files/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "Get one ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "Update ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleFilesUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "Delete ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-nulis-ai": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleNulisAI", + "tags": [ + "ArticleNulisAI" + ], + "summary": "Get all ArticleNulisAI", + "parameters": [ + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "categoryId", + "in": "query" + }, + { + "type": "integer", + "name": "creatorId", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "htmlDescription", + "in": "query" + }, + { + "type": "integer", + "name": "nulisAiId", + "in": "query" + }, + { + "type": "string", + "name": "tags", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleNulisAI", + "tags": [ + "ArticleNulisAI" + ], + "summary": "Create ArticleNulisAI", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleNulisAICreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-nulis-ai/publish": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for publish ArticleNulisAI", + "tags": [ + "ArticleNulisAI" + ], + "summary": "publish ArticleNulisAI", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleNulisAIUpdateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-nulis-ai/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleNulisAI", + "tags": [ + "ArticleNulisAI" + ], + "summary": "Get one ArticleNulisAI", + "parameters": [ + { + "type": "integer", + "description": "ArticleNulisAI ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleNulisAI", + "tags": [ + "ArticleNulisAI" + ], + "summary": "update ArticleNulisAI", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleNulisAIUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleNulisAI ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleNulisAI", + "tags": [ + "ArticleNulisAI" + ], + "summary": "delete ArticleNulisAI", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleNulisAI ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Articles", + "tags": [ + "Articles" + ], + "summary": "Get all Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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", + "name": "category", + "in": "query" + }, + { + "type": "integer", + "name": "categoryId", + "in": "query" + }, + { + "type": "integer", + "name": "createdById", + "in": "query" + }, + { + "type": "string", + "name": "customCreatorName", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "endDate", + "in": "query" + }, + { + "type": "boolean", + "name": "isBanner", + "in": "query" + }, + { + "type": "boolean", + "name": "isDraft", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "string", + "name": "source", + "in": "query" + }, + { + "type": "string", + "name": "startDate", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "tags", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "typeId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Articles", + "tags": [ + "Articles" + ], + "summary": "Create Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticlesCreateRequest" + } + } + ], + "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/banner/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Update Banner Articles", + "tags": [ + "Articles" + ], + "summary": "Update Banner Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "boolean", + "description": "Articles Banner Status", + "name": "isBanner", + "in": "query", + "required": true + } + ], + "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/old-id/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Articles", + "tags": [ + "Articles" + ], + "summary": "Get one Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Articles Old ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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/pending-approval": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting articles pending approval for current user level", + "tags": [ + "Articles" + ], + "summary": "Get Pending Approvals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "items per page", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "article type id", + "name": "typeId", + "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/publish-scheduling": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Publish Schedule of Article. Supports both date-only format (2006-01-02) and datetime format (2006-01-02 15:04:05)", + "tags": [ + "Articles" + ], + "summary": "PublishScheduling Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "article id", + "name": "id", + "in": "query" + }, + { + "type": "string", + "description": "publish date/time (format: 2006-01-02 or 2006-01-02 15:04:05)", + "name": "date", + "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/slug/{slug}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Articles by slug", + "tags": [ + "Articles" + ], + "summary": "Get one Articles by Slug", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Articles Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "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/statistic/monthly": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ArticleMonthlyStats of Article", + "tags": [ + "Articles" + ], + "summary": "ArticleMonthlyStats Articles", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "year", + "name": "year", + "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/statistic/summary": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Summary Stats of Article", + "tags": [ + "Articles" + ], + "summary": "SummaryStats Articles", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + } + ], + "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/statistic/user-levels": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ArticlePerUserLevelStats of Article", + "tags": [ + "Articles" + ], + "summary": "ArticlePerUserLevelStats Articles", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "start date", + "name": "startDate", + "in": "query" + }, + { + "type": "string", + "description": "start date", + "name": "endDate", + "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/thumbnail/viewer/{thumbnailName}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for View Thumbnail of Article", + "tags": [ + "Articles" + ], + "summary": "Viewer Articles Thumbnail", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Articles Thumbnail Name", + "name": "thumbnailName", + "in": "path", + "required": true + } + ], + "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/thumbnail/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Save Thumbnail of Articles", + "produces": [ + "application/json" + ], + "tags": [ + "Articles" + ], + "summary": "Save Thumbnail Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "file", + "description": "Upload thumbnail", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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/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": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "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}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Articles", + "tags": [ + "Articles" + ], + "summary": "Get one Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Articles", + "tags": [ + "Articles" + ], + "summary": "Update Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticlesUpdateRequest" + } + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Articles", + "tags": [ + "Articles" + ], + "summary": "Delete Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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}/approval-status": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting article approval status and workflow progress", + "tags": [ + "Articles" + ], + "summary": "Get Article Approval Status", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "article id", + "name": "id", + "in": "path", + "required": true + } + ], + "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}/publish": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for publishing an article", + "tags": [ + "Articles" + ], + "summary": "Publish Article", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "article id", + "name": "id", + "in": "path", + "required": true + } + ], + "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}/submit-approval": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for submitting article for approval workflow", + "tags": [ + "Articles" + ], + "summary": "Submit Article for Approval", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "article id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "approval request data", + "name": "req", + "in": "body", + "schema": { + "$ref": "#/definitions/web-qudo-be_app_module_articles_request.SubmitForApprovalRequest" + } + } + ], + "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}/unpublish": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for unpublishing an article", + "tags": [ + "Articles" + ], + "summary": "Unpublish Article", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "article id", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/bookmarks": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Bookmarks", + "tags": [ + "Bookmarks" + ], + "summary": "Get all Bookmarks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating new Bookmark", + "tags": [ + "Bookmarks" + ], + "summary": "Create new Bookmark", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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" + }, + { + "description": "Bookmark data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.BookmarksCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/bookmarks/summary": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting bookmark summary including total count and recent bookmarks", + "tags": [ + "Bookmarks" + ], + "summary": "Get Bookmark Summary for User", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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" + } + ], + "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" + } + } + } + } + }, + "/bookmarks/toggle/{articleId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for toggling bookmark status for an article", + "tags": [ + "Bookmarks" + ], + "summary": "Toggle Bookmark (Add/Remove)", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "description": "Article ID", + "name": "articleId", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/bookmarks/user": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting Bookmarks by User ID", + "tags": [ + "Bookmarks" + ], + "summary": "Get Bookmarks by User ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + } + }, + "/bookmarks/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting Bookmark by ID", + "tags": [ + "Bookmarks" + ], + "summary": "Get Bookmark by ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "description": "Bookmark ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting Bookmark", + "tags": [ + "Bookmarks" + ], + "summary": "Delete Bookmark", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "description": "Bookmark ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/cities": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Cities", + "tags": [ + "Untags" + ], + "summary": "Get all Cities", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Cities", + "tags": [ + "Untags" + ], + "summary": "Create Cities", + "parameters": [ + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CitiesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/cities/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Cities", + "tags": [ + "Untags" + ], + "summary": "Get one Cities", + "parameters": [ + { + "type": "integer", + "description": "Cities ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Cities", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Untags" + ], + "summary": "Update Cities", + "parameters": [ + { + "type": "integer", + "description": "Cities ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CitiesUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Cities", + "tags": [ + "Untags" + ], + "summary": "Delete Cities", + "parameters": [ + { + "type": "integer", + "description": "Cities ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/client-approval-settings": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting client approval settings", + "tags": [ + "ClientApprovalSettings" + ], + "summary": "Get Client Approval Settings", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating client approval settings", + "tags": [ + "ClientApprovalSettings" + ], + "summary": "Update 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.UpdateClientApprovalSettingsRequest" + } + } + ], + "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" + } + } + } + }, + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting client approval settings", + "tags": [ + "ClientApprovalSettings" + ], + "summary": "Delete Client Approval Settings", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + } + ], + "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" + } + } + } + } + }, + "/client-approval-settings/default-workflow": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for setting default workflow for client", + "tags": [ + "ClientApprovalSettings" + ], + "summary": "Set Default Workflow", + "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.SetDefaultWorkflowRequest" + } + } + ], + "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" + } + } + } + } + }, + "/client-approval-settings/disable": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for disabling approval system and auto-publish pending articles", + "tags": [ + "ClientApprovalSettings" + ], + "summary": "Disable Approval System", + "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.DisableApprovalRequest" + } + } + ], + "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" + } + } + } + } + }, + "/client-approval-settings/enable": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for enabling approval system with smooth transition", + "tags": [ + "ClientApprovalSettings" + ], + "summary": "Enable Approval System", + "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.EnableApprovalRequest" + } + } + ], + "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" + } + } + } + } + }, + "/client-approval-settings/exempt-categories/{action}/{category_id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for adding/removing categories from approval exemption", + "tags": [ + "ClientApprovalSettings" + ], + "summary": "Manage Exempt Categories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Action: add or remove", + "name": "action", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Category ID", + "name": "category_id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/client-approval-settings/exempt-roles/{action}/{role_id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for adding/removing roles from approval exemption", + "tags": [ + "ClientApprovalSettings" + ], + "summary": "Manage Exempt Roles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Action: add or remove", + "name": "action", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Role ID", + "name": "role_id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/client-approval-settings/exempt-users/{action}/{user_id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for adding/removing users from approval exemption", + "tags": [ + "ClientApprovalSettings" + ], + "summary": "Manage Exempt Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Action: add or remove", + "name": "action", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "user_id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/client-approval-settings/toggle": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for toggling approval requirement on/off", + "tags": [ + "ClientApprovalSettings" + ], + "summary": "Toggle Approval Requirement", + "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.ToggleApprovalRequest" + } + } + ], + "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" + } + } + } + } + }, + "/clients": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Clients", + "tags": [ + "Clients" + ], + "summary": "Get all Clients", + "parameters": [ + { + "type": "integer", + "name": "createdBy", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Clients", + "tags": [ + "Clients" + ], + "summary": "Create Clients", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ClientsCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/clients/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Clients", + "tags": [ + "Clients" + ], + "summary": "Get one Clients", + "parameters": [ + { + "type": "integer", + "description": "Clients ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Clients", + "tags": [ + "Clients" + ], + "summary": "update Clients", + "parameters": [ + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ClientsUpdateRequest" + } + }, + { + "type": "string", + "description": "Clients ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Clients", + "tags": [ + "Clients" + ], + "summary": "delete Clients", + "parameters": [ + { + "type": "string", + "description": "Clients ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/custom-static-pages": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "Get all CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "htmlBody", + "in": "query" + }, + { + "type": "string", + "name": "slug", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "Create CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CustomStaticPagesCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/custom-static-pages/slug/{slug}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "Get one CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "CustomStaticPages Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/custom-static-pages/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "Get one CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "CustomStaticPages ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "update CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CustomStaticPagesUpdateRequest" + } + }, + { + "type": "integer", + "description": "CustomStaticPages ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "delete CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "CustomStaticPages ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/districts": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Districts", + "tags": [ + "Untags" + ], + "summary": "Get all Districts", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Districts", + "tags": [ + "Untags" + ], + "summary": "Create Districts", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/districts/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Districts", + "tags": [ + "Untags" + ], + "summary": "Get one Districts", + "parameters": [ + { + "type": "integer", + "description": "Districts ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Districts", + "tags": [ + "Untags" + ], + "summary": "Update Districts", + "parameters": [ + { + "type": "integer", + "description": "Districts ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Districts", + "tags": [ + "Untags" + ], + "summary": "Delete Districts", + "parameters": [ + { + "type": "integer", + "description": "Districts ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/feedbacks": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "Get all Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "name": "commentFromEmail", + "in": "query" + }, + { + "type": "string", + "name": "commentFromName", + "in": "query" + }, + { + "type": "string", + "name": "endDate", + "in": "query" + }, + { + "type": "string", + "name": "message", + "in": "query" + }, + { + "type": "string", + "name": "startDate", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "Create Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.FeedbacksCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/feedbacks/statistic/monthly": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for FeedbackMonthlyStats of Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "FeedbackMonthlyStats Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "year", + "name": "year", + "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" + } + } + } + } + }, + "/feedbacks/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "Get one Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "Feedbacks ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "update Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.FeedbacksUpdateRequest" + } + }, + { + "type": "integer", + "description": "Feedbacks ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "delete Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Feedbacks ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/magazine-files": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "Get all MagazineFiles", + "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" + } + } + } + } + }, + "/magazine-files/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "Create MagazineFiles", + "parameters": [ + { + "type": "string", + "description": "Magazine File Name", + "name": "filename", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/magazine-files/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "Get one MagazineFiles", + "parameters": [ + { + "type": "integer", + "description": "MagazineFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "Update MagazineFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "MagazineFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "delete MagazineFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "MagazineFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/magazine-files/{magazineId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "Create MagazineFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "file", + "description": "Upload file", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Magazine file title", + "name": "title", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Magazine file description", + "name": "description", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Magazine ID", + "name": "magazineId", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/magazines": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Magazines", + "tags": [ + "Magazines" + ], + "summary": "Get all Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "name": "createdById", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "string", + "name": "pageUrl", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "thumbnailPath", + "in": "query" + }, + { + "type": "string", + "name": "thumbnailUrl", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Magazines", + "tags": [ + "Magazines" + ], + "summary": "Create Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MagazinesCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/magazines/thumbnail/viewer/{thumbnailName}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for View Thumbnail of Magazines", + "tags": [ + "Magazines" + ], + "summary": "Viewer Magazines Thumbnail", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Magazines Thumbnail Name", + "name": "thumbnailName", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/magazines/thumbnail/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Save Thumbnail of Magazines", + "produces": [ + "application/json" + ], + "tags": [ + "Magazines" + ], + "summary": "Save Thumbnail Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Magazine ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "file", + "description": "Upload thumbnail", + "name": "files", + "in": "formData", + "required": true + } + ], + "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" + } + } + } + } + }, + "/magazines/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Magazines", + "tags": [ + "Magazines" + ], + "summary": "Get one Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "Magazines ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Magazines", + "tags": [ + "Magazines" + ], + "summary": "Update Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Magazines ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MagazinesUpdateRequest" + } + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Magazines", + "tags": [ + "Magazines" + ], + "summary": "Delete Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Magazines ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/master-menus": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all MasterMenus", + "tags": [ + "MasterMenus" + ], + "summary": "Get all MasterMenus", + "parameters": [ + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "integer", + "name": "moduleId", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "parentMenuId", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MasterMenus", + "tags": [ + "MasterMenus" + ], + "summary": "Create MasterMenus", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MasterMenusCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/master-menus/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one MasterMenus", + "tags": [ + "MasterMenus" + ], + "summary": "Get one MasterMenus", + "parameters": [ + { + "type": "integer", + "description": "MasterMenus ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update MasterMenus", + "tags": [ + "MasterMenus" + ], + "summary": "Update MasterMenus", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "MasterMenus ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete MasterMenus", + "tags": [ + "MasterMenus" + ], + "summary": "Delete MasterMenus", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "MasterMenus ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/master-modules": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all MasterModules", + "tags": [ + "MasterModules" + ], + "summary": "Get all MasterModules", + "parameters": [ + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MasterModules", + "tags": [ + "MasterModules" + ], + "summary": "Create MasterModules", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MasterModulesCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/master-modules/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one MasterModules", + "tags": [ + "MasterModules" + ], + "summary": "Get one MasterModules", + "parameters": [ + { + "type": "integer", + "description": "MasterModules ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update MasterModules", + "tags": [ + "MasterModules" + ], + "summary": "Update MasterModules", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "MasterModules ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MasterModulesUpdateRequest" + } + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete MasterModules", + "tags": [ + "MasterModules" + ], + "summary": "Delete MasterModules", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "MasterModules ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/master-statuses": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all MasterStatuses", + "tags": [ + "Untags" + ], + "summary": "Get all MasterStatuses", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MasterStatuses", + "tags": [ + "Untags" + ], + "summary": "Create MasterStatuses", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/master-statuses/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one MasterStatuses", + "tags": [ + "Untags" + ], + "summary": "Get one MasterStatuses", + "parameters": [ + { + "type": "integer", + "description": "MasterStatuses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update MasterStatuses", + "tags": [ + "Untags" + ], + "summary": "Update MasterStatuses", + "parameters": [ + { + "type": "integer", + "description": "MasterStatuses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete MasterStatuses", + "tags": [ + "Untags" + ], + "summary": "Delete MasterStatuses", + "parameters": [ + { + "type": "integer", + "description": "MasterStatuses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/provinces": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Provinces", + "tags": [ + "Untags" + ], + "summary": "Get all Provinces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Provinces", + "tags": [ + "Untags" + ], + "summary": "Create Provinces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/provinces/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Provinces", + "tags": [ + "Untags" + ], + "summary": "Get one Provinces", + "parameters": [ + { + "type": "integer", + "description": "Provinces ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Provinces", + "tags": [ + "Untags" + ], + "summary": "Update Provinces", + "parameters": [ + { + "type": "integer", + "description": "Provinces ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Provinces", + "tags": [ + "Untags" + ], + "summary": "Delete Provinces", + "parameters": [ + { + "type": "integer", + "description": "Provinces ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/schedules": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Schedules", + "tags": [ + "Schedules" + ], + "summary": "Get all Schedules", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "name": "createdById", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "endDate", + "in": "query" + }, + { + "type": "boolean", + "name": "isLiveStreaming", + "in": "query" + }, + { + "type": "string", + "name": "location", + "in": "query" + }, + { + "type": "string", + "name": "speakers", + "in": "query" + }, + { + "type": "string", + "name": "startDate", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "typeId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Schedule", + "tags": [ + "Schedules" + ], + "summary": "Create Schedule", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SchedulesCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/schedules/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Schedule", + "tags": [ + "Schedules" + ], + "summary": "Get one Schedule", + "parameters": [ + { + "type": "integer", + "description": "Schedule ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Schedule", + "tags": [ + "Schedules" + ], + "summary": "Update Schedule", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SchedulesUpdateRequest" + } + }, + { + "type": "integer", + "description": "Schedule ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Schedule", + "tags": [ + "Schedules" + ], + "summary": "Delete Schedule", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Schedule ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/subscription": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Subscription", + "tags": [ + "Subscription" + ], + "summary": "Get all Subscription", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "name": "email", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Subscription", + "tags": [ + "Subscription" + ], + "summary": "Create Subscription", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SubscriptionCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/subscription/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Subscription", + "tags": [ + "Subscription" + ], + "summary": "Get one Subscription", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "Subscription ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Subscription", + "tags": [ + "Subscription" + ], + "summary": "update Subscription", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SubscriptionUpdateRequest" + } + }, + { + "type": "integer", + "description": "Subscription ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Subscription", + "tags": [ + "Subscription" + ], + "summary": "delete Subscription", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Subscription ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/user-levels": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "Get all UserLevels", + "parameters": [ + { + "type": "string", + "description": "Client Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "levelNumber", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "parentLevelId", + "in": "query" + }, + { + "type": "integer", + "name": "provinceId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "Create UserLevels", + "parameters": [ + { + "type": "string", + "description": "Client Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLevelsCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/user-levels/alias/{alias}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "Get one UserLevels", + "parameters": [ + { + "type": "string", + "description": "Client Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "UserLevels Alias", + "name": "alias", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/user-levels/enable-approval": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Enable Approval of Article", + "tags": [ + "UserLevels" + ], + "summary": "EnableApproval Articles", + "parameters": [ + { + "type": "string", + "description": "Client Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLevelsApprovalRequest" + } + } + ], + "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" + } + } + } + } + }, + "/user-levels/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "Get one UserLevels", + "parameters": [ + { + "type": "string", + "description": "Client Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "UserLevels ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "update UserLevels", + "parameters": [ + { + "type": "string", + "description": "Client Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLevelsUpdateRequest" + } + }, + { + "type": "integer", + "description": "UserLevels ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "delete UserLevels", + "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", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "UserLevels ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/user-role-accesses": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all UserRoleAccesses", + "tags": [ + "UserRoleAccesses" + ], + "summary": "Get all UserRoleAccesses", + "parameters": [ + { + "type": "boolean", + "name": "isActive", + "in": "query", + "required": true + }, + { + "type": "integer", + "name": "menuId", + "in": "query", + "required": true + }, + { + "type": "integer", + "name": "userRoleId", + "in": "query", + "required": true + } + ], + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create UserRoleAccesses", + "tags": [ + "UserRoleAccesses" + ], + "summary": "Create UserRoleAccesses", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserRoleAccessesCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/user-role-accesses/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserRoleAccesses", + "tags": [ + "UserRoleAccesses" + ], + "summary": "Get one UserRoleAccesses", + "parameters": [ + { + "type": "integer", + "description": "UserRoleAccesses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update UserRoleAccesses", + "tags": [ + "UserRoleAccesses" + ], + "summary": "update UserRoleAccesses", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserRoleAccessesUpdateRequest" + } + }, + { + "type": "integer", + "description": "UserRoleAccesses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete UserRoleAccesses", + "tags": [ + "UserRoleAccesses" + ], + "summary": "delete UserRoleAccesses", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "UserRoleAccesses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/user-role-level-details": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all UserRoleLevelDetails", + "tags": [ + "Task" + ], + "summary": "Get all UserRoleLevelDetails", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create UserRoleLevelDetails", + "tags": [ + "Task" + ], + "summary": "Create UserRoleLevelDetails", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/user-role-level-details/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserRoleLevelDetails", + "tags": [ + "Task" + ], + "summary": "Get one UserRoleLevelDetails", + "parameters": [ + { + "type": "integer", + "description": "UserRoleLevelDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update UserRoleLevelDetails", + "tags": [ + "Task" + ], + "summary": "update UserRoleLevelDetails", + "parameters": [ + { + "type": "integer", + "description": "UserRoleLevelDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete UserRoleLevelDetails", + "tags": [ + "Task" + ], + "summary": "delete UserRoleLevelDetails", + "parameters": [ + { + "type": "integer", + "description": "UserRoleLevelDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/user-roles": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all UserRoles", + "tags": [ + "UserRoles" + ], + "summary": "Get all UserRoles", + "parameters": [ + { + "type": "string", + "name": "code", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "userLevelId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create UserRoles", + "tags": [ + "UserRoles" + ], + "summary": "Create UserRoles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserRolesCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/user-roles/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserRoles", + "tags": [ + "UserRoles" + ], + "summary": "Get one UserRoles", + "parameters": [ + { + "type": "integer", + "description": "UserRoles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update UserRoles", + "tags": [ + "UserRoles" + ], + "summary": "update UserRoles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserRolesUpdateRequest" + } + }, + { + "type": "integer", + "description": "UserRoles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete UserRoles", + "tags": [ + "UserRoles" + ], + "summary": "delete UserRoles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "UserRoles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/users": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Users", + "tags": [ + "Users" + ], + "summary": "Get all Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "name": "email", + "in": "query" + }, + { + "type": "string", + "name": "fullname", + "in": "query" + }, + { + "type": "string", + "name": "genderType", + "in": "query" + }, + { + "type": "string", + "name": "identityGroup", + "in": "query" + }, + { + "type": "string", + "name": "identityGroupNumber", + "in": "query" + }, + { + "type": "string", + "name": "identityNumber", + "in": "query" + }, + { + "type": "string", + "name": "identityType", + "in": "query" + }, + { + "type": "string", + "name": "phoneNumber", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "userRoleId", + "in": "query" + }, + { + "type": "string", + "name": "username", + "in": "query" + }, + { + "type": "string", + "name": "workType", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Users", + "tags": [ + "Users" + ], + "summary": "Create Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UsersCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/users/detail/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Users", + "tags": [ + "Users" + ], + "summary": "Get one Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "Users ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/users/email-validation": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Email Validation Users", + "tags": [ + "Users" + ], + "summary": "EmailValidation Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserEmailValidationRequest" + } + } + ], + "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" + } + } + } + } + }, + "/users/forgot-password": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ForgotPassword Users", + "tags": [ + "Users" + ], + "summary": "ForgotPassword Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserForgotPassword" + } + } + ], + "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" + } + } + } + } + }, + "/users/info": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ShowUserInfo", + "tags": [ + "Users" + ], + "summary": "ShowInfo Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + } + ], + "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" + } + } + } + } + }, + "/users/login": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Login Users", + "tags": [ + "Users" + ], + "summary": "Login Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLogin" + } + } + ], + "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" + } + } + } + } + }, + "/users/otp-request": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for OtpRequest Users", + "tags": [ + "Users" + ], + "summary": "OtpRequest Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserOtpRequest" + } + } + ], + "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" + } + } + } + } + }, + "/users/otp-validation": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for OtpValidation Users", + "tags": [ + "Users" + ], + "summary": "OtpValidation Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserOtpValidation" + } + } + ], + "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" + } + } + } + } + }, + "/users/pareto-login": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ParetoLogin Users", + "tags": [ + "Users" + ], + "summary": "ParetoLogin Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLogin" + } + } + ], + "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" + } + } + } + } + }, + "/users/reset-password": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ResetPassword Users", + "tags": [ + "Users" + ], + "summary": "ResetPassword Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserResetPassword" + } + } + ], + "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" + } + } + } + } + }, + "/users/save-password": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for SavePassword Users", + "tags": [ + "Users" + ], + "summary": "SavePassword Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserSavePassword" + } + } + ], + "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" + } + } + } + } + }, + "/users/setup-email": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Setup Email Users", + "tags": [ + "Users" + ], + "summary": "SetupEmail Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserEmailValidationRequest" + } + } + ], + "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" + } + } + } + } + }, + "/users/username/{username}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Users", + "tags": [ + "Users" + ], + "summary": "Get one Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Username", + "name": "username", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/users/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Users", + "tags": [ + "Users" + ], + "summary": "update Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Users ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UsersUpdateRequest" + } + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Users", + "tags": [ + "Users" + ], + "summary": "delete Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Users ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + } + }, + "definitions": { + "paginator.Pagination": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "nextPage": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "previousPage": { + "type": "integer" + }, + "sort": { + "type": "string" + }, + "sortBy": { + "type": "string" + }, + "totalPage": { + "type": "integer" + } + } + }, + "request.ActivityLogsCreateRequest": { + "type": "object", + "required": [ + "activityTypeId", + "url" + ], + "properties": { + "activityTypeId": { + "type": "integer" + }, + "articleId": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "userId": { + "type": "integer" + }, + "visitorIp": { + "type": "string" + } + } + }, + "request.ActivityLogsUpdateRequest": { + "type": "object", + "required": [ + "activityTypeId", + "id", + "url" + ], + "properties": { + "activityTypeId": { + "type": "integer" + }, + "articleId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "userId": { + "type": "integer" + } + } + }, + "request.AdvertisementCreateRequest": { + "type": "object", + "required": [ + "description", + "placement", + "redirectLink", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "placement": { + "type": "string" + }, + "redirectLink": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.AdvertisementUpdateRequest": { + "type": "object", + "required": [ + "description", + "id", + "placement", + "redirectLink", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "placement": { + "type": "string" + }, + "redirectLink": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.ApprovalActionRequest": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "request.ApprovalWorkflowStepRequest": { + "type": "object", + "required": [ + "requiredUserLevelId", + "stepName", + "stepOrder" + ], + "properties": { + "autoApproveAfterHours": { + "type": "integer" + }, + "canSkip": { + "type": "boolean" + }, + "isActive": { + "type": "boolean" + }, + "requiredUserLevelId": { + "type": "integer" + }, + "stepName": { + "type": "string" + }, + "stepOrder": { + "type": "integer" + } + } + }, + "request.ApprovalWorkflowsCreateRequest": { + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "autoPublish": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isDefault": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "requiresApproval": { + "type": "boolean" + }, + "steps": { + "type": "array", + "items": { + "$ref": "#/definitions/request.ApprovalWorkflowStepRequest" + } + } + } + }, + "request.ApprovalWorkflowsUpdateRequest": { + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "autoPublish": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isDefault": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "requiresApproval": { + "type": "boolean" + } + } + }, + "request.ApprovalWorkflowsWithStepsCreateRequest": { + "type": "object", + "required": [ + "description", + "name", + "steps" + ], + "properties": { + "autoPublish": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isDefault": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "requiresApproval": { + "type": "boolean" + }, + "steps": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/request.ApprovalWorkflowStepRequest" + } + } + } + }, + "request.ApprovalWorkflowsWithStepsUpdateRequest": { + "type": "object", + "required": [ + "description", + "name", + "steps" + ], + "properties": { + "autoPublish": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isDefault": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "requiresApproval": { + "type": "boolean" + }, + "steps": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/request.ApprovalWorkflowStepRequest" + } + } + } + }, + "request.ArticleApprovalStepLogsCreateRequest": { + "type": "object", + "required": [ + "approvalStatusId", + "articleApprovalFlowId", + "workflowStepId" + ], + "properties": { + "approvalStatusId": { + "type": "integer" + }, + "approvedAt": { + "type": "string" + }, + "approverUserId": { + "type": "integer" + }, + "articleApprovalFlowId": { + "type": "integer" + }, + "comments": { + "type": "string", + "maxLength": 1000 + }, + "dueDate": { + "type": "string" + }, + "isAutoApproved": { + "type": "boolean" + }, + "workflowStepId": { + "type": "integer" + } + } + }, + "request.ArticleApprovalStepLogsUpdateRequest": { + "type": "object", + "properties": { + "approvalStatusId": { + "type": "integer" + }, + "approvedAt": { + "type": "string" + }, + "approverUserId": { + "type": "integer" + }, + "comments": { + "type": "string", + "maxLength": 1000 + }, + "dueDate": { + "type": "string" + }, + "isAutoApproved": { + "type": "boolean" + } + } + }, + "request.ArticleApprovalsCreateRequest": { + "type": "object", + "required": [ + "articleId", + "message", + "statusId" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.ArticleApprovalsUpdateRequest": { + "type": "object", + "required": [ + "articleId", + "id", + "message", + "statusId" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.ArticleCategoriesCreateRequest": { + "type": "object", + "required": [ + "description", + "statusId", + "title" + ], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "oldCategoryId": { + "type": "integer" + }, + "parentId": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.ArticleCategoriesUpdateRequest": { + "type": "object", + "required": [ + "description", + "id", + "statusId", + "title" + ], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isPublish": { + "type": "boolean" + }, + "parentId": { + "type": "integer" + }, + "publishedAt": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.ArticleCommentsApprovalRequest": { + "type": "object", + "required": [ + "id", + "statusId" + ], + "properties": { + "id": { + "type": "integer" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.ArticleCommentsCreateRequest": { + "type": "object", + "required": [ + "articleId", + "message" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "isPublic": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "parentId": { + "type": "integer" + } + } + }, + "request.ArticleCommentsUpdateRequest": { + "type": "object", + "required": [ + "articleId", + "id", + "message" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "isPublic": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "parentId": { + "type": "integer" + } + } + }, + "request.ArticleFilesUpdateRequest": { + "type": "object", + "required": [ + "articleId", + "id", + "isPublish", + "publishedAt", + "statusId" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "fileAlt": { + "type": "string" + }, + "fileName": { + "type": "string" + }, + "filePath": { + "type": "string" + }, + "fileThumbnail": { + "type": "string" + }, + "fileUrl": { + "type": "string" + }, + "heightPixel": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isPublish": { + "type": "boolean" + }, + "publishedAt": { + "type": "string" + }, + "size": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "widthPixel": { + "type": "string" + } + } + }, + "request.ArticleNulisAICreateRequest": { + "type": "object", + "required": [ + "articleId", + "categoryId", + "creatorId", + "description", + "htmlDescription", + "nulisAiId", + "tags", + "title" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "categoryId": { + "type": "integer" + }, + "creatorId": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "htmlDescription": { + "type": "string" + }, + "nulisAiId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.ArticleNulisAIUpdateRequest": { + "type": "object", + "required": [ + "articleId", + "categoryId", + "creatorId", + "description", + "htmlDescription", + "id", + "nulisAiId", + "tags", + "title" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "categoryId": { + "type": "integer" + }, + "creatorId": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "htmlDescription": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "nulisAiId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "request.ArticlesCreateRequest": { + "type": "object", + "required": [ + "categoryIds", + "description", + "htmlDescription", + "slug", + "tags", + "title", + "typeId" + ], + "properties": { + "aiArticleId": { + "type": "integer" + }, + "categoryIds": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdById": { + "type": "integer" + }, + "customCreatorName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "htmlDescription": { + "type": "string" + }, + "isDraft": { + "type": "boolean" + }, + "isPublish": { + "type": "boolean" + }, + "oldId": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "source": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + }, + "typeId": { + "type": "integer" + } + } + }, + "request.ArticlesUpdateRequest": { + "type": "object", + "required": [ + "categoryIds", + "description", + "htmlDescription", + "slug", + "tags", + "title", + "typeId" + ], + "properties": { + "aiArticleId": { + "type": "integer" + }, + "categoryIds": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdById": { + "type": "integer" + }, + "customCreatorName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "htmlDescription": { + "type": "string" + }, + "isDraft": { + "type": "boolean" + }, + "isPublish": { + "type": "boolean" + }, + "slug": { + "type": "string" + }, + "source": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + }, + "typeId": { + "type": "integer" + } + } + }, + "request.BookmarksCreateRequest": { + "type": "object", + "required": [ + "articleId" + ], + "properties": { + "articleId": { + "type": "integer" + } + } + }, + "request.BulkCreateApprovalWorkflowStepsRequest": { + "type": "object", + "required": [ + "steps", + "workflowId" + ], + "properties": { + "steps": { + "type": "array", + "maxItems": 20, + "minItems": 1, + "items": { + "$ref": "#/definitions/request.CreateApprovalWorkflowStepsRequest" + } + }, + "workflowId": { + "type": "integer" + } + } + }, + "request.BulkProcessApprovalRequest": { + "type": "object", + "required": [ + "approvalStatusId", + "logIds", + "statusId", + "stepLogIds", + "userId" + ], + "properties": { + "approvalStatusId": { + "type": "integer" + }, + "comments": { + "type": "string", + "maxLength": 1000 + }, + "logIds": { + "type": "array", + "maxItems": 50, + "minItems": 1, + "items": { + "type": "integer" + } + }, + "statusId": { + "type": "integer" + }, + "stepLogIds": { + "type": "array", + "maxItems": 50, + "minItems": 1, + "items": { + "type": "integer" + } + }, + "userId": { + "type": "integer" + } + } + }, + "request.CitiesCreateRequest": { + "type": "object", + "required": [ + "city_name", + "prov_id" + ], + "properties": { + "city_name": { + "type": "string" + }, + "prov_id": { + "type": "integer" + } + } + }, + "request.CitiesUpdateRequest": { + "type": "object", + "required": [ + "city_name", + "id", + "prov_id" + ], + "properties": { + "city_name": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "prov_id": { + "type": "integer" + } + } + }, + "request.ClientsCreateRequest": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "createdById": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, + "request.ClientsUpdateRequest": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "createdById": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, + "request.CreateApprovalWorkflowStepsRequest": { + "type": "object", + "required": [ + "approverRoleId", + "stepName", + "stepOrder", + "workflowId" + ], + "properties": { + "approverRoleId": { + "type": "integer" + }, + "autoApprove": { + "type": "boolean" + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "isOptional": { + "type": "boolean" + }, + "requiresComment": { + "type": "boolean" + }, + "stepName": { + "type": "string", + "maxLength": 100, + "minLength": 3 + }, + "stepOrder": { + "type": "integer", + "minimum": 1 + }, + "timeoutHours": { + "type": "integer", + "maximum": 720, + "minimum": 1 + }, + "workflowId": { + "type": "integer" + } + } + }, + "request.CreateClientApprovalSettingsRequest": { + "type": "object", + "properties": { + "approvalExemptCategories": { + "type": "array", + "items": { + "type": "integer" + } + }, + "approvalExemptRoles": { + "type": "array", + "items": { + "type": "integer" + } + }, + "approvalExemptUsers": { + "type": "array", + "items": { + "type": "integer" + } + }, + "autoPublishArticles": { + "type": "boolean" + }, + "defaultWorkflowId": { + "type": "integer", + "minimum": 1 + }, + "isActive": { + "type": "boolean" + }, + "requireApprovalFor": { + "type": "array", + "items": { + "type": "string" + } + }, + "requiresApproval": { + "type": "boolean" + }, + "skipApprovalFor": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "request.CustomStaticPagesCreateRequest": { + "type": "object", + "required": [ + "htmlBody", + "slug", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "htmlBody": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.CustomStaticPagesUpdateRequest": { + "type": "object", + "required": [ + "htmlBody", + "id", + "slug", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "htmlBody": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "request.DisableApprovalRequest": { + "type": "object", + "required": [ + "handleAction", + "reason" + ], + "properties": { + "handleAction": { + "description": "How to handle pending articles", + "type": "string", + "enum": [ + "auto_approve", + "keep_pending", + "reset_to_draft" + ] + }, + "reason": { + "type": "string", + "maxLength": 500 + } + } + }, + "request.EnableApprovalRequest": { + "type": "object", + "properties": { + "defaultWorkflowId": { + "type": "integer", + "minimum": 1 + }, + "reason": { + "type": "string", + "maxLength": 500 + } + } + }, + "request.FeedbacksCreateRequest": { + "type": "object", + "required": [ + "commentFromEmail", + "commentFromName", + "message" + ], + "properties": { + "commentFromEmail": { + "type": "string" + }, + "commentFromName": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "request.FeedbacksUpdateRequest": { + "type": "object", + "required": [ + "commentFromEmail", + "commentFromName", + "id", + "message" + ], + "properties": { + "commentFromEmail": { + "type": "string" + }, + "commentFromName": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "message": { + "type": "string" + } + } + }, + "request.MagazinesCreateRequest": { + "type": "object", + "required": [ + "description", + "statusId", + "title" + ], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "isPublish": { + "type": "boolean" + }, + "pageUrl": { + "type": "string" + }, + "publishedAt": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "thumbnailPath": { + "type": "string" + }, + "thumbnailUrl": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.MagazinesUpdateRequest": { + "type": "object", + "required": [ + "description", + "id", + "statusId", + "title" + ], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isPublish": { + "type": "boolean" + }, + "pageUrl": { + "type": "string" + }, + "publishedAt": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "thumbnailPath": { + "type": "string" + }, + "thumbnailUrl": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.MasterMenusCreateRequest": { + "type": "object", + "required": [ + "description", + "group", + "moduleId", + "name", + "statusId" + ], + "properties": { + "description": { + "type": "string" + }, + "group": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "moduleId": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parentMenuId": { + "type": "integer" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.MasterModulesCreateRequest": { + "type": "object", + "required": [ + "description", + "name", + "pathUrl", + "statusId" + ], + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "pathUrl": { + "type": "string" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.MasterModulesUpdateRequest": { + "type": "object", + "required": [ + "description", + "id", + "name", + "pathUrl", + "statusId" + ], + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "pathUrl": { + "type": "string" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.ProcessApprovalRequest": { + "type": "object", + "required": [ + "approvalStatusId", + "statusId", + "userId" + ], + "properties": { + "approvalStatusId": { + "type": "integer" + }, + "comments": { + "type": "string", + "maxLength": 1000 + }, + "statusId": { + "type": "integer" + }, + "userId": { + "type": "integer" + } + } + }, + "request.RejectionRequest": { + "type": "object", + "required": [ + "reason" + ], + "properties": { + "reason": { + "type": "string" + } + } + }, + "request.ReorderApprovalWorkflowStepsRequest": { + "type": "object", + "required": [ + "stepOrders" + ], + "properties": { + "stepOrders": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "id", + "stepOrder" + ], + "properties": { + "id": { + "type": "integer" + }, + "stepOrder": { + "type": "integer", + "minimum": 1 + } + } + } + } + } + }, + "request.ResubmitRequest": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "request.RevisionRequest": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + }, + "request.SchedulesCreateRequest": { + "type": "object", + "required": [ + "description", + "location", + "speakers", + "title", + "typeId" + ], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "endDate": { + "type": "string" + }, + "endTime": { + "type": "string" + }, + "isLiveStreaming": { + "type": "boolean" + }, + "liveStreamingUrl": { + "type": "string" + }, + "location": { + "type": "string" + }, + "posterImagePath": { + "type": "string" + }, + "speakers": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "startTime": { + "type": "string" + }, + "title": { + "type": "string" + }, + "typeId": { + "type": "integer" + } + } + }, + "request.SchedulesUpdateRequest": { + "type": "object", + "required": [ + "description", + "location", + "speakers", + "title", + "typeId" + ], + "properties": { + "description": { + "type": "string" + }, + "endDate": { + "type": "string" + }, + "endTime": { + "type": "string" + }, + "isLiveStreaming": { + "type": "boolean" + }, + "liveStreamingUrl": { + "type": "string" + }, + "location": { + "type": "string" + }, + "posterImagePath": { + "type": "string" + }, + "speakers": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "startTime": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "typeId": { + "type": "integer" + } + } + }, + "request.SetDefaultWorkflowRequest": { + "type": "object", + "properties": { + "workflowId": { + "type": "integer", + "minimum": 1 + } + } + }, + "request.SubscriptionCreateRequest": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string" + } + } + }, + "request.SubscriptionUpdateRequest": { + "type": "object", + "required": [ + "email", + "id" + ], + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "integer" + } + } + }, + "request.ToggleApprovalRequest": { + "type": "object", + "properties": { + "requiresApproval": { + "type": "boolean" + } + } + }, + "request.UpdateApprovalWorkflowStepsRequest": { + "type": "object", + "properties": { + "approverRoleId": { + "type": "integer" + }, + "autoApprove": { + "type": "boolean" + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "isOptional": { + "type": "boolean" + }, + "requiresComment": { + "type": "boolean" + }, + "stepName": { + "type": "string", + "maxLength": 100, + "minLength": 3 + }, + "stepOrder": { + "type": "integer", + "minimum": 1 + }, + "timeoutHours": { + "type": "integer", + "maximum": 720, + "minimum": 1 + } + } + }, + "request.UpdateClientApprovalSettingsRequest": { + "type": "object", + "properties": { + "approvalExemptCategories": { + "type": "array", + "items": { + "type": "integer" + } + }, + "approvalExemptRoles": { + "type": "array", + "items": { + "type": "integer" + } + }, + "approvalExemptUsers": { + "type": "array", + "items": { + "type": "integer" + } + }, + "autoPublishArticles": { + "type": "boolean" + }, + "defaultWorkflowId": { + "description": "double pointer to allow nil", + "type": "integer" + }, + "isActive": { + "type": "boolean" + }, + "requireApprovalFor": { + "type": "array", + "items": { + "type": "string" + } + }, + "requiresApproval": { + "type": "boolean" + }, + "skipApprovalFor": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "request.UserEmailValidationRequest": { + "type": "object", + "properties": { + "newEmail": { + "type": "string" + }, + "oldEmail": { + "type": "string" + }, + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "request.UserForgotPassword": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + } + }, + "request.UserLevelsApprovalRequest": { + "type": "object", + "required": [ + "ids", + "isApprovalActive" + ], + "properties": { + "ids": { + "type": "string" + }, + "isApprovalActive": { + "type": "boolean" + } + } + }, + "request.UserLevelsCreateRequest": { + "type": "object", + "required": [ + "aliasName", + "levelNumber", + "name" + ], + "properties": { + "aliasName": { + "type": "string" + }, + "group": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isApprovalActive": { + "type": "boolean" + }, + "levelNumber": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parentLevelId": { + "type": "integer" + }, + "provinceId": { + "type": "integer" + } + } + }, + "request.UserLevelsUpdateRequest": { + "type": "object", + "required": [ + "aliasName", + "levelNumber", + "name" + ], + "properties": { + "aliasName": { + "type": "string" + }, + "group": { + "type": "string" + }, + "isApprovalActive": { + "type": "boolean" + }, + "levelNumber": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parentLevelId": { + "type": "integer" + }, + "provinceId": { + "type": "integer" + } + } + }, + "request.UserLogin": { + "type": "object", + "properties": { + "password": { + "type": "string" + }, + "refreshToken": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "request.UserOtpRequest": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "request.UserOtpValidation": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "otpCode": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "request.UserResetPassword": { + "type": "object", + "properties": { + "codeRequest": { + "type": "string" + }, + "confirmPassword": { + "type": "string" + }, + "password": { + "type": "string" + }, + "userId": { + "type": "string" + } + } + }, + "request.UserRoleAccessesCreateRequest": { + "type": "object", + "required": [ + "isAdminEnabled", + "isApprovalEnabled", + "isDeleteEnabled", + "isInsertEnabled", + "isUpdateEnabled", + "isViewEnabled", + "menuId" + ], + "properties": { + "isAdminEnabled": { + "type": "boolean" + }, + "isApprovalEnabled": { + "type": "boolean" + }, + "isDeleteEnabled": { + "type": "boolean" + }, + "isInsertEnabled": { + "type": "boolean" + }, + "isUpdateEnabled": { + "type": "boolean" + }, + "isViewEnabled": { + "type": "boolean" + }, + "menuId": { + "type": "integer" + } + } + }, + "request.UserRoleAccessesUpdateRequest": { + "type": "object", + "required": [ + "id", + "is_admin_enabled", + "is_approval_enabled", + "is_delete_enabled", + "is_insert_enabled", + "is_update_enabled", + "is_view_enabled", + "menu_id", + "user_role_id" + ], + "properties": { + "id": { + "type": "integer" + }, + "is_admin_enabled": { + "type": "boolean" + }, + "is_approval_enabled": { + "type": "boolean" + }, + "is_delete_enabled": { + "type": "boolean" + }, + "is_insert_enabled": { + "type": "boolean" + }, + "is_update_enabled": { + "type": "boolean" + }, + "is_view_enabled": { + "type": "boolean" + }, + "menu_id": { + "type": "integer" + }, + "user_role_id": { + "type": "integer" + } + } + }, + "request.UserRolesCreateRequest": { + "type": "object", + "required": [ + "code", + "description", + "name", + "statusId", + "userLevelIds", + "userRoleAccess" + ], + "properties": { + "code": { + "type": "string" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "userLevelIds": { + "type": "array", + "items": { + "type": "integer" + } + }, + "userRoleAccess": { + "type": "array", + "items": { + "$ref": "#/definitions/request.UserRoleAccessesCreateRequest" + } + } + } + }, + "request.UserRolesUpdateRequest": { + "type": "object", + "required": [ + "code", + "description", + "level_number", + "name", + "status_id", + "userLevelIds" + ], + "properties": { + "code": { + "type": "string" + }, + "description": { + "type": "string" + }, + "level_number": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "status_id": { + "type": "integer" + }, + "userLevelIds": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "request.UserSavePassword": { + "type": "object", + "properties": { + "confirmPassword": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "request.UsersCreateRequest": { + "type": "object", + "required": [ + "email", + "fullname", + "password", + "userLevelId", + "userRoleId", + "username" + ], + "properties": { + "address": { + "type": "string" + }, + "dateOfBirth": { + "type": "string" + }, + "email": { + "type": "string" + }, + "fullname": { + "type": "string" + }, + "genderType": { + "type": "string" + }, + "identityGroup": { + "type": "string" + }, + "identityGroupNumber": { + "type": "string" + }, + "identityNumber": { + "type": "string" + }, + "identityType": { + "type": "string" + }, + "lastEducation": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phoneNumber": { + "type": "string" + }, + "userLevelId": { + "type": "integer" + }, + "userRoleId": { + "type": "integer" + }, + "username": { + "type": "string" + }, + "workType": { + "type": "string" + } + } + }, + "request.UsersUpdateRequest": { + "type": "object", + "required": [ + "email", + "fullname", + "userLevelId", + "userRoleId", + "username" + ], + "properties": { + "address": { + "type": "string" + }, + "dateOfBirth": { + "type": "string" + }, + "email": { + "type": "string" + }, + "fullname": { + "type": "string" + }, + "genderType": { + "type": "string" + }, + "identityGroup": { + "type": "string" + }, + "identityGroupNumber": { + "type": "string" + }, + "identityNumber": { + "type": "string" + }, + "identityType": { + "type": "string" + }, + "lastEducation": { + "type": "string" + }, + "phoneNumber": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "userLevelId": { + "type": "integer" + }, + "userRoleId": { + "type": "integer" + }, + "username": { + "type": "string" + }, + "workType": { + "type": "string" + } + } + }, + "response.BadRequestError": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 400 + }, + "message": { + "type": "string", + "example": "bad request" + }, + "success": { + "type": "boolean", + "example": false + } + } + }, + "response.InternalServerError": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 500 + }, + "message": { + "type": "string", + "example": "internal server error" + }, + "success": { + "type": "boolean", + "example": false + } + } + }, + "response.Response": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 200 + }, + "data": {}, + "messages": { + "type": "array", + "items": {} + }, + "meta": {}, + "success": { + "type": "boolean", + "example": true + } + } + }, + "response.UnauthorizedError": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 401 + }, + "message": { + "type": "string", + "example": "unauthorized access" + }, + "success": { + "type": "boolean", + "example": false + } + } + }, + "web-qudo-be_app_module_article_approval_flows_request.SubmitForApprovalRequest": { + "type": "object", + "required": [ + "articleId" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "workflowId": { + "type": "integer" + } + } + }, + "web-qudo-be_app_module_articles_request.SubmitForApprovalRequest": { + "type": "object", + "properties": { + "message": { + "type": "string", + "maxLength": 500 + }, + "workflow_id": { + "type": "integer", + "minimum": 1 + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "", + Description: "", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json new file mode 100644 index 0000000..91fdb2b --- /dev/null +++ b/docs/swagger/swagger.json @@ -0,0 +1,17561 @@ +{ + "swagger": "2.0", + "info": { + "contact": {} + }, + "paths": { + "/activity-logs": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ActivityLogs", + "tags": ["ActivityLogs"], + "summary": "Get all ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "name": "activityTypeId", + "in": "query" + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "string", + "name": "url", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ActivityLogs", + "tags": ["ActivityLogs"], + "summary": "Create ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ActivityLogsCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/activity-logs/detail/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ActivityLogs", + "tags": ["ActivityLogs"], + "summary": "Get one ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "ActivityLogs ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/activity-logs/statistics": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for get activity stats ActivityLogs", + "tags": ["ActivityLogs"], + "summary": "Get activity stats ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + } + ], + "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" + } + } + } + } + }, + "/activity-logs/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ActivityLogs", + "tags": ["ActivityLogs"], + "summary": "update ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ActivityLogsUpdateRequest" + } + }, + { + "type": "integer", + "description": "ActivityLogs ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ActivityLogs", + "tags": ["ActivityLogs"], + "summary": "delete ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "ActivityLogs ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/advertisement": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Advertisement", + "tags": ["Advertisement"], + "summary": "Get all Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "string", + "name": "placement", + "in": "query" + }, + { + "type": "string", + "name": "redirectLink", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Advertisement", + "tags": ["Advertisement"], + "summary": "Create Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AdvertisementCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/advertisement/publish/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Update Publish Advertisement", + "tags": ["Advertisement"], + "summary": "Update Publish Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "boolean", + "description": "Advertisement Publish Status", + "name": "isPublish", + "in": "query", + "required": true + } + ], + "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" + } + } + } + } + }, + "/advertisement/upload/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Upload File Advertisement", + "produces": ["application/json"], + "tags": ["Advertisement"], + "summary": "Upload Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "file", + "description": "Upload file", + "name": "file", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/advertisement/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Viewer Advertisement", + "tags": ["Advertisement"], + "summary": "Viewer Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Content File Name", + "name": "filename", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/advertisement/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Advertisement", + "tags": ["Advertisement"], + "summary": "Get one Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Advertisement", + "tags": ["Advertisement"], + "summary": "update Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AdvertisementUpdateRequest" + } + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Advertisement", + "tags": ["Advertisement"], + "summary": "delete Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflow-steps": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ApprovalWorkflowSteps", + "tags": ["ApprovalWorkflowSteps"], + "summary": "Get all ApprovalWorkflowSteps", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Workflow ID filter", + "name": "workflowId", + "in": "query" + }, + { + "type": "integer", + "description": "Step order filter", + "name": "stepOrder", + "in": "query" + }, + { + "type": "string", + "description": "Step name filter", + "name": "stepName", + "in": "query" + }, + { + "type": "integer", + "description": "User level ID filter", + "name": "userLevelId", + "in": "query" + }, + { + "type": "boolean", + "description": "Is optional filter", + "name": "isOptional", + "in": "query" + }, + { + "type": "boolean", + "description": "Is active filter", + "name": "isActive", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for saving ApprovalWorkflowSteps", + "tags": ["ApprovalWorkflowSteps"], + "summary": "Save ApprovalWorkflowSteps", + "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.CreateApprovalWorkflowStepsRequest" + } + } + ], + "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" + } + } + } + } + }, + "/approval-workflow-steps/bulk": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for bulk creating ApprovalWorkflowSteps", + "tags": ["ApprovalWorkflowSteps"], + "summary": "Bulk create ApprovalWorkflowSteps", + "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.BulkCreateApprovalWorkflowStepsRequest" + } + } + ], + "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" + } + } + } + } + }, + "/approval-workflow-steps/role/{roleId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ApprovalWorkflowSteps by Role ID", + "tags": ["ApprovalWorkflowSteps"], + "summary": "Get ApprovalWorkflowSteps by Role ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Role ID", + "name": "roleId", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflow-steps/workflow/{workflowId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ApprovalWorkflowSteps by Workflow ID", + "tags": ["ApprovalWorkflowSteps"], + "summary": "Get ApprovalWorkflowSteps by Workflow ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Workflow ID", + "name": "workflowId", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflow-steps/workflow/{workflowId}/reorder": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for reordering ApprovalWorkflowSteps", + "tags": ["ApprovalWorkflowSteps"], + "summary": "Reorder ApprovalWorkflowSteps", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Workflow ID", + "name": "workflowId", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ReorderApprovalWorkflowStepsRequest" + } + } + ], + "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" + } + } + } + } + }, + "/approval-workflow-steps/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ApprovalWorkflowSteps", + "tags": ["ApprovalWorkflowSteps"], + "summary": "Get one ApprovalWorkflowSteps", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflowSteps ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating ApprovalWorkflowSteps", + "tags": ["ApprovalWorkflowSteps"], + "summary": "Update ApprovalWorkflowSteps", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflowSteps ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UpdateApprovalWorkflowStepsRequest" + } + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting ApprovalWorkflowSteps", + "tags": ["ApprovalWorkflowSteps"], + "summary": "Delete ApprovalWorkflowSteps", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflowSteps ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflows": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ApprovalWorkflows", + "tags": ["ApprovalWorkflows"], + "summary": "Get all ApprovalWorkflows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isActive", + "in": "query" + }, + { + "type": "boolean", + "name": "isDefault", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for saving ApprovalWorkflows", + "tags": ["ApprovalWorkflows"], + "summary": "Save ApprovalWorkflows", + "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.ApprovalWorkflowsCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/approval-workflows/default": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting default ApprovalWorkflows", + "tags": ["ApprovalWorkflows"], + "summary": "Get default ApprovalWorkflows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflows/with-steps": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating ApprovalWorkflows with steps", + "tags": ["ApprovalWorkflows"], + "summary": "Create ApprovalWorkflows with steps", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "description": "ApprovalWorkflows with steps data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ApprovalWorkflowsWithStepsCreateRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "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" + } + } + } + } + }, + "/approval-workflows/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ApprovalWorkflows", + "tags": ["ApprovalWorkflows"], + "summary": "Get one ApprovalWorkflows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflows ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating ApprovalWorkflows", + "tags": ["ApprovalWorkflows"], + "summary": "Update ApprovalWorkflows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflows ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ApprovalWorkflowsUpdateRequest" + } + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting ApprovalWorkflows", + "tags": ["ApprovalWorkflows"], + "summary": "Delete ApprovalWorkflows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflows ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflows/{id}/activate": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for activating ApprovalWorkflows", + "tags": ["ApprovalWorkflows"], + "summary": "Activate ApprovalWorkflows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflows ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflows/{id}/deactivate": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deactivating ApprovalWorkflows", + "tags": ["ApprovalWorkflows"], + "summary": "Deactivate ApprovalWorkflows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflows ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflows/{id}/set-default": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for setting default ApprovalWorkflows", + "tags": ["ApprovalWorkflows"], + "summary": "Set default ApprovalWorkflows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflows ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/approval-workflows/{id}/with-steps": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ApprovalWorkflows with steps", + "tags": ["ApprovalWorkflows"], + "summary": "Get ApprovalWorkflows with steps", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflows ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating ApprovalWorkflows with steps", + "tags": ["ApprovalWorkflows"], + "summary": "Update ApprovalWorkflows with steps", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ApprovalWorkflows ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "ApprovalWorkflows with steps data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ApprovalWorkflowsWithStepsUpdateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approval-flows": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleApprovalFlows", + "tags": ["ArticleApprovalFlows"], + "summary": "Get all ArticleApprovalFlows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "currentStep", + "in": "query" + }, + { + "type": "string", + "name": "dateFrom", + "in": "query" + }, + { + "type": "string", + "name": "dateTo", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "submittedBy", + "in": "query" + }, + { + "type": "integer", + "name": "workflowId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + } + }, + "/article-approval-flows/analytics": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting approval analytics", + "tags": ["ArticleApprovalFlows"], + "summary": "Get approval analytics", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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", + "description": "Period filter (daily, weekly, monthly)", + "name": "period", + "in": "query" + }, + { + "type": "string", + "description": "Start date filter (YYYY-MM-DD)", + "name": "startDate", + "in": "query" + }, + { + "type": "string", + "description": "End date filter (YYYY-MM-DD)", + "name": "endDate", + "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" + } + } + } + } + }, + "/article-approval-flows/dashboard-stats": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting dashboard statistics", + "tags": ["ArticleApprovalFlows"], + "summary": "Get dashboard statistics", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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" + } + ], + "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" + } + } + } + } + }, + "/article-approval-flows/history": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting approval history", + "tags": ["ArticleApprovalFlows"], + "summary": "Get approval history", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Article ID filter", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "description": "User ID filter", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + } + }, + "/article-approval-flows/my-queue": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting my approval queue", + "tags": ["ArticleApprovalFlows"], + "summary": "Get my approval queue", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "boolean", + "description": "Include article preview", + "name": "includePreview", + "in": "query" + }, + { + "type": "boolean", + "description": "Show only urgent articles", + "name": "urgentOnly", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + } + }, + "/article-approval-flows/pending": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting pending approvals", + "tags": ["ArticleApprovalFlows"], + "summary": "Get pending approvals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + } + }, + "/article-approval-flows/submit": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for submitting article for approval", + "tags": ["ArticleApprovalFlows"], + "summary": "Submit article for approval", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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" + }, + { + "description": "Submit for approval data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/web-qudo-be_app_module_article_approval_flows_request.SubmitForApprovalRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "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" + } + } + } + } + }, + "/article-approval-flows/workload-stats": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting workload statistics", + "tags": ["ArticleApprovalFlows"], + "summary": "Get workload statistics", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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" + } + ], + "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" + } + } + } + } + }, + "/article-approval-flows/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleApprovalFlows", + "tags": ["ArticleApprovalFlows"], + "summary": "Get one ArticleApprovalFlows", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleApprovalFlows ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-approval-flows/{id}/approve": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for approving article", + "tags": ["ArticleApprovalFlows"], + "summary": "Approve article", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "description": "ArticleApprovalFlows ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Approval action data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ApprovalActionRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approval-flows/{id}/reject": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for rejecting article", + "tags": ["ArticleApprovalFlows"], + "summary": "Reject article", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "description": "ArticleApprovalFlows ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Rejection data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.RejectionRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approval-flows/{id}/request-revision": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for requesting revision for article", + "tags": ["ArticleApprovalFlows"], + "summary": "Request revision for article", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "description": "ArticleApprovalFlows ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Revision request data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.RevisionRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approval-flows/{id}/resubmit": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for resubmitting article after revision", + "tags": ["ArticleApprovalFlows"], + "summary": "Resubmit article after revision", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "description": "ArticleApprovalFlows ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Resubmit data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ResubmitRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approval-step-logs": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleApprovalStepLogs", + "tags": ["ArticleApprovalStepLogs"], + "summary": "Get all ArticleApprovalStepLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "approvalStatusId", + "in": "query" + }, + { + "type": "integer", + "name": "approverUserId", + "in": "query" + }, + { + "type": "integer", + "name": "articleApprovalFlowId", + "in": "query" + }, + { + "type": "string", + "name": "dateFrom", + "in": "query" + }, + { + "type": "string", + "name": "dateTo", + "in": "query" + }, + { + "type": "boolean", + "name": "isAutoApproved", + "in": "query" + }, + { + "type": "integer", + "name": "workflowStepId", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for saving ArticleApprovalStepLogs", + "tags": ["ArticleApprovalStepLogs"], + "summary": "Save ArticleApprovalStepLogs", + "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.ArticleApprovalStepLogsCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approval-step-logs/approver/{user_id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ArticleApprovalStepLogs by Approver User ID", + "tags": ["ArticleApprovalStepLogs"], + "summary": "Get ArticleApprovalStepLogs by Approver User ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Approver User ID", + "name": "user_id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-approval-step-logs/bulk-process": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for bulk processing approval steps", + "tags": ["ArticleApprovalStepLogs"], + "summary": "Bulk Process Approvals", + "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.BulkProcessApprovalRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approval-step-logs/flow/{flow_id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ArticleApprovalStepLogs by Approval Flow ID", + "tags": ["ArticleApprovalStepLogs"], + "summary": "Get ArticleApprovalStepLogs by Approval Flow ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Approval Flow ID", + "name": "flow_id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-approval-step-logs/history": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting approval history", + "tags": ["ArticleApprovalStepLogs"], + "summary": "Get Approval History", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Filter by article ID", + "name": "article_id", + "in": "query" + }, + { + "type": "integer", + "description": "Filter by user ID", + "name": "user_id", + "in": "query" + }, + { + "type": "string", + "description": "Filter from date (YYYY-MM-DD)", + "name": "from_date", + "in": "query" + }, + { + "type": "string", + "description": "Filter to date (YYYY-MM-DD)", + "name": "to_date", + "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" + } + } + } + } + }, + "/article-approval-step-logs/overdue": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting overdue approvals", + "tags": ["ArticleApprovalStepLogs"], + "summary": "Get Overdue Approvals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Filter by user ID", + "name": "user_id", + "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" + } + } + } + } + }, + "/article-approval-step-logs/pending": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting pending approvals", + "tags": ["ArticleApprovalStepLogs"], + "summary": "Get Pending Approvals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Filter by user ID", + "name": "user_id", + "in": "query" + }, + { + "type": "integer", + "description": "Filter by role ID", + "name": "role_id", + "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" + } + } + } + } + }, + "/article-approval-step-logs/stats": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting approval statistics", + "tags": ["ArticleApprovalStepLogs"], + "summary": "Get Approval Statistics", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Filter from date (YYYY-MM-DD)", + "name": "from_date", + "in": "query" + }, + { + "type": "string", + "description": "Filter to date (YYYY-MM-DD)", + "name": "to_date", + "in": "query" + }, + { + "type": "integer", + "description": "Filter by workflow ID", + "name": "workflow_id", + "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" + } + } + } + } + }, + "/article-approval-step-logs/step/{step_id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ArticleApprovalStepLogs by Workflow Step ID", + "tags": ["ArticleApprovalStepLogs"], + "summary": "Get ArticleApprovalStepLogs by Workflow Step ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Workflow Step ID", + "name": "step_id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-approval-step-logs/user/{user_id}/workload": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting user workload statistics", + "tags": ["ArticleApprovalStepLogs"], + "summary": "Get User Workload", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "user_id", + "in": "path", + "required": true + }, + { + "type": "boolean", + "description": "Include detailed statistics", + "name": "include_stats", + "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" + } + } + } + } + }, + "/article-approval-step-logs/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleApprovalStepLogs", + "tags": ["ArticleApprovalStepLogs"], + "summary": "Get one ArticleApprovalStepLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleApprovalStepLogs ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating ArticleApprovalStepLogs", + "tags": ["ArticleApprovalStepLogs"], + "summary": "Update ArticleApprovalStepLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleApprovalStepLogs ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleApprovalStepLogsUpdateRequest" + } + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting ArticleApprovalStepLogs", + "tags": ["ArticleApprovalStepLogs"], + "summary": "Delete ArticleApprovalStepLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleApprovalStepLogs ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-approval-step-logs/{id}/auto-approve": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for automatically approving a step", + "tags": ["ArticleApprovalStepLogs"], + "summary": "Auto Approve Step", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Step Log ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Auto approval reason", + "name": "reason", + "in": "query", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-approval-step-logs/{id}/process": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for processing approval step", + "tags": ["ArticleApprovalStepLogs"], + "summary": "Process Approval", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Step Log ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ProcessApprovalRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approvals": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleApprovals", + "tags": ["ArticleApprovals"], + "summary": "Get all ArticleApprovals", + "parameters": [ + { + "type": "integer", + "name": "approvalAtLevel", + "in": "query" + }, + { + "type": "integer", + "name": "approvalBy", + "in": "query" + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "string", + "name": "message", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleApprovals", + "tags": ["ArticleApprovals"], + "summary": "Create ArticleApprovals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleApprovalsCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-approvals/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleApprovals", + "tags": ["ArticleApprovals"], + "summary": "Get one ArticleApprovals", + "parameters": [ + { + "type": "integer", + "description": "ArticleApprovals ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleApprovals", + "tags": ["ArticleApprovals"], + "summary": "update ArticleApprovals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleApprovalsUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleApprovals ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleApprovals", + "tags": ["ArticleApprovals"], + "summary": "delete ArticleApprovals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleApprovals ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-categories": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleCategories", + "tags": ["Article Categories"], + "summary": "Get all ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "name": "UserLevelId", + "in": "query" + }, + { + "type": "integer", + "name": "UserLevelNumber", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "integer", + "name": "parentId", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleCategories", + "tags": ["Article Categories"], + "summary": "Create ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCategoriesCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-categories/old/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleCategories", + "tags": ["Article Categories"], + "summary": "Get one ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleCategories Old ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-categories/slug/{slug}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleCategories", + "tags": ["Article Categories"], + "summary": "Get one ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "ArticleCategories Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-categories/thumbnail/viewer/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for View Thumbnail of ArticleCategories", + "tags": ["Article Categories"], + "summary": "Viewer ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-categories/thumbnail/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Upload ArticleCategories Thumbnail", + "produces": ["application/json"], + "tags": ["Article Categories"], + "summary": "Upload ArticleCategories Thumbnail", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "file", + "description": "Upload thumbnail", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-categories/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleCategories", + "tags": ["Article Categories"], + "summary": "Get one ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleCategories", + "tags": ["Article Categories"], + "summary": "update ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCategoriesUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleCategories", + "tags": ["Article Categories"], + "summary": "delete ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-category-details": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleCategoryDetails", + "tags": ["Untags"], + "summary": "Get all ArticleCategoryDetails", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleCategoryDetails", + "tags": ["Untags"], + "summary": "Create ArticleCategoryDetails", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/article-category-details/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleCategoryDetails", + "tags": ["Untags"], + "summary": "Get one ArticleCategoryDetails", + "parameters": [ + { + "type": "integer", + "description": "ArticleCategoryDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleCategoryDetails", + "tags": ["Untags"], + "summary": "update ArticleCategoryDetails", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleCategoryDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleCategoryDetails", + "tags": ["Untags"], + "summary": "delete ArticleCategoryDetails", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleCategoryDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/article-comments": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleComments", + "tags": ["ArticleComments"], + "summary": "Get all ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "commentFrom", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublic", + "in": "query" + }, + { + "type": "string", + "name": "message", + "in": "query" + }, + { + "type": "integer", + "name": "parentId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleComments", + "tags": ["ArticleComments"], + "summary": "Create ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCommentsCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-comments/approval": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Approval ArticleComments", + "tags": ["ArticleComments"], + "summary": "Approval ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCommentsApprovalRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-comments/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleComments", + "tags": ["ArticleComments"], + "summary": "Get one ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleComments", + "tags": ["ArticleComments"], + "summary": "update ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCommentsUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleComments", + "tags": ["ArticleComments"], + "summary": "delete ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-files": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleFiles", + "tags": ["Article Files"], + "summary": "Get all ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "string", + "name": "fileName", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + } + }, + "/article-files/upload-status/{uploadId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for GetUploadStatus ArticleFiles", + "tags": ["Article Files"], + "summary": "GetUploadStatus ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Upload ID of ArticleFiles", + "name": "uploadId", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-files/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Viewer ArticleFiles", + "tags": ["Article Files"], + "summary": "Viewer ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Article File Name", + "name": "filename", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-files/{articleId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleFiles", + "produces": ["application/json"], + "tags": ["Article Files"], + "summary": "Upload ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "file", + "description": "Upload file", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Article ID", + "name": "articleId", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-files/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleFiles", + "tags": ["Article Files"], + "summary": "Get one ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleFiles", + "tags": ["Article Files"], + "summary": "Update ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleFilesUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleFiles", + "tags": ["Article Files"], + "summary": "Delete ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/article-nulis-ai": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleNulisAI", + "tags": ["ArticleNulisAI"], + "summary": "Get all ArticleNulisAI", + "parameters": [ + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "categoryId", + "in": "query" + }, + { + "type": "integer", + "name": "creatorId", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "htmlDescription", + "in": "query" + }, + { + "type": "integer", + "name": "nulisAiId", + "in": "query" + }, + { + "type": "string", + "name": "tags", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleNulisAI", + "tags": ["ArticleNulisAI"], + "summary": "Create ArticleNulisAI", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleNulisAICreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-nulis-ai/publish": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for publish ArticleNulisAI", + "tags": ["ArticleNulisAI"], + "summary": "publish ArticleNulisAI", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleNulisAIUpdateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/article-nulis-ai/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleNulisAI", + "tags": ["ArticleNulisAI"], + "summary": "Get one ArticleNulisAI", + "parameters": [ + { + "type": "integer", + "description": "ArticleNulisAI ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleNulisAI", + "tags": ["ArticleNulisAI"], + "summary": "update ArticleNulisAI", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleNulisAIUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleNulisAI ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleNulisAI", + "tags": ["ArticleNulisAI"], + "summary": "delete ArticleNulisAI", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "ArticleNulisAI ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Articles", + "tags": ["Articles"], + "summary": "Get all Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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", + "name": "category", + "in": "query" + }, + { + "type": "integer", + "name": "categoryId", + "in": "query" + }, + { + "type": "integer", + "name": "createdById", + "in": "query" + }, + { + "type": "string", + "name": "customCreatorName", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "endDate", + "in": "query" + }, + { + "type": "boolean", + "name": "isBanner", + "in": "query" + }, + { + "type": "boolean", + "name": "isDraft", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "string", + "name": "source", + "in": "query" + }, + { + "type": "string", + "name": "startDate", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "tags", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "typeId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Articles", + "tags": ["Articles"], + "summary": "Create Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticlesCreateRequest" + } + } + ], + "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/banner/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Update Banner Articles", + "tags": ["Articles"], + "summary": "Update Banner Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "boolean", + "description": "Articles Banner Status", + "name": "isBanner", + "in": "query", + "required": true + } + ], + "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/old-id/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Articles", + "tags": ["Articles"], + "summary": "Get one Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Articles Old ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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/pending-approval": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting articles pending approval for current user level", + "tags": ["Articles"], + "summary": "Get Pending Approvals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "items per page", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "article type id", + "name": "typeId", + "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/publish-scheduling": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Publish Schedule of Article. Supports both date-only format (2006-01-02) and datetime format (2006-01-02 15:04:05)", + "tags": ["Articles"], + "summary": "PublishScheduling Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "article id", + "name": "id", + "in": "query" + }, + { + "type": "string", + "description": "publish date/time (format: 2006-01-02 or 2006-01-02 15:04:05)", + "name": "date", + "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/slug/{slug}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Articles by slug", + "tags": ["Articles"], + "summary": "Get one Articles by Slug", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Articles Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "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/statistic/monthly": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ArticleMonthlyStats of Article", + "tags": ["Articles"], + "summary": "ArticleMonthlyStats Articles", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "year", + "name": "year", + "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/statistic/summary": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Summary Stats of Article", + "tags": ["Articles"], + "summary": "SummaryStats Articles", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + } + ], + "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/statistic/user-levels": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ArticlePerUserLevelStats of Article", + "tags": ["Articles"], + "summary": "ArticlePerUserLevelStats Articles", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "start date", + "name": "startDate", + "in": "query" + }, + { + "type": "string", + "description": "start date", + "name": "endDate", + "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/thumbnail/viewer/{thumbnailName}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for View Thumbnail of Article", + "tags": ["Articles"], + "summary": "Viewer Articles Thumbnail", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Articles Thumbnail Name", + "name": "thumbnailName", + "in": "path", + "required": true + } + ], + "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/thumbnail/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Save Thumbnail of Articles", + "produces": ["application/json"], + "tags": ["Articles"], + "summary": "Save Thumbnail Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "file", + "description": "Upload thumbnail", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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/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": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "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}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Articles", + "tags": ["Articles"], + "summary": "Get one Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Articles", + "tags": ["Articles"], + "summary": "Update Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticlesUpdateRequest" + } + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Articles", + "tags": ["Articles"], + "summary": "Delete Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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}/approval-status": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting article approval status and workflow progress", + "tags": ["Articles"], + "summary": "Get Article Approval Status", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "article id", + "name": "id", + "in": "path", + "required": true + } + ], + "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}/publish": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for publishing an article", + "tags": ["Articles"], + "summary": "Publish Article", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "article id", + "name": "id", + "in": "path", + "required": true + } + ], + "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}/submit-approval": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for submitting article for approval workflow", + "tags": ["Articles"], + "summary": "Submit Article for Approval", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "article id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "approval request data", + "name": "req", + "in": "body", + "schema": { + "$ref": "#/definitions/web-qudo-be_app_module_articles_request.SubmitForApprovalRequest" + } + } + ], + "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}/unpublish": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for unpublishing an article", + "tags": ["Articles"], + "summary": "Unpublish Article", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "article id", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/bookmarks": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Bookmarks", + "tags": ["Bookmarks"], + "summary": "Get all Bookmarks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating new Bookmark", + "tags": ["Bookmarks"], + "summary": "Create new Bookmark", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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" + }, + { + "description": "Bookmark data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.BookmarksCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/bookmarks/summary": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting bookmark summary including total count and recent bookmarks", + "tags": ["Bookmarks"], + "summary": "Get Bookmark Summary for User", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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" + } + ], + "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" + } + } + } + } + }, + "/bookmarks/toggle/{articleId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for toggling bookmark status for an article", + "tags": ["Bookmarks"], + "summary": "Toggle Bookmark (Add/Remove)", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "description": "Article ID", + "name": "articleId", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/bookmarks/user": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting Bookmarks by User ID", + "tags": ["Bookmarks"], + "summary": "Get Bookmarks by User ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + } + }, + "/bookmarks/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting Bookmark by ID", + "tags": ["Bookmarks"], + "summary": "Get Bookmark by ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "description": "Bookmark ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting Bookmark", + "tags": ["Bookmarks"], + "summary": "Delete Bookmark", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "description": "Bookmark ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/cities": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Cities", + "tags": ["Untags"], + "summary": "Get all Cities", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Cities", + "tags": ["Untags"], + "summary": "Create Cities", + "parameters": [ + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CitiesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/cities/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Cities", + "tags": ["Untags"], + "summary": "Get one Cities", + "parameters": [ + { + "type": "integer", + "description": "Cities ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Cities", + "consumes": ["application/json"], + "produces": ["application/json"], + "tags": ["Untags"], + "summary": "Update Cities", + "parameters": [ + { + "type": "integer", + "description": "Cities ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CitiesUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Cities", + "tags": ["Untags"], + "summary": "Delete Cities", + "parameters": [ + { + "type": "integer", + "description": "Cities ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/client-approval-settings": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting client approval settings", + "tags": ["ClientApprovalSettings"], + "summary": "Get Client Approval Settings", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating client approval settings", + "tags": ["ClientApprovalSettings"], + "summary": "Update 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.UpdateClientApprovalSettingsRequest" + } + } + ], + "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" + } + } + } + }, + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting client approval settings", + "tags": ["ClientApprovalSettings"], + "summary": "Delete Client Approval Settings", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + } + ], + "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" + } + } + } + } + }, + "/client-approval-settings/default-workflow": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for setting default workflow for client", + "tags": ["ClientApprovalSettings"], + "summary": "Set Default Workflow", + "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.SetDefaultWorkflowRequest" + } + } + ], + "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" + } + } + } + } + }, + "/client-approval-settings/disable": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for disabling approval system and auto-publish pending articles", + "tags": ["ClientApprovalSettings"], + "summary": "Disable Approval System", + "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.DisableApprovalRequest" + } + } + ], + "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" + } + } + } + } + }, + "/client-approval-settings/enable": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for enabling approval system with smooth transition", + "tags": ["ClientApprovalSettings"], + "summary": "Enable Approval System", + "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.EnableApprovalRequest" + } + } + ], + "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" + } + } + } + } + }, + "/client-approval-settings/exempt-categories/{action}/{category_id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for adding/removing categories from approval exemption", + "tags": ["ClientApprovalSettings"], + "summary": "Manage Exempt Categories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Action: add or remove", + "name": "action", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Category ID", + "name": "category_id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/client-approval-settings/exempt-roles/{action}/{role_id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for adding/removing roles from approval exemption", + "tags": ["ClientApprovalSettings"], + "summary": "Manage Exempt Roles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Action: add or remove", + "name": "action", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Role ID", + "name": "role_id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/client-approval-settings/exempt-users/{action}/{user_id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for adding/removing users from approval exemption", + "tags": ["ClientApprovalSettings"], + "summary": "Manage Exempt Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Action: add or remove", + "name": "action", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "user_id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/client-approval-settings/toggle": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for toggling approval requirement on/off", + "tags": ["ClientApprovalSettings"], + "summary": "Toggle Approval Requirement", + "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.ToggleApprovalRequest" + } + } + ], + "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" + } + } + } + } + }, + "/clients": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Clients", + "tags": ["Clients"], + "summary": "Get all Clients", + "parameters": [ + { + "type": "integer", + "name": "createdBy", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Clients", + "tags": ["Clients"], + "summary": "Create Clients", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ClientsCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/clients/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Clients", + "tags": ["Clients"], + "summary": "Get one Clients", + "parameters": [ + { + "type": "integer", + "description": "Clients ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Clients", + "tags": ["Clients"], + "summary": "update Clients", + "parameters": [ + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ClientsUpdateRequest" + } + }, + { + "type": "string", + "description": "Clients ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Clients", + "tags": ["Clients"], + "summary": "delete Clients", + "parameters": [ + { + "type": "string", + "description": "Clients ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/custom-static-pages": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all CustomStaticPages", + "tags": ["CustomStaticPages"], + "summary": "Get all CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "htmlBody", + "in": "query" + }, + { + "type": "string", + "name": "slug", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create CustomStaticPages", + "tags": ["CustomStaticPages"], + "summary": "Create CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CustomStaticPagesCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/custom-static-pages/slug/{slug}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one CustomStaticPages", + "tags": ["CustomStaticPages"], + "summary": "Get one CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "CustomStaticPages Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/custom-static-pages/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one CustomStaticPages", + "tags": ["CustomStaticPages"], + "summary": "Get one CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "CustomStaticPages ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update CustomStaticPages", + "tags": ["CustomStaticPages"], + "summary": "update CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CustomStaticPagesUpdateRequest" + } + }, + { + "type": "integer", + "description": "CustomStaticPages ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete CustomStaticPages", + "tags": ["CustomStaticPages"], + "summary": "delete CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "CustomStaticPages ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/districts": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Districts", + "tags": ["Untags"], + "summary": "Get all Districts", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Districts", + "tags": ["Untags"], + "summary": "Create Districts", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/districts/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Districts", + "tags": ["Untags"], + "summary": "Get one Districts", + "parameters": [ + { + "type": "integer", + "description": "Districts ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Districts", + "tags": ["Untags"], + "summary": "Update Districts", + "parameters": [ + { + "type": "integer", + "description": "Districts ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Districts", + "tags": ["Untags"], + "summary": "Delete Districts", + "parameters": [ + { + "type": "integer", + "description": "Districts ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/feedbacks": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Feedbacks", + "tags": ["Feedbacks"], + "summary": "Get all Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "name": "commentFromEmail", + "in": "query" + }, + { + "type": "string", + "name": "commentFromName", + "in": "query" + }, + { + "type": "string", + "name": "endDate", + "in": "query" + }, + { + "type": "string", + "name": "message", + "in": "query" + }, + { + "type": "string", + "name": "startDate", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Feedbacks", + "tags": ["Feedbacks"], + "summary": "Create Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.FeedbacksCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/feedbacks/statistic/monthly": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for FeedbackMonthlyStats of Feedbacks", + "tags": ["Feedbacks"], + "summary": "FeedbackMonthlyStats Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "year", + "name": "year", + "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" + } + } + } + } + }, + "/feedbacks/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Feedbacks", + "tags": ["Feedbacks"], + "summary": "Get one Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "Feedbacks ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Feedbacks", + "tags": ["Feedbacks"], + "summary": "update Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.FeedbacksUpdateRequest" + } + }, + { + "type": "integer", + "description": "Feedbacks ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Feedbacks", + "tags": ["Feedbacks"], + "summary": "delete Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Feedbacks ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/magazine-files": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all MagazineFiles", + "tags": ["Magazine Files"], + "summary": "Get all MagazineFiles", + "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" + } + } + } + } + }, + "/magazine-files/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MagazineFiles", + "tags": ["Magazine Files"], + "summary": "Create MagazineFiles", + "parameters": [ + { + "type": "string", + "description": "Magazine File Name", + "name": "filename", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/magazine-files/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one MagazineFiles", + "tags": ["Magazine Files"], + "summary": "Get one MagazineFiles", + "parameters": [ + { + "type": "integer", + "description": "MagazineFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update MagazineFiles", + "tags": ["Magazine Files"], + "summary": "Update MagazineFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "MagazineFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete MagazineFiles", + "tags": ["Magazine Files"], + "summary": "delete MagazineFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "MagazineFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/magazine-files/{magazineId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MagazineFiles", + "tags": ["Magazine Files"], + "summary": "Create MagazineFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "file", + "description": "Upload file", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Magazine file title", + "name": "title", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Magazine file description", + "name": "description", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Magazine ID", + "name": "magazineId", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/magazines": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Magazines", + "tags": ["Magazines"], + "summary": "Get all Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "name": "createdById", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "string", + "name": "pageUrl", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "thumbnailPath", + "in": "query" + }, + { + "type": "string", + "name": "thumbnailUrl", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Magazines", + "tags": ["Magazines"], + "summary": "Create Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MagazinesCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/magazines/thumbnail/viewer/{thumbnailName}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for View Thumbnail of Magazines", + "tags": ["Magazines"], + "summary": "Viewer Magazines Thumbnail", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Magazines Thumbnail Name", + "name": "thumbnailName", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/magazines/thumbnail/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Save Thumbnail of Magazines", + "produces": ["application/json"], + "tags": ["Magazines"], + "summary": "Save Thumbnail Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Magazine ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "file", + "description": "Upload thumbnail", + "name": "files", + "in": "formData", + "required": true + } + ], + "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" + } + } + } + } + }, + "/magazines/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Magazines", + "tags": ["Magazines"], + "summary": "Get one Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "Magazines ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Magazines", + "tags": ["Magazines"], + "summary": "Update Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Magazines ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MagazinesUpdateRequest" + } + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Magazines", + "tags": ["Magazines"], + "summary": "Delete Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Magazines ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/master-menus": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all MasterMenus", + "tags": ["MasterMenus"], + "summary": "Get all MasterMenus", + "parameters": [ + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "integer", + "name": "moduleId", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "parentMenuId", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MasterMenus", + "tags": ["MasterMenus"], + "summary": "Create MasterMenus", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MasterMenusCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/master-menus/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one MasterMenus", + "tags": ["MasterMenus"], + "summary": "Get one MasterMenus", + "parameters": [ + { + "type": "integer", + "description": "MasterMenus ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update MasterMenus", + "tags": ["MasterMenus"], + "summary": "Update MasterMenus", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "MasterMenus ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete MasterMenus", + "tags": ["MasterMenus"], + "summary": "Delete MasterMenus", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "MasterMenus ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/master-modules": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all MasterModules", + "tags": ["MasterModules"], + "summary": "Get all MasterModules", + "parameters": [ + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MasterModules", + "tags": ["MasterModules"], + "summary": "Create MasterModules", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MasterModulesCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/master-modules/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one MasterModules", + "tags": ["MasterModules"], + "summary": "Get one MasterModules", + "parameters": [ + { + "type": "integer", + "description": "MasterModules ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update MasterModules", + "tags": ["MasterModules"], + "summary": "Update MasterModules", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "MasterModules ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MasterModulesUpdateRequest" + } + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete MasterModules", + "tags": ["MasterModules"], + "summary": "Delete MasterModules", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "MasterModules ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/master-statuses": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all MasterStatuses", + "tags": ["Untags"], + "summary": "Get all MasterStatuses", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MasterStatuses", + "tags": ["Untags"], + "summary": "Create MasterStatuses", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/master-statuses/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one MasterStatuses", + "tags": ["Untags"], + "summary": "Get one MasterStatuses", + "parameters": [ + { + "type": "integer", + "description": "MasterStatuses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update MasterStatuses", + "tags": ["Untags"], + "summary": "Update MasterStatuses", + "parameters": [ + { + "type": "integer", + "description": "MasterStatuses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete MasterStatuses", + "tags": ["Untags"], + "summary": "Delete MasterStatuses", + "parameters": [ + { + "type": "integer", + "description": "MasterStatuses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/provinces": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Provinces", + "tags": ["Untags"], + "summary": "Get all Provinces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Provinces", + "tags": ["Untags"], + "summary": "Create Provinces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/provinces/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Provinces", + "tags": ["Untags"], + "summary": "Get one Provinces", + "parameters": [ + { + "type": "integer", + "description": "Provinces ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Provinces", + "tags": ["Untags"], + "summary": "Update Provinces", + "parameters": [ + { + "type": "integer", + "description": "Provinces ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Provinces", + "tags": ["Untags"], + "summary": "Delete Provinces", + "parameters": [ + { + "type": "integer", + "description": "Provinces ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/schedules": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Schedules", + "tags": ["Schedules"], + "summary": "Get all Schedules", + "parameters": [ + { + "type": "string", + "description": "Insert the X-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": "integer", + "name": "createdById", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "endDate", + "in": "query" + }, + { + "type": "boolean", + "name": "isLiveStreaming", + "in": "query" + }, + { + "type": "string", + "name": "location", + "in": "query" + }, + { + "type": "string", + "name": "speakers", + "in": "query" + }, + { + "type": "string", + "name": "startDate", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "typeId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Schedule", + "tags": ["Schedules"], + "summary": "Create Schedule", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SchedulesCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/schedules/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Schedule", + "tags": ["Schedules"], + "summary": "Get one Schedule", + "parameters": [ + { + "type": "integer", + "description": "Schedule ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Schedule", + "tags": ["Schedules"], + "summary": "Update Schedule", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SchedulesUpdateRequest" + } + }, + { + "type": "integer", + "description": "Schedule ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Schedule", + "tags": ["Schedules"], + "summary": "Delete Schedule", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Schedule ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/subscription": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Subscription", + "tags": ["Subscription"], + "summary": "Get all Subscription", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "name": "email", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Subscription", + "tags": ["Subscription"], + "summary": "Create Subscription", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SubscriptionCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/subscription/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Subscription", + "tags": ["Subscription"], + "summary": "Get one Subscription", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "Subscription ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Subscription", + "tags": ["Subscription"], + "summary": "update Subscription", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SubscriptionUpdateRequest" + } + }, + { + "type": "integer", + "description": "Subscription ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Subscription", + "tags": ["Subscription"], + "summary": "delete Subscription", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Subscription ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/user-levels": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all UserLevels", + "tags": ["UserLevels"], + "summary": "Get all UserLevels", + "parameters": [ + { + "type": "string", + "description": "Client Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "levelNumber", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "parentLevelId", + "in": "query" + }, + { + "type": "integer", + "name": "provinceId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create UserLevels", + "tags": ["UserLevels"], + "summary": "Create UserLevels", + "parameters": [ + { + "type": "string", + "description": "Client Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLevelsCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/user-levels/alias/{alias}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserLevels", + "tags": ["UserLevels"], + "summary": "Get one UserLevels", + "parameters": [ + { + "type": "string", + "description": "Client Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "UserLevels Alias", + "name": "alias", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/user-levels/enable-approval": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Enable Approval of Article", + "tags": ["UserLevels"], + "summary": "EnableApproval Articles", + "parameters": [ + { + "type": "string", + "description": "Client Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLevelsApprovalRequest" + } + } + ], + "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" + } + } + } + } + }, + "/user-levels/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserLevels", + "tags": ["UserLevels"], + "summary": "Get one UserLevels", + "parameters": [ + { + "type": "string", + "description": "Client Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "UserLevels ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update UserLevels", + "tags": ["UserLevels"], + "summary": "update UserLevels", + "parameters": [ + { + "type": "string", + "description": "Client Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLevelsUpdateRequest" + } + }, + { + "type": "integer", + "description": "UserLevels ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete UserLevels", + "tags": ["UserLevels"], + "summary": "delete UserLevels", + "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", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "UserLevels ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/user-role-accesses": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all UserRoleAccesses", + "tags": ["UserRoleAccesses"], + "summary": "Get all UserRoleAccesses", + "parameters": [ + { + "type": "boolean", + "name": "isActive", + "in": "query", + "required": true + }, + { + "type": "integer", + "name": "menuId", + "in": "query", + "required": true + }, + { + "type": "integer", + "name": "userRoleId", + "in": "query", + "required": true + } + ], + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create UserRoleAccesses", + "tags": ["UserRoleAccesses"], + "summary": "Create UserRoleAccesses", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserRoleAccessesCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/user-role-accesses/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserRoleAccesses", + "tags": ["UserRoleAccesses"], + "summary": "Get one UserRoleAccesses", + "parameters": [ + { + "type": "integer", + "description": "UserRoleAccesses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update UserRoleAccesses", + "tags": ["UserRoleAccesses"], + "summary": "update UserRoleAccesses", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserRoleAccessesUpdateRequest" + } + }, + { + "type": "integer", + "description": "UserRoleAccesses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete UserRoleAccesses", + "tags": ["UserRoleAccesses"], + "summary": "delete UserRoleAccesses", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "UserRoleAccesses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/user-role-level-details": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all UserRoleLevelDetails", + "tags": ["Task"], + "summary": "Get all UserRoleLevelDetails", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create UserRoleLevelDetails", + "tags": ["Task"], + "summary": "Create UserRoleLevelDetails", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/user-role-level-details/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserRoleLevelDetails", + "tags": ["Task"], + "summary": "Get one UserRoleLevelDetails", + "parameters": [ + { + "type": "integer", + "description": "UserRoleLevelDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update UserRoleLevelDetails", + "tags": ["Task"], + "summary": "update UserRoleLevelDetails", + "parameters": [ + { + "type": "integer", + "description": "UserRoleLevelDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete UserRoleLevelDetails", + "tags": ["Task"], + "summary": "delete UserRoleLevelDetails", + "parameters": [ + { + "type": "integer", + "description": "UserRoleLevelDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/user-roles": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all UserRoles", + "tags": ["UserRoles"], + "summary": "Get all UserRoles", + "parameters": [ + { + "type": "string", + "name": "code", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "userLevelId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create UserRoles", + "tags": ["UserRoles"], + "summary": "Create UserRoles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserRolesCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/user-roles/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserRoles", + "tags": ["UserRoles"], + "summary": "Get one UserRoles", + "parameters": [ + { + "type": "integer", + "description": "UserRoles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update UserRoles", + "tags": ["UserRoles"], + "summary": "update UserRoles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserRolesUpdateRequest" + } + }, + { + "type": "integer", + "description": "UserRoles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete UserRoles", + "tags": ["UserRoles"], + "summary": "delete UserRoles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "UserRoles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/users": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Users", + "tags": ["Users"], + "summary": "Get all Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "name": "email", + "in": "query" + }, + { + "type": "string", + "name": "fullname", + "in": "query" + }, + { + "type": "string", + "name": "genderType", + "in": "query" + }, + { + "type": "string", + "name": "identityGroup", + "in": "query" + }, + { + "type": "string", + "name": "identityGroupNumber", + "in": "query" + }, + { + "type": "string", + "name": "identityNumber", + "in": "query" + }, + { + "type": "string", + "name": "identityType", + "in": "query" + }, + { + "type": "string", + "name": "phoneNumber", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "userRoleId", + "in": "query" + }, + { + "type": "string", + "name": "username", + "in": "query" + }, + { + "type": "string", + "name": "workType", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "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" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Users", + "tags": ["Users"], + "summary": "Create Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UsersCreateRequest" + } + } + ], + "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" + } + } + } + } + }, + "/users/detail/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Users", + "tags": ["Users"], + "summary": "Get one Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "integer", + "description": "Users ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/users/email-validation": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Email Validation Users", + "tags": ["Users"], + "summary": "EmailValidation Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserEmailValidationRequest" + } + } + ], + "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" + } + } + } + } + }, + "/users/forgot-password": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ForgotPassword Users", + "tags": ["Users"], + "summary": "ForgotPassword Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserForgotPassword" + } + } + ], + "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" + } + } + } + } + }, + "/users/info": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ShowUserInfo", + "tags": ["Users"], + "summary": "ShowInfo Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + } + ], + "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" + } + } + } + } + }, + "/users/login": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Login Users", + "tags": ["Users"], + "summary": "Login Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLogin" + } + } + ], + "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" + } + } + } + } + }, + "/users/otp-request": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for OtpRequest Users", + "tags": ["Users"], + "summary": "OtpRequest Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserOtpRequest" + } + } + ], + "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" + } + } + } + } + }, + "/users/otp-validation": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for OtpValidation Users", + "tags": ["Users"], + "summary": "OtpValidation Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserOtpValidation" + } + } + ], + "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" + } + } + } + } + }, + "/users/pareto-login": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ParetoLogin Users", + "tags": ["Users"], + "summary": "ParetoLogin Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLogin" + } + } + ], + "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" + } + } + } + } + }, + "/users/reset-password": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ResetPassword Users", + "tags": ["Users"], + "summary": "ResetPassword Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserResetPassword" + } + } + ], + "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" + } + } + } + } + }, + "/users/save-password": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for SavePassword Users", + "tags": ["Users"], + "summary": "SavePassword Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserSavePassword" + } + } + ], + "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" + } + } + } + } + }, + "/users/setup-email": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Setup Email Users", + "tags": ["Users"], + "summary": "SetupEmail Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserEmailValidationRequest" + } + } + ], + "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" + } + } + } + } + }, + "/users/username/{username}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Users", + "tags": ["Users"], + "summary": "Get one Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Username", + "name": "username", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + }, + "/users/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Users", + "tags": ["Users"], + "summary": "update Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Users ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UsersUpdateRequest" + } + } + ], + "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": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Users", + "tags": ["Users"], + "summary": "delete Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Users ID", + "name": "id", + "in": "path", + "required": true + } + ], + "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" + } + } + } + } + } + }, + "definitions": { + "paginator.Pagination": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "nextPage": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "previousPage": { + "type": "integer" + }, + "sort": { + "type": "string" + }, + "sortBy": { + "type": "string" + }, + "totalPage": { + "type": "integer" + } + } + }, + "request.ActivityLogsCreateRequest": { + "type": "object", + "required": ["activityTypeId", "url"], + "properties": { + "activityTypeId": { + "type": "integer" + }, + "articleId": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "userId": { + "type": "integer" + }, + "visitorIp": { + "type": "string" + } + } + }, + "request.ActivityLogsUpdateRequest": { + "type": "object", + "required": ["activityTypeId", "id", "url"], + "properties": { + "activityTypeId": { + "type": "integer" + }, + "articleId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "userId": { + "type": "integer" + } + } + }, + "request.AdvertisementCreateRequest": { + "type": "object", + "required": ["description", "placement", "redirectLink", "title"], + "properties": { + "description": { + "type": "string" + }, + "placement": { + "type": "string" + }, + "redirectLink": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.AdvertisementUpdateRequest": { + "type": "object", + "required": ["description", "id", "placement", "redirectLink", "title"], + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "placement": { + "type": "string" + }, + "redirectLink": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.ApprovalActionRequest": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "request.ApprovalWorkflowStepRequest": { + "type": "object", + "required": ["requiredUserLevelId", "stepName", "stepOrder"], + "properties": { + "autoApproveAfterHours": { + "type": "integer" + }, + "canSkip": { + "type": "boolean" + }, + "isActive": { + "type": "boolean" + }, + "requiredUserLevelId": { + "type": "integer" + }, + "stepName": { + "type": "string" + }, + "stepOrder": { + "type": "integer" + } + } + }, + "request.ApprovalWorkflowsCreateRequest": { + "type": "object", + "required": ["description", "name"], + "properties": { + "autoPublish": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isDefault": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "requiresApproval": { + "type": "boolean" + }, + "steps": { + "type": "array", + "items": { + "$ref": "#/definitions/request.ApprovalWorkflowStepRequest" + } + } + } + }, + "request.ApprovalWorkflowsUpdateRequest": { + "type": "object", + "required": ["description", "name"], + "properties": { + "autoPublish": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isDefault": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "requiresApproval": { + "type": "boolean" + } + } + }, + "request.ApprovalWorkflowsWithStepsCreateRequest": { + "type": "object", + "required": ["description", "name", "steps"], + "properties": { + "autoPublish": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isDefault": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "requiresApproval": { + "type": "boolean" + }, + "steps": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/request.ApprovalWorkflowStepRequest" + } + } + } + }, + "request.ApprovalWorkflowsWithStepsUpdateRequest": { + "type": "object", + "required": ["description", "name", "steps"], + "properties": { + "autoPublish": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isDefault": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "requiresApproval": { + "type": "boolean" + }, + "steps": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/request.ApprovalWorkflowStepRequest" + } + } + } + }, + "request.ArticleApprovalStepLogsCreateRequest": { + "type": "object", + "required": [ + "approvalStatusId", + "articleApprovalFlowId", + "workflowStepId" + ], + "properties": { + "approvalStatusId": { + "type": "integer" + }, + "approvedAt": { + "type": "string" + }, + "approverUserId": { + "type": "integer" + }, + "articleApprovalFlowId": { + "type": "integer" + }, + "comments": { + "type": "string", + "maxLength": 1000 + }, + "dueDate": { + "type": "string" + }, + "isAutoApproved": { + "type": "boolean" + }, + "workflowStepId": { + "type": "integer" + } + } + }, + "request.ArticleApprovalStepLogsUpdateRequest": { + "type": "object", + "properties": { + "approvalStatusId": { + "type": "integer" + }, + "approvedAt": { + "type": "string" + }, + "approverUserId": { + "type": "integer" + }, + "comments": { + "type": "string", + "maxLength": 1000 + }, + "dueDate": { + "type": "string" + }, + "isAutoApproved": { + "type": "boolean" + } + } + }, + "request.ArticleApprovalsCreateRequest": { + "type": "object", + "required": ["articleId", "message", "statusId"], + "properties": { + "articleId": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.ArticleApprovalsUpdateRequest": { + "type": "object", + "required": ["articleId", "id", "message", "statusId"], + "properties": { + "articleId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.ArticleCategoriesCreateRequest": { + "type": "object", + "required": ["description", "statusId", "title"], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "oldCategoryId": { + "type": "integer" + }, + "parentId": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.ArticleCategoriesUpdateRequest": { + "type": "object", + "required": ["description", "id", "statusId", "title"], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isPublish": { + "type": "boolean" + }, + "parentId": { + "type": "integer" + }, + "publishedAt": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.ArticleCommentsApprovalRequest": { + "type": "object", + "required": ["id", "statusId"], + "properties": { + "id": { + "type": "integer" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.ArticleCommentsCreateRequest": { + "type": "object", + "required": ["articleId", "message"], + "properties": { + "articleId": { + "type": "integer" + }, + "isPublic": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "parentId": { + "type": "integer" + } + } + }, + "request.ArticleCommentsUpdateRequest": { + "type": "object", + "required": ["articleId", "id", "message"], + "properties": { + "articleId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "isPublic": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "parentId": { + "type": "integer" + } + } + }, + "request.ArticleFilesUpdateRequest": { + "type": "object", + "required": ["articleId", "id", "isPublish", "publishedAt", "statusId"], + "properties": { + "articleId": { + "type": "integer" + }, + "fileAlt": { + "type": "string" + }, + "fileName": { + "type": "string" + }, + "filePath": { + "type": "string" + }, + "fileThumbnail": { + "type": "string" + }, + "fileUrl": { + "type": "string" + }, + "heightPixel": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isPublish": { + "type": "boolean" + }, + "publishedAt": { + "type": "string" + }, + "size": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "widthPixel": { + "type": "string" + } + } + }, + "request.ArticleNulisAICreateRequest": { + "type": "object", + "required": [ + "articleId", + "categoryId", + "creatorId", + "description", + "htmlDescription", + "nulisAiId", + "tags", + "title" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "categoryId": { + "type": "integer" + }, + "creatorId": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "htmlDescription": { + "type": "string" + }, + "nulisAiId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.ArticleNulisAIUpdateRequest": { + "type": "object", + "required": [ + "articleId", + "categoryId", + "creatorId", + "description", + "htmlDescription", + "id", + "nulisAiId", + "tags", + "title" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "categoryId": { + "type": "integer" + }, + "creatorId": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "htmlDescription": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "nulisAiId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "request.ArticlesCreateRequest": { + "type": "object", + "required": [ + "categoryIds", + "description", + "htmlDescription", + "slug", + "tags", + "title", + "typeId" + ], + "properties": { + "aiArticleId": { + "type": "integer" + }, + "categoryIds": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdById": { + "type": "integer" + }, + "customCreatorName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "htmlDescription": { + "type": "string" + }, + "isDraft": { + "type": "boolean" + }, + "isPublish": { + "type": "boolean" + }, + "oldId": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "source": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + }, + "typeId": { + "type": "integer" + } + } + }, + "request.ArticlesUpdateRequest": { + "type": "object", + "required": [ + "categoryIds", + "description", + "htmlDescription", + "slug", + "tags", + "title", + "typeId" + ], + "properties": { + "aiArticleId": { + "type": "integer" + }, + "categoryIds": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdById": { + "type": "integer" + }, + "customCreatorName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "htmlDescription": { + "type": "string" + }, + "isDraft": { + "type": "boolean" + }, + "isPublish": { + "type": "boolean" + }, + "slug": { + "type": "string" + }, + "source": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + }, + "typeId": { + "type": "integer" + } + } + }, + "request.BookmarksCreateRequest": { + "type": "object", + "required": ["articleId"], + "properties": { + "articleId": { + "type": "integer" + } + } + }, + "request.BulkCreateApprovalWorkflowStepsRequest": { + "type": "object", + "required": ["steps", "workflowId"], + "properties": { + "steps": { + "type": "array", + "maxItems": 20, + "minItems": 1, + "items": { + "$ref": "#/definitions/request.CreateApprovalWorkflowStepsRequest" + } + }, + "workflowId": { + "type": "integer" + } + } + }, + "request.BulkProcessApprovalRequest": { + "type": "object", + "required": [ + "approvalStatusId", + "logIds", + "statusId", + "stepLogIds", + "userId" + ], + "properties": { + "approvalStatusId": { + "type": "integer" + }, + "comments": { + "type": "string", + "maxLength": 1000 + }, + "logIds": { + "type": "array", + "maxItems": 50, + "minItems": 1, + "items": { + "type": "integer" + } + }, + "statusId": { + "type": "integer" + }, + "stepLogIds": { + "type": "array", + "maxItems": 50, + "minItems": 1, + "items": { + "type": "integer" + } + }, + "userId": { + "type": "integer" + } + } + }, + "request.CitiesCreateRequest": { + "type": "object", + "required": ["city_name", "prov_id"], + "properties": { + "city_name": { + "type": "string" + }, + "prov_id": { + "type": "integer" + } + } + }, + "request.CitiesUpdateRequest": { + "type": "object", + "required": ["city_name", "id", "prov_id"], + "properties": { + "city_name": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "prov_id": { + "type": "integer" + } + } + }, + "request.ClientsCreateRequest": { + "type": "object", + "required": ["name"], + "properties": { + "createdById": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, + "request.ClientsUpdateRequest": { + "type": "object", + "required": ["name"], + "properties": { + "createdById": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, + "request.CreateApprovalWorkflowStepsRequest": { + "type": "object", + "required": ["approverRoleId", "stepName", "stepOrder", "workflowId"], + "properties": { + "approverRoleId": { + "type": "integer" + }, + "autoApprove": { + "type": "boolean" + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "isOptional": { + "type": "boolean" + }, + "requiresComment": { + "type": "boolean" + }, + "stepName": { + "type": "string", + "maxLength": 100, + "minLength": 3 + }, + "stepOrder": { + "type": "integer", + "minimum": 1 + }, + "timeoutHours": { + "type": "integer", + "maximum": 720, + "minimum": 1 + }, + "workflowId": { + "type": "integer" + } + } + }, + "request.CreateClientApprovalSettingsRequest": { + "type": "object", + "properties": { + "approvalExemptCategories": { + "type": "array", + "items": { + "type": "integer" + } + }, + "approvalExemptRoles": { + "type": "array", + "items": { + "type": "integer" + } + }, + "approvalExemptUsers": { + "type": "array", + "items": { + "type": "integer" + } + }, + "autoPublishArticles": { + "type": "boolean" + }, + "defaultWorkflowId": { + "type": "integer", + "minimum": 1 + }, + "isActive": { + "type": "boolean" + }, + "requireApprovalFor": { + "type": "array", + "items": { + "type": "string" + } + }, + "requiresApproval": { + "type": "boolean" + }, + "skipApprovalFor": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "request.CustomStaticPagesCreateRequest": { + "type": "object", + "required": ["htmlBody", "slug", "title"], + "properties": { + "description": { + "type": "string" + }, + "htmlBody": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.CustomStaticPagesUpdateRequest": { + "type": "object", + "required": ["htmlBody", "id", "slug", "title"], + "properties": { + "description": { + "type": "string" + }, + "htmlBody": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "request.DisableApprovalRequest": { + "type": "object", + "required": ["handleAction", "reason"], + "properties": { + "handleAction": { + "description": "How to handle pending articles", + "type": "string", + "enum": ["auto_approve", "keep_pending", "reset_to_draft"] + }, + "reason": { + "type": "string", + "maxLength": 500 + } + } + }, + "request.EnableApprovalRequest": { + "type": "object", + "properties": { + "defaultWorkflowId": { + "type": "integer", + "minimum": 1 + }, + "reason": { + "type": "string", + "maxLength": 500 + } + } + }, + "request.FeedbacksCreateRequest": { + "type": "object", + "required": ["commentFromEmail", "commentFromName", "message"], + "properties": { + "commentFromEmail": { + "type": "string" + }, + "commentFromName": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "request.FeedbacksUpdateRequest": { + "type": "object", + "required": ["commentFromEmail", "commentFromName", "id", "message"], + "properties": { + "commentFromEmail": { + "type": "string" + }, + "commentFromName": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "message": { + "type": "string" + } + } + }, + "request.MagazinesCreateRequest": { + "type": "object", + "required": ["description", "statusId", "title"], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "isPublish": { + "type": "boolean" + }, + "pageUrl": { + "type": "string" + }, + "publishedAt": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "thumbnailPath": { + "type": "string" + }, + "thumbnailUrl": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.MagazinesUpdateRequest": { + "type": "object", + "required": ["description", "id", "statusId", "title"], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isPublish": { + "type": "boolean" + }, + "pageUrl": { + "type": "string" + }, + "publishedAt": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "thumbnailPath": { + "type": "string" + }, + "thumbnailUrl": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.MasterMenusCreateRequest": { + "type": "object", + "required": ["description", "group", "moduleId", "name", "statusId"], + "properties": { + "description": { + "type": "string" + }, + "group": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "moduleId": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parentMenuId": { + "type": "integer" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.MasterModulesCreateRequest": { + "type": "object", + "required": ["description", "name", "pathUrl", "statusId"], + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "pathUrl": { + "type": "string" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.MasterModulesUpdateRequest": { + "type": "object", + "required": ["description", "id", "name", "pathUrl", "statusId"], + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "pathUrl": { + "type": "string" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.ProcessApprovalRequest": { + "type": "object", + "required": ["approvalStatusId", "statusId", "userId"], + "properties": { + "approvalStatusId": { + "type": "integer" + }, + "comments": { + "type": "string", + "maxLength": 1000 + }, + "statusId": { + "type": "integer" + }, + "userId": { + "type": "integer" + } + } + }, + "request.RejectionRequest": { + "type": "object", + "required": ["reason"], + "properties": { + "reason": { + "type": "string" + } + } + }, + "request.ReorderApprovalWorkflowStepsRequest": { + "type": "object", + "required": ["stepOrders"], + "properties": { + "stepOrders": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["id", "stepOrder"], + "properties": { + "id": { + "type": "integer" + }, + "stepOrder": { + "type": "integer", + "minimum": 1 + } + } + } + } + } + }, + "request.ResubmitRequest": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "request.RevisionRequest": { + "type": "object", + "required": ["message"], + "properties": { + "message": { + "type": "string" + } + } + }, + "request.SchedulesCreateRequest": { + "type": "object", + "required": ["description", "location", "speakers", "title", "typeId"], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "endDate": { + "type": "string" + }, + "endTime": { + "type": "string" + }, + "isLiveStreaming": { + "type": "boolean" + }, + "liveStreamingUrl": { + "type": "string" + }, + "location": { + "type": "string" + }, + "posterImagePath": { + "type": "string" + }, + "speakers": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "startTime": { + "type": "string" + }, + "title": { + "type": "string" + }, + "typeId": { + "type": "integer" + } + } + }, + "request.SchedulesUpdateRequest": { + "type": "object", + "required": ["description", "location", "speakers", "title", "typeId"], + "properties": { + "description": { + "type": "string" + }, + "endDate": { + "type": "string" + }, + "endTime": { + "type": "string" + }, + "isLiveStreaming": { + "type": "boolean" + }, + "liveStreamingUrl": { + "type": "string" + }, + "location": { + "type": "string" + }, + "posterImagePath": { + "type": "string" + }, + "speakers": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "startTime": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "typeId": { + "type": "integer" + } + } + }, + "request.SetDefaultWorkflowRequest": { + "type": "object", + "properties": { + "workflowId": { + "type": "integer", + "minimum": 1 + } + } + }, + "request.SubscriptionCreateRequest": { + "type": "object", + "required": ["email"], + "properties": { + "email": { + "type": "string" + } + } + }, + "request.SubscriptionUpdateRequest": { + "type": "object", + "required": ["email", "id"], + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "integer" + } + } + }, + "request.ToggleApprovalRequest": { + "type": "object", + "properties": { + "requiresApproval": { + "type": "boolean" + } + } + }, + "request.UpdateApprovalWorkflowStepsRequest": { + "type": "object", + "properties": { + "approverRoleId": { + "type": "integer" + }, + "autoApprove": { + "type": "boolean" + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "isOptional": { + "type": "boolean" + }, + "requiresComment": { + "type": "boolean" + }, + "stepName": { + "type": "string", + "maxLength": 100, + "minLength": 3 + }, + "stepOrder": { + "type": "integer", + "minimum": 1 + }, + "timeoutHours": { + "type": "integer", + "maximum": 720, + "minimum": 1 + } + } + }, + "request.UpdateClientApprovalSettingsRequest": { + "type": "object", + "properties": { + "approvalExemptCategories": { + "type": "array", + "items": { + "type": "integer" + } + }, + "approvalExemptRoles": { + "type": "array", + "items": { + "type": "integer" + } + }, + "approvalExemptUsers": { + "type": "array", + "items": { + "type": "integer" + } + }, + "autoPublishArticles": { + "type": "boolean" + }, + "defaultWorkflowId": { + "description": "double pointer to allow nil", + "type": "integer" + }, + "isActive": { + "type": "boolean" + }, + "requireApprovalFor": { + "type": "array", + "items": { + "type": "string" + } + }, + "requiresApproval": { + "type": "boolean" + }, + "skipApprovalFor": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "request.UserEmailValidationRequest": { + "type": "object", + "properties": { + "newEmail": { + "type": "string" + }, + "oldEmail": { + "type": "string" + }, + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "request.UserForgotPassword": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + } + }, + "request.UserLevelsApprovalRequest": { + "type": "object", + "required": ["ids", "isApprovalActive"], + "properties": { + "ids": { + "type": "string" + }, + "isApprovalActive": { + "type": "boolean" + } + } + }, + "request.UserLevelsCreateRequest": { + "type": "object", + "required": ["aliasName", "levelNumber", "name"], + "properties": { + "aliasName": { + "type": "string" + }, + "group": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isApprovalActive": { + "type": "boolean" + }, + "levelNumber": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parentLevelId": { + "type": "integer" + }, + "provinceId": { + "type": "integer" + } + } + }, + "request.UserLevelsUpdateRequest": { + "type": "object", + "required": ["aliasName", "levelNumber", "name"], + "properties": { + "aliasName": { + "type": "string" + }, + "group": { + "type": "string" + }, + "isApprovalActive": { + "type": "boolean" + }, + "levelNumber": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parentLevelId": { + "type": "integer" + }, + "provinceId": { + "type": "integer" + } + } + }, + "request.UserLogin": { + "type": "object", + "properties": { + "password": { + "type": "string" + }, + "refreshToken": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "request.UserOtpRequest": { + "type": "object", + "required": ["email"], + "properties": { + "email": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "request.UserOtpValidation": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "otpCode": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "request.UserResetPassword": { + "type": "object", + "properties": { + "codeRequest": { + "type": "string" + }, + "confirmPassword": { + "type": "string" + }, + "password": { + "type": "string" + }, + "userId": { + "type": "string" + } + } + }, + "request.UserRoleAccessesCreateRequest": { + "type": "object", + "required": [ + "isAdminEnabled", + "isApprovalEnabled", + "isDeleteEnabled", + "isInsertEnabled", + "isUpdateEnabled", + "isViewEnabled", + "menuId" + ], + "properties": { + "isAdminEnabled": { + "type": "boolean" + }, + "isApprovalEnabled": { + "type": "boolean" + }, + "isDeleteEnabled": { + "type": "boolean" + }, + "isInsertEnabled": { + "type": "boolean" + }, + "isUpdateEnabled": { + "type": "boolean" + }, + "isViewEnabled": { + "type": "boolean" + }, + "menuId": { + "type": "integer" + } + } + }, + "request.UserRoleAccessesUpdateRequest": { + "type": "object", + "required": [ + "id", + "is_admin_enabled", + "is_approval_enabled", + "is_delete_enabled", + "is_insert_enabled", + "is_update_enabled", + "is_view_enabled", + "menu_id", + "user_role_id" + ], + "properties": { + "id": { + "type": "integer" + }, + "is_admin_enabled": { + "type": "boolean" + }, + "is_approval_enabled": { + "type": "boolean" + }, + "is_delete_enabled": { + "type": "boolean" + }, + "is_insert_enabled": { + "type": "boolean" + }, + "is_update_enabled": { + "type": "boolean" + }, + "is_view_enabled": { + "type": "boolean" + }, + "menu_id": { + "type": "integer" + }, + "user_role_id": { + "type": "integer" + } + } + }, + "request.UserRolesCreateRequest": { + "type": "object", + "required": [ + "code", + "description", + "name", + "statusId", + "userLevelIds", + "userRoleAccess" + ], + "properties": { + "code": { + "type": "string" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "userLevelIds": { + "type": "array", + "items": { + "type": "integer" + } + }, + "userRoleAccess": { + "type": "array", + "items": { + "$ref": "#/definitions/request.UserRoleAccessesCreateRequest" + } + } + } + }, + "request.UserRolesUpdateRequest": { + "type": "object", + "required": [ + "code", + "description", + "level_number", + "name", + "status_id", + "userLevelIds" + ], + "properties": { + "code": { + "type": "string" + }, + "description": { + "type": "string" + }, + "level_number": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "status_id": { + "type": "integer" + }, + "userLevelIds": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "request.UserSavePassword": { + "type": "object", + "properties": { + "confirmPassword": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "request.UsersCreateRequest": { + "type": "object", + "required": [ + "email", + "fullname", + "password", + "userLevelId", + "userRoleId", + "username" + ], + "properties": { + "address": { + "type": "string" + }, + "dateOfBirth": { + "type": "string" + }, + "email": { + "type": "string" + }, + "fullname": { + "type": "string" + }, + "genderType": { + "type": "string" + }, + "identityGroup": { + "type": "string" + }, + "identityGroupNumber": { + "type": "string" + }, + "identityNumber": { + "type": "string" + }, + "identityType": { + "type": "string" + }, + "lastEducation": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phoneNumber": { + "type": "string" + }, + "userLevelId": { + "type": "integer" + }, + "userRoleId": { + "type": "integer" + }, + "username": { + "type": "string" + }, + "workType": { + "type": "string" + } + } + }, + "request.UsersUpdateRequest": { + "type": "object", + "required": [ + "email", + "fullname", + "userLevelId", + "userRoleId", + "username" + ], + "properties": { + "address": { + "type": "string" + }, + "dateOfBirth": { + "type": "string" + }, + "email": { + "type": "string" + }, + "fullname": { + "type": "string" + }, + "genderType": { + "type": "string" + }, + "identityGroup": { + "type": "string" + }, + "identityGroupNumber": { + "type": "string" + }, + "identityNumber": { + "type": "string" + }, + "identityType": { + "type": "string" + }, + "lastEducation": { + "type": "string" + }, + "phoneNumber": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "userLevelId": { + "type": "integer" + }, + "userRoleId": { + "type": "integer" + }, + "username": { + "type": "string" + }, + "workType": { + "type": "string" + } + } + }, + "response.BadRequestError": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 400 + }, + "message": { + "type": "string", + "example": "bad request" + }, + "success": { + "type": "boolean", + "example": false + } + } + }, + "response.InternalServerError": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 500 + }, + "message": { + "type": "string", + "example": "internal server error" + }, + "success": { + "type": "boolean", + "example": false + } + } + }, + "response.Response": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 200 + }, + "data": {}, + "messages": { + "type": "array", + "items": {} + }, + "meta": {}, + "success": { + "type": "boolean", + "example": true + } + } + }, + "response.UnauthorizedError": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 401 + }, + "message": { + "type": "string", + "example": "unauthorized access" + }, + "success": { + "type": "boolean", + "example": false + } + } + }, + "web-qudo-be_app_module_article_approval_flows_request.SubmitForApprovalRequest": { + "type": "object", + "required": ["articleId"], + "properties": { + "articleId": { + "type": "integer" + }, + "workflowId": { + "type": "integer" + } + } + }, + "web-qudo-be_app_module_articles_request.SubmitForApprovalRequest": { + "type": "object", + "properties": { + "message": { + "type": "string", + "maxLength": 500 + }, + "workflow_id": { + "type": "integer", + "minimum": 1 + } + } + } + } +} diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml new file mode 100644 index 0000000..261692d --- /dev/null +++ b/docs/swagger/swagger.yaml @@ -0,0 +1,11738 @@ +definitions: + paginator.Pagination: + properties: + count: + type: integer + limit: + type: integer + nextPage: + type: integer + page: + type: integer + previousPage: + type: integer + sort: + type: string + sortBy: + type: string + totalPage: + type: integer + type: object + request.ActivityLogsCreateRequest: + properties: + activityTypeId: + type: integer + articleId: + type: integer + url: + type: string + userId: + type: integer + visitorIp: + type: string + required: + - activityTypeId + - url + type: object + request.ActivityLogsUpdateRequest: + properties: + activityTypeId: + type: integer + articleId: + type: integer + id: + type: integer + url: + type: string + userId: + type: integer + required: + - activityTypeId + - id + - url + type: object + request.AdvertisementCreateRequest: + properties: + description: + type: string + placement: + type: string + redirectLink: + type: string + title: + type: string + required: + - description + - placement + - redirectLink + - title + type: object + request.AdvertisementUpdateRequest: + properties: + description: + type: string + id: + type: integer + placement: + type: string + redirectLink: + type: string + title: + type: string + required: + - description + - id + - placement + - redirectLink + - title + type: object + request.ApprovalActionRequest: + properties: + message: + type: string + type: object + request.ApprovalWorkflowStepRequest: + properties: + autoApproveAfterHours: + type: integer + canSkip: + type: boolean + isActive: + type: boolean + requiredUserLevelId: + type: integer + stepName: + type: string + stepOrder: + type: integer + required: + - requiredUserLevelId + - stepName + - stepOrder + type: object + request.ApprovalWorkflowsCreateRequest: + properties: + autoPublish: + type: boolean + description: + type: string + isActive: + type: boolean + isDefault: + type: boolean + name: + type: string + requiresApproval: + type: boolean + steps: + items: + $ref: "#/definitions/request.ApprovalWorkflowStepRequest" + type: array + required: + - description + - name + type: object + request.ApprovalWorkflowsUpdateRequest: + properties: + autoPublish: + type: boolean + description: + type: string + isActive: + type: boolean + isDefault: + type: boolean + name: + type: string + requiresApproval: + type: boolean + required: + - description + - name + type: object + request.ApprovalWorkflowsWithStepsCreateRequest: + properties: + autoPublish: + type: boolean + description: + type: string + isActive: + type: boolean + isDefault: + type: boolean + name: + type: string + requiresApproval: + type: boolean + steps: + items: + $ref: "#/definitions/request.ApprovalWorkflowStepRequest" + minItems: 1 + type: array + required: + - description + - name + - steps + type: object + request.ApprovalWorkflowsWithStepsUpdateRequest: + properties: + autoPublish: + type: boolean + description: + type: string + isActive: + type: boolean + isDefault: + type: boolean + name: + type: string + requiresApproval: + type: boolean + steps: + items: + $ref: "#/definitions/request.ApprovalWorkflowStepRequest" + minItems: 1 + type: array + required: + - description + - name + - steps + type: object + request.ArticleApprovalStepLogsCreateRequest: + properties: + approvalStatusId: + type: integer + approvedAt: + type: string + approverUserId: + type: integer + articleApprovalFlowId: + type: integer + comments: + maxLength: 1000 + type: string + dueDate: + type: string + isAutoApproved: + type: boolean + workflowStepId: + type: integer + required: + - approvalStatusId + - articleApprovalFlowId + - workflowStepId + type: object + request.ArticleApprovalStepLogsUpdateRequest: + properties: + approvalStatusId: + type: integer + approvedAt: + type: string + approverUserId: + type: integer + comments: + maxLength: 1000 + type: string + dueDate: + type: string + isAutoApproved: + type: boolean + type: object + request.ArticleApprovalsCreateRequest: + properties: + articleId: + type: integer + message: + type: string + statusId: + type: integer + required: + - articleId + - message + - statusId + type: object + request.ArticleApprovalsUpdateRequest: + properties: + articleId: + type: integer + id: + type: integer + message: + type: string + statusId: + type: integer + required: + - articleId + - id + - message + - statusId + type: object + request.ArticleCategoriesCreateRequest: + properties: + createdById: + type: integer + description: + type: string + oldCategoryId: + type: integer + parentId: + type: integer + slug: + type: string + statusId: + type: integer + tags: + type: string + title: + type: string + required: + - description + - statusId + - title + type: object + request.ArticleCategoriesUpdateRequest: + properties: + createdById: + type: integer + description: + type: string + id: + type: integer + isPublish: + type: boolean + parentId: + type: integer + publishedAt: + type: string + slug: + type: string + statusId: + type: integer + tags: + type: string + title: + type: string + required: + - description + - id + - statusId + - title + type: object + request.ArticleCommentsApprovalRequest: + properties: + id: + type: integer + statusId: + type: integer + required: + - id + - statusId + type: object + request.ArticleCommentsCreateRequest: + properties: + articleId: + type: integer + isPublic: + type: boolean + message: + type: string + parentId: + type: integer + required: + - articleId + - message + type: object + request.ArticleCommentsUpdateRequest: + properties: + articleId: + type: integer + id: + type: integer + isPublic: + type: boolean + message: + type: string + parentId: + type: integer + required: + - articleId + - id + - message + type: object + request.ArticleFilesUpdateRequest: + properties: + articleId: + type: integer + fileAlt: + type: string + fileName: + type: string + filePath: + type: string + fileThumbnail: + type: string + fileUrl: + type: string + heightPixel: + type: string + id: + type: integer + isPublish: + type: boolean + publishedAt: + type: string + size: + type: string + statusId: + type: integer + widthPixel: + type: string + required: + - articleId + - id + - isPublish + - publishedAt + - statusId + type: object + request.ArticleNulisAICreateRequest: + properties: + articleId: + type: integer + categoryId: + type: integer + creatorId: + type: integer + description: + type: string + htmlDescription: + type: string + nulisAiId: + type: integer + tags: + type: string + title: + type: string + required: + - articleId + - categoryId + - creatorId + - description + - htmlDescription + - nulisAiId + - tags + - title + type: object + request.ArticleNulisAIUpdateRequest: + properties: + articleId: + type: integer + categoryId: + type: integer + creatorId: + type: integer + description: + type: string + htmlDescription: + type: string + id: + type: integer + nulisAiId: + type: integer + tags: + type: string + title: + type: string + updated_at: + type: string + required: + - articleId + - categoryId + - creatorId + - description + - htmlDescription + - id + - nulisAiId + - tags + - title + type: object + request.ArticlesCreateRequest: + properties: + aiArticleId: + type: integer + categoryIds: + type: string + createdAt: + type: string + createdById: + type: integer + customCreatorName: + type: string + description: + type: string + htmlDescription: + type: string + isDraft: + type: boolean + isPublish: + type: boolean + oldId: + type: integer + slug: + type: string + source: + type: string + tags: + type: string + title: + type: string + typeId: + type: integer + required: + - categoryIds + - description + - htmlDescription + - slug + - tags + - title + - typeId + type: object + request.ArticlesUpdateRequest: + properties: + aiArticleId: + type: integer + categoryIds: + type: string + createdAt: + type: string + createdById: + type: integer + customCreatorName: + type: string + description: + type: string + htmlDescription: + type: string + isDraft: + type: boolean + isPublish: + type: boolean + slug: + type: string + source: + type: string + statusId: + type: integer + tags: + type: string + title: + type: string + typeId: + type: integer + required: + - categoryIds + - description + - htmlDescription + - slug + - tags + - title + - typeId + type: object + request.BookmarksCreateRequest: + properties: + articleId: + type: integer + required: + - articleId + type: object + request.BulkCreateApprovalWorkflowStepsRequest: + properties: + steps: + items: + $ref: "#/definitions/request.CreateApprovalWorkflowStepsRequest" + maxItems: 20 + minItems: 1 + type: array + workflowId: + type: integer + required: + - steps + - workflowId + type: object + request.BulkProcessApprovalRequest: + properties: + approvalStatusId: + type: integer + comments: + maxLength: 1000 + type: string + logIds: + items: + type: integer + maxItems: 50 + minItems: 1 + type: array + statusId: + type: integer + stepLogIds: + items: + type: integer + maxItems: 50 + minItems: 1 + type: array + userId: + type: integer + required: + - approvalStatusId + - logIds + - statusId + - stepLogIds + - userId + type: object + request.CitiesCreateRequest: + properties: + city_name: + type: string + prov_id: + type: integer + required: + - city_name + - prov_id + type: object + request.CitiesUpdateRequest: + properties: + city_name: + type: string + id: + type: integer + prov_id: + type: integer + required: + - city_name + - id + - prov_id + type: object + request.ClientsCreateRequest: + properties: + createdById: + type: integer + name: + type: string + required: + - name + type: object + request.ClientsUpdateRequest: + properties: + createdById: + type: integer + name: + type: string + required: + - name + type: object + request.CreateApprovalWorkflowStepsRequest: + properties: + approverRoleId: + type: integer + autoApprove: + type: boolean + description: + maxLength: 500 + type: string + isOptional: + type: boolean + requiresComment: + type: boolean + stepName: + maxLength: 100 + minLength: 3 + type: string + stepOrder: + minimum: 1 + type: integer + timeoutHours: + maximum: 720 + minimum: 1 + type: integer + workflowId: + type: integer + required: + - approverRoleId + - stepName + - stepOrder + - workflowId + type: object + request.CreateClientApprovalSettingsRequest: + properties: + approvalExemptCategories: + items: + type: integer + type: array + approvalExemptRoles: + items: + type: integer + type: array + approvalExemptUsers: + items: + type: integer + type: array + autoPublishArticles: + type: boolean + defaultWorkflowId: + minimum: 1 + type: integer + isActive: + type: boolean + requireApprovalFor: + items: + type: string + type: array + requiresApproval: + type: boolean + skipApprovalFor: + items: + type: string + type: array + type: object + request.CustomStaticPagesCreateRequest: + properties: + description: + type: string + htmlBody: + type: string + slug: + type: string + title: + type: string + required: + - htmlBody + - slug + - title + type: object + request.CustomStaticPagesUpdateRequest: + properties: + description: + type: string + htmlBody: + type: string + id: + type: integer + slug: + type: string + title: + type: string + updated_at: + type: string + required: + - htmlBody + - id + - slug + - title + type: object + request.DisableApprovalRequest: + properties: + handleAction: + description: How to handle pending articles + enum: + - auto_approve + - keep_pending + - reset_to_draft + type: string + reason: + maxLength: 500 + type: string + required: + - handleAction + - reason + type: object + request.EnableApprovalRequest: + properties: + defaultWorkflowId: + minimum: 1 + type: integer + reason: + maxLength: 500 + type: string + type: object + request.FeedbacksCreateRequest: + properties: + commentFromEmail: + type: string + commentFromName: + type: string + message: + type: string + required: + - commentFromEmail + - commentFromName + - message + type: object + request.FeedbacksUpdateRequest: + properties: + commentFromEmail: + type: string + commentFromName: + type: string + id: + type: integer + message: + type: string + required: + - commentFromEmail + - commentFromName + - id + - message + type: object + request.MagazinesCreateRequest: + properties: + createdById: + type: integer + description: + type: string + isPublish: + type: boolean + pageUrl: + type: string + publishedAt: + type: string + statusId: + type: integer + thumbnailPath: + type: string + thumbnailUrl: + type: string + title: + type: string + required: + - description + - statusId + - title + type: object + request.MagazinesUpdateRequest: + properties: + createdById: + type: integer + description: + type: string + id: + type: integer + isPublish: + type: boolean + pageUrl: + type: string + publishedAt: + type: string + statusId: + type: integer + thumbnailPath: + type: string + thumbnailUrl: + type: string + title: + type: string + required: + - description + - id + - statusId + - title + type: object + request.MasterMenusCreateRequest: + properties: + description: + type: string + group: + type: string + icon: + type: string + moduleId: + type: integer + name: + type: string + parentMenuId: + type: integer + statusId: + type: integer + required: + - description + - group + - moduleId + - name + - statusId + type: object + request.MasterModulesCreateRequest: + properties: + description: + type: string + name: + type: string + pathUrl: + type: string + statusId: + type: integer + required: + - description + - name + - pathUrl + - statusId + type: object + request.MasterModulesUpdateRequest: + properties: + description: + type: string + id: + type: integer + name: + type: string + pathUrl: + type: string + statusId: + type: integer + required: + - description + - id + - name + - pathUrl + - statusId + type: object + request.ProcessApprovalRequest: + properties: + approvalStatusId: + type: integer + comments: + maxLength: 1000 + type: string + statusId: + type: integer + userId: + type: integer + required: + - approvalStatusId + - statusId + - userId + type: object + request.RejectionRequest: + properties: + reason: + type: string + required: + - reason + type: object + request.ReorderApprovalWorkflowStepsRequest: + properties: + stepOrders: + items: + properties: + id: + type: integer + stepOrder: + minimum: 1 + type: integer + required: + - id + - stepOrder + type: object + minItems: 1 + type: array + required: + - stepOrders + type: object + request.ResubmitRequest: + properties: + message: + type: string + type: object + request.RevisionRequest: + properties: + message: + type: string + required: + - message + type: object + request.SchedulesCreateRequest: + properties: + createdById: + type: integer + description: + type: string + endDate: + type: string + endTime: + type: string + isLiveStreaming: + type: boolean + liveStreamingUrl: + type: string + location: + type: string + posterImagePath: + type: string + speakers: + type: string + startDate: + type: string + startTime: + type: string + title: + type: string + typeId: + type: integer + required: + - description + - location + - speakers + - title + - typeId + type: object + request.SchedulesUpdateRequest: + properties: + description: + type: string + endDate: + type: string + endTime: + type: string + isLiveStreaming: + type: boolean + liveStreamingUrl: + type: string + location: + type: string + posterImagePath: + type: string + speakers: + type: string + startDate: + type: string + startTime: + type: string + statusId: + type: integer + title: + type: string + typeId: + type: integer + required: + - description + - location + - speakers + - title + - typeId + type: object + request.SetDefaultWorkflowRequest: + properties: + workflowId: + minimum: 1 + type: integer + type: object + request.SubscriptionCreateRequest: + properties: + email: + type: string + required: + - email + type: object + request.SubscriptionUpdateRequest: + properties: + email: + type: string + id: + type: integer + required: + - email + - id + type: object + request.ToggleApprovalRequest: + properties: + requiresApproval: + type: boolean + type: object + request.UpdateApprovalWorkflowStepsRequest: + properties: + approverRoleId: + type: integer + autoApprove: + type: boolean + description: + maxLength: 500 + type: string + isOptional: + type: boolean + requiresComment: + type: boolean + stepName: + maxLength: 100 + minLength: 3 + type: string + stepOrder: + minimum: 1 + type: integer + timeoutHours: + maximum: 720 + minimum: 1 + type: integer + type: object + request.UpdateClientApprovalSettingsRequest: + properties: + approvalExemptCategories: + items: + type: integer + type: array + approvalExemptRoles: + items: + type: integer + type: array + approvalExemptUsers: + items: + type: integer + type: array + autoPublishArticles: + type: boolean + defaultWorkflowId: + description: double pointer to allow nil + type: integer + isActive: + type: boolean + requireApprovalFor: + items: + type: string + type: array + requiresApproval: + type: boolean + skipApprovalFor: + items: + type: string + type: array + type: object + request.UserEmailValidationRequest: + properties: + newEmail: + type: string + oldEmail: + type: string + password: + type: string + username: + type: string + type: object + request.UserForgotPassword: + properties: + username: + type: string + type: object + request.UserLevelsApprovalRequest: + properties: + ids: + type: string + isApprovalActive: + type: boolean + required: + - ids + - isApprovalActive + type: object + request.UserLevelsCreateRequest: + properties: + aliasName: + type: string + group: + type: string + isActive: + type: boolean + isApprovalActive: + type: boolean + levelNumber: + type: integer + name: + type: string + parentLevelId: + type: integer + provinceId: + type: integer + required: + - aliasName + - levelNumber + - name + type: object + request.UserLevelsUpdateRequest: + properties: + aliasName: + type: string + group: + type: string + isApprovalActive: + type: boolean + levelNumber: + type: integer + name: + type: string + parentLevelId: + type: integer + provinceId: + type: integer + required: + - aliasName + - levelNumber + - name + type: object + request.UserLogin: + properties: + password: + type: string + refreshToken: + type: string + username: + type: string + type: object + request.UserOtpRequest: + properties: + email: + type: string + name: + type: string + required: + - email + type: object + request.UserOtpValidation: + properties: + email: + type: string + otpCode: + type: string + username: + type: string + type: object + request.UserResetPassword: + properties: + codeRequest: + type: string + confirmPassword: + type: string + password: + type: string + userId: + type: string + type: object + request.UserRoleAccessesCreateRequest: + properties: + isAdminEnabled: + type: boolean + isApprovalEnabled: + type: boolean + isDeleteEnabled: + type: boolean + isInsertEnabled: + type: boolean + isUpdateEnabled: + type: boolean + isViewEnabled: + type: boolean + menuId: + type: integer + required: + - isAdminEnabled + - isApprovalEnabled + - isDeleteEnabled + - isInsertEnabled + - isUpdateEnabled + - isViewEnabled + - menuId + type: object + request.UserRoleAccessesUpdateRequest: + properties: + id: + type: integer + is_admin_enabled: + type: boolean + is_approval_enabled: + type: boolean + is_delete_enabled: + type: boolean + is_insert_enabled: + type: boolean + is_update_enabled: + type: boolean + is_view_enabled: + type: boolean + menu_id: + type: integer + user_role_id: + type: integer + required: + - id + - is_admin_enabled + - is_approval_enabled + - is_delete_enabled + - is_insert_enabled + - is_update_enabled + - is_view_enabled + - menu_id + - user_role_id + type: object + request.UserRolesCreateRequest: + properties: + code: + type: string + description: + type: string + name: + type: string + statusId: + type: integer + userLevelIds: + items: + type: integer + type: array + userRoleAccess: + items: + $ref: "#/definitions/request.UserRoleAccessesCreateRequest" + type: array + required: + - code + - description + - name + - statusId + - userLevelIds + - userRoleAccess + type: object + request.UserRolesUpdateRequest: + properties: + code: + type: string + description: + type: string + level_number: + type: integer + name: + type: string + status_id: + type: integer + userLevelIds: + items: + type: integer + type: array + required: + - code + - description + - level_number + - name + - status_id + - userLevelIds + type: object + request.UserSavePassword: + properties: + confirmPassword: + type: string + password: + type: string + type: object + request.UsersCreateRequest: + properties: + address: + type: string + dateOfBirth: + type: string + email: + type: string + fullname: + type: string + genderType: + type: string + identityGroup: + type: string + identityGroupNumber: + type: string + identityNumber: + type: string + identityType: + type: string + lastEducation: + type: string + password: + type: string + phoneNumber: + type: string + userLevelId: + type: integer + userRoleId: + type: integer + username: + type: string + workType: + type: string + required: + - email + - fullname + - password + - userLevelId + - userRoleId + - username + type: object + request.UsersUpdateRequest: + properties: + address: + type: string + dateOfBirth: + type: string + email: + type: string + fullname: + type: string + genderType: + type: string + identityGroup: + type: string + identityGroupNumber: + type: string + identityNumber: + type: string + identityType: + type: string + lastEducation: + type: string + phoneNumber: + type: string + statusId: + type: integer + userLevelId: + type: integer + userRoleId: + type: integer + username: + type: string + workType: + type: string + required: + - email + - fullname + - userLevelId + - userRoleId + - username + type: object + response.BadRequestError: + properties: + code: + example: 400 + type: integer + message: + example: bad request + type: string + success: + example: false + type: boolean + type: object + response.InternalServerError: + properties: + code: + example: 500 + type: integer + message: + example: internal server error + type: string + success: + example: false + type: boolean + type: object + response.Response: + properties: + code: + example: 200 + type: integer + data: {} + messages: + items: {} + type: array + meta: {} + success: + example: true + type: boolean + type: object + response.UnauthorizedError: + properties: + code: + example: 401 + type: integer + message: + example: unauthorized access + type: string + success: + example: false + type: boolean + type: object + web-qudo-be_app_module_article_approval_flows_request.SubmitForApprovalRequest: + properties: + articleId: + type: integer + workflowId: + type: integer + required: + - articleId + type: object + web-qudo-be_app_module_articles_request.SubmitForApprovalRequest: + properties: + message: + maxLength: 500 + type: string + workflow_id: + minimum: 1 + type: integer + type: object +info: + contact: {} +paths: + /activity-logs: + get: + description: API for getting all ActivityLogs + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - in: query + name: activityTypeId + type: integer + - in: query + name: articleId + type: integer + - in: query + name: url + type: string + - in: query + name: userId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all ActivityLogs + tags: + - ActivityLogs + post: + description: API for create ActivityLogs + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ActivityLogsCreateRequest" + 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 ActivityLogs + tags: + - ActivityLogs + /activity-logs/{id}: + delete: + description: API for delete ActivityLogs + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: ActivityLogs ID + in: path + name: id + required: true + 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: delete ActivityLogs + tags: + - ActivityLogs + put: + description: API for update ActivityLogs + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ActivityLogsUpdateRequest" + - description: ActivityLogs ID + in: path + name: id + required: true + 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: update ActivityLogs + tags: + - ActivityLogs + /activity-logs/detail/{id}: + get: + description: API for getting one ActivityLogs + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: ActivityLogs ID + in: path + name: id + required: true + 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 one ActivityLogs + tags: + - ActivityLogs + /activity-logs/statistics: + get: + description: API for get activity stats ActivityLogs + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + 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 activity stats ActivityLogs + tags: + - ActivityLogs + /advertisement: + get: + description: API for getting all Advertisement + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - in: query + name: description + type: string + - in: query + name: isPublish + type: boolean + - in: query + name: placement + type: string + - in: query + name: redirectLink + type: string + - in: query + name: statusId + type: integer + - in: query + name: title + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all Advertisement + tags: + - Advertisement + post: + description: API for create Advertisement + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.AdvertisementCreateRequest" + 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 Advertisement + tags: + - Advertisement + /advertisement/{id}: + delete: + description: API for delete Advertisement + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Advertisement ID + in: path + name: id + required: true + 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: delete Advertisement + tags: + - Advertisement + get: + description: API for getting one Advertisement + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Advertisement ID + in: path + name: id + required: true + 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 one Advertisement + tags: + - Advertisement + put: + description: API for update Advertisement + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.AdvertisementUpdateRequest" + - description: Advertisement ID + in: path + name: id + required: true + 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: update Advertisement + tags: + - Advertisement + /advertisement/publish/{id}: + put: + description: API for Update Publish Advertisement + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Advertisement ID + in: path + name: id + required: true + type: integer + - description: Advertisement Publish Status + in: query + name: isPublish + required: true + type: boolean + 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: Update Publish Advertisement + tags: + - Advertisement + /advertisement/upload/{id}: + post: + description: API for Upload File Advertisement + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Upload file + in: formData + name: file + required: true + type: file + - description: Advertisement ID + in: path + name: id + required: true + type: integer + produces: + - application/json + 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: Upload Advertisement + tags: + - Advertisement + /advertisement/viewer/{filename}: + get: + description: API for Viewer Advertisement + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Content File Name + in: path + name: filename + required: true + type: string + 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: Viewer Advertisement + tags: + - Advertisement + /approval-workflow-steps: + get: + description: API for getting all ApprovalWorkflowSteps + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Workflow ID filter + in: query + name: workflowId + type: integer + - description: Step order filter + in: query + name: stepOrder + type: integer + - description: Step name filter + in: query + name: stepName + type: string + - description: User level ID filter + in: query + name: userLevelId + type: integer + - description: Is optional filter + in: query + name: isOptional + type: boolean + - description: Is active filter + in: query + name: isActive + type: boolean + 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 all ApprovalWorkflowSteps + tags: + - ApprovalWorkflowSteps + post: + description: API for saving ApprovalWorkflowSteps + 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.CreateApprovalWorkflowStepsRequest" + 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: Save ApprovalWorkflowSteps + tags: + - ApprovalWorkflowSteps + /approval-workflow-steps/{id}: + delete: + description: API for deleting ApprovalWorkflowSteps + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: ApprovalWorkflowSteps ID + in: path + name: id + required: true + 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: Delete ApprovalWorkflowSteps + tags: + - ApprovalWorkflowSteps + get: + description: API for getting one ApprovalWorkflowSteps + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: ApprovalWorkflowSteps ID + in: path + name: id + required: true + 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 one ApprovalWorkflowSteps + tags: + - ApprovalWorkflowSteps + put: + description: API for updating ApprovalWorkflowSteps + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: ApprovalWorkflowSteps ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UpdateApprovalWorkflowStepsRequest" + 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: Update ApprovalWorkflowSteps + tags: + - ApprovalWorkflowSteps + /approval-workflow-steps/bulk: + post: + description: API for bulk creating ApprovalWorkflowSteps + 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.BulkCreateApprovalWorkflowStepsRequest" + 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: Bulk create ApprovalWorkflowSteps + tags: + - ApprovalWorkflowSteps + /approval-workflow-steps/role/{roleId}: + get: + description: API for getting ApprovalWorkflowSteps by Role ID + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Role ID + in: path + name: roleId + required: true + 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 ApprovalWorkflowSteps by Role ID + tags: + - ApprovalWorkflowSteps + /approval-workflow-steps/workflow/{workflowId}: + get: + description: API for getting ApprovalWorkflowSteps by Workflow ID + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Workflow ID + in: path + name: workflowId + required: true + 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 ApprovalWorkflowSteps by Workflow ID + tags: + - ApprovalWorkflowSteps + /approval-workflow-steps/workflow/{workflowId}/reorder: + put: + description: API for reordering ApprovalWorkflowSteps + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Workflow ID + in: path + name: workflowId + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ReorderApprovalWorkflowStepsRequest" + 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: Reorder ApprovalWorkflowSteps + tags: + - ApprovalWorkflowSteps + /approval-workflows: + get: + description: API for getting all ApprovalWorkflows + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - in: query + name: description + type: string + - in: query + name: isActive + type: boolean + - in: query + name: isDefault + type: boolean + - in: query + name: name + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all ApprovalWorkflows + tags: + - ApprovalWorkflows + post: + description: API for saving ApprovalWorkflows + 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.ApprovalWorkflowsCreateRequest" + 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: Save ApprovalWorkflows + tags: + - ApprovalWorkflows + /approval-workflows/{id}: + delete: + description: API for deleting ApprovalWorkflows + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: ApprovalWorkflows ID + in: path + name: id + required: true + 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: Delete ApprovalWorkflows + tags: + - ApprovalWorkflows + get: + description: API for getting one ApprovalWorkflows + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: ApprovalWorkflows ID + in: path + name: id + required: true + 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 one ApprovalWorkflows + tags: + - ApprovalWorkflows + put: + description: API for updating ApprovalWorkflows + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: ApprovalWorkflows ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ApprovalWorkflowsUpdateRequest" + 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: Update ApprovalWorkflows + tags: + - ApprovalWorkflows + /approval-workflows/{id}/activate: + put: + description: API for activating ApprovalWorkflows + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: ApprovalWorkflows ID + in: path + name: id + required: true + 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: Activate ApprovalWorkflows + tags: + - ApprovalWorkflows + /approval-workflows/{id}/deactivate: + put: + description: API for deactivating ApprovalWorkflows + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: ApprovalWorkflows ID + in: path + name: id + required: true + 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: Deactivate ApprovalWorkflows + tags: + - ApprovalWorkflows + /approval-workflows/{id}/set-default: + put: + description: API for setting default ApprovalWorkflows + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: ApprovalWorkflows ID + in: path + name: id + required: true + 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: Set default ApprovalWorkflows + tags: + - ApprovalWorkflows + /approval-workflows/{id}/with-steps: + get: + description: API for getting ApprovalWorkflows with steps + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: ApprovalWorkflows ID + in: path + name: id + required: true + 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 ApprovalWorkflows with steps + tags: + - ApprovalWorkflows + put: + description: API for updating ApprovalWorkflows with steps + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: ApprovalWorkflows ID + in: path + name: id + required: true + type: integer + - description: ApprovalWorkflows with steps data + in: body + name: req + required: true + schema: + $ref: "#/definitions/request.ApprovalWorkflowsWithStepsUpdateRequest" + 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: Update ApprovalWorkflows with steps + tags: + - ApprovalWorkflows + /approval-workflows/default: + get: + description: API for getting default ApprovalWorkflows + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + 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 default ApprovalWorkflows + tags: + - ApprovalWorkflows + /approval-workflows/with-steps: + post: + description: API for creating ApprovalWorkflows with steps + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: ApprovalWorkflows with steps data + in: body + name: req + required: true + schema: + $ref: "#/definitions/request.ApprovalWorkflowsWithStepsCreateRequest" + responses: + "201": + description: Created + 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 ApprovalWorkflows with steps + tags: + - ApprovalWorkflows + /article-approval-flows: + get: + description: API for getting all ArticleApprovalFlows + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - in: query + name: articleId + type: integer + - in: query + name: currentStep + type: integer + - in: query + name: dateFrom + type: string + - in: query + name: dateTo + type: string + - in: query + name: statusId + type: integer + - in: query + name: submittedBy + type: integer + - in: query + name: workflowId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all ArticleApprovalFlows + tags: + - ArticleApprovalFlows + /article-approval-flows/{id}: + get: + description: API for getting one ArticleApprovalFlows + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: ArticleApprovalFlows ID + in: path + name: id + required: true + 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 one ArticleApprovalFlows + tags: + - ArticleApprovalFlows + /article-approval-flows/{id}/approve: + put: + description: API for approving article + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: ArticleApprovalFlows ID + in: path + name: id + required: true + type: integer + - description: Approval action data + in: body + name: req + required: true + schema: + $ref: "#/definitions/request.ApprovalActionRequest" + 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: Approve article + tags: + - ArticleApprovalFlows + /article-approval-flows/{id}/reject: + put: + description: API for rejecting article + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: ArticleApprovalFlows ID + in: path + name: id + required: true + type: integer + - description: Rejection data + in: body + name: req + required: true + schema: + $ref: "#/definitions/request.RejectionRequest" + 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: Reject article + tags: + - ArticleApprovalFlows + /article-approval-flows/{id}/request-revision: + put: + description: API for requesting revision for article + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: ArticleApprovalFlows ID + in: path + name: id + required: true + type: integer + - description: Revision request data + in: body + name: req + required: true + schema: + $ref: "#/definitions/request.RevisionRequest" + 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: Request revision for article + tags: + - ArticleApprovalFlows + /article-approval-flows/{id}/resubmit: + put: + description: API for resubmitting article after revision + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: ArticleApprovalFlows ID + in: path + name: id + required: true + type: integer + - description: Resubmit data + in: body + name: req + required: true + schema: + $ref: "#/definitions/request.ResubmitRequest" + 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: Resubmit article after revision + tags: + - ArticleApprovalFlows + /article-approval-flows/analytics: + get: + description: API for getting approval analytics + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Period filter (daily, weekly, monthly) + in: query + name: period + type: string + - description: Start date filter (YYYY-MM-DD) + in: query + name: startDate + type: string + - description: End date filter (YYYY-MM-DD) + in: query + name: endDate + type: string + 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 approval analytics + tags: + - ArticleApprovalFlows + /article-approval-flows/dashboard-stats: + get: + description: API for getting dashboard statistics + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + 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 dashboard statistics + tags: + - ArticleApprovalFlows + /article-approval-flows/history: + get: + description: API for getting approval history + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Article ID filter + in: query + name: articleId + type: integer + - description: User ID filter + in: query + name: userId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 approval history + tags: + - ArticleApprovalFlows + /article-approval-flows/my-queue: + get: + description: API for getting my approval queue + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + 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 + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 my approval queue + tags: + - ArticleApprovalFlows + /article-approval-flows/pending: + get: + description: API for getting pending approvals + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 pending approvals + tags: + - ArticleApprovalFlows + /article-approval-flows/submit: + post: + description: API for submitting article for approval + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Submit for approval data + in: body + name: req + required: true + schema: + $ref: "#/definitions/web-qudo-be_app_module_article_approval_flows_request.SubmitForApprovalRequest" + responses: + "201": + description: Created + 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: Submit article for approval + tags: + - ArticleApprovalFlows + /article-approval-flows/workload-stats: + get: + description: API for getting workload statistics + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + 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 workload statistics + tags: + - ArticleApprovalFlows + /article-approval-step-logs: + get: + description: API for getting all ArticleApprovalStepLogs + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - in: query + name: approvalStatusId + type: integer + - in: query + name: approverUserId + type: integer + - in: query + name: articleApprovalFlowId + type: integer + - in: query + name: dateFrom + type: string + - in: query + name: dateTo + type: string + - in: query + name: isAutoApproved + type: boolean + - in: query + name: workflowStepId + 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 all ArticleApprovalStepLogs + tags: + - ArticleApprovalStepLogs + post: + description: API for saving ArticleApprovalStepLogs + 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.ArticleApprovalStepLogsCreateRequest" + 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: Save ArticleApprovalStepLogs + tags: + - ArticleApprovalStepLogs + /article-approval-step-logs/{id}: + delete: + description: API for deleting ArticleApprovalStepLogs + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: ArticleApprovalStepLogs ID + in: path + name: id + required: true + 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: Delete ArticleApprovalStepLogs + tags: + - ArticleApprovalStepLogs + get: + description: API for getting one ArticleApprovalStepLogs + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: ArticleApprovalStepLogs ID + in: path + name: id + required: true + 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 one ArticleApprovalStepLogs + tags: + - ArticleApprovalStepLogs + put: + description: API for updating ArticleApprovalStepLogs + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: ArticleApprovalStepLogs ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ArticleApprovalStepLogsUpdateRequest" + 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: Update ArticleApprovalStepLogs + tags: + - ArticleApprovalStepLogs + /article-approval-step-logs/{id}/auto-approve: + post: + description: API for automatically approving a step + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Step Log ID + in: path + name: id + required: true + type: integer + - description: Auto approval reason + in: query + name: reason + required: true + type: string + 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: Auto Approve Step + tags: + - ArticleApprovalStepLogs + /article-approval-step-logs/{id}/process: + post: + description: API for processing approval step + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Step Log ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ProcessApprovalRequest" + 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: Process Approval + tags: + - ArticleApprovalStepLogs + /article-approval-step-logs/approver/{user_id}: + get: + description: API for getting ArticleApprovalStepLogs by Approver User ID + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Approver User ID + in: path + name: user_id + required: true + 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 ArticleApprovalStepLogs by Approver User ID + tags: + - ArticleApprovalStepLogs + /article-approval-step-logs/bulk-process: + post: + description: API for bulk processing approval steps + 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.BulkProcessApprovalRequest" + 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: Bulk Process Approvals + tags: + - ArticleApprovalStepLogs + /article-approval-step-logs/flow/{flow_id}: + get: + description: API for getting ArticleApprovalStepLogs by Approval Flow ID + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Approval Flow ID + in: path + name: flow_id + required: true + 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 ArticleApprovalStepLogs by Approval Flow ID + tags: + - ArticleApprovalStepLogs + /article-approval-step-logs/history: + get: + description: API for getting approval history + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Filter by article ID + in: query + name: article_id + type: integer + - description: Filter by user ID + in: query + name: user_id + type: integer + - description: Filter from date (YYYY-MM-DD) + in: query + name: from_date + type: string + - description: Filter to date (YYYY-MM-DD) + in: query + name: to_date + type: string + 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 Approval History + tags: + - ArticleApprovalStepLogs + /article-approval-step-logs/overdue: + get: + description: API for getting overdue approvals + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Filter by user ID + in: query + name: user_id + 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 Overdue Approvals + tags: + - ArticleApprovalStepLogs + /article-approval-step-logs/pending: + get: + description: API for getting pending approvals + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Filter by user ID + in: query + name: user_id + type: integer + - description: Filter by role ID + in: query + name: role_id + 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 Pending Approvals + tags: + - ArticleApprovalStepLogs + /article-approval-step-logs/stats: + get: + description: API for getting approval statistics + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Filter from date (YYYY-MM-DD) + in: query + name: from_date + type: string + - description: Filter to date (YYYY-MM-DD) + in: query + name: to_date + type: string + - description: Filter by workflow ID + in: query + name: workflow_id + 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 Approval Statistics + tags: + - ArticleApprovalStepLogs + /article-approval-step-logs/step/{step_id}: + get: + description: API for getting ArticleApprovalStepLogs by Workflow Step ID + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Workflow Step ID + in: path + name: step_id + required: true + 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 ArticleApprovalStepLogs by Workflow Step ID + tags: + - ArticleApprovalStepLogs + /article-approval-step-logs/user/{user_id}/workload: + get: + description: API for getting user workload statistics + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: User ID + in: path + name: user_id + required: true + type: integer + - description: Include detailed statistics + in: query + name: include_stats + type: boolean + 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 User Workload + tags: + - ArticleApprovalStepLogs + /article-approvals: + get: + description: API for getting all ArticleApprovals + parameters: + - in: query + name: approvalAtLevel + type: integer + - in: query + name: approvalBy + type: integer + - in: query + name: articleId + type: integer + - in: query + name: message + type: string + - in: query + name: statusId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all ArticleApprovals + tags: + - ArticleApprovals + post: + description: API for create ArticleApprovals + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ArticleApprovalsCreateRequest" + 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 ArticleApprovals + tags: + - ArticleApprovals + /article-approvals/{id}: + delete: + description: API for delete ArticleApprovals + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: ArticleApprovals ID + in: path + name: id + required: true + 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: delete ArticleApprovals + tags: + - ArticleApprovals + get: + description: API for getting one ArticleApprovals + parameters: + - description: ArticleApprovals ID + in: path + name: id + required: true + 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 one ArticleApprovals + tags: + - ArticleApprovals + put: + description: API for update ArticleApprovals + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ArticleApprovalsUpdateRequest" + - description: ArticleApprovals ID + in: path + name: id + required: true + 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: update ArticleApprovals + tags: + - ArticleApprovals + /article-categories: + get: + description: API for getting all ArticleCategories + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - in: query + name: UserLevelId + type: integer + - in: query + name: UserLevelNumber + type: integer + - in: query + name: description + type: string + - in: query + name: isPublish + type: boolean + - in: query + name: parentId + type: integer + - in: query + name: statusId + type: integer + - in: query + name: title + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all ArticleCategories + tags: + - Article Categories + post: + description: API for create ArticleCategories + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ArticleCategoriesCreateRequest" + 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 ArticleCategories + tags: + - Article Categories + /article-categories/{id}: + delete: + description: API for delete ArticleCategories + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: ArticleCategories ID + in: path + name: id + required: true + 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: delete ArticleCategories + tags: + - Article Categories + get: + description: API for getting one ArticleCategories + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: ArticleCategories ID + in: path + name: id + required: true + 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 one ArticleCategories + tags: + - Article Categories + put: + description: API for update ArticleCategories + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ArticleCategoriesUpdateRequest" + - description: ArticleCategories ID + in: path + name: id + required: true + 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: update ArticleCategories + tags: + - Article Categories + /article-categories/old/{id}: + get: + description: API for getting one ArticleCategories + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: ArticleCategories Old ID + in: path + name: id + required: true + 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 one ArticleCategories + tags: + - Article Categories + /article-categories/slug/{slug}: + get: + description: API for getting one ArticleCategories + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: ArticleCategories Slug + in: path + name: slug + required: true + type: string + 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 one ArticleCategories + tags: + - Article Categories + /article-categories/thumbnail/{id}: + post: + description: API for Upload ArticleCategories Thumbnail + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Upload thumbnail + in: formData + name: files + required: true + type: file + - description: ArticleCategories ID + in: path + name: id + required: true + type: integer + produces: + - application/json + 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: Upload ArticleCategories Thumbnail + tags: + - Article Categories + /article-categories/thumbnail/viewer/{id}: + get: + description: API for View Thumbnail of ArticleCategories + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: ArticleCategories ID + in: path + name: id + required: true + type: string + 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: Viewer ArticleCategories + tags: + - Article Categories + /article-category-details: + get: + description: API for getting all ArticleCategoryDetails + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Get all ArticleCategoryDetails + tags: + - Untags + post: + description: API for create ArticleCategoryDetails + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Create ArticleCategoryDetails + tags: + - Untags + /article-category-details/{id}: + delete: + description: API for delete ArticleCategoryDetails + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: ArticleCategoryDetails ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: delete ArticleCategoryDetails + tags: + - Untags + get: + description: API for getting one ArticleCategoryDetails + parameters: + - description: ArticleCategoryDetails ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Get one ArticleCategoryDetails + tags: + - Untags + put: + description: API for update ArticleCategoryDetails + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: ArticleCategoryDetails ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: update ArticleCategoryDetails + tags: + - Untags + /article-comments: + get: + description: API for getting all ArticleComments + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - in: query + name: articleId + type: integer + - in: query + name: commentFrom + type: integer + - in: query + name: isPublic + type: boolean + - in: query + name: message + type: string + - in: query + name: parentId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all ArticleComments + tags: + - ArticleComments + post: + description: API for create ArticleComments + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ArticleCommentsCreateRequest" + 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 ArticleComments + tags: + - ArticleComments + /article-comments/{id}: + delete: + description: API for delete ArticleComments + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: ArticleComments ID + in: path + name: id + required: true + 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: delete ArticleComments + tags: + - ArticleComments + get: + description: API for getting one ArticleComments + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: ArticleComments ID + in: path + name: id + required: true + 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 one ArticleComments + tags: + - ArticleComments + put: + description: API for update ArticleComments + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ArticleCommentsUpdateRequest" + - description: ArticleComments ID + in: path + name: id + required: true + 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: update ArticleComments + tags: + - ArticleComments + /article-comments/approval: + post: + description: API for Approval ArticleComments + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ArticleCommentsApprovalRequest" + 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: Approval ArticleComments + tags: + - ArticleComments + /article-files: + get: + description: API for getting all ArticleFiles + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - in: query + name: articleId + type: integer + - in: query + name: fileName + type: string + - in: query + name: isPublish + type: boolean + - in: query + name: statusId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all ArticleFiles + tags: + - Article Files + /article-files/{articleId}: + post: + description: API for create ArticleFiles + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Upload file + in: formData + name: files + required: true + type: file + - description: Article ID + in: path + name: articleId + required: true + type: integer + produces: + - application/json + 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: Upload ArticleFiles + tags: + - Article Files + /article-files/{id}: + delete: + description: API for delete ArticleFiles + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: ArticleFiles ID + in: path + name: id + required: true + 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: Delete ArticleFiles + tags: + - Article Files + get: + description: API for getting one ArticleFiles + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: ArticleFiles ID + in: path + name: id + required: true + 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 one ArticleFiles + tags: + - Article Files + put: + description: API for update ArticleFiles + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ArticleFilesUpdateRequest" + - description: ArticleFiles ID + in: path + name: id + required: true + 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: Update ArticleFiles + tags: + - Article Files + /article-files/upload-status/{uploadId}: + get: + description: API for GetUploadStatus ArticleFiles + parameters: + - description: Upload ID of ArticleFiles + in: path + name: uploadId + required: true + type: string + 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: GetUploadStatus ArticleFiles + tags: + - Article Files + /article-files/viewer/{filename}: + get: + description: API for Viewer ArticleFiles + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Article File Name + in: path + name: filename + required: true + type: string + 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: Viewer ArticleFiles + tags: + - Article Files + /article-nulis-ai: + get: + description: API for getting all ArticleNulisAI + parameters: + - in: query + name: articleId + type: integer + - in: query + name: categoryId + type: integer + - in: query + name: creatorId + type: integer + - in: query + name: description + type: string + - in: query + name: htmlDescription + type: string + - in: query + name: nulisAiId + type: integer + - in: query + name: tags + type: string + - in: query + name: title + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all ArticleNulisAI + tags: + - ArticleNulisAI + post: + description: API for create ArticleNulisAI + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ArticleNulisAICreateRequest" + 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 ArticleNulisAI + tags: + - ArticleNulisAI + /article-nulis-ai/{id}: + delete: + description: API for delete ArticleNulisAI + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: ArticleNulisAI ID + in: path + name: id + required: true + 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: delete ArticleNulisAI + tags: + - ArticleNulisAI + get: + description: API for getting one ArticleNulisAI + parameters: + - description: ArticleNulisAI ID + in: path + name: id + required: true + 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 one ArticleNulisAI + tags: + - ArticleNulisAI + put: + description: API for update ArticleNulisAI + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ArticleNulisAIUpdateRequest" + - description: ArticleNulisAI ID + in: path + name: id + required: true + 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: update ArticleNulisAI + tags: + - ArticleNulisAI + /article-nulis-ai/publish: + post: + description: API for publish ArticleNulisAI + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ArticleNulisAIUpdateRequest" + 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: publish ArticleNulisAI + tags: + - ArticleNulisAI + /articles: + get: + description: API for getting all Articles + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - in: query + name: category + type: string + - in: query + name: categoryId + type: integer + - in: query + name: createdById + type: integer + - in: query + name: customCreatorName + type: string + - in: query + name: description + type: string + - in: query + name: endDate + type: string + - in: query + name: isBanner + type: boolean + - in: query + name: isDraft + type: boolean + - in: query + name: isPublish + type: boolean + - in: query + name: source + type: string + - in: query + name: startDate + type: string + - in: query + name: statusId + type: integer + - in: query + name: tags + type: string + - in: query + name: title + type: string + - in: query + name: typeId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all Articles + tags: + - Articles + post: + description: API for create Articles + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ArticlesCreateRequest" + 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 Articles + tags: + - Articles + /articles/{id}: + delete: + description: API for delete Articles + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Articles ID + in: path + name: id + required: true + 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: Delete Articles + tags: + - Articles + get: + description: API for getting one Articles + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Articles ID + in: path + name: id + required: true + 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 one Articles + tags: + - Articles + put: + description: API for update Articles + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ArticlesUpdateRequest" + - description: Articles ID + in: path + name: id + required: true + 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: Update Articles + tags: + - Articles + /articles/{id}/approval-status: + get: + description: API for getting article approval status and workflow progress + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: article id + in: path + name: id + required: true + 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 Article Approval Status + tags: + - Articles + /articles/{id}/publish: + put: + description: API for publishing an article + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: article id + in: path + name: id + required: true + 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: Publish Article + tags: + - Articles + /articles/{id}/submit-approval: + post: + description: API for submitting article for approval workflow + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: article id + in: path + name: id + required: true + type: integer + - description: approval request data + in: body + name: req + schema: + $ref: "#/definitions/web-qudo-be_app_module_articles_request.SubmitForApprovalRequest" + 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: Submit Article for Approval + tags: + - Articles + /articles/{id}/unpublish: + put: + description: API for unpublishing an article + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: article id + in: path + name: id + required: true + 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: Unpublish Article + tags: + - Articles + /articles/banner/{id}: + put: + description: API for Update Banner Articles + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Articles ID + in: path + name: id + required: true + type: integer + - description: Articles Banner Status + in: query + name: isBanner + required: true + type: boolean + 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: Update Banner Articles + tags: + - Articles + /articles/old-id/{id}: + get: + description: API for getting one Articles + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Articles Old ID + in: path + name: id + required: true + 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 one Articles + tags: + - Articles + /articles/pending-approval: + get: + description: API for getting articles pending approval for current user level + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: page number + in: query + name: page + type: integer + - description: items per page + in: query + name: limit + type: integer + - description: article type id + in: query + name: typeId + 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 Pending Approvals + tags: + - Articles + /articles/publish-scheduling: + post: + description: + API for Publish Schedule of Article. Supports both date-only format + (2006-01-02) and datetime format (2006-01-02 15:04:05) + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: article id + in: query + name: id + type: integer + - description: "publish date/time (format: 2006-01-02 or 2006-01-02 15:04:05)" + in: query + name: date + type: string + 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: PublishScheduling Articles + tags: + - Articles + /articles/slug/{slug}: + get: + description: API for getting one Articles by slug + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Articles Slug + in: path + name: slug + required: true + type: string + 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 one Articles by Slug + tags: + - Articles + /articles/statistic/monthly: + get: + description: API for ArticleMonthlyStats of Article + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: year + in: query + name: year + 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: ArticleMonthlyStats Articles + tags: + - Articles + /articles/statistic/summary: + get: + description: API for Summary Stats of Article + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + 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: SummaryStats Articles + tags: + - Articles + /articles/statistic/user-levels: + get: + description: API for ArticlePerUserLevelStats of Article + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: start date + in: query + name: startDate + type: string + - description: start date + in: query + name: endDate + type: string + 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: ArticlePerUserLevelStats Articles + tags: + - Articles + /articles/thumbnail/{id}: + post: + description: API for Save Thumbnail of Articles + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Upload thumbnail + in: formData + name: files + required: true + type: file + - description: Articles ID + in: path + name: id + required: true + type: integer + produces: + - application/json + 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: Save Thumbnail Articles + tags: + - Articles + /articles/thumbnail/viewer/{thumbnailName}: + get: + description: API for View Thumbnail of Article + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Articles Thumbnail Name + in: path + name: thumbnailName + required: true + type: string + 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: Viewer Articles Thumbnail + tags: + - 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: Bearer + description: Insert your access token + in: header + name: Authorization + 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 + /bookmarks: + get: + description: API for getting all Bookmarks + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - in: query + name: articleId + type: integer + - in: query + name: userId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all Bookmarks + tags: + - Bookmarks + post: + description: API for creating new Bookmark + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Bookmark data + in: body + name: req + required: true + schema: + $ref: "#/definitions/request.BookmarksCreateRequest" + 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 new Bookmark + tags: + - Bookmarks + /bookmarks/{id}: + delete: + description: API for deleting Bookmark + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Bookmark ID + in: path + name: id + required: true + 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: Delete Bookmark + tags: + - Bookmarks + get: + description: API for getting Bookmark by ID + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Bookmark ID + in: path + name: id + required: true + 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 Bookmark by ID + tags: + - Bookmarks + /bookmarks/summary: + get: + description: + API for getting bookmark summary including total count and recent + bookmarks + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + 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 Bookmark Summary for User + tags: + - Bookmarks + /bookmarks/toggle/{articleId}: + post: + description: API for toggling bookmark status for an article + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Article ID + in: path + name: articleId + required: true + 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: Toggle Bookmark (Add/Remove) + tags: + - Bookmarks + /bookmarks/user: + get: + description: API for getting Bookmarks by User ID + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - in: query + name: articleId + type: integer + - in: query + name: userId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 Bookmarks by User ID + tags: + - Bookmarks + /cities: + get: + description: API for getting all Cities + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Get all Cities + tags: + - Untags + post: + description: API for create Cities + parameters: + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.CitiesCreateRequest" + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Create Cities + tags: + - Untags + /cities/{id}: + delete: + description: API for delete Cities + parameters: + - description: Cities ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Delete Cities + tags: + - Untags + get: + description: API for getting one Cities + parameters: + - description: Cities ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Get one Cities + tags: + - Untags + put: + consumes: + - application/json + description: API for update Cities + parameters: + - description: Cities ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.CitiesUpdateRequest" + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Update Cities + tags: + - Untags + /client-approval-settings: + delete: + description: API for deleting client approval settings + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + 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: Delete Client Approval Settings + tags: + - ClientApprovalSettings + get: + description: API for getting client approval settings + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + 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 Client Approval Settings + tags: + - 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: + description: API for updating 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.UpdateClientApprovalSettingsRequest" + 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: Update Client Approval Settings + tags: + - ClientApprovalSettings + /client-approval-settings/default-workflow: + post: + description: API for setting default workflow for client + 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.SetDefaultWorkflowRequest" + 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: Set Default Workflow + tags: + - ClientApprovalSettings + /client-approval-settings/disable: + post: + description: API for disabling approval system and auto-publish pending articles + 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.DisableApprovalRequest" + 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: Disable Approval System + tags: + - ClientApprovalSettings + /client-approval-settings/enable: + post: + description: API for enabling approval system with smooth transition + 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.EnableApprovalRequest" + 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: Enable Approval System + tags: + - ClientApprovalSettings + /client-approval-settings/exempt-categories/{action}/{category_id}: + post: + description: API for adding/removing categories from approval exemption + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: "Action: add or remove" + in: path + name: action + required: true + type: string + - description: Category ID + in: path + name: category_id + required: true + 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: Manage Exempt Categories + tags: + - ClientApprovalSettings + /client-approval-settings/exempt-roles/{action}/{role_id}: + post: + description: API for adding/removing roles from approval exemption + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: "Action: add or remove" + in: path + name: action + required: true + type: string + - description: Role ID + in: path + name: role_id + required: true + 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: Manage Exempt Roles + tags: + - ClientApprovalSettings + /client-approval-settings/exempt-users/{action}/{user_id}: + post: + description: API for adding/removing users from approval exemption + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: "Action: add or remove" + in: path + name: action + required: true + type: string + - description: User ID + in: path + name: user_id + required: true + 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: Manage Exempt Users + tags: + - ClientApprovalSettings + /client-approval-settings/toggle: + post: + description: API for toggling approval requirement on/off + 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.ToggleApprovalRequest" + 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: Toggle Approval Requirement + tags: + - ClientApprovalSettings + /clients: + get: + description: API for getting all Clients + parameters: + - in: query + name: createdBy + type: integer + - in: query + name: name + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all Clients + tags: + - Clients + post: + description: API for create Clients + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ClientsCreateRequest" + 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 Clients + tags: + - Clients + /clients/{id}: + delete: + description: API for delete Clients + parameters: + - description: Clients ID + in: path + name: id + required: true + type: string + 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: delete Clients + tags: + - Clients + get: + description: API for getting one Clients + parameters: + - description: Clients ID + in: path + name: id + required: true + 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 one Clients + tags: + - Clients + put: + description: API for update Clients + parameters: + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.ClientsUpdateRequest" + - description: Clients ID + in: path + name: id + required: true + type: string + 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: update Clients + tags: + - Clients + /custom-static-pages: + get: + description: API for getting all CustomStaticPages + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - in: query + name: description + type: string + - in: query + name: htmlBody + type: string + - in: query + name: slug + type: string + - in: query + name: title + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all CustomStaticPages + tags: + - CustomStaticPages + post: + description: API for create CustomStaticPages + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.CustomStaticPagesCreateRequest" + 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 CustomStaticPages + tags: + - CustomStaticPages + /custom-static-pages/{id}: + delete: + description: API for delete CustomStaticPages + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: CustomStaticPages ID + in: path + name: id + required: true + 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: delete CustomStaticPages + tags: + - CustomStaticPages + get: + description: API for getting one CustomStaticPages + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: CustomStaticPages ID + in: path + name: id + required: true + 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 one CustomStaticPages + tags: + - CustomStaticPages + put: + description: API for update CustomStaticPages + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.CustomStaticPagesUpdateRequest" + - description: CustomStaticPages ID + in: path + name: id + required: true + 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: update CustomStaticPages + tags: + - CustomStaticPages + /custom-static-pages/slug/{slug}: + get: + description: API for getting one CustomStaticPages + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: CustomStaticPages Slug + in: path + name: slug + required: true + type: string + 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 one CustomStaticPages + tags: + - CustomStaticPages + /districts: + get: + description: API for getting all Districts + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Get all Districts + tags: + - Untags + post: + description: API for create Districts + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Create Districts + tags: + - Untags + /districts/{id}: + delete: + description: API for delete Districts + parameters: + - description: Districts ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Delete Districts + tags: + - Untags + get: + description: API for getting one Districts + parameters: + - description: Districts ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Get one Districts + tags: + - Untags + put: + description: API for update Districts + parameters: + - description: Districts ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Update Districts + tags: + - Untags + /feedbacks: + get: + description: API for getting all Feedbacks + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - in: query + name: commentFromEmail + type: string + - in: query + name: commentFromName + type: string + - in: query + name: endDate + type: string + - in: query + name: message + type: string + - in: query + name: startDate + type: string + - in: query + name: statusId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all Feedbacks + tags: + - Feedbacks + post: + description: API for create Feedbacks + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.FeedbacksCreateRequest" + 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 Feedbacks + tags: + - Feedbacks + /feedbacks/{id}: + delete: + description: API for delete Feedbacks + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Feedbacks ID + in: path + name: id + required: true + 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: delete Feedbacks + tags: + - Feedbacks + get: + description: API for getting one Feedbacks + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Feedbacks ID + in: path + name: id + required: true + 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 one Feedbacks + tags: + - Feedbacks + put: + description: API for update Feedbacks + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.FeedbacksUpdateRequest" + - description: Feedbacks ID + in: path + name: id + required: true + 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: update Feedbacks + tags: + - Feedbacks + /feedbacks/statistic/monthly: + get: + description: API for FeedbackMonthlyStats of Feedbacks + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: year + in: query + name: year + 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: FeedbackMonthlyStats Feedbacks + tags: + - Feedbacks + /magazine-files: + get: + description: API for getting all MagazineFiles + 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 all MagazineFiles + tags: + - Magazine Files + /magazine-files/{id}: + delete: + description: API for delete MagazineFiles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: MagazineFiles ID + in: path + name: id + required: true + 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: delete MagazineFiles + tags: + - Magazine Files + get: + description: API for getting one MagazineFiles + parameters: + - description: MagazineFiles ID + in: path + name: id + required: true + 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 one MagazineFiles + tags: + - Magazine Files + put: + description: API for update MagazineFiles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: MagazineFiles ID + in: path + name: id + required: true + 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: Update MagazineFiles + tags: + - Magazine Files + /magazine-files/{magazineId}: + post: + description: API for create MagazineFiles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Upload file + in: formData + name: files + required: true + type: file + - description: Magazine file title + in: formData + name: title + required: true + type: string + - description: Magazine file description + in: formData + name: description + required: true + type: string + - description: Magazine ID + in: path + name: magazineId + required: true + 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: Create MagazineFiles + tags: + - Magazine Files + /magazine-files/viewer/{filename}: + get: + description: API for create MagazineFiles + parameters: + - description: Magazine File Name + in: path + name: filename + required: true + type: string + 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 MagazineFiles + tags: + - Magazine Files + /magazines: + get: + description: API for getting all Magazines + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - in: query + name: createdById + type: integer + - in: query + name: description + type: string + - in: query + name: isPublish + type: boolean + - in: query + name: pageUrl + type: string + - in: query + name: statusId + type: integer + - in: query + name: thumbnailPath + type: string + - in: query + name: thumbnailUrl + type: string + - in: query + name: title + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all Magazines + tags: + - Magazines + post: + description: API for create Magazines + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.MagazinesCreateRequest" + 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 Magazines + tags: + - Magazines + /magazines/{id}: + delete: + description: API for delete Magazines + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Magazines ID + in: path + name: id + required: true + 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: Delete Magazines + tags: + - Magazines + get: + description: API for getting one Magazines + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Magazines ID + in: path + name: id + required: true + 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 one Magazines + tags: + - Magazines + put: + description: API for update Magazines + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Magazines ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.MagazinesUpdateRequest" + 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: Update Magazines + tags: + - Magazines + /magazines/thumbnail/{id}: + post: + description: API for Save Thumbnail of Magazines + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Magazine ID + in: path + name: id + required: true + type: integer + - description: Upload thumbnail + in: formData + name: files + required: true + type: file + produces: + - application/json + 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: Save Thumbnail Magazines + tags: + - Magazines + /magazines/thumbnail/viewer/{thumbnailName}: + get: + description: API for View Thumbnail of Magazines + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Magazines Thumbnail Name + in: path + name: thumbnailName + required: true + type: string + 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: Viewer Magazines Thumbnail + tags: + - Magazines + /master-menus: + get: + description: API for getting all MasterMenus + parameters: + - in: query + name: description + type: string + - in: query + name: moduleId + type: integer + - in: query + name: name + type: string + - in: query + name: parentMenuId + type: integer + - in: query + name: statusId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all MasterMenus + tags: + - MasterMenus + post: + description: API for create MasterMenus + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.MasterMenusCreateRequest" + 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 MasterMenus + tags: + - MasterMenus + /master-menus/{id}: + delete: + description: API for delete MasterMenus + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: MasterMenus ID + in: path + name: id + required: true + 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: Delete MasterMenus + tags: + - MasterMenus + get: + description: API for getting one MasterMenus + parameters: + - description: MasterMenus ID + in: path + name: id + required: true + 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 one MasterMenus + tags: + - MasterMenus + put: + description: API for update MasterMenus + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: MasterMenus ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Update MasterMenus + tags: + - MasterMenus + /master-modules: + get: + description: API for getting all MasterModules + parameters: + - in: query + name: description + type: string + - in: query + name: name + type: string + - in: query + name: statusId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all MasterModules + tags: + - MasterModules + post: + description: API for create MasterModules + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.MasterModulesCreateRequest" + 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 MasterModules + tags: + - MasterModules + /master-modules/{id}: + delete: + description: API for delete MasterModules + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: MasterModules ID + in: path + name: id + required: true + 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: Delete MasterModules + tags: + - MasterModules + get: + description: API for getting one MasterModules + parameters: + - description: MasterModules ID + in: path + name: id + required: true + 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 one MasterModules + tags: + - MasterModules + put: + description: API for update MasterModules + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: MasterModules ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.MasterModulesUpdateRequest" + 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: Update MasterModules + tags: + - MasterModules + /master-statuses: + get: + description: API for getting all MasterStatuses + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Get all MasterStatuses + tags: + - Untags + post: + description: API for create MasterStatuses + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Create MasterStatuses + tags: + - Untags + /master-statuses/{id}: + delete: + description: API for delete MasterStatuses + parameters: + - description: MasterStatuses ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Delete MasterStatuses + tags: + - Untags + get: + description: API for getting one MasterStatuses + parameters: + - description: MasterStatuses ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Get one MasterStatuses + tags: + - Untags + put: + description: API for update MasterStatuses + parameters: + - description: MasterStatuses ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Update MasterStatuses + tags: + - Untags + /provinces: + get: + description: API for getting all Provinces + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Get all Provinces + tags: + - Untags + post: + description: API for create Provinces + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Create Provinces + tags: + - Untags + /provinces/{id}: + delete: + description: API for delete Provinces + parameters: + - description: Provinces ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Delete Provinces + tags: + - Untags + get: + description: API for getting one Provinces + parameters: + - description: Provinces ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Get one Provinces + tags: + - Untags + put: + description: API for update Provinces + parameters: + - description: Provinces ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Update Provinces + tags: + - Untags + /schedules: + get: + description: API for getting all Schedules + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - in: query + name: createdById + type: integer + - in: query + name: description + type: string + - in: query + name: endDate + type: string + - in: query + name: isLiveStreaming + type: boolean + - in: query + name: location + type: string + - in: query + name: speakers + type: string + - in: query + name: startDate + type: string + - in: query + name: statusId + type: integer + - in: query + name: title + type: string + - in: query + name: typeId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all Schedules + tags: + - Schedules + post: + description: API for create Schedule + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.SchedulesCreateRequest" + 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 Schedule + tags: + - Schedules + /schedules/{id}: + delete: + description: API for delete Schedule + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Schedule ID + in: path + name: id + required: true + 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: Delete Schedule + tags: + - Schedules + get: + description: API for getting one Schedule + parameters: + - description: Schedule ID + in: path + name: id + required: true + 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 one Schedule + tags: + - Schedules + put: + description: API for update Schedule + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.SchedulesUpdateRequest" + - description: Schedule ID + in: path + name: id + required: true + 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: Update Schedule + tags: + - Schedules + /subscription: + get: + description: API for getting all Subscription + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - in: query + name: email + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all Subscription + tags: + - Subscription + post: + description: API for create Subscription + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.SubscriptionCreateRequest" + 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 Subscription + tags: + - Subscription + /subscription/{id}: + delete: + description: API for delete Subscription + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Subscription ID + in: path + name: id + required: true + 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: delete Subscription + tags: + - Subscription + get: + description: API for getting one Subscription + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Subscription ID + in: path + name: id + required: true + 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 one Subscription + tags: + - Subscription + put: + description: API for update Subscription + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.SubscriptionUpdateRequest" + - description: Subscription ID + in: path + name: id + required: true + 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: update Subscription + tags: + - Subscription + /user-levels: + get: + description: API for getting all UserLevels + parameters: + - description: Client Key + in: header + name: X-Client-Key + required: true + type: string + - in: query + name: levelNumber + type: integer + - in: query + name: name + type: string + - in: query + name: parentLevelId + type: integer + - in: query + name: provinceId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all UserLevels + tags: + - UserLevels + post: + description: API for create UserLevels + parameters: + - description: Client Key + in: header + name: X-Client-Key + required: true + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UserLevelsCreateRequest" + 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 UserLevels + tags: + - UserLevels + /user-levels/{id}: + delete: + description: API for delete UserLevels + parameters: + - description: Client Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: UserLevels ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: delete UserLevels + tags: + - UserLevels + get: + description: API for getting one UserLevels + parameters: + - description: Client Key + in: header + name: X-Client-Key + required: true + type: string + - description: UserLevels ID + in: path + name: id + required: true + 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 one UserLevels + tags: + - UserLevels + put: + description: API for update UserLevels + parameters: + - description: Client Key + in: header + name: X-Client-Key + required: true + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UserLevelsUpdateRequest" + - description: UserLevels ID + in: path + name: id + required: true + 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: update UserLevels + tags: + - UserLevels + /user-levels/alias/{alias}: + get: + description: API for getting one UserLevels + parameters: + - description: Client Key + in: header + name: X-Client-Key + required: true + type: string + - description: UserLevels Alias + in: path + name: alias + required: true + type: string + 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 one UserLevels + tags: + - UserLevels + /user-levels/enable-approval: + post: + description: API for Enable Approval of Article + parameters: + - description: Client Key + in: header + name: X-Client-Key + required: true + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UserLevelsApprovalRequest" + 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: EnableApproval Articles + tags: + - UserLevels + /user-role-accesses: + get: + description: API for getting all UserRoleAccesses + parameters: + - in: query + name: isActive + required: true + type: boolean + - in: query + name: menuId + required: true + type: integer + - in: query + name: userRoleId + required: true + 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 all UserRoleAccesses + tags: + - UserRoleAccesses + post: + description: API for create UserRoleAccesses + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UserRoleAccessesCreateRequest" + 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 UserRoleAccesses + tags: + - UserRoleAccesses + /user-role-accesses/{id}: + delete: + description: API for delete UserRoleAccesses + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: UserRoleAccesses ID + in: path + name: id + required: true + 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: delete UserRoleAccesses + tags: + - UserRoleAccesses + get: + description: API for getting one UserRoleAccesses + parameters: + - description: UserRoleAccesses ID + in: path + name: id + required: true + 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 one UserRoleAccesses + tags: + - UserRoleAccesses + put: + description: API for update UserRoleAccesses + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UserRoleAccessesUpdateRequest" + - description: UserRoleAccesses ID + in: path + name: id + required: true + 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: update UserRoleAccesses + tags: + - UserRoleAccesses + /user-role-level-details: + get: + description: API for getting all UserRoleLevelDetails + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Get all UserRoleLevelDetails + tags: + - Task + post: + description: API for create UserRoleLevelDetails + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Create UserRoleLevelDetails + tags: + - Task + /user-role-level-details/{id}: + delete: + description: API for delete UserRoleLevelDetails + parameters: + - description: UserRoleLevelDetails ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: delete UserRoleLevelDetails + tags: + - Task + get: + description: API for getting one UserRoleLevelDetails + parameters: + - description: UserRoleLevelDetails ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: Get one UserRoleLevelDetails + tags: + - Task + put: + description: API for update UserRoleLevelDetails + parameters: + - description: UserRoleLevelDetails ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: "#/definitions/response.Response" + "401": + description: Unauthorized + schema: + $ref: "#/definitions/response.Response" + "404": + description: Not Found + schema: + $ref: "#/definitions/response.Response" + "422": + description: Unprocessable Entity + schema: + $ref: "#/definitions/response.Response" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/response.Response" + security: + - Bearer: [] + summary: update UserRoleLevelDetails + tags: + - Task + /user-roles: + get: + description: API for getting all UserRoles + parameters: + - in: query + name: code + type: string + - in: query + name: description + type: string + - in: query + name: name + type: string + - in: query + name: statusId + type: integer + - in: query + name: userLevelId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all UserRoles + tags: + - UserRoles + post: + description: API for create UserRoles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UserRolesCreateRequest" + 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 UserRoles + tags: + - UserRoles + /user-roles/{id}: + delete: + description: API for delete UserRoles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: UserRoles ID + in: path + name: id + required: true + 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: delete UserRoles + tags: + - UserRoles + get: + description: API for getting one UserRoles + parameters: + - description: UserRoles ID + in: path + name: id + required: true + 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 one UserRoles + tags: + - UserRoles + put: + description: API for update UserRoles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UserRolesUpdateRequest" + - description: UserRoles ID + in: path + name: id + required: true + 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: update UserRoles + tags: + - UserRoles + /users: + get: + description: API for getting all Users + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - in: query + name: email + type: string + - in: query + name: fullname + type: string + - in: query + name: genderType + type: string + - in: query + name: identityGroup + type: string + - in: query + name: identityGroupNumber + type: string + - in: query + name: identityNumber + type: string + - in: query + name: identityType + type: string + - in: query + name: phoneNumber + type: string + - in: query + name: statusId + type: integer + - in: query + name: userRoleId + type: integer + - in: query + name: username + type: string + - in: query + name: workType + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + 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 all Users + tags: + - Users + post: + description: API for create Users + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UsersCreateRequest" + 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 Users + tags: + - Users + /users/{id}: + delete: + description: API for delete Users + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Users ID + in: path + name: id + required: true + 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: delete Users + tags: + - Users + put: + description: API for update Users + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Users ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UsersUpdateRequest" + 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: update Users + tags: + - Users + /users/detail/{id}: + get: + description: API for getting one Users + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Users ID + in: path + name: id + required: true + 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 one Users + tags: + - Users + /users/email-validation: + post: + description: API for Email Validation Users + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UserEmailValidationRequest" + 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: EmailValidation Users + tags: + - Users + /users/forgot-password: + post: + description: API for ForgotPassword Users + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UserForgotPassword" + 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: ForgotPassword Users + tags: + - Users + /users/info: + get: + description: API for ShowUserInfo + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + 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: ShowInfo Users + tags: + - Users + /users/login: + post: + description: API for Login Users + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UserLogin" + 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: Login Users + tags: + - Users + /users/otp-request: + post: + description: API for OtpRequest Users + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UserOtpRequest" + 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: OtpRequest Users + tags: + - Users + /users/otp-validation: + post: + description: API for OtpValidation Users + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UserOtpValidation" + 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: OtpValidation Users + tags: + - Users + /users/pareto-login: + post: + description: API for ParetoLogin Users + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UserLogin" + 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: ParetoLogin Users + tags: + - Users + /users/reset-password: + post: + description: API for ResetPassword Users + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UserResetPassword" + 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: ResetPassword Users + tags: + - Users + /users/save-password: + post: + description: API for SavePassword Users + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UserSavePassword" + 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: SavePassword Users + tags: + - Users + /users/setup-email: + post: + description: API for Setup Email Users + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: "#/definitions/request.UserEmailValidationRequest" + 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: SetupEmail Users + tags: + - Users + /users/username/{username}: + get: + description: API for getting one Users + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + type: string + - description: Username + in: path + name: username + required: true + type: string + 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 one Users + tags: + - Users +swagger: "2.0" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b07ed68 --- /dev/null +++ b/go.mod @@ -0,0 +1,80 @@ +module web-qudo-be + +go 1.21.6 + +require ( + aidanwoods.dev/go-paseto v1.5.3 + github.com/Nerzal/gocloak/v13 v13.9.0 + github.com/arsmn/fiber-swagger/v2 v2.31.1 + github.com/efectn/fx-zerolog v1.1.0 + github.com/go-co-op/gocron v1.37.0 + github.com/go-playground/locales v0.14.1 + github.com/go-playground/universal-translator v0.18.1 + github.com/go-playground/validator/v10 v10.17.0 + github.com/gofiber/fiber/v2 v2.52.4 + github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/google/uuid v1.6.0 + github.com/minio/minio-go/v7 v7.0.68 + github.com/pelletier/go-toml/v2 v2.1.1 + github.com/rs/zerolog v1.31.0 + github.com/swaggo/swag v1.16.3 + github.com/wneessen/go-mail v0.6.1 + go.uber.org/fx v1.20.1 + gorm.io/driver/postgres v1.5.4 + gorm.io/gorm v1.25.6 +) + +require ( + aidanwoods.dev/go-result v0.1.0 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/go-openapi/jsonpointer v0.20.3 // indirect + github.com/go-openapi/jsonreference v0.20.5 // indirect + github.com/go-openapi/spec v0.20.15 // indirect + github.com/go-openapi/swag v0.22.10 // indirect + github.com/go-resty/resty/v2 v2.7.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.4.3 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/minio/md5-simd v1.1.2 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/philhofer/fwd v1.1.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/rs/xid v1.5.0 // indirect + github.com/segmentio/ksuid v1.0.4 // indirect + github.com/swaggo/files v1.0.1 // indirect + github.com/tinylib/msgp v1.1.8 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.52.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/dig v1.17.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..418901d --- /dev/null +++ b/go.sum @@ -0,0 +1,326 @@ +aidanwoods.dev/go-paseto v1.5.3 h1:y3pRY9MLWBhfO9VuCN0Bkyxa7Xmkt5coipYJfaOZgOs= +aidanwoods.dev/go-paseto v1.5.3/go.mod h1://T4uDrCXnzls7pKeCXaQ/zC3xv0KtgGMk4wnlOAHSs= +aidanwoods.dev/go-result v0.1.0 h1:y/BMIRX6q3HwaorX1Wzrjo3WUdiYeyWbvGe18hKS3K8= +aidanwoods.dev/go-result v0.1.0/go.mod h1:yridkWghM7AXSFA6wzx0IbsurIm1Lhuro3rYef8FBHM= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Nerzal/gocloak/v13 v13.9.0 h1:YWsJsdM5b0yhM2Ba3MLydiOlujkBry4TtdzfIzSVZhw= +github.com/Nerzal/gocloak/v13 v13.9.0/go.mod h1:YYuDcXZ7K2zKECyVP7pPqjKxx2AzYSpKDj8d6GuyM10= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/arsmn/fiber-swagger/v2 v2.31.1 h1:VmX+flXiGGNqLX3loMEEzL3BMOZFSPwBEWR04GA6Mco= +github.com/arsmn/fiber-swagger/v2 v2.31.1/go.mod h1:ZHhMprtB3M6jd2mleG03lPGhHH0lk9u3PtfWS1cBhMA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/efectn/fx-zerolog v1.1.0 h1:n/DYCo53t/mXhL6OasOI/4+JQCYa2doc1G3ogvTGoRY= +github.com/efectn/fx-zerolog v1.1.0/go.mod h1:j7ixjXFvkky0z4s7kX0Dz8O/D+E0TQo9uG+GHJijeqQ= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= +github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.20.3 h1:jykzYWS/kyGtsHfRt6aV8JTB9pcQAXPIA7qlZ5aRlyk= +github.com/go-openapi/jsonpointer v0.20.3/go.mod h1:c7l0rjoouAuIxCm8v/JWKRgMjDG/+/7UBWsXMrv6PsM= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.20.5 h1:hutI+cQI+HbSQaIGSfsBsYI0pHk+CATf8Fk5gCSj0yI= +github.com/go-openapi/jsonreference v0.20.5/go.mod h1:thAqAp31UABtI+FQGKAQfmv7DbFpKNUlva2UPCxKu2Y= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.15 h1:8bDcVxF607pTh9NpPwgsH4J5Uhh5mV5XoWnkurdiY+U= +github.com/go-openapi/spec v0.20.15/go.mod h1:o0upgqg5uYFG7O5mADrDVmSG3Wa6y6OLhwiCqQ+sTv4= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.10 h1:4y86NVn7Z2yYd6pfS4Z+Nyh3aAUL3Nul+LMbhFKy0gA= +github.com/go-openapi/swag v0.22.10/go.mod h1:Cnn8BYtRlx6BNE3DPN86f/xkapGIcLWzh3CLEb4C1jI= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= +github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= +github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofiber/fiber/v2 v2.31.0/go.mod h1:1Ega6O199a3Y7yDGuM9FyXDPYQfv+7/y48wl6WCwUF4= +github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= +github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= +github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= +github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= +github.com/minio/minio-go/v7 v7.0.68 h1:hTqSIfLlpXaKuNy4baAp4Jjy2sqZEN9hRxD0M4aOfrQ= +github.com/minio/minio-go/v7 v7.0.68/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= +github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= +github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= +github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= +github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= +github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= +github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= +github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= +github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= +github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/wneessen/go-mail v0.6.1 h1:cDGqlGuEEhdILRe53VFzmM9WBk8Xh/QMvbO0oxrNJB4= +github.com/wneessen/go-mail v0.6.1/go.mod h1:G702XlFhzHV0Z4w9j2VsH5K9dJDvj0hx+yOOp1oX9vc= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= +go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk= +go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= +gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= +gorm.io/gorm v1.25.6 h1:V92+vVda1wEISSOMtodHVRcUIOPYa2tgQtyF+DfFx+A= +gorm.io/gorm v1.25.6/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/hl.exe b/hl.exe new file mode 100644 index 0000000..2957141 Binary files /dev/null and b/hl.exe differ diff --git a/main.go b/main.go new file mode 100644 index 0000000..bb84d3f --- /dev/null +++ b/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "time" + "web-qudo-be/app/database" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/activity_logs" + "web-qudo-be/app/module/advertisement" + "web-qudo-be/app/module/approval_workflow_steps" + "web-qudo-be/app/module/approval_workflows" + "web-qudo-be/app/module/article_approval_flows" + "web-qudo-be/app/module/article_approval_step_logs" + "web-qudo-be/app/module/article_approvals" + "web-qudo-be/app/module/article_categories" + "web-qudo-be/app/module/article_category_details" + "web-qudo-be/app/module/article_comments" + "web-qudo-be/app/module/article_files" + "web-qudo-be/app/module/article_nulis_ai" + "web-qudo-be/app/module/articles" + "web-qudo-be/app/module/bookmarks" + "web-qudo-be/app/module/cities" + "web-qudo-be/app/module/client_approval_settings" + "web-qudo-be/app/module/clients" + "web-qudo-be/app/module/custom_static_pages" + "web-qudo-be/app/module/districts" + "web-qudo-be/app/module/feedbacks" + "web-qudo-be/app/module/magazine_files" + "web-qudo-be/app/module/magazines" + "web-qudo-be/app/module/master_menus" + "web-qudo-be/app/module/master_modules" + "web-qudo-be/app/module/provinces" + "web-qudo-be/app/module/schedules" + "web-qudo-be/app/module/subscription" + "web-qudo-be/app/module/user_levels" + "web-qudo-be/app/module/user_role_accesses" + "web-qudo-be/app/module/user_role_level_details" + "web-qudo-be/app/module/user_roles" + "web-qudo-be/app/module/users" + "web-qudo-be/app/router" + "web-qudo-be/config/config" + "web-qudo-be/config/logger" + "web-qudo-be/config/webserver" + + fxzerolog "github.com/efectn/fx-zerolog" + "go.uber.org/fx" +) + +func main() { + + fx.New( + /* provide patterns */ + // config + fx.Provide(config.NewConfig), + // logging + fx.Provide(logger.NewLogger), + // fiber + fx.Provide(webserver.NewFiber), + // database + fx.Provide(database.NewDatabase), + // middleware + fx.Provide(middleware.NewMiddleware), + // data storage + fx.Provide(config.NewMinio), + // user management + fx.Provide(config.NewKeycloakConfig), + // router + fx.Provide(router.NewRouter), + // smtp config + fx.Provide(config.NewSmtpConfig), + + // provide modules + activity_logs.NewActivityLogsModule, + advertisement.NewAdvertisementModule, + approval_workflows.NewApprovalWorkflowsModule, + approval_workflow_steps.NewApprovalWorkflowStepsModule, + article_approval_flows.NewArticleApprovalFlowsModule, + article_approval_step_logs.NewArticleApprovalStepLogsModule, + article_categories.NewArticleCategoriesModule, + article_category_details.NewArticleCategoryDetailsModule, + article_files.NewArticleFilesModule, + article_approvals.NewArticleApprovalsModule, + articles.NewArticlesModule, + article_comments.NewArticleCommentsModule, + article_nulis_ai.NewArticleNulisAIModule, + bookmarks.NewBookmarksModule, + cities.NewCitiesModule, + client_approval_settings.NewClientApprovalSettingsModule, + clients.NewClientsModule, + custom_static_pages.NewCustomStaticPagesModule, + districts.NewDistrictsModule, + feedbacks.NewFeedbacksModule, + magazines.NewMagazinesModule, + magazine_files.NewMagazineFilesModule, + master_menus.NewMasterMenusModule, + master_modules.NewMasterModulesModule, + provinces.NewProvincesModule, + schedules.NewSchedulesModule, + subscription.NewSubscriptionModule, + user_levels.NewUserLevelsModule, + user_roles.NewUserRolesModule, + user_role_accesses.NewUserRoleAccessesModule, + users.NewUsersModule, + user_role_level_details.NewUserRoleLevelDetailsModule, + + // start aplication + fx.Invoke(webserver.Start), + + // start scheduling + fx.Invoke(webserver.RunScheduling), + + // define logger + fx.WithLogger(fxzerolog.Init()), + + fx.StartTimeout(600*time.Second), + ).Run() +} diff --git a/plan/api-documentation.md b/plan/api-documentation.md new file mode 100644 index 0000000..7158035 --- /dev/null +++ b/plan/api-documentation.md @@ -0,0 +1,818 @@ +# API Documentation - Sistem Approval Artikel Dinamis + +## Overview +Dokumentasi ini menjelaskan API endpoints yang tersedia untuk sistem approval artikel dinamis. Sistem ini memungkinkan pembuatan workflow approval yang fleksibel dengan multiple level dan proses revisi. + +## Base URL +``` +http://localhost:8800/api +``` + +## Authentication +Semua endpoint memerlukan Bearer token authentication: +``` +Authorization: Bearer +``` + +## 1. Approval Workflows Management + +### 1.1 Get All Workflows +```http +GET /approval-workflows +``` + +**Query Parameters:** +- `page` (optional): Page number (default: 1) +- `limit` (optional): Items per page (default: 10) +- `search` (optional): Search by workflow name +- `is_active` (optional): Filter by active status (true/false) + +**Response:** +```json +{ + "success": true, + "messages": ["Workflows retrieved successfully"], + "data": [ + { + "id": 1, + "name": "Standard 3-Level Approval", + "description": "Default workflow with 3 approval levels", + "is_active": true, + "created_at": "2024-01-15T10:30:00Z", + "updated_at": "2024-01-15T10:30:00Z", + "steps": [ + { + "id": 1, + "step_order": 1, + "user_level_id": 3, + "user_level": { + "id": 3, + "level_name": "Approver Level 3", + "level_number": 3 + }, + "is_required": true, + "can_skip": false + } + ] + } + ], + "pagination": { + "current_page": 1, + "per_page": 10, + "total": 5, + "total_pages": 1 + } +} +``` + +### 1.2 Get Workflow by ID +```http +GET /approval-workflows/{id} +``` + +**Response:** +```json +{ + "success": true, + "messages": ["Workflow retrieved successfully"], + "data": { + "id": 1, + "name": "Standard 3-Level Approval", + "description": "Default workflow with 3 approval levels", + "is_active": true, + "steps": [ + { + "id": 1, + "step_order": 1, + "user_level_id": 3, + "is_required": true, + "can_skip": false + } + ] + } +} +``` + +### 1.3 Create New Workflow +```http +POST /approval-workflows +``` + +**Request Body:** +```json +{ + "name": "Custom 2-Level Approval", + "description": "Fast track approval for urgent articles", + "steps": [ + { + "stepOrder": 1, + "userLevelId": 2, + "isRequired": true, + "canSkip": false + }, + { + "stepOrder": 2, + "userLevelId": 1, + "isRequired": true, + "canSkip": false + } + ] +} +``` + +**Validation Rules:** +- `name`: Required, max 255 characters +- `steps`: Required, minimum 1 step +- `stepOrder`: Must be sequential starting from 1 +- `userLevelId`: Must exist in user_levels table + +**Response:** +```json +{ + "success": true, + "messages": ["Workflow created successfully"], + "data": { + "id": 5, + "name": "Custom 2-Level Approval", + "description": "Fast track approval for urgent articles", + "is_active": true, + "created_at": "2024-01-15T14:30:00Z" + } +} +``` + +### 1.4 Update Workflow +```http +PUT /approval-workflows/{id} +``` + +**Request Body:** +```json +{ + "name": "Updated Workflow Name", + "description": "Updated description", + "is_active": true +} +``` + +### 1.5 Delete Workflow +```http +DELETE /approval-workflows/{id} +``` + +**Note:** Workflow can only be deleted if no articles are currently using it. + +## 2. Article Approval Management + +### 2.1 Submit Article for Approval +```http +POST /articles/{id}/submit-approval +``` + +**Request Body:** +```json +{ + "workflowId": 2 +} +``` + +**Response:** +```json +{ + "success": true, + "messages": ["Article submitted for approval successfully"], + "data": { + "id": 15, + "article_id": 123, + "workflow_id": 2, + "current_step": 1, + "status_id": 1, + "created_at": "2024-01-15T15:00:00Z" + } +} +``` + +### 2.2 Approve Current Step +```http +POST /articles/{id}/approve +``` + +**Request Body:** +```json +{ + "message": "Article content is excellent, approved for next level" +} +``` + +**Response:** +```json +{ + "success": true, + "messages": ["Article approved successfully"], + "data": { + "next_step": 2, + "status": "moved_to_next_level" + } +} +``` + +### 2.3 Reject Article +```http +POST /articles/{id}/reject +``` + +**Request Body:** +```json +{ + "message": "Article does not meet quality standards" +} +``` + +**Response:** +```json +{ + "success": true, + "messages": ["Article rejected successfully"] +} +``` + +### 2.4 Request Revision +```http +POST /articles/{id}/request-revision +``` + +**Request Body:** +```json +{ + "message": "Please fix grammar issues in paragraph 3 and add more references" +} +``` + +**Response:** +```json +{ + "success": true, + "messages": ["Revision requested successfully"] +} +``` + +### 2.5 Resubmit After Revision +```http +POST /articles/{id}/resubmit +``` + +**Request Body:** +```json +{ + "message": "Fixed all requested issues" +} +``` + +**Response:** +```json +{ + "success": true, + "messages": ["Article resubmitted successfully"], + "data": { + "current_step": 1, + "status": "back_in_approval_flow" + } +} +``` + +## 3. Approval Dashboard + +### 3.1 Get Pending Approvals for Current User +```http +GET /approvals/pending +``` + +**Query Parameters:** +- `page` (optional): Page number (default: 1) +- `limit` (optional): Items per page (default: 10, max: 50) +- `priority` (optional): Filter by priority (high, medium, low) +- `category_id` (optional): Filter by article category +- `search` (optional): Search in article title or author name +- `date_from` (optional): Filter articles submitted from date (YYYY-MM-DD) +- `date_to` (optional): Filter articles submitted to date (YYYY-MM-DD) +- `sort_by` (optional): Sort by field (waiting_time, created_at, title, priority) +- `sort_order` (optional): Sort order (asc, desc) - default: desc +- `workflow_id` (optional): Filter by specific workflow + +**Response:** +```json +{ + "success": true, + "messages": ["Pending approvals retrieved successfully"], + "data": [ + { + "id": 15, + "article": { + "id": 123, + "title": "Understanding Machine Learning", + "excerpt": "This article explores the fundamentals of machine learning...", + "author": { + "id": 45, + "name": "John Doe", + "email": "john@example.com", + "profile_picture": "https://example.com/avatar.jpg" + }, + "category": { + "id": 5, + "name": "Technology", + "color": "#3B82F6" + }, + "word_count": 1250, + "estimated_read_time": "5 min", + "created_at": "2024-01-15T10:00:00Z", + "submitted_at": "2024-01-15T14:30:00Z" + }, + "workflow": { + "id": 2, + "name": "Standard 3-Level Approval", + "total_steps": 3 + }, + "current_step": 2, + "my_step_order": 2, + "waiting_since": "2024-01-15T14:30:00Z", + "waiting_duration_hours": 6.5, + "priority": "medium", + "previous_approvals": [ + { + "step_order": 1, + "approver_name": "Jane Smith", + "approved_at": "2024-01-15T14:30:00Z", + "message": "Content looks good" + } + ], + "urgency_score": 7.5, + "can_approve": true, + "can_reject": true, + "can_request_revision": true + } + ], + "pagination": { + "current_page": 1, + "per_page": 10, + "total": 5, + "total_pages": 1 + }, + "summary": { + "total_pending": 5, + "high_priority": 2, + "medium_priority": 2, + "low_priority": 1, + "overdue_count": 1, + "avg_waiting_hours": 8.2 + } +} +``` + +### 3.2 Get Approval History for Article +```http +GET /articles/{id}/approval-history +``` + +**Response:** +```json +{ + "success": true, + "messages": ["Approval history retrieved successfully"], + "data": { + "article_id": 123, + "workflow": { + "id": 2, + "name": "Standard 3-Level Approval" + }, + "current_step": 2, + "status": "in_progress", + "history": [ + { + "id": 1, + "step_order": 1, + "action": "approved", + "message": "Good content, approved", + "approver": { + "id": 67, + "name": "Jane Smith", + "level": "Approver Level 3" + }, + "approved_at": "2024-01-15T14:30:00Z" + } + ] + } +} +``` + +### 3.3 Get My Approval Statistics +```http +GET /approvals/my-stats +``` + +**Response:** +```json +{ + "success": true, + "messages": ["Statistics retrieved successfully"], + "data": { + "pending_count": 5, + "overdue_count": 1, + "approved_today": 3, + "approved_this_week": 15, + "approved_this_month": 45, + "rejected_this_month": 2, + "revision_requests_this_month": 8, + "average_approval_time_hours": 4.5, + "fastest_approval_minutes": 15, + "slowest_approval_hours": 48, + "approval_rate_percentage": 85.2, + "categories_breakdown": [ + { + "category_name": "Technology", + "pending": 2, + "approved_this_month": 12 + }, + { + "category_name": "Health", + "pending": 3, + "approved_this_month": 8 + } + ] + } +} +``` + +### 3.4 Get Articles Requiring My Approval (Detailed View) +```http +GET /approvals/my-queue +``` + +**Query Parameters:** +- Same as `/approvals/pending` but specifically filtered for current user's level +- `include_preview` (optional): Include article content preview (true/false) +- `urgency_only` (optional): Show only urgent articles (true/false) + +**Response:** +```json +{ + "success": true, + "messages": ["My approval queue retrieved successfully"], + "data": [ + { + "id": 15, + "article": { + "id": 123, + "title": "Understanding Machine Learning", + "content_preview": "Machine learning is a subset of artificial intelligence that enables computers to learn and improve from experience without being explicitly programmed...", + "full_content_available": true, + "author": { + "id": 45, + "name": "John Doe", + "reputation_score": 8.5, + "articles_published": 25, + "approval_success_rate": 92.3 + }, + "submission_notes": "This is an updated version based on previous feedback", + "tags": ["AI", "Technology", "Education"], + "seo_score": 85, + "readability_score": "Good" + }, + "approval_context": { + "my_role_in_workflow": "Senior Editor Review", + "step_description": "Review content quality and technical accuracy", + "expected_action": "Approve or request technical revisions", + "deadline": "2024-01-16T18:00:00Z", + "is_overdue": false, + "escalation_available": true + }, + "workflow_progress": { + "completed_steps": 1, + "total_steps": 3, + "progress_percentage": 33.3, + "next_approver": "Chief Editor" + } + } + ] +} +``` + +### 3.5 Get Approval Workload Distribution +```http +GET /approvals/workload +``` + +**Response:** +```json +{ + "success": true, + "messages": ["Workload distribution retrieved successfully"], + "data": { + "my_level": { + "level_name": "Senior Editor", + "level_number": 2, + "pending_articles": 5, + "avg_daily_approvals": 8.5 + }, + "team_comparison": [ + { + "approver_name": "Jane Smith", + "level_name": "Senior Editor", + "pending_articles": 3, + "avg_response_time_hours": 2.5 + }, + { + "approver_name": "Mike Johnson", + "level_name": "Senior Editor", + "pending_articles": 7, + "avg_response_time_hours": 4.2 + } + ], + "bottlenecks": [ + { + "level_name": "Chief Editor", + "pending_count": 12, + "avg_waiting_time_hours": 18.5, + "is_bottleneck": true + } + ] + } +} +``` + +### 3.6 Bulk Approval Actions +```http +POST /approvals/bulk-action +``` + +**Request Body:** +```json +{ + "article_ids": [123, 124, 125], + "action": "approve", + "message": "Bulk approval for similar content type", + "apply_to_similar": false +} +``` + +**Response:** +```json +{ + "success": true, + "messages": ["Bulk action completed successfully"], + "data": { + "processed_count": 3, + "successful_count": 3, + "failed_count": 0, + "results": [ + { + "article_id": 123, + "status": "success", + "next_step": 3 + }, + { + "article_id": 124, + "status": "success", + "next_step": 3 + }, + { + "article_id": 125, + "status": "success", + "next_step": "published" + } + ] + } +} +``` + +## 4. User Level Management + +### 4.1 Get Available User Levels +```http +GET /user-levels +``` + +**Response:** +```json +{ + "success": true, + "messages": ["User levels retrieved successfully"], + "data": [ + { + "id": 1, + "level_name": "Chief Editor", + "level_number": 1, + "parent_level_id": null, + "is_approval_active": true + }, + { + "id": 2, + "level_name": "Senior Editor", + "level_number": 2, + "parent_level_id": 1, + "is_approval_active": true + }, + { + "id": 3, + "level_name": "Junior Editor", + "level_number": 3, + "parent_level_id": 2, + "is_approval_active": true + } + ] +} +``` + +## 5. Error Responses + +### 5.1 Validation Error (400) +```json +{ + "success": false, + "messages": ["Validation failed"], + "errors": { + "name": ["Name is required"], + "steps": ["At least one step is required"] + } +} +``` + +### 5.2 Unauthorized (401) +```json +{ + "success": false, + "messages": ["Unauthorized access"] +} +``` + +### 5.3 Forbidden (403) +```json +{ + "success": false, + "messages": ["You don't have permission to approve this article"] +} +``` + +### 5.4 Not Found (404) +```json +{ + "success": false, + "messages": ["Article not found"] +} +``` + +### 5.5 Business Logic Error (422) +```json +{ + "success": false, + "messages": ["Article already has active approval flow"] +} +``` + +### 5.6 Internal Server Error (500) +```json +{ + "success": false, + "messages": ["Internal server error occurred"] +} +``` + +## 6. Webhook Events (Optional) + +Sistem dapat mengirim webhook notifications untuk event-event penting: + +### 6.1 Article Submitted for Approval +```json +{ + "event": "article.submitted_for_approval", + "timestamp": "2024-01-15T15:00:00Z", + "data": { + "article_id": 123, + "workflow_id": 2, + "submitted_by": { + "id": 45, + "name": "John Doe" + }, + "next_approver_level": 3 + } +} +``` + +### 6.2 Article Approved +```json +{ + "event": "article.approved", + "timestamp": "2024-01-15T16:00:00Z", + "data": { + "article_id": 123, + "approved_by": { + "id": 67, + "name": "Jane Smith", + "level": 3 + }, + "current_step": 2, + "is_final_approval": false + } +} +``` + +### 6.3 Article Published +```json +{ + "event": "article.published", + "timestamp": "2024-01-15T17:00:00Z", + "data": { + "article_id": 123, + "final_approver": { + "id": 89, + "name": "Chief Editor", + "level": 1 + }, + "total_approval_time_hours": 6.5 + } +} +``` + +### 6.4 Revision Requested +```json +{ + "event": "article.revision_requested", + "timestamp": "2024-01-15T16:30:00Z", + "data": { + "article_id": 123, + "requested_by": { + "id": 67, + "name": "Jane Smith", + "level": 2 + }, + "message": "Please fix grammar issues", + "author": { + "id": 45, + "name": "John Doe" + } + } +} +``` + +## 7. Rate Limiting + +API menggunakan rate limiting untuk mencegah abuse: +- **General endpoints**: 100 requests per minute per user +- **Approval actions**: 50 requests per minute per user +- **Workflow management**: 20 requests per minute per user + +## 8. Pagination + +Semua endpoint yang mengembalikan list menggunakan pagination: +- Default `limit`: 10 +- Maximum `limit`: 100 +- Default `page`: 1 + +## 9. Filtering dan Sorting + +Beberapa endpoint mendukung filtering dan sorting: + +### Query Parameters untuk Filtering: +- `status`: Filter by status +- `user_level`: Filter by user level +- `date_from`: Filter from date (YYYY-MM-DD) +- `date_to`: Filter to date (YYYY-MM-DD) +- `search`: Search in title/content + +### Query Parameters untuk Sorting: +- `sort_by`: Field to sort by (created_at, updated_at, title, etc.) +- `sort_order`: asc atau desc (default: desc) + +Contoh: +``` +GET /articles?status=pending&sort_by=created_at&sort_order=asc&page=1&limit=20 +``` + +## 10. Bulk Operations + +### 10.1 Bulk Approve Articles +```http +POST /articles/bulk-approve +``` + +**Request Body:** +```json +{ + "article_ids": [123, 124, 125], + "message": "Bulk approval for similar articles" +} +``` + +### 10.2 Bulk Assign Workflow +```http +POST /articles/bulk-assign-workflow +``` + +**Request Body:** +```json +{ + "article_ids": [123, 124, 125], + "workflow_id": 2 +} +``` + +Dokumentasi API ini memberikan panduan lengkap untuk mengintegrasikan sistem approval artikel dinamis dengan frontend atau sistem eksternal lainnya. \ No newline at end of file diff --git a/plan/approval-workflow-architecture.md b/plan/approval-workflow-architecture.md new file mode 100644 index 0000000..9b008a7 --- /dev/null +++ b/plan/approval-workflow-architecture.md @@ -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. diff --git a/plan/approval-workflow-flow-diagram.md b/plan/approval-workflow-flow-diagram.md new file mode 100644 index 0000000..670a168 --- /dev/null +++ b/plan/approval-workflow-flow-diagram.md @@ -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. diff --git a/plan/database-relationships-detailed.md b/plan/database-relationships-detailed.md new file mode 100644 index 0000000..86cb4ac --- /dev/null +++ b/plan/database-relationships-detailed.md @@ -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. diff --git a/plan/dynamic-article-approval-system-plan.md b/plan/dynamic-article-approval-system-plan.md new file mode 100644 index 0000000..2116aa6 --- /dev/null +++ b/plan/dynamic-article-approval-system-plan.md @@ -0,0 +1,335 @@ +# Plan Implementasi Sistem Approval Artikel Dinamis + +## 1. Analisis Sistem Saat Ini + +### Struktur Database yang Sudah Ada: +- **Articles**: Memiliki field `NeedApprovalFrom`, `HasApprovedBy`, `StatusId` untuk tracking approval +- **ArticleApprovals**: Menyimpan history approval dengan `ApprovalAtLevel` +- **Users**: Terhubung dengan `UserLevels` dan `UserRoles` +- **UserLevels**: Memiliki `LevelNumber`, `ParentLevelId`, `IsApprovalActive` +- **UserRoles**: Terhubung dengan `UserLevels` melalui `UserRoleLevelDetails` +- **UserRoleAccesses**: Memiliki `IsApprovalEnabled` untuk kontrol akses approval +- **MasterApprovalStatuses**: Status approval (pending, approved, rejected) + +### Logika Approval Saat Ini: +- Hard-coded untuk 3 level (level 1, 2, 3) +- Approval naik ke parent level jika disetujui +- Kembali ke contributor jika ditolak +- Status: 1=Pending, 2=Approved, 3=Rejected + +## 2. Kebutuhan Sistem Baru + +### Functional Requirements: +1. **Dynamic Approval Levels**: Sistem harus mendukung 1-N level approval +2. **Flexible Workflow**: Approval flow bisa diubah sewaktu-waktu +3. **Role-based Access**: Approver dan Contributor dengan hak akses berbeda +4. **Revision Handling**: Artikel kembali ke contributor saat revisi diminta +5. **Audit Trail**: Tracking lengkap history approval + +### User Stories: +- Sebagai **Contributor**: Saya bisa membuat artikel dan submit untuk approval +- Sebagai **Approver Level N**: Saya bisa approve/reject/request revision artikel +- Sebagai **Admin**: Saya bisa mengatur approval workflow secara dinamis + +## 3. Desain Database Baru + +### 3.1 Tabel Baru yang Diperlukan: + +#### `approval_workflows` +```sql +CREATE TABLE approval_workflows ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description TEXT, + is_active BOOLEAN DEFAULT true, + client_id UUID, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +``` + +#### `approval_workflow_steps` +```sql +CREATE TABLE approval_workflow_steps ( + id SERIAL PRIMARY KEY, + workflow_id INT REFERENCES approval_workflows(id), + step_order INT NOT NULL, + user_level_id INT REFERENCES user_levels(id), + is_required BOOLEAN DEFAULT true, + can_skip BOOLEAN DEFAULT false, + client_id UUID, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +``` + +#### `article_approval_flows` +```sql +CREATE TABLE article_approval_flows ( + id SERIAL PRIMARY KEY, + article_id INT REFERENCES articles(id), + workflow_id INT REFERENCES approval_workflows(id), + current_step INT DEFAULT 1, + status_id INT DEFAULT 1, -- 1=In Progress, 2=Completed, 3=Rejected + client_id UUID, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +``` + +#### `article_approval_step_logs` +```sql +CREATE TABLE article_approval_step_logs ( + id SERIAL PRIMARY KEY, + article_flow_id INT REFERENCES article_approval_flows(id), + step_order INT NOT NULL, + user_level_id INT REFERENCES user_levels(id), + approved_by INT REFERENCES users(id), + action VARCHAR(50) NOT NULL, -- 'approved', 'rejected', 'revision_requested' + message TEXT, + approved_at TIMESTAMP, + client_id UUID, + created_at TIMESTAMP DEFAULT NOW() +); +``` + +### 3.2 Modifikasi Tabel Existing: + +#### `articles` - Tambah field: +```sql +ALTER TABLE articles ADD COLUMN workflow_id INT REFERENCES approval_workflows(id); +ALTER TABLE articles ADD COLUMN current_approval_step INT DEFAULT 0; +-- Keep existing fields: need_approval_from, has_approved_by, status_id untuk backward compatibility +``` + +#### `user_roles` - Tambah field: +```sql +ALTER TABLE user_roles ADD COLUMN role_type VARCHAR(50) DEFAULT 'contributor'; -- 'contributor', 'approver', 'admin' +``` + +## 4. Implementasi Backend + +### 4.1 Entities Baru: +- `ApprovalWorkflows` +- `ApprovalWorkflowSteps` +- `ArticleApprovalFlows` +- `ArticleApprovalStepLogs` + +### 4.2 Modules Baru: + +#### `approval_workflows` +- **Repository**: CRUD operations untuk workflow management +- **Service**: Business logic untuk workflow creation/modification +- **Controller**: API endpoints untuk workflow management +- **Request/Response**: DTOs untuk workflow operations + +#### `article_approval_flows` +- **Repository**: Tracking approval progress per artikel +- **Service**: Core approval logic, step progression +- **Controller**: API untuk approval actions +- **Request/Response**: DTOs untuk approval operations + +### 4.3 Modifikasi Modules Existing: + +#### `articles/service` +- Refactor `UpdateApproval()` method untuk menggunakan dynamic workflow +- Tambah method `InitiateApprovalFlow()` untuk memulai approval process +- Tambah method `ProcessApprovalStep()` untuk handle approval actions + +#### `article_approvals` +- Extend untuk support dynamic workflow +- Integrate dengan `ArticleApprovalStepLogs` + +## 5. API Design + +### 5.1 Workflow Management APIs: +``` +GET /api/approval-workflows # List all workflows +POST /api/approval-workflows # Create new workflow +GET /api/approval-workflows/{id} # Get workflow details +PUT /api/approval-workflows/{id} # Update workflow +DELETE /api/approval-workflows/{id} # Delete workflow + +GET /api/approval-workflows/{id}/steps # Get workflow steps +POST /api/approval-workflows/{id}/steps # Add workflow step +PUT /api/approval-workflow-steps/{id} # Update workflow step +DELETE /api/approval-workflow-steps/{id} # Delete workflow step +``` + +### 5.2 Article Approval APIs: +``` +POST /api/articles/{id}/submit-approval # Submit article for approval +POST /api/articles/{id}/approve # Approve current step +POST /api/articles/{id}/reject # Reject article +POST /api/articles/{id}/request-revision # Request revision +GET /api/articles/{id}/approval-history # Get approval history +GET /api/articles/pending-approval # Get articles pending approval for current user +``` + +### 5.3 Request/Response Models: + +#### Workflow Creation: +```json +{ + "name": "Standard Article Approval", + "description": "3-level approval process", + "steps": [ + { + "stepOrder": 1, + "userLevelId": 3, + "isRequired": true, + "canSkip": false + }, + { + "stepOrder": 2, + "userLevelId": 2, + "isRequired": true, + "canSkip": false + }, + { + "stepOrder": 3, + "userLevelId": 1, + "isRequired": true, + "canSkip": false + } + ] +} +``` + +#### Approval Action: +```json +{ + "action": "approved", // "approved", "rejected", "revision_requested" + "message": "Article looks good, approved for next level" +} +``` + +## 6. Business Logic Flow + +### 6.1 Article Submission Flow: +1. Contributor creates article (status: draft) +2. Contributor submits for approval +3. System creates `ArticleApprovalFlow` record +4. System sets article status to "pending approval" +5. System notifies first level approver + +### 6.2 Approval Process Flow: +1. Approver receives notification +2. Approver reviews article +3. Approver takes action: + - **Approve**: Move to next step or publish if final step + - **Reject**: Set article status to rejected, end flow + - **Request Revision**: Send back to contributor + +### 6.3 Revision Flow: +1. Article returns to contributor +2. Contributor makes revisions +3. Contributor resubmits +4. Approval flow restarts from step 1 + +## 7. Implementation Phases + +### Phase 1: Database & Core Entities +- [ ] Create new database tables +- [ ] Implement new entities +- [ ] Create migration scripts +- [ ] Update existing entities + +### Phase 2: Workflow Management +- [ ] Implement `approval_workflows` module +- [ ] Create workflow CRUD operations +- [ ] Implement workflow step management +- [ ] Create admin interface for workflow setup + +### Phase 3: Dynamic Approval Engine +- [ ] Implement `article_approval_flows` module +- [ ] Refactor articles service for dynamic approval +- [ ] Create approval processing logic +- [ ] Implement notification system + +### Phase 4: API & Integration +- [ ] Create approval APIs +- [ ] Update existing article APIs +- [ ] Implement role-based access control +- [ ] Create approval dashboard + +### Phase 5: Testing & Migration +- [ ] Unit tests for all new modules +- [ ] Integration tests for approval flows +- [ ] Data migration from old system +- [ ] Performance testing + +## 8. Backward Compatibility + +### Migration Strategy: +1. **Dual System**: Run old and new system parallel +2. **Default Workflow**: Create default workflow matching current 3-level system +3. **Gradual Migration**: Migrate existing articles to new system +4. **Fallback**: Keep old approval logic as fallback + +### Data Migration: +```sql +-- Create default workflow +INSERT INTO approval_workflows (name, description) +VALUES ('Legacy 3-Level Approval', 'Default 3-level approval system'); + +-- Create workflow steps +INSERT INTO approval_workflow_steps (workflow_id, step_order, user_level_id) +VALUES +(1, 1, 3), -- Level 3 approver +(1, 2, 2), -- Level 2 approver +(1, 3, 1); -- Level 1 approver + +-- Update existing articles +UPDATE articles SET workflow_id = 1 WHERE workflow_id IS NULL; +``` + +## 9. Security Considerations + +### Access Control: +- **Contributor**: Can only create and edit own articles +- **Approver**: Can only approve articles at their assigned level +- **Admin**: Can manage workflows and override approvals + +### Validation: +- Validate user has permission for approval level +- Prevent approval of own articles +- Validate workflow step sequence +- Audit all approval actions + +## 10. Performance Considerations + +### Optimization: +- Index on `article_approval_flows.article_id` +- Index on `article_approval_flows.workflow_id` +- Index on `article_approval_step_logs.article_flow_id` +- Cache active workflows +- Batch notification processing + +### Monitoring: +- Track approval processing time +- Monitor workflow bottlenecks +- Alert on stuck approvals +- Dashboard for approval metrics + +## 11. Future Enhancements + +### Possible Extensions: +1. **Conditional Workflows**: Different workflows based on article type/category +2. **Parallel Approvals**: Multiple approvers at same level +3. **Time-based Escalation**: Auto-escalate if approval takes too long +4. **External Integrations**: Email/Slack notifications +5. **Approval Templates**: Pre-defined approval messages +6. **Bulk Approvals**: Approve multiple articles at once + +--- + +**Estimasi Waktu Implementasi**: 4-6 minggu +**Kompleksitas**: Medium-High +**Risk Level**: Medium (karena perubahan core business logic) + +**Next Steps**: +1. Review dan approval plan ini +2. Setup development environment +3. Mulai implementasi Phase 1 +4. Regular progress review setiap minggu \ No newline at end of file diff --git a/plan/end-to-end-testing-scenarios.md b/plan/end-to-end-testing-scenarios.md new file mode 100644 index 0000000..f15e4e6 --- /dev/null +++ b/plan/end-to-end-testing-scenarios.md @@ -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": "

A comprehensive look at the latest AI breakthrough that could change everything

", + "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": "

Comprehensive review of the latest smartphone

", + "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": "

Breaking news about major technology acquisition

", + "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.* diff --git a/plan/implementation-examples.md b/plan/implementation-examples.md new file mode 100644 index 0000000..4c42db5 --- /dev/null +++ b/plan/implementation-examples.md @@ -0,0 +1,1104 @@ +# Contoh Implementasi Kode - Sistem Approval Artikel Dinamis + +## 1. Database Entities + +### ApprovalWorkflows Entity + +```go +// app/database/entity/approval_workflows.entity.go +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type ApprovalWorkflows struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Name string `json:"name" gorm:"type:varchar;not null"` + Description *string `json:"description" gorm:"type:text"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + + // Relations + Steps []ApprovalWorkflowSteps `json:"steps" gorm:"foreignKey:WorkflowId;references:ID"` +} +``` + +### ApprovalWorkflowSteps Entity + +```go +// app/database/entity/approval_workflow_steps.entity.go +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type ApprovalWorkflowSteps struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + WorkflowId uint `json:"workflow_id" gorm:"type:int4;not null"` + StepOrder int `json:"step_order" gorm:"type:int4;not null"` + UserLevelId uint `json:"user_level_id" gorm:"type:int4;not null"` + IsRequired *bool `json:"is_required" gorm:"type:bool;default:true"` + CanSkip *bool `json:"can_skip" gorm:"type:bool;default:false"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + + // Relations + Workflow *ApprovalWorkflows `json:"workflow" gorm:"foreignKey:WorkflowId;references:ID"` + UserLevel *UserLevels `json:"user_level" gorm:"foreignKey:UserLevelId;references:ID"` +} +``` + +### ArticleApprovalFlows Entity + +```go +// app/database/entity/article_approval_flows.entity.go +package entity + +import ( + "github.com/google/uuid" + "time" +) + +type ArticleApprovalFlows struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + ArticleId uint `json:"article_id" gorm:"type:int4;not null"` + WorkflowId uint `json:"workflow_id" gorm:"type:int4;not null"` + CurrentStep int `json:"current_step" gorm:"type:int4;default:1"` + StatusId int `json:"status_id" gorm:"type:int4;default:1"` // 1=In Progress, 2=Completed, 3=Rejected + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + + // Relations + Article *Articles `json:"article" gorm:"foreignKey:ArticleId;references:ID"` + Workflow *ApprovalWorkflows `json:"workflow" gorm:"foreignKey:WorkflowId;references:ID"` + StepLogs []ArticleApprovalStepLogs `json:"step_logs" gorm:"foreignKey:ArticleFlowId;references:ID"` +} +``` + +### ArticleApprovalStepLogs Entity + +```go +// app/database/entity/article_approval_step_logs.entity.go +package entity + +import ( + "github.com/google/uuid" + "time" + users "web-qudo-be/app/database/entity/users" +) + +type ArticleApprovalStepLogs struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + ArticleFlowId uint `json:"article_flow_id" gorm:"type:int4;not null"` + StepOrder int `json:"step_order" gorm:"type:int4;not null"` + UserLevelId uint `json:"user_level_id" gorm:"type:int4;not null"` + ApprovedBy uint `json:"approved_by" gorm:"type:int4;not null"` + Action string `json:"action" gorm:"type:varchar(50);not null"` // 'approved', 'rejected', 'revision_requested' + Message *string `json:"message" gorm:"type:text"` + ApprovedAt *time.Time `json:"approved_at" gorm:"type:timestamp"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + + // Relations + ArticleFlow *ArticleApprovalFlows `json:"article_flow" gorm:"foreignKey:ArticleFlowId;references:ID"` + UserLevel *UserLevels `json:"user_level" gorm:"foreignKey:UserLevelId;references:ID"` + Approver *users.Users `json:"approver" gorm:"foreignKey:ApprovedBy;references:ID"` +} +``` + +## 2. Service Layer Implementation + +### ApprovalWorkflowService + +```go +// app/module/approval_workflows/service/approval_workflows.service.go +package service + +import ( + "errors" + "github.com/google/uuid" + "github.com/rs/zerolog" + "web-qudo-be/app/database/entity" + "web-qudo-be/app/module/approval_workflows/repository" + "web-qudo-be/app/module/approval_workflows/request" + "web-qudo-be/app/module/approval_workflows/response" + "web-qudo-be/utils/paginator" +) + +type approvalWorkflowService struct { + Repo repository.ApprovalWorkflowRepository + Log zerolog.Logger +} + +type ApprovalWorkflowService interface { + All(clientId *uuid.UUID, req request.ApprovalWorkflowQueryRequest) ([]*response.ApprovalWorkflowResponse, paginator.Pagination, error) + Show(clientId *uuid.UUID, id uint) (*response.ApprovalWorkflowResponse, error) + Save(clientId *uuid.UUID, req request.ApprovalWorkflowCreateRequest) (*entity.ApprovalWorkflows, error) + Update(clientId *uuid.UUID, id uint, req request.ApprovalWorkflowUpdateRequest) error + Delete(clientId *uuid.UUID, id uint) error + GetActiveWorkflow(clientId *uuid.UUID) (*entity.ApprovalWorkflows, error) + ValidateWorkflow(workflowId uint) error +} + +func NewApprovalWorkflowService(repo repository.ApprovalWorkflowRepository, log zerolog.Logger) ApprovalWorkflowService { + return &approvalWorkflowService{ + Repo: repo, + Log: log, + } +} + +func (s *approvalWorkflowService) Save(clientId *uuid.UUID, req request.ApprovalWorkflowCreateRequest) (*entity.ApprovalWorkflows, error) { + // Validate workflow steps + if len(req.Steps) == 0 { + return nil, errors.New("workflow must have at least one step") + } + + // Validate step order sequence + for i, step := range req.Steps { + if step.StepOrder != i+1 { + return nil, errors.New("step order must be sequential starting from 1") + } + } + + workflow := req.ToEntity() + workflow.ClientId = clientId + + return s.Repo.Create(workflow) +} + +func (s *approvalWorkflowService) ValidateWorkflow(workflowId uint) error { + workflow, err := s.Repo.FindOneWithSteps(workflowId) + if err != nil { + return err + } + + if workflow.IsActive == nil || !*workflow.IsActive { + return errors.New("workflow is not active") + } + + if len(workflow.Steps) == 0 { + return errors.New("workflow has no steps defined") + } + + return nil +} +``` + +### Dynamic Article Approval Service + +```go +// app/module/article_approval_flows/service/article_approval_flows.service.go +package service + +import ( + "errors" + "fmt" + "github.com/google/uuid" + "github.com/rs/zerolog" + "time" + "web-qudo-be/app/database/entity" + users "web-qudo-be/app/database/entity/users" + "web-qudo-be/app/module/article_approval_flows/repository" + "web-qudo-be/app/module/articles/repository" as articlesRepo + "web-qudo-be/app/module/approval_workflows/service" as workflowService +) + +type articleApprovalFlowService struct { + Repo repository.ArticleApprovalFlowRepository + ArticlesRepo articlesRepo.ArticlesRepository + WorkflowService workflowService.ApprovalWorkflowService + Log zerolog.Logger +} + +type ArticleApprovalFlowService interface { + InitiateApprovalFlow(clientId *uuid.UUID, articleId uint, workflowId uint) (*entity.ArticleApprovalFlows, error) + ProcessApprovalStep(clientId *uuid.UUID, articleId uint, action string, message *string, approver *users.Users) error + GetPendingApprovals(clientId *uuid.UUID, userLevelId uint, filters ApprovalFilters) ([]*entity.ArticleApprovalFlows, paginator.Pagination, error) + GetMyApprovalQueue(clientId *uuid.UUID, userLevelId uint, filters ApprovalFilters) ([]*DetailedApprovalFlow, paginator.Pagination, error) + GetApprovalHistory(clientId *uuid.UUID, articleId uint) ([]*entity.ArticleApprovalStepLogs, error) + GetApprovalStatistics(clientId *uuid.UUID, userLevelId uint) (*ApprovalStatistics, error) + GetWorkloadDistribution(clientId *uuid.UUID, userLevelId uint) (*WorkloadDistribution, error) + BulkApprovalAction(clientId *uuid.UUID, articleIds []uint, action string, message *string, approver *users.Users) (*BulkActionResult, error) +} + +// Supporting structs for enhanced approver dashboard +type ApprovalFilters struct { + Page int + Limit int + Priority *string + CategoryId *uint + Search *string + DateFrom *time.Time + DateTo *time.Time + SortBy string + SortOrder string + WorkflowId *uint + UrgencyOnly bool + IncludePreview bool +} + +type DetailedApprovalFlow struct { + ID uint `json:"id"` + Article DetailedArticleInfo `json:"article"` + ApprovalContext ApprovalContext `json:"approval_context"` + WorkflowProgress WorkflowProgress `json:"workflow_progress"` +} + +type DetailedArticleInfo struct { + ID uint `json:"id"` + Title string `json:"title"` + ContentPreview *string `json:"content_preview"` + FullContentAvailable bool `json:"full_content_available"` + Author AuthorInfo `json:"author"` + Category CategoryInfo `json:"category"` + SubmissionNotes *string `json:"submission_notes"` + Tags []string `json:"tags"` + WordCount int `json:"word_count"` + EstimatedReadTime string `json:"estimated_read_time"` + SEOScore int `json:"seo_score"` + ReadabilityScore string `json:"readability_score"` + CreatedAt time.Time `json:"created_at"` + SubmittedAt time.Time `json:"submitted_at"` +} + +type AuthorInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + ProfilePicture *string `json:"profile_picture"` + ReputationScore float64 `json:"reputation_score"` + ArticlesPublished int `json:"articles_published"` + ApprovalSuccessRate float64 `json:"approval_success_rate"` +} + +type CategoryInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + Color string `json:"color"` +} + +type ApprovalContext struct { + MyRoleInWorkflow string `json:"my_role_in_workflow"` + StepDescription string `json:"step_description"` + ExpectedAction string `json:"expected_action"` + Deadline *time.Time `json:"deadline"` + IsOverdue bool `json:"is_overdue"` + EscalationAvailable bool `json:"escalation_available"` +} + +type WorkflowProgress struct { + CompletedSteps int `json:"completed_steps"` + TotalSteps int `json:"total_steps"` + ProgressPercentage float64 `json:"progress_percentage"` + NextApprover string `json:"next_approver"` +} + +type ApprovalStatistics struct { + PendingCount int `json:"pending_count"` + OverdueCount int `json:"overdue_count"` + ApprovedToday int `json:"approved_today"` + ApprovedThisWeek int `json:"approved_this_week"` + ApprovedThisMonth int `json:"approved_this_month"` + RejectedThisMonth int `json:"rejected_this_month"` + RevisionRequestsThisMonth int `json:"revision_requests_this_month"` + AverageApprovalTimeHours float64 `json:"average_approval_time_hours"` + FastestApprovalMinutes int `json:"fastest_approval_minutes"` + SlowestApprovalHours int `json:"slowest_approval_hours"` + ApprovalRatePercentage float64 `json:"approval_rate_percentage"` + CategoriesBreakdown []CategoryBreakdown `json:"categories_breakdown"` +} + +type CategoryBreakdown struct { + CategoryName string `json:"category_name"` + Pending int `json:"pending"` + ApprovedThisMonth int `json:"approved_this_month"` +} + +type WorkloadDistribution struct { + MyLevel LevelWorkload `json:"my_level"` + TeamComparison []TeamMember `json:"team_comparison"` + Bottlenecks []BottleneckInfo `json:"bottlenecks"` +} + +type LevelWorkload struct { + LevelName string `json:"level_name"` + LevelNumber int `json:"level_number"` + PendingArticles int `json:"pending_articles"` + AvgDailyApprovals float64 `json:"avg_daily_approvals"` +} + +type TeamMember struct { + ApproverName string `json:"approver_name"` + LevelName string `json:"level_name"` + PendingArticles int `json:"pending_articles"` + AvgResponseTimeHours float64 `json:"avg_response_time_hours"` +} + +type BottleneckInfo struct { + LevelName string `json:"level_name"` + PendingCount int `json:"pending_count"` + AvgWaitingTimeHours float64 `json:"avg_waiting_time_hours"` + IsBottleneck bool `json:"is_bottleneck"` +} + +type BulkActionResult struct { + ProcessedCount int `json:"processed_count"` + SuccessfulCount int `json:"successful_count"` + FailedCount int `json:"failed_count"` + Results []BulkActionItemResult `json:"results"` +} + +type BulkActionItemResult struct { + ArticleId uint `json:"article_id"` + Status string `json:"status"` + NextStep string `json:"next_step"` + Error *string `json:"error,omitempty"` +} + +func NewArticleApprovalFlowService( + repo repository.ArticleApprovalFlowRepository, + articlesRepo articlesRepo.ArticlesRepository, + workflowService workflowService.ApprovalWorkflowService, + log zerolog.Logger, +) ArticleApprovalFlowService { + return &articleApprovalFlowService{ + Repo: repo, + ArticlesRepo: articlesRepo, + WorkflowService: workflowService, + Log: log, + } +} + +func (s *articleApprovalFlowService) InitiateApprovalFlow(clientId *uuid.UUID, articleId uint, workflowId uint) (*entity.ArticleApprovalFlows, error) { + // Validate workflow + err := s.WorkflowService.ValidateWorkflow(workflowId) + if err != nil { + return nil, fmt.Errorf("invalid workflow: %v", err) + } + + // Check if article already has active approval flow + existingFlow, _ := s.Repo.FindActiveByArticleId(articleId) + if existingFlow != nil { + return nil, errors.New("article already has active approval flow") + } + + // Create new approval flow + approvalFlow := &entity.ArticleApprovalFlows{ + ArticleId: articleId, + WorkflowId: workflowId, + CurrentStep: 1, + StatusId: 1, // In Progress + ClientId: clientId, + } + + // Update article status + statusId := 1 // Pending approval + err = s.ArticlesRepo.UpdateSkipNull(clientId, articleId, &entity.Articles{ + StatusId: &statusId, + WorkflowId: &workflowId, + CurrentApprovalStep: &[]int{1}[0], + }) + if err != nil { + return nil, fmt.Errorf("failed to update article status: %v", err) + } + + return s.Repo.Create(approvalFlow) +} + +func (s *articleApprovalFlowService) ProcessApprovalStep(clientId *uuid.UUID, articleId uint, action string, message *string, approver *users.Users) error { + // Get active approval flow + approvalFlow, err := s.Repo.FindActiveByArticleId(articleId) + if err != nil { + return fmt.Errorf("approval flow not found: %v", err) + } + + // Get workflow with steps + workflow, err := s.WorkflowService.GetWorkflowWithSteps(approvalFlow.WorkflowId) + if err != nil { + return fmt.Errorf("workflow not found: %v", err) + } + + // Find current step + var currentStep *entity.ApprovalWorkflowSteps + for _, step := range workflow.Steps { + if step.StepOrder == approvalFlow.CurrentStep { + currentStep = &step + break + } + } + + if currentStep == nil { + return errors.New("current step not found in workflow") + } + + // Validate approver has permission for this step + if approver.UserLevelId != currentStep.UserLevelId { + return errors.New("approver does not have permission for this approval step") + } + + // Create step log + now := time.Now() + stepLog := &entity.ArticleApprovalStepLogs{ + ArticleFlowId: approvalFlow.ID, + StepOrder: approvalFlow.CurrentStep, + UserLevelId: currentStep.UserLevelId, + ApprovedBy: approver.ID, + Action: action, + Message: message, + ApprovedAt: &now, + ClientId: clientId, + } + + err = s.Repo.CreateStepLog(stepLog) + if err != nil { + return fmt.Errorf("failed to create step log: %v", err) + } + + // Process action + switch action { + case "approved": + return s.processApproval(clientId, approvalFlow, workflow) + case "rejected": + return s.processRejection(clientId, approvalFlow) + case "revision_requested": + return s.processRevisionRequest(clientId, approvalFlow) + default: + return errors.New("invalid action") + } +} + +func (s *articleApprovalFlowService) processApproval(clientId *uuid.UUID, approvalFlow *entity.ArticleApprovalFlows, workflow *entity.ApprovalWorkflows) error { + // Check if this is the last step + maxStep := 0 + for _, step := range workflow.Steps { + if step.StepOrder > maxStep { + maxStep = step.StepOrder + } + } + + if approvalFlow.CurrentStep >= maxStep { + // Final approval - publish article + return s.finalizeApproval(clientId, approvalFlow) + } else { + // Move to next step + return s.moveToNextStep(clientId, approvalFlow) + } +} + +func (s *articleApprovalFlowService) finalizeApproval(clientId *uuid.UUID, approvalFlow *entity.ArticleApprovalFlows) error { + // Update approval flow status + approvalFlow.StatusId = 2 // Completed + err := s.Repo.Update(approvalFlow.ID, approvalFlow) + if err != nil { + return err + } + + // Update article status to published + isPublish := true + statusId := 2 // Published + now := time.Now() + + err = s.ArticlesRepo.UpdateSkipNull(clientId, approvalFlow.ArticleId, &entity.Articles{ + StatusId: &statusId, + IsPublish: &isPublish, + PublishedAt: &now, + CurrentApprovalStep: nil, // Clear approval step + }) + + return err +} + +func (s *articleApprovalFlowService) moveToNextStep(clientId *uuid.UUID, approvalFlow *entity.ArticleApprovalFlows) error { + // Move to next step + approvalFlow.CurrentStep++ + err := s.Repo.Update(approvalFlow.ID, approvalFlow) + if err != nil { + return err + } + + // Update article current step + err = s.ArticlesRepo.UpdateSkipNull(clientId, approvalFlow.ArticleId, &entity.Articles{ + CurrentApprovalStep: &approvalFlow.CurrentStep, + }) + + return err +} + +func (s *articleApprovalFlowService) processRejection(clientId *uuid.UUID, approvalFlow *entity.ArticleApprovalFlows) error { + // Update approval flow status + approvalFlow.StatusId = 3 // Rejected + err := s.Repo.Update(approvalFlow.ID, approvalFlow) + if err != nil { + return err + } + + // Update article status + statusId := 3 // Rejected + err = s.ArticlesRepo.UpdateSkipNull(clientId, approvalFlow.ArticleId, &entity.Articles{ + StatusId: &statusId, + CurrentApprovalStep: nil, + }) + + return err +} + +func (s *articleApprovalFlowService) processRevisionRequest(clientId *uuid.UUID, approvalFlow *entity.ArticleApprovalFlows) error { + // Reset approval flow to step 0 (back to contributor) + approvalFlow.CurrentStep = 0 + approvalFlow.StatusId = 4 // Revision Requested + err := s.Repo.Update(approvalFlow.ID, approvalFlow) + if err != nil { + return err + } + + // Update article status + statusId := 4 // Revision Requested + err = s.ArticlesRepo.UpdateSkipNull(clientId, approvalFlow.ArticleId, &entity.Articles{ + StatusId: &statusId, + CurrentApprovalStep: &[]int{0}[0], + }) + + return err +} + +// Enhanced methods for approver dashboard +func (s *articleApprovalFlowService) GetMyApprovalQueue(clientId *uuid.UUID, userLevelId uint, filters ApprovalFilters) ([]*DetailedApprovalFlow, paginator.Pagination, error) { + // Build query with filters + query := s.Repo.BuildApprovalQueueQuery(clientId, userLevelId, filters) + + // Get paginated results + approvalFlows, pagination, err := s.Repo.GetPaginatedApprovalQueue(query, filters.Page, filters.Limit) + if err != nil { + return nil, paginator.Pagination{}, err + } + + // Transform to detailed approval flows + detailedFlows := make([]*DetailedApprovalFlow, 0, len(approvalFlows)) + for _, flow := range approvalFlows { + detailedFlow, err := s.transformToDetailedFlow(flow, filters.IncludePreview) + if err != nil { + s.Log.Error().Err(err).Uint("flow_id", flow.ID).Msg("Failed to transform approval flow") + continue + } + detailedFlows = append(detailedFlows, detailedFlow) + } + + return detailedFlows, pagination, nil +} + +func (s *articleApprovalFlowService) GetApprovalStatistics(clientId *uuid.UUID, userLevelId uint) (*ApprovalStatistics, error) { + stats := &ApprovalStatistics{} + + // Get pending count + pendingCount, err := s.Repo.GetPendingCountByLevel(clientId, userLevelId) + if err != nil { + return nil, err + } + stats.PendingCount = pendingCount + + // Get overdue count + overdueCount, err := s.Repo.GetOverdueCountByLevel(clientId, userLevelId) + if err != nil { + return nil, err + } + stats.OverdueCount = overdueCount + + // Get approval counts by time period + now := time.Now() + startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + startOfWeek := startOfDay.AddDate(0, 0, -int(now.Weekday())) + startOfMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) + + stats.ApprovedToday, _ = s.Repo.GetApprovedCountByPeriod(clientId, userLevelId, startOfDay, now) + stats.ApprovedThisWeek, _ = s.Repo.GetApprovedCountByPeriod(clientId, userLevelId, startOfWeek, now) + stats.ApprovedThisMonth, _ = s.Repo.GetApprovedCountByPeriod(clientId, userLevelId, startOfMonth, now) + stats.RejectedThisMonth, _ = s.Repo.GetRejectedCountByPeriod(clientId, userLevelId, startOfMonth, now) + stats.RevisionRequestsThisMonth, _ = s.Repo.GetRevisionRequestCountByPeriod(clientId, userLevelId, startOfMonth, now) + + // Get timing statistics + timingStats, err := s.Repo.GetApprovalTimingStats(clientId, userLevelId) + if err == nil { + stats.AverageApprovalTimeHours = timingStats.AverageHours + stats.FastestApprovalMinutes = timingStats.FastestMinutes + stats.SlowestApprovalHours = timingStats.SlowestHours + } + + // Calculate approval rate + totalProcessed := stats.ApprovedThisMonth + stats.RejectedThisMonth + if totalProcessed > 0 { + stats.ApprovalRatePercentage = float64(stats.ApprovedThisMonth) / float64(totalProcessed) * 100 + } + + // Get categories breakdown + categoriesBreakdown, err := s.Repo.GetCategoriesBreakdown(clientId, userLevelId) + if err == nil { + stats.CategoriesBreakdown = categoriesBreakdown + } + + return stats, nil +} + +func (s *articleApprovalFlowService) GetWorkloadDistribution(clientId *uuid.UUID, userLevelId uint) (*WorkloadDistribution, error) { + distribution := &WorkloadDistribution{} + + // Get my level workload + myLevel, err := s.Repo.GetLevelWorkload(clientId, userLevelId) + if err != nil { + return nil, err + } + distribution.MyLevel = *myLevel + + // Get team comparison (same level users) + teamMembers, err := s.Repo.GetTeamWorkloadComparison(clientId, userLevelId) + if err == nil { + distribution.TeamComparison = teamMembers + } + + // Identify bottlenecks + bottlenecks, err := s.Repo.GetWorkflowBottlenecks(clientId) + if err == nil { + distribution.Bottlenecks = bottlenecks + } + + return distribution, nil +} + +func (s *articleApprovalFlowService) BulkApprovalAction(clientId *uuid.UUID, articleIds []uint, action string, message *string, approver *users.Users) (*BulkActionResult, error) { + result := &BulkActionResult{ + ProcessedCount: len(articleIds), + Results: make([]BulkActionItemResult, 0, len(articleIds)), + } + + for _, articleId := range articleIds { + itemResult := BulkActionItemResult{ + ArticleId: articleId, + } + + err := s.ProcessApprovalStep(clientId, articleId, action, message, approver) + if err != nil { + itemResult.Status = "failed" + errorMsg := err.Error() + itemResult.Error = &errorMsg + result.FailedCount++ + } else { + itemResult.Status = "success" + + // Get next step info + approvalFlow, err := s.Repo.FindActiveByArticleId(articleId) + if err == nil { + if approvalFlow.StatusId == 2 { + itemResult.NextStep = "published" + } else { + itemResult.NextStep = fmt.Sprintf("%d", approvalFlow.CurrentStep) + } + } + + result.SuccessfulCount++ + } + + result.Results = append(result.Results, itemResult) + } + + return result, nil +} + +func (s *articleApprovalFlowService) transformToDetailedFlow(flow *entity.ArticleApprovalFlows, includePreview bool) (*DetailedApprovalFlow, error) { + // This method would transform the basic approval flow to detailed view + // Implementation would include: + // 1. Fetch article details with author info + // 2. Calculate approval context (deadlines, role description) + // 3. Build workflow progress information + // 4. Add content preview if requested + + detailedFlow := &DetailedApprovalFlow{ + ID: flow.ID, + // Article details would be populated from database joins + // ApprovalContext would be calculated based on workflow step + // WorkflowProgress would show current position in workflow + } + + return detailedFlow, nil +} +``` + +## 3. Controller Implementation + +### Article Approval Controller + +```go +// app/module/articles/controller/articles_approval.controller.go +package controller + +import ( + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + "strconv" + "web-qudo-be/app/middleware" + "web-qudo-be/app/module/article_approval_flows/service" + "web-qudo-be/app/module/articles/request" + usersRepository "web-qudo-be/app/module/users/repository" + utilRes "web-qudo-be/utils/response" + utilSvc "web-qudo-be/utils/service" + utilVal "web-qudo-be/utils/validator" +) + +type articleApprovalController struct { + approvalFlowService service.ArticleApprovalFlowService + usersRepo usersRepository.UsersRepository + log zerolog.Logger +} + +type ArticleApprovalController interface { + SubmitForApproval(c *fiber.Ctx) error + ApproveStep(c *fiber.Ctx) error + RejectArticle(c *fiber.Ctx) error + RequestRevision(c *fiber.Ctx) error + GetPendingApprovals(c *fiber.Ctx) error + GetApprovalHistory(c *fiber.Ctx) error +} + +func NewArticleApprovalController( + approvalFlowService service.ArticleApprovalFlowService, + usersRepo usersRepository.UsersRepository, + log zerolog.Logger, +) ArticleApprovalController { + return &articleApprovalController{ + approvalFlowService: approvalFlowService, + usersRepo: usersRepo, + log: log, + } +} + +// SubmitForApproval submit article for approval +// @Summary Submit article for approval +// @Description API for submitting article for approval workflow +// @Tags Articles +// @Security Bearer +// @Param id path int true "Article ID" +// @Param payload body request.SubmitApprovalRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/{id}/submit-approval [post] +func (ctrl *articleApprovalController) SubmitForApproval(c *fiber.Ctx) error { + clientId := middleware.GetClientId(c) + + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.SubmitApprovalRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + approvalFlow, err := ctrl.approvalFlowService.InitiateApprovalFlow(clientId, uint(id), req.WorkflowId) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Article submitted for approval successfully"}, + Data: approvalFlow, + }) +} + +// ApproveStep approve current approval step +// @Summary Approve article step +// @Description API for approving current approval step +// @Tags Articles +// @Security Bearer +// @Param id path int true "Article ID" +// @Param payload body request.ApprovalActionRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/{id}/approve [post] +func (ctrl *articleApprovalController) ApproveStep(c *fiber.Ctx) error { + clientId := middleware.GetClientId(c) + authToken := c.Get("Authorization") + + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ApprovalActionRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get current user info + approver := utilSvc.GetUserInfo(ctrl.log, ctrl.usersRepo, authToken) + + err = ctrl.approvalFlowService.ProcessApprovalStep(clientId, uint(id), "approved", req.Message, approver) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Article approved successfully"}, + }) +} + +// RequestRevision request revision for article +// @Summary Request article revision +// @Description API for requesting article revision +// @Tags Articles +// @Security Bearer +// @Param id path int true "Article ID" +// @Param payload body request.ApprovalActionRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/{id}/request-revision [post] +func (ctrl *articleApprovalController) RequestRevision(c *fiber.Ctx) error { + clientId := middleware.GetClientId(c) + authToken := c.Get("Authorization") + + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ApprovalActionRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get current user info + approver := utilSvc.GetUserInfo(ctrl.log, ctrl.usersRepo, authToken) + + err = ctrl.approvalFlowService.ProcessApprovalStep(clientId, uint(id), "revision_requested", req.Message, approver) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Revision requested successfully"}, + }) +} +``` + +## 4. Request/Response Models + +### Request Models + +```go +// app/module/articles/request/approval.request.go +package request + +type SubmitApprovalRequest struct { + WorkflowId uint `json:"workflowId" validate:"required"` +} + +type ApprovalActionRequest struct { + Message *string `json:"message"` +} + +// app/module/approval_workflows/request/approval_workflows.request.go +package request + +import "web-qudo-be/app/database/entity" + +type ApprovalWorkflowCreateRequest struct { + Name string `json:"name" validate:"required"` + Description *string `json:"description"` + Steps []ApprovalWorkflowStepRequest `json:"steps" validate:"required,min=1"` +} + +type ApprovalWorkflowStepRequest struct { + StepOrder int `json:"stepOrder" validate:"required,min=1"` + UserLevelId uint `json:"userLevelId" validate:"required"` + IsRequired bool `json:"isRequired"` + CanSkip bool `json:"canSkip"` +} + +func (req ApprovalWorkflowCreateRequest) ToEntity() *entity.ApprovalWorkflows { + workflow := &entity.ApprovalWorkflows{ + Name: req.Name, + Description: req.Description, + IsActive: &[]bool{true}[0], + } + + for _, stepReq := range req.Steps { + step := entity.ApprovalWorkflowSteps{ + StepOrder: stepReq.StepOrder, + UserLevelId: stepReq.UserLevelId, + IsRequired: &stepReq.IsRequired, + CanSkip: &stepReq.CanSkip, + } + workflow.Steps = append(workflow.Steps, step) + } + + return workflow +} +``` + +## 5. Migration Scripts + +### Database Migration + +```sql +-- migrations/001_create_approval_system_tables.sql + +-- Create approval_workflows table +CREATE TABLE approval_workflows ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description TEXT, + is_active BOOLEAN DEFAULT true, + client_id UUID, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- Create approval_workflow_steps table +CREATE TABLE approval_workflow_steps ( + id SERIAL PRIMARY KEY, + workflow_id INT NOT NULL REFERENCES approval_workflows(id) ON DELETE CASCADE, + step_order INT NOT NULL, + user_level_id INT NOT NULL REFERENCES user_levels(id), + is_required BOOLEAN DEFAULT true, + can_skip BOOLEAN DEFAULT false, + client_id UUID, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + UNIQUE(workflow_id, step_order) +); + +-- Create article_approval_flows table +CREATE TABLE article_approval_flows ( + id SERIAL PRIMARY KEY, + article_id INT NOT NULL REFERENCES articles(id) ON DELETE CASCADE, + workflow_id INT NOT NULL REFERENCES approval_workflows(id), + current_step INT DEFAULT 1, + status_id INT DEFAULT 1, -- 1=In Progress, 2=Completed, 3=Rejected, 4=Revision Requested + client_id UUID, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- Create article_approval_step_logs table +CREATE TABLE article_approval_step_logs ( + id SERIAL PRIMARY KEY, + article_flow_id INT NOT NULL REFERENCES article_approval_flows(id) ON DELETE CASCADE, + step_order INT NOT NULL, + user_level_id INT NOT NULL REFERENCES user_levels(id), + approved_by INT NOT NULL REFERENCES users(id), + action VARCHAR(50) NOT NULL CHECK (action IN ('approved', 'rejected', 'revision_requested')), + message TEXT, + approved_at TIMESTAMP, + client_id UUID, + created_at TIMESTAMP DEFAULT NOW() +); + +-- Add indexes for performance +CREATE INDEX idx_approval_workflows_client_id ON approval_workflows(client_id); +CREATE INDEX idx_approval_workflows_is_active ON approval_workflows(is_active); +CREATE INDEX idx_approval_workflow_steps_workflow_id ON approval_workflow_steps(workflow_id); +CREATE INDEX idx_article_approval_flows_article_id ON article_approval_flows(article_id); +CREATE INDEX idx_article_approval_flows_status_id ON article_approval_flows(status_id); +CREATE INDEX idx_article_approval_step_logs_article_flow_id ON article_approval_step_logs(article_flow_id); +CREATE INDEX idx_article_approval_step_logs_approved_by ON article_approval_step_logs(approved_by); + +-- Modify articles table +ALTER TABLE articles ADD COLUMN workflow_id INT REFERENCES approval_workflows(id); +ALTER TABLE articles ADD COLUMN current_approval_step INT DEFAULT 0; + +-- Create default workflow for backward compatibility +INSERT INTO approval_workflows (name, description, is_active) +VALUES ('Legacy 3-Level Approval', 'Default 3-level approval system for backward compatibility', true); + +-- Get the workflow ID (assuming it's 1 for the first insert) +INSERT INTO approval_workflow_steps (workflow_id, step_order, user_level_id, is_required, can_skip) +VALUES +(1, 1, 3, true, false), -- Level 3 approver (first step) +(1, 2, 2, true, false), -- Level 2 approver (second step) +(1, 3, 1, true, false); -- Level 1 approver (final step) + +-- Update existing articles to use default workflow +UPDATE articles SET workflow_id = 1 WHERE workflow_id IS NULL; +``` + +## 6. Usage Examples + +### Creating a Custom Workflow + +```bash +# Create a 2-level approval workflow +curl -X POST http://localhost:8800/api/approval-workflows \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "name": "Simple 2-Level Approval", + "description": "Quick approval for simple articles", + "steps": [ + { + "stepOrder": 1, + "userLevelId": 2, + "isRequired": true, + "canSkip": false + }, + { + "stepOrder": 2, + "userLevelId": 1, + "isRequired": true, + "canSkip": false + } + ] + }' +``` + +### Submitting Article for Approval + +```bash +# Submit article ID 123 for approval using workflow ID 2 +curl -X POST http://localhost:8800/api/articles/123/submit-approval \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "workflowId": 2 + }' +``` + +### Approving an Article + +```bash +# Approve article ID 123 +curl -X POST http://localhost:8800/api/articles/123/approve \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "message": "Article looks good, approved for next level" + }' +``` + +### Requesting Revision + +```bash +# Request revision for article ID 123 +curl -X POST http://localhost:8800/api/articles/123/request-revision \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "message": "Please fix the grammar issues in paragraph 2" + }' +``` + +Dengan implementasi ini, sistem approval artikel menjadi sangat fleksibel dan dapat disesuaikan dengan kebutuhan organisasi yang berbeda-beda. Workflow dapat dibuat, dimodifikasi, dan dinonaktifkan tanpa mengubah kode aplikasi. diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..1c60de2 --- /dev/null +++ b/readme.md @@ -0,0 +1,7 @@ +``` +go get -v ./... +go mod vendor -v + +swag init -g main.go --output docs/swagger +go run main.go +``` \ No newline at end of file diff --git a/storage/ascii_art.txt b/storage/ascii_art.txt new file mode 100644 index 0000000..040b6c0 --- /dev/null +++ b/storage/ascii_art.txt @@ -0,0 +1,9 @@ + +______ _ _ _____ _ _ +| ___(_) | / ___| | | | +| |_ _| |__ ___ _ __ \ `--.| |_ __ _ _ __| |_ ___ _ __ +| _| | | '_ \ / _ \ '__| `--. \ __/ _` | '__| __/ _ \ '__| +| | | | |_) | __/ | /\__/ / || (_| | | | || __/ | +\_| |_|_.__/ \___|_| \____/ \__\__,_|_| \__\___|_| + + diff --git a/storage/private.go b/storage/private.go new file mode 100644 index 0000000..c4af254 --- /dev/null +++ b/storage/private.go @@ -0,0 +1,8 @@ +package storage + +import ( + "embed" +) + +//go:embed private/* +var Private embed.FS diff --git a/storage/private/example.html b/storage/private/example.html new file mode 100644 index 0000000..f8f360d --- /dev/null +++ b/storage/private/example.html @@ -0,0 +1 @@ +Example html file for private storage. \ No newline at end of file diff --git a/storage/public/example.txt b/storage/public/example.txt new file mode 100644 index 0000000..15e7931 --- /dev/null +++ b/storage/public/example.txt @@ -0,0 +1 @@ +Example txt file for public storage. \ No newline at end of file diff --git a/utils/index.utils.go b/utils/index.utils.go new file mode 100644 index 0000000..4d891a2 --- /dev/null +++ b/utils/index.utils.go @@ -0,0 +1,17 @@ +package utils + +import "github.com/gofiber/fiber/v2" + +func IsEnabled(key bool) func(c *fiber.Ctx) bool { + if key { + return nil + } + + return func(c *fiber.Ctx) bool { return true } +} + +func CsrfErrorHandler(c *fiber.Ctx, err error) error { + return c.Status(fiber.StatusForbidden).JSON(fiber.Map{ + "error": "CSRF protection: " + err.Error(), + }) +} diff --git a/utils/middleware/client_utils.go b/utils/middleware/client_utils.go new file mode 100644 index 0000000..98b161c --- /dev/null +++ b/utils/middleware/client_utils.go @@ -0,0 +1,56 @@ +package middleware + +import ( + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" + "gorm.io/gorm" + "web-qudo-be/app/middleware" +) + +// AddClientFilter adds client_id filter to GORM query +func AddClientFilter(db *gorm.DB, c *fiber.Ctx) *gorm.DB { + if c == nil { + return db + } + clientID := middleware.GetClientID(c) + if clientID != nil { + return db.Where("client_id = ?", clientID) + } + return db +} + +// SetClientID sets client_id in the given struct if it has a ClientId field +func SetClientID(c *fiber.Ctx, model interface{}) { + if c == nil { + return + } + clientID := middleware.GetClientID(c) + if clientID != nil { + // Use reflection to set ClientId field if it exists + if setter, ok := model.(interface{ SetClientID(*uuid.UUID) }); ok { + setter.SetClientID(clientID) + } + } +} + +// ValidateClientAccess validates if the current client has access to the resource +func ValidateClientAccess(db *gorm.DB, c *fiber.Ctx, tableName string, resourceID interface{}) error { + if c == nil { + return nil // Skip validation for background jobs + } + clientID := middleware.GetClientID(c) + if clientID == nil { + return fiber.NewError(fiber.StatusUnauthorized, "Client not authenticated") + } + + var count int64 + if err := db.Table(tableName).Where("id = ? AND client_id = ?", resourceID, clientID).Count(&count).Error; err != nil { + return err + } + + if count == 0 { + return fiber.NewError(fiber.StatusForbidden, "Access denied to this resource") + } + + return nil +} diff --git a/utils/paginator/index.paginator.go b/utils/paginator/index.paginator.go new file mode 100644 index 0000000..9b7823a --- /dev/null +++ b/utils/paginator/index.paginator.go @@ -0,0 +1,72 @@ +package paginator + +import ( + "math" + "strconv" + + "github.com/gofiber/fiber/v2" +) + +const ( + defaultLimit = 10 + defaultSort = "desc" + defaultSortBy = "id" +) + +type Pagination struct { + Limit int `json:"limit,omitempty"` + Offset int `json:"-"` + Page int `json:"page,omitempty"` + NextPage int `json:"nextPage,omitempty"` + PreviousPage int `json:"previousPage,omitempty"` + Count int64 `json:"count,omitempty"` + TotalPage int `json:"totalPage,omitempty"` + Sort string `json:"sort,omitempty"` + SortBy string `json:"sortBy,omitempty"` +} + +func Paging(p *Pagination) *Pagination { + p.TotalPage = int(math.Ceil(float64(p.Count) / float64(p.Limit))) + if p.Page > 1 { + p.PreviousPage = p.Page - 1 + } else { + p.PreviousPage = p.Page + } + if p.Page == p.TotalPage { + p.NextPage = p.Page + } else { + p.NextPage = p.Page + 1 + } + return p +} + +func Paginate(c *fiber.Ctx) (*Pagination, error) { + limit, err := strconv.Atoi(c.Query("limit")) + if err != nil { + limit = defaultLimit + } + page, err := strconv.Atoi(c.Query("page")) + if err != nil { + page = 1 + } + sort := c.Query("sort") + if sort == "" { + sort = defaultSort + } + sortBy := c.Query("sortBy") + if sortBy == "" { + sortBy = defaultSortBy + } + p := &Pagination{ + Limit: limit, + Page: page, + Sort: sort, + SortBy: sortBy, + } + if p.Page == 0 { + p.Page = 1 + } + + p.Offset = (p.Page - 1) * p.Limit + return p, nil +} diff --git a/utils/response/error.response.go b/utils/response/error.response.go new file mode 100644 index 0000000..7c831bb --- /dev/null +++ b/utils/response/error.response.go @@ -0,0 +1,19 @@ +package response + +type BadRequestError struct { + Success bool `json:"success" example:"false"` + Code int `json:"code" example:"400"` + Message string `json:"message" example:"bad request"` +} + +type UnauthorizedError struct { + Success bool `json:"success" example:"false"` + Code int `json:"code" example:"401"` + Message string `json:"message" example:"unauthorized access"` +} + +type InternalServerError struct { + Success bool `json:"success" example:"false"` + Code int `json:"code" example:"500"` + Message string `json:"message" example:"internal server error"` +} diff --git a/utils/response/index.response.go b/utils/response/index.response.go new file mode 100644 index 0000000..562bf11 --- /dev/null +++ b/utils/response/index.response.go @@ -0,0 +1,112 @@ +package response + +import ( + "fmt" + "strings" + val "web-qudo-be/utils/validator" + + validator "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog/log" +) + +// Alias for any slice +type Messages = []any + +type Error struct { + Success bool `json:"success"` + Code int `json:"code"` + Message any `json:"message"` +} + +// error makes it compatible with the error interface +func (e *Error) Error() string { + return fmt.Sprint(e.Message) +} + +// A struct to return normal response +type Response struct { + Success bool `json:"success" example:"true"` + Code int `json:"code" example:"200"` + Messages Messages `json:"messages"` + Data any `json:"data,omitempty"` + Meta any `json:"meta,omitempty"` +} + +// nothiing to describe this fucking variable +var IsProduction bool + +// Default error handler +var ErrorHandler = func(c *fiber.Ctx, err error) error { + resp := Response{ + Code: fiber.StatusInternalServerError, + } + + // handle errors + if c, ok := err.(validator.ValidationErrors); ok { + resp.Code = fiber.StatusUnprocessableEntity + resp.Messages = Messages{removeTopStruct(c.Translate(val.Trans))} + } else if c, ok := err.(*fiber.Error); ok { + resp.Code = c.Code + resp.Messages = Messages{c.Message} + } else if c, ok := err.(*Error); ok { + resp.Code = c.Code + resp.Messages = Messages{c.Message} + + // for ent and other errors + if resp.Messages == nil { + resp.Messages = Messages{err} + } + } else { + resp.Messages = Messages{err.Error()} + } + + if !IsProduction { + log.Error().Err(err).Msg("From: Fiber's error handler") + } + + return Resp(c, resp) +} + +// function to return pretty json response +func Resp(c *fiber.Ctx, resp Response) error { + // set status + if resp.Code == 0 { + resp.Code = fiber.StatusOK + } + c.Status(resp.Code) + // return response + return c.JSON(resp) +} + +// remove unecessary fields from validator message +func removeTopStruct(fields map[string]string) map[string]string { + res := map[string]string{} + + for field, msg := range fields { + stripStruct := field[strings.Index(field, ".")+1:] + res[stripStruct] = msg + } + + return res +} + +func Unauthorized() *Response { + return &Response{ + Success: false, + Code: 401, + Data: nil, + Messages: Messages{ + "Unauthorized", + }, + } +} + +// ErrorBadRequest returns a bad request error response +func ErrorBadRequest(c *fiber.Ctx, message string) error { + return c.Status(fiber.StatusBadRequest).JSON(Response{ + Success: false, + Code: 400, + Messages: Messages{message}, + }) +} diff --git a/utils/service/string.service.go b/utils/service/string.service.go new file mode 100644 index 0000000..d753f93 --- /dev/null +++ b/utils/service/string.service.go @@ -0,0 +1,64 @@ +package service + +import ( + "crypto/rand" + "encoding/json" + "math/big" + "strings" + "unicode" +) + +func IsNumeric(str string) bool { + for _, char := range str { + if !unicode.IsDigit(char) { + return false + } + } + return true +} + +func MakeSlug(s string) string { + // Convert to lowercase + s = strings.ToLower(s) + + // Replace spaces with hyphens + s = strings.ReplaceAll(s, " ", "-") + + // Remove non-alphanumeric characters + return strings.Map(func(r rune) rune { + if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-' { + return r + } + return -1 + }, s) +} + +func GenerateNumericCode(codeLength int) (string, error) { + const digits = "0123456789" + result := make([]byte, codeLength) + + for i := 0; i < codeLength; i++ { + num, err := rand.Int(rand.Reader, big.NewInt(int64(len(digits)))) + if err != nil { + return "", err + } + result[i] = digits[num.Int64()] + } + + return string(result), nil +} + +func StructToMap(obj interface{}) (map[string]interface{}, error) { + var result map[string]interface{} + jsonData, err := json.Marshal(obj) + if err != nil { + return nil, err + } + + err = json.Unmarshal(jsonData, &result) + if err != nil { + return nil, err + } + + return result, nil +} diff --git a/utils/service/user_utils.service.go b/utils/service/user_utils.service.go new file mode 100644 index 0000000..81b607e --- /dev/null +++ b/utils/service/user_utils.service.go @@ -0,0 +1,42 @@ +package service + +import ( + "github.com/golang-jwt/jwt/v5" + "github.com/rs/zerolog" + "strings" + "time" + "web-qudo-be/app/database/entity/users" + "web-qudo-be/app/module/users/repository" +) + +func GetUserInfo(log zerolog.Logger, repo repository.UsersRepository, bearerToken string) *users.Users { + tokenString := strings.TrimPrefix(bearerToken, "Bearer ") + token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{}) + if err != nil { + return nil + } + claims := token.Claims.(jwt.MapClaims) + sub := claims["sub"].(string) + + user, err := repo.FindByKeycloakId(sub) + if err != nil { + return nil + } + + log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:GetUserInfo", "UserInfo:GetUserInfo"). + Interface("payload", user).Msg("") + + return user +} + +func GetUserId(bearerToken string) *string { + tokenString := strings.TrimPrefix(bearerToken, "Bearer ") + token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{}) + if err != nil { + return nil + } + claims := token.Claims.(jwt.MapClaims) + sub := claims["sub"].(string) + return &sub +} diff --git a/utils/validator/index.validator.go b/utils/validator/index.validator.go new file mode 100644 index 0000000..d68aafd --- /dev/null +++ b/utils/validator/index.validator.go @@ -0,0 +1,63 @@ +package response + +import ( + "github.com/go-playground/locales/en" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" + ent "github.com/go-playground/validator/v10/translations/en" + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog/log" + "reflect" +) + +var ( + validate *validator.Validate + uni *ut.UniversalTranslator + Trans ut.Translator +) + +func init() { + validate = validator.New() + + uni = ut.New(en.New()) + Trans, _ = uni.GetTranslator("en") + + if err := ent.RegisterDefaultTranslations(validate, Trans); err != nil && !fiber.IsChild() { + log.Panic().Err(err).Msg("") + } +} + +func ValidateStruct(input any) error { + return validate.Struct(input) +} + +func ParseBody(c *fiber.Ctx, body any) error { + if err := c.BodyParser(body); err != nil { + return err + } + + return nil +} + +func ParseAndValidate(c *fiber.Ctx, body any) error { + v := reflect.ValueOf(body) + + switch v.Kind() { + case reflect.Ptr: + err := ParseBody(c, body) + if err != nil { + return err + } + + return ValidateStruct(v.Elem().Interface()) + case reflect.Struct: + err := ParseBody(c, &body) + if err != nil { + return err + } + + return ValidateStruct(v) + default: + return nil + } +} diff --git a/web-qudo-be.exe b/web-qudo-be.exe new file mode 100644 index 0000000..162f894 Binary files /dev/null and b/web-qudo-be.exe differ