feat: update advertisement, users, articles, etc

This commit is contained in:
hanif salafi 2025-04-07 08:12:02 +07:00
parent 4f3e92808b
commit 31ad889695
30 changed files with 1293 additions and 44 deletions

View File

@ -11,6 +11,7 @@ type Advertisement struct {
ContentFileName *string `json:"content_file_name" gorm:"type:varchar"` ContentFileName *string `json:"content_file_name" gorm:"type:varchar"`
Placement string `json:"placement" gorm:"type:varchar"` Placement string `json:"placement" gorm:"type:varchar"`
StatusId int `json:"status_id" gorm:"type:int4"` StatusId int `json:"status_id" gorm:"type:int4"`
IsPublish bool `json:"is_publish" gorm:"type:bool;default:true"`
IsActive bool `json:"is_active" gorm:"type:bool;default:true"` IsActive bool `json:"is_active" gorm:"type:bool;default:true"`
CreatedAt time.Time `json:"created_at" gorm:"default:now()"` CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`

View File

@ -24,6 +24,7 @@ type Articles struct {
NeedApprovalFrom *int `json:"need_approval_from" gorm:"type:int4"` NeedApprovalFrom *int `json:"need_approval_from" gorm:"type:int4"`
HasApprovedBy *string `json:"has_approved_by" gorm:"type:varchar"` HasApprovedBy *string `json:"has_approved_by" gorm:"type:varchar"`
IsPublish *bool `json:"is_publish" gorm:"type:bool;default:false"` 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"` PublishedAt *time.Time `json:"published_at" gorm:"type:timestamp"`
IsDraft *bool `json:"is_draft" gorm:"type:bool;default:false"` IsDraft *bool `json:"is_draft" gorm:"type:bool;default:false"`
DraftedAt *time.Time `json:"drafted_at" gorm:"type:timestamp"` DraftedAt *time.Time `json:"drafted_at" gorm:"type:timestamp"`

View File

@ -0,0 +1,17 @@
package entity
import "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
CreatedAt time.Time
}

View File

@ -2,10 +2,11 @@ package entity
import "time" import "time"
type RegistrationOtps struct { type OneTimePasswords struct {
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
Email string `json:"email" gorm:"type:varchar"` Email string `json:"email" gorm:"type:varchar"`
Name *string `json:"name" 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"` OtpCode string `json:"otp_code" gorm:"type:varchar"`
ValidUntil time.Time `json:"valid_until" gorm:"default:(NOW() + INTERVAL '10 minutes')"` ValidUntil time.Time `json:"valid_until" gorm:"default:(NOW() + INTERVAL '10 minutes')"`
IsActive bool `json:"is_active" gorm:"type:bool"` IsActive bool `json:"is_active" gorm:"type:bool"`

View File

@ -28,6 +28,7 @@ type Users struct {
CreatedById *uint `json:"created_by_id" gorm:"type:int4"` CreatedById *uint `json:"created_by_id" gorm:"type:int4"`
ProfilePicturePath *string `json:"profile_picture_path" gorm:"type:varchar"` ProfilePicturePath *string `json:"profile_picture_path" gorm:"type:varchar"`
TempPassword *string `json:"temp_password" gorm:"type:varchar"` TempPassword *string `json:"temp_password" gorm:"type:varchar"`
IsEmailUpdated *bool `json:"is_email_updated" gorm:"type:bool;default:false"`
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
CreatedAt time.Time `json:"created_at" gorm:"default:now()"` CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`

View File

@ -93,6 +93,7 @@ func Models() []interface{} {
entity.ArticleFiles{}, entity.ArticleFiles{},
entity.ArticleComments{}, entity.ArticleComments{},
entity.ArticleNulisAI{}, entity.ArticleNulisAI{},
entity.AuditTrails{},
entity.Cities{}, entity.Cities{},
entity.CustomStaticPages{}, entity.CustomStaticPages{},
entity.Districts{}, entity.Districts{},
@ -105,7 +106,7 @@ func Models() []interface{} {
entity.MasterStatuses{}, entity.MasterStatuses{},
entity.MasterApprovalStatuses{}, entity.MasterApprovalStatuses{},
entity.Provinces{}, entity.Provinces{},
entity.RegistrationOtps{}, entity.OneTimePasswords{},
entity.UserLevels{}, entity.UserLevels{},
entity.UserRoles{}, entity.UserRoles{},
entity.UserRoleAccesses{}, entity.UserRoleAccesses{},

View File

@ -1,6 +1,7 @@
package middleware package middleware
import ( import (
"go-humas-be/app/database"
"go-humas-be/config/config" "go-humas-be/config/config"
"go-humas-be/utils" "go-humas-be/utils"
"time" "time"
@ -12,6 +13,8 @@ import (
"github.com/gofiber/fiber/v2/middleware/monitor" "github.com/gofiber/fiber/v2/middleware/monitor"
"github.com/gofiber/fiber/v2/middleware/pprof" "github.com/gofiber/fiber/v2/middleware/pprof"
"github.com/gofiber/fiber/v2/middleware/recover" "github.com/gofiber/fiber/v2/middleware/recover"
auditTrails "go-humas-be/config/middleware"
) )
// Middleware is a struct that contains all the middleware functions // Middleware is a struct that contains all the middleware functions
@ -28,7 +31,7 @@ func NewMiddleware(app *fiber.App, cfg *config.Config) *Middleware {
} }
// Register registers all the middleware functions // Register registers all the middleware functions
func (m *Middleware) Register() { func (m *Middleware) Register(db *database.Database) {
// Add Extra Middlewares // Add Extra Middlewares
m.App.Use(limiter.New(limiter.Config{ m.App.Use(limiter.New(limiter.Config{
@ -60,6 +63,8 @@ func (m *Middleware) Register() {
MaxAge: 12, MaxAge: 12,
})) }))
m.App.Use(auditTrails.AuditTrailsMiddleware(db.DB))
//m.App.Use(filesystem.New(filesystem.Config{ //m.App.Use(filesystem.New(filesystem.Config{
// Next: utils.IsEnabled(m.Cfg.Middleware.FileSystem.Enable), // Next: utils.IsEnabled(m.Cfg.Middleware.FileSystem.Enable),
// Root: http.Dir(m.Cfg.Middleware.FileSystem.Root), // Root: http.Dir(m.Cfg.Middleware.FileSystem.Root),

View File

@ -48,7 +48,9 @@ func (_i *AdvertisementRouter) RegisterAdvertisementRoutes() {
router.Get("/:id", advertisementController.Show) router.Get("/:id", advertisementController.Show)
router.Post("/", advertisementController.Save) router.Post("/", advertisementController.Save)
router.Post("/upload/:id", advertisementController.Upload) router.Post("/upload/:id", advertisementController.Upload)
router.Get("/viewer/:filename", advertisementController.Viewer)
router.Put("/:id", advertisementController.Update) router.Put("/:id", advertisementController.Update)
router.Put("/publish/:id", advertisementController.UpdatePublish)
router.Delete("/:id", advertisementController.Delete) router.Delete("/:id", advertisementController.Delete)
}) })
} }

View File

@ -23,7 +23,9 @@ type AdvertisementController interface {
Save(c *fiber.Ctx) error Save(c *fiber.Ctx) error
Upload(c *fiber.Ctx) error Upload(c *fiber.Ctx) error
Update(c *fiber.Ctx) error Update(c *fiber.Ctx) error
UpdatePublish(c *fiber.Ctx) error
Delete(c *fiber.Ctx) error Delete(c *fiber.Ctx) error
Viewer(c *fiber.Ctx) error
} }
func NewAdvertisementController(advertisementService service.AdvertisementService, log zerolog.Logger) AdvertisementController { func NewAdvertisementController(advertisementService service.AdvertisementService, log zerolog.Logger) AdvertisementController {
@ -197,6 +199,40 @@ func (_i *advertisementController) Update(c *fiber.Ctx) error {
}) })
} }
// UpdatePublish Advertisement
// @Summary Update Publish Advertisement
// @Description API for Update Publish Advertisement
// @Tags Advertisement
// @Security Bearer
// @Param id path int true "Advertisement ID"
// @Param isPublish path 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.Params("isPublish"))
if err != nil {
return err
}
err = _i.advertisementService.UpdatePublish(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 // Delete delete Advertisement
// @Summary delete Advertisement // @Summary delete Advertisement
// @Description API for delete Advertisement // @Description API for delete Advertisement
@ -224,3 +260,18 @@ func (_i *advertisementController) Delete(c *fiber.Ctx) error {
Messages: utilRes.Messages{"Advertisement successfully deleted"}, Messages: utilRes.Messages{"Advertisement successfully deleted"},
}) })
} }
// Viewer Advertisement
// @Summary Viewer Advertisement
// @Description API for Viewer Advertisement
// @Tags Advertisement
// @Security Bearer
// @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 {
return _i.advertisementService.Viewer(c)
}

View File

@ -19,6 +19,7 @@ type advertisementRepository struct {
type AdvertisementRepository interface { type AdvertisementRepository interface {
GetAll(req request.AdvertisementQueryRequest) (advertisements []*entity.Advertisement, paging paginator.Pagination, err error) GetAll(req request.AdvertisementQueryRequest) (advertisements []*entity.Advertisement, paging paginator.Pagination, err error)
FindOne(id uint) (advertisement *entity.Advertisement, err error) FindOne(id uint) (advertisement *entity.Advertisement, err error)
FindByFilename(contentFilename string) (advertisement *entity.Advertisement, err error)
Create(advertisement *entity.Advertisement) (advertisementReturn *entity.Advertisement, err error) Create(advertisement *entity.Advertisement) (advertisementReturn *entity.Advertisement, err error)
Update(id uint, advertisement *entity.Advertisement) (err error) Update(id uint, advertisement *entity.Advertisement) (err error)
Delete(id uint) (err error) Delete(id uint) (err error)
@ -88,6 +89,15 @@ func (_i *advertisementRepository) FindOne(id uint) (advertisement *entity.Adver
return advertisement, nil return advertisement, nil
} }
func (_i *advertisementRepository) FindByFilename(contentFilename string) (advertisement *entity.Advertisement, err error) {
if err := _i.DB.DB.Where("content_file_name = ?", contentFilename).First(&advertisement).Error; err != nil {
return nil, err
}
return advertisement, nil
}
func (_i *advertisementRepository) Create(advertisement *entity.Advertisement) (advertisementReturn *entity.Advertisement, err error) { func (_i *advertisementRepository) Create(advertisement *entity.Advertisement) (advertisementReturn *entity.Advertisement, err error) {
result := _i.DB.DB.Create(advertisement) result := _i.DB.DB.Create(advertisement)
return advertisement, result.Error return advertisement, result.Error

View File

@ -16,6 +16,7 @@ type AdvertisementQueryRequest struct {
Description *string `json:"description"` Description *string `json:"description"`
RedirectLink *string `json:"redirectLink"` RedirectLink *string `json:"redirectLink"`
Placement *string `json:"placement"` Placement *string `json:"placement"`
IsPublish *bool `json:"isPublish"`
StatusId *int `json:"statusId"` StatusId *int `json:"statusId"`
Pagination *paginator.Pagination `json:"pagination"` Pagination *paginator.Pagination `json:"pagination"`
} }
@ -34,6 +35,8 @@ func (req AdvertisementCreateRequest) ToEntity() *entity.Advertisement {
RedirectLink: req.RedirectLink, RedirectLink: req.RedirectLink,
Placement: req.Placement, Placement: req.Placement,
StatusId: 1, StatusId: 1,
IsPublish: true,
IsActive: true,
} }
} }
@ -62,6 +65,7 @@ type AdvertisementQueryRequestContext struct {
RedirectLink string `json:"redirectLink"` RedirectLink string `json:"redirectLink"`
Placement string `json:"placement"` Placement string `json:"placement"`
StatusId string `json:"statusId"` StatusId string `json:"statusId"`
IsPublish string `json:"isPublish"`
} }
func (req AdvertisementQueryRequestContext) ToParamRequest() AdvertisementQueryRequest { func (req AdvertisementQueryRequestContext) ToParamRequest() AdvertisementQueryRequest {
@ -79,6 +83,12 @@ func (req AdvertisementQueryRequestContext) ToParamRequest() AdvertisementQueryR
if placement := req.Placement; placement != "" { if placement := req.Placement; placement != "" {
request.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 != "" { if statusIdStr := req.StatusId; statusIdStr != "" {
statusId, err := strconv.Atoi(statusIdStr) statusId, err := strconv.Atoi(statusIdStr)
if err == nil { if err == nil {

View File

@ -14,7 +14,10 @@ import (
usersRepository "go-humas-be/app/module/users/repository" usersRepository "go-humas-be/app/module/users/repository"
minioStorage "go-humas-be/config/config" minioStorage "go-humas-be/config/config"
"go-humas-be/utils/paginator" "go-humas-be/utils/paginator"
"io"
"log"
"math/rand" "math/rand"
"mime"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -36,7 +39,9 @@ type AdvertisementService interface {
Save(req request.AdvertisementCreateRequest) (advertisement *entity.Advertisement, err error) Save(req request.AdvertisementCreateRequest) (advertisement *entity.Advertisement, err error)
Upload(c *fiber.Ctx, id uint) (err error) Upload(c *fiber.Ctx, id uint) (err error)
Update(id uint, req request.AdvertisementUpdateRequest) (err error) Update(id uint, req request.AdvertisementUpdateRequest) (err error)
UpdatePublish(id uint, isPublish bool) (err error)
Delete(id uint) error Delete(id uint) error
Viewer(c *fiber.Ctx) (err error)
} }
// NewAdvertisementService init AdvertisementService // NewAdvertisementService init AdvertisementService
@ -165,6 +170,17 @@ func (_i *advertisementService) Update(id uint, req request.AdvertisementUpdateR
return _i.Repo.Update(id, req.ToEntity()) return _i.Repo.Update(id, req.ToEntity())
} }
func (_i *advertisementService) UpdatePublish(id uint, isPublish bool) (err error) {
result, err := _i.Repo.FindOne(id)
if err != nil {
return err
}
result.IsPublish = isPublish
return _i.Repo.Update(id, result)
}
func (_i *advertisementService) Delete(id uint) error { func (_i *advertisementService) Delete(id uint) error {
result, err := _i.Repo.FindOne(id) result, err := _i.Repo.FindOne(id)
if err != nil { if err != nil {
@ -174,3 +190,58 @@ func (_i *advertisementService) Delete(id uint) error {
result.IsActive = false result.IsActive = false
return _i.Repo.Update(id, result) return _i.Repo.Update(id, result)
} }
func (_i *advertisementService) 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.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]
}

View File

@ -193,8 +193,8 @@ func (_i *articleFilesController) Delete(c *fiber.Ctx) error {
} }
// Viewer ArticleFiles // Viewer ArticleFiles
// @Summary Create ArticleFiles // @Summary Viewer ArticleFiles
// @Description API for create ArticleFiles // @Description API for Viewer ArticleFiles
// @Tags Article Files // @Tags Article Files
// @Security Bearer // @Security Bearer
// @Param filename path string true "Article File Name" // @Param filename path string true "Article File Name"

View File

@ -48,6 +48,7 @@ func (_i *ArticlesRouter) RegisterArticlesRoutes() {
router.Get("/:id", articlesController.Show) router.Get("/:id", articlesController.Show)
router.Post("/", articlesController.Save) router.Post("/", articlesController.Save)
router.Put("/:id", articlesController.Update) router.Put("/:id", articlesController.Update)
router.Put("/banner/:id", articlesController.UpdateBanner)
router.Post("/thumbnail/:id", articlesController.SaveThumbnail) router.Post("/thumbnail/:id", articlesController.SaveThumbnail)
router.Get("/thumbnail/viewer/:thumbnailName", articlesController.Viewer) router.Get("/thumbnail/viewer/:thumbnailName", articlesController.Viewer)
router.Post("/publish-scheduling", articlesController.PublishScheduling) router.Post("/publish-scheduling", articlesController.PublishScheduling)

View File

@ -21,6 +21,7 @@ type ArticlesController interface {
Save(c *fiber.Ctx) error Save(c *fiber.Ctx) error
SaveThumbnail(c *fiber.Ctx) error SaveThumbnail(c *fiber.Ctx) error
Update(c *fiber.Ctx) error Update(c *fiber.Ctx) error
UpdateBanner(c *fiber.Ctx) error
Delete(c *fiber.Ctx) error Delete(c *fiber.Ctx) error
Viewer(c *fiber.Ctx) error Viewer(c *fiber.Ctx) error
SummaryStats(c *fiber.Ctx) error SummaryStats(c *fiber.Ctx) error
@ -199,6 +200,40 @@ func (_i *articlesController) Update(c *fiber.Ctx) error {
}) })
} }
// UpdateBanner Articles
// @Summary Update Banner Articles
// @Description API for Update Banner Articles
// @Tags Articles
// @Security Bearer
// @Param id path int true "Articles ID"
// @Param isBanner path 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.Params("isBanner"))
if err != nil {
return err
}
err = _i.articlesService.UpdateBanner(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 // Delete Articles
// @Summary Delete Articles // @Summary Delete Articles
// @Description API for delete Articles // @Description API for delete Articles

View File

@ -55,6 +55,7 @@ type ArticlesService interface {
Delete(id uint) error Delete(id uint) error
UpdateActivityCount(id uint, activityTypeId int) (err error) UpdateActivityCount(id uint, activityTypeId int) (err error)
UpdateApproval(id uint, statusId int, userLevelId int, userLevelNumber int, userParentLevelId int) (err error) UpdateApproval(id uint, statusId int, userLevelId int, userLevelNumber int, userParentLevelId int) (err error)
UpdateBanner(id uint, isBanner bool) error
Viewer(c *fiber.Ctx) error Viewer(c *fiber.Ctx) error
SummaryStats(authToken string) (summaryStats *response.ArticleSummaryStats, err error) SummaryStats(authToken string) (summaryStats *response.ArticleSummaryStats, err error)
ArticlePerUserLevelStats(authToken string, startDate *string, endDate *string) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error) ArticlePerUserLevelStats(authToken string, startDate *string, endDate *string) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error)
@ -577,7 +578,15 @@ func (_i *articlesService) PublishScheduling(id uint, publishSchedule string) er
return err return err
} }
result.PublishSchedule = &publishSchedule result.PublishSchedule = &publishSchedule
return _i.Repo.Update(id, result)
}
func (_i *articlesService) UpdateBanner(id uint, isBanner bool) error {
result, err := _i.Repo.FindOne(id)
if err != nil {
return err
}
result.IsBanner = &isBanner
return _i.Repo.Update(id, result) return _i.Repo.Update(id, result)
} }

View File

@ -31,6 +31,8 @@ type UsersController interface {
ForgotPassword(c *fiber.Ctx) error ForgotPassword(c *fiber.Ctx) error
OtpRequest(c *fiber.Ctx) error OtpRequest(c *fiber.Ctx) error
OtpValidation(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 { func NewUsersController(usersService service.UsersService) UsersController {
@ -469,3 +471,59 @@ func (_i *usersController) OtpValidation(c *fiber.Ctx) error {
Messages: utilRes.Messages{"OTP is valid"}, Messages: utilRes.Messages{"OTP is valid"},
}) })
} }
// EmailValidation Users
// @Summary EmailValidation Users
// @Description API for Email Validation Users
// @Tags Users
// @Security Bearer
// @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
}
messageResponse, err := _i.usersService.EmailValidationPreLogin(*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 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
}
messageResponse, err := _i.usersService.SetupEmail(*req)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{messageResponse},
})
}

View File

@ -28,8 +28,9 @@ type UsersRepository interface {
CreateForgotPassword(forgotPasswords *entity.ForgotPasswords) (err error) CreateForgotPassword(forgotPasswords *entity.ForgotPasswords) (err error)
UpdateForgotPassword(id uint, forgotPasswords *entity.ForgotPasswords) (err error) UpdateForgotPassword(id uint, forgotPasswords *entity.ForgotPasswords) (err error)
FindForgotPassword(keycloakId string, code string) (forgotPasswords *entity.ForgotPasswords, err error) FindForgotPassword(keycloakId string, code string) (forgotPasswords *entity.ForgotPasswords, err error)
CreateRegistrationOtps(registrationOtps *entity.RegistrationOtps) (err error) CreateOtp(otp *entity.OneTimePasswords) (err error)
FindRegistrationOtps(email string, code string) (registrationOtps *entity.RegistrationOtps, 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 { func NewUsersRepository(db *database.Database, log zerolog.Logger) UsersRepository {
@ -168,15 +169,23 @@ func (_i *usersRepository) FindForgotPassword(keycloakId string, code string) (f
return forgotPasswords, nil return forgotPasswords, nil
} }
func (_i *usersRepository) CreateRegistrationOtps(registrationOtps *entity.RegistrationOtps) (err error) { func (_i *usersRepository) CreateOtp(otp *entity.OneTimePasswords) (err error) {
result := _i.DB.DB.Create(registrationOtps) result := _i.DB.DB.Create(otp)
return result.Error return result.Error
} }
func (_i *usersRepository) FindRegistrationOtps(email string, code string) (registrationOtps *entity.RegistrationOtps, err 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(&registrationOtps).Error; err != nil { if err := _i.DB.DB.Where("email = ?", email).Where("otp_code = ?", code).First(&otp).Error; err != nil {
return nil, err return nil, err
} }
return registrationOtps, nil 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
} }

View File

@ -129,14 +129,22 @@ type UserResetPassword struct {
ConfirmPassword string `json:"confirmPassword"` 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 { type UserOtpRequest struct {
Email string `json:"email" validate:"required,email"` Email string `json:"email" validate:"required,email"`
Name *string `json:"name"` Name *string `json:"name"`
} }
type UserOtpValidation struct { type UserOtpValidation struct {
Email string `json:"email"` Email *string `json:"email"`
OtpCode string `json:"otpCode"` Username *string `json:"username"`
OtpCode string `json:"otpCode"`
} }
type UsersQueryRequestContext struct { type UsersQueryRequestContext struct {

View File

@ -44,8 +44,12 @@ type UsersService interface {
SavePassword(req request.UserSavePassword, authToken string) (err error) SavePassword(req request.UserSavePassword, authToken string) (err error)
ResetPassword(req request.UserResetPassword) (err error) ResetPassword(req request.UserResetPassword) (err error)
ForgotPassword(req request.UserForgotPassword) (err error) ForgotPassword(req request.UserForgotPassword) (err error)
EmailValidationPreLogin(req request.UserEmailValidationRequest) (msgResponse *string, err error)
SetupEmail(req request.UserEmailValidationRequest) (msgResponse *string, err error)
OtpRequest(req request.UserOtpRequest) (err error) OtpRequest(req request.UserOtpRequest) (err error)
OtpValidation(req request.UserOtpValidation) (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 // NewUsersService init UsersService
@ -343,47 +347,50 @@ func (_i *usersService) OtpRequest(req request.UserOtpRequest) (err error) {
_i.Log.Info().Interface("data", req).Msg("") _i.Log.Info().Interface("data", req).Msg("")
codeRequest, err := utilSvc.GenerateNumericCode(6) codeRequest, err := utilSvc.GenerateNumericCode(6)
if req.Name == nil {
req.Name = &req.Email
}
if err != nil { if err != nil {
return err return err
} }
otpReq := entity.RegistrationOtps{ otpReq := entity.OneTimePasswords{
Email: req.Email, Email: req.Email,
Name: req.Name, Name: req.Name,
OtpCode: codeRequest, OtpCode: codeRequest,
IsActive: true, IsActive: true,
} }
subject := "[HUMAS POLRI] Permintaan OTP" err = _i.Repo.CreateOtp(&otpReq)
htmlBody := fmt.Sprintf("<p>Hai %s !</p><p>Berikut kode OTP yang digunakan untuk verifikasi.</p>", *req.Name)
htmlBody += fmt.Sprintf("<p style='padding: 10px 50px; background: #eef2f6; border-radius: 8px; max-width: 300px; text-align: center'><b>%s</b></p>", codeRequest)
htmlBody += "<p style='padding-top: 10px;'>Kode diatas hanya berlaku selama 10 menit. Harap segera masukan kode tersebut pada aplikasi HUMAS POLRI.</p>"
htmlBody += "<p style='padding-top: 10px; padding-bottom: 10px'>Demi menjaga kerahasiaan data kamu, mohon jangan membagikan kode OTP ke siapapun.</p>"
err = _i.Smtp.SendEmail(subject, req.Email, req.Email, htmlBody)
if err != nil { if err != nil {
return err return err
} }
err = _i.Repo.CreateRegistrationOtps(&otpReq) err = _i.SendRegistrationOtp(*req.Name, req.Email, codeRequest)
if err != nil { if err != nil {
return err return err
} }
// send otp to email
return nil return nil
} }
func (_i *usersService) OtpValidation(req request.UserOtpValidation) (err error) { func (_i *usersService) OtpValidation(req request.UserOtpValidation) (err error) {
_i.Log.Info().Interface("data", req).Msg("") _i.Log.Info().Interface("data", req).Msg("")
registrationOtp, err := _i.Repo.FindRegistrationOtps(req.Email, req.OtpCode) var otp *entity.OneTimePasswords
if err != nil { if req.Email == nil {
return fmt.Errorf("OTP is not valid") 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 registrationOtp != nil { if otp != nil {
if registrationOtp.ValidUntil.Before(time.Now()) { if otp.ValidUntil.Before(time.Now()) {
return fmt.Errorf("OTP has expired") return fmt.Errorf("OTP has expired")
} }
@ -393,6 +400,108 @@ func (_i *usersService) OtpValidation(req request.UserOtpValidation) (err error)
} }
} }
func (_i *usersService) EmailValidationPreLogin(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(*req.Username)
if findUser == nil || err != nil {
return nil, fmt.Errorf("username / password incorrect")
}
isTrue := true
if findUser.IsEmailUpdated != &isTrue {
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(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(*req.Username)
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
err = _i.Repo.Update(findUser.ID, findUser)
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) { func ParseJWTToken(token string) (map[string]interface{}, error) {
// Pisahkan JWT menjadi 3 bagian: header, payload, dan signature // Pisahkan JWT menjadi 3 bagian: header, payload, dan signature
parts := strings.Split(token, ".") parts := strings.Split(token, ".")
@ -414,3 +523,25 @@ func ParseJWTToken(token string) (map[string]interface{}, error) {
return payload, nil return payload, nil
} }
func (_i *usersService) SendLoginOtp(name string, email string, otp string) error {
subject := "[HUMAS POLRI] Permintaan OTP"
htmlBody := fmt.Sprintf("<p>Hai %s !</p><p>Berikut kode OTP yang digunakan untuk Login.</p>", name)
htmlBody += fmt.Sprintf("<p style='padding: 10px 50px; background: #eef2f6; border-radius: 8px; max-width: 300px; text-align: center'><b>%s</b></p>", otp)
htmlBody += "<p style='padding-top: 10px;'>Kode diatas hanya berlaku selama 10 menit. Harap segera masukkan kode tersebut pada aplikasi HUMAS POLRI.</p>"
htmlBody += "<p style='padding-top: 10px; padding-bottom: 10px'>Demi menjaga kerahasiaan data kamu, mohon jangan membagikan kode OTP ke siapapun.</p>"
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("<p>Hai %s !</p><p>Berikut kode OTP yang digunakan untuk Verifikasi Registrasi.</p>", name)
htmlBody += fmt.Sprintf("<p style='padding: 10px 50px; background: #eef2f6; border-radius: 8px; max-width: 300px; text-align: center'><b>%s</b></p>", otp)
htmlBody += "<p style='padding-top: 10px;'>Kode diatas hanya berlaku selama 10 menit. Harap segera masukkan kode tersebut pada aplikasi HUMAS POLRI.</p>"
htmlBody += "<p style='padding-top: 10px; padding-bottom: 10px'>Demi menjaga kerahasiaan data kamu, mohon jangan membagikan kode OTP ke siapapun.</p>"
err := _i.Smtp.SendEmail(subject, email, name, htmlBody)
return err
}

View File

@ -58,6 +58,7 @@ func (_i *UsersRouter) RegisterUsersRoutes() {
router.Post("/forgot-password", usersController.ForgotPassword) router.Post("/forgot-password", usersController.ForgotPassword)
router.Post("/otp-request", usersController.OtpRequest) router.Post("/otp-request", usersController.OtpRequest)
router.Post("/otp-validation", usersController.OtpValidation) router.Post("/otp-validation", usersController.OtpValidation)
router.Post("/email-validation", usersController.EmailValidation)
router.Post("/setup-email", usersController.SetupEmail)
}) })
} }

View File

@ -72,6 +72,10 @@ type middleware = struct {
Max int Max int
Expiration time.Duration `toml:"expiration_seconds"` Expiration time.Duration `toml:"expiration_seconds"`
} }
AuditTrails struct {
Enable bool
}
} }
// minio struct config // minio struct config

View File

@ -0,0 +1,42 @@
package middleware
import (
"encoding/json"
"github.com/gofiber/fiber/v2"
"go-humas-be/app/database/entity"
utilSvc "go-humas-be/utils/service"
"gorm.io/gorm"
"time"
)
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: c.IP(),
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
}
}

View File

@ -14,7 +14,7 @@ body-limit = 1048576000 # "100 * 1024 * 1024"
[db.postgres] [db.postgres]
dsn = "postgresql://humas_user:HumasDB@2024@38.47.180.165:5432/humas_db" # <driver>://<username>:<password>@<host>:<port>/<database> dsn = "postgresql://humas_user:HumasDB@2024@38.47.180.165:5432/humas_db" # <driver>://<username>:<password>@<host>:<port>/<database>
log-mode = "NONE" log-mode = "NONE"
migrate = true migrate = false
seed = false seed = false
[logger] [logger]
@ -39,7 +39,7 @@ level = 1
enable = true enable = true
[middleware.monitor] [middleware.monitor]
enable = false enable = true
path = "/monitor" path = "/monitor"
[middleware.pprof] [middleware.pprof]
@ -49,10 +49,13 @@ enable = true
enable = true enable = true
[middleware.limiter] [middleware.limiter]
enable = false enable = true
max = 20 max = 20
expiration_seconds = 60 expiration_seconds = 60
[middleware.audittrails]
enable = true
[keycloak] [keycloak]
endpoint = "http://38.47.180.165:8008" endpoint = "http://38.47.180.165:8008"
realm = "humas" realm = "humas"

View File

@ -48,8 +48,14 @@ func Start(lifecycle fx.Lifecycle, cfg *config.Config, fiber *fiber.App, router
lifecycle.Append( lifecycle.Append(
fx.Hook{ fx.Hook{
OnStart: func(ctx context.Context) error { OnStart: func(ctx context.Context) error {
// Register middlewares & routes
middlewares.Register() // Connect database
db.ConnectDatabase()
// Register middlewares
middlewares.Register(db)
// Register routes
router.Register() router.Register()
// Custom Startup Messages // Custom Startup Messages
@ -108,8 +114,6 @@ func Start(lifecycle fx.Lifecycle, cfg *config.Config, fiber *fiber.App, router
} }
}() }()
db.ConnectDatabase()
migrateFlag := flag.Bool("migrate", db.Cfg.DB.Postgres.Migrate, "migrate the database") migrateFlag := flag.Bool("migrate", db.Cfg.DB.Postgres.Migrate, "migrate the database")
seedFlag := flag.Bool("seed", db.Cfg.DB.Postgres.Seed, "seed the database") seedFlag := flag.Bool("seed", db.Cfg.DB.Postgres.Seed, "seed the database")
flag.Parse() flag.Parse()

View File

@ -343,6 +343,11 @@ const docTemplate = `{
"name": "description", "name": "description",
"in": "query" "in": "query"
}, },
{
"type": "boolean",
"name": "isPublish",
"in": "query"
},
{ {
"type": "string", "type": "string",
"name": "placement", "name": "placement",
@ -489,6 +494,62 @@ const docTemplate = `{
} }
} }
}, },
"/advertisement/publish/{id}": {
"put": {
"security": [
{
"Bearer": []
}
],
"description": "API for Update Publish Advertisement",
"tags": [
"Advertisement"
],
"summary": "Update Publish Advertisement",
"parameters": [
{
"type": "integer",
"description": "Advertisement ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "Advertisement Publish Status",
"name": "isPublish",
"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/upload/{id}": { "/advertisement/upload/{id}": {
"post": { "post": {
"security": [ "security": [
@ -548,6 +609,55 @@ const docTemplate = `{
} }
} }
}, },
"/advertisement/viewer/{filename}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for Viewer Advertisement",
"tags": [
"Advertisement"
],
"summary": "Viewer Advertisement",
"parameters": [
{
"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}": { "/advertisement/{id}": {
"get": { "get": {
"security": [ "security": [
@ -2332,11 +2442,11 @@ const docTemplate = `{
"Bearer": [] "Bearer": []
} }
], ],
"description": "API for create ArticleFiles", "description": "API for Viewer ArticleFiles",
"tags": [ "tags": [
"Article Files" "Article Files"
], ],
"summary": "Create ArticleFiles", "summary": "Viewer ArticleFiles",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@ -3148,6 +3258,62 @@ const docTemplate = `{
} }
} }
}, },
"/articles/banner/{id}": {
"put": {
"security": [
{
"Bearer": []
}
],
"description": "API for Update Banner Articles",
"tags": [
"Articles"
],
"summary": "Update Banner Articles",
"parameters": [
{
"type": "integer",
"description": "Articles ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "Articles Banner Status",
"name": "isBanner",
"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/publish-scheduling": { "/articles/publish-scheduling": {
"post": { "post": {
"security": [ "security": [
@ -8164,6 +8330,57 @@ const docTemplate = `{
} }
} }
}, },
"/users/email-validation": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "API for Email Validation Users",
"tags": [
"Users"
],
"summary": "EmailValidation Users",
"parameters": [
{
"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": { "/users/forgot-password": {
"post": { "post": {
"security": [ "security": [
@ -8579,6 +8796,57 @@ const docTemplate = `{
} }
} }
}, },
"/users/setup-email": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "API for Setup Email Users",
"tags": [
"Users"
],
"summary": "SetupEmail Users",
"parameters": [
{
"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}": { "/users/username/{username}": {
"get": { "get": {
"security": [ "security": [
@ -9568,6 +9836,23 @@ const docTemplate = `{
} }
} }
}, },
"request.UserEmailValidationRequest": {
"type": "object",
"properties": {
"newEmail": {
"type": "string"
},
"oldEmail": {
"type": "string"
},
"password": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"request.UserForgotPassword": { "request.UserForgotPassword": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -9677,6 +9962,9 @@ const docTemplate = `{
}, },
"otpCode": { "otpCode": {
"type": "string" "type": "string"
},
"username": {
"type": "string"
} }
} }
}, },

View File

@ -332,6 +332,11 @@
"name": "description", "name": "description",
"in": "query" "in": "query"
}, },
{
"type": "boolean",
"name": "isPublish",
"in": "query"
},
{ {
"type": "string", "type": "string",
"name": "placement", "name": "placement",
@ -478,6 +483,62 @@
} }
} }
}, },
"/advertisement/publish/{id}": {
"put": {
"security": [
{
"Bearer": []
}
],
"description": "API for Update Publish Advertisement",
"tags": [
"Advertisement"
],
"summary": "Update Publish Advertisement",
"parameters": [
{
"type": "integer",
"description": "Advertisement ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "Advertisement Publish Status",
"name": "isPublish",
"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/upload/{id}": { "/advertisement/upload/{id}": {
"post": { "post": {
"security": [ "security": [
@ -537,6 +598,55 @@
} }
} }
}, },
"/advertisement/viewer/{filename}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for Viewer Advertisement",
"tags": [
"Advertisement"
],
"summary": "Viewer Advertisement",
"parameters": [
{
"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}": { "/advertisement/{id}": {
"get": { "get": {
"security": [ "security": [
@ -2321,11 +2431,11 @@
"Bearer": [] "Bearer": []
} }
], ],
"description": "API for create ArticleFiles", "description": "API for Viewer ArticleFiles",
"tags": [ "tags": [
"Article Files" "Article Files"
], ],
"summary": "Create ArticleFiles", "summary": "Viewer ArticleFiles",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@ -3137,6 +3247,62 @@
} }
} }
}, },
"/articles/banner/{id}": {
"put": {
"security": [
{
"Bearer": []
}
],
"description": "API for Update Banner Articles",
"tags": [
"Articles"
],
"summary": "Update Banner Articles",
"parameters": [
{
"type": "integer",
"description": "Articles ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "Articles Banner Status",
"name": "isBanner",
"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/publish-scheduling": { "/articles/publish-scheduling": {
"post": { "post": {
"security": [ "security": [
@ -8153,6 +8319,57 @@
} }
} }
}, },
"/users/email-validation": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "API for Email Validation Users",
"tags": [
"Users"
],
"summary": "EmailValidation Users",
"parameters": [
{
"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": { "/users/forgot-password": {
"post": { "post": {
"security": [ "security": [
@ -8568,6 +8785,57 @@
} }
} }
}, },
"/users/setup-email": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "API for Setup Email Users",
"tags": [
"Users"
],
"summary": "SetupEmail Users",
"parameters": [
{
"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}": { "/users/username/{username}": {
"get": { "get": {
"security": [ "security": [
@ -9557,6 +9825,23 @@
} }
} }
}, },
"request.UserEmailValidationRequest": {
"type": "object",
"properties": {
"newEmail": {
"type": "string"
},
"oldEmail": {
"type": "string"
},
"password": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"request.UserForgotPassword": { "request.UserForgotPassword": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -9666,6 +9951,9 @@
}, },
"otpCode": { "otpCode": {
"type": "string" "type": "string"
},
"username": {
"type": "string"
} }
} }
}, },

View File

@ -573,6 +573,17 @@ definitions:
- pathUrl - pathUrl
- statusId - statusId
type: object type: object
request.UserEmailValidationRequest:
properties:
newEmail:
type: string
oldEmail:
type: string
password:
type: string
username:
type: string
type: object
request.UserForgotPassword: request.UserForgotPassword:
properties: properties:
username: username:
@ -646,6 +657,8 @@ definitions:
type: string type: string
otpCode: otpCode:
type: string type: string
username:
type: string
type: object type: object
request.UserResetPassword: request.UserResetPassword:
properties: properties:
@ -1109,6 +1122,9 @@ paths:
- in: query - in: query
name: description name: description
type: string type: string
- in: query
name: isPublish
type: boolean
- in: query - in: query
name: placement name: placement
type: string type: string
@ -1301,6 +1317,42 @@ paths:
summary: update Advertisement summary: update Advertisement
tags: tags:
- Advertisement - Advertisement
/advertisement/publish/{id}:
put:
description: API for Update Publish Advertisement
parameters:
- description: Advertisement ID
in: path
name: id
required: true
type: integer
- description: Advertisement Publish Status
in: path
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}: /advertisement/upload/{id}:
post: post:
description: API for Upload File Advertisement description: API for Upload File Advertisement
@ -1339,6 +1391,37 @@ paths:
summary: Upload Advertisement summary: Upload Advertisement
tags: tags:
- Advertisement - Advertisement
/advertisement/viewer/{filename}:
get:
description: API for Viewer Advertisement
parameters:
- 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
/article-approvals: /article-approvals:
get: get:
description: API for getting all ArticleApprovals description: API for getting all ArticleApprovals
@ -2501,7 +2584,7 @@ paths:
- Article Files - Article Files
/article-files/viewer/{filename}: /article-files/viewer/{filename}:
get: get:
description: API for create ArticleFiles description: API for Viewer ArticleFiles
parameters: parameters:
- description: Article File Name - description: Article File Name
in: path in: path
@ -2527,7 +2610,7 @@ paths:
$ref: '#/definitions/response.InternalServerError' $ref: '#/definitions/response.InternalServerError'
security: security:
- Bearer: [] - Bearer: []
summary: Create ArticleFiles summary: Viewer ArticleFiles
tags: tags:
- Article Files - Article Files
/article-nulis-ai: /article-nulis-ai:
@ -2978,6 +3061,42 @@ paths:
summary: Update Articles summary: Update Articles
tags: tags:
- Articles - Articles
/articles/banner/{id}:
put:
description: API for Update Banner Articles
parameters:
- description: Articles ID
in: path
name: id
required: true
type: integer
- description: Articles Banner Status
in: path
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/publish-scheduling: /articles/publish-scheduling:
post: post:
description: API for Publish Schedule of Article description: API for Publish Schedule of Article
@ -6123,6 +6242,38 @@ paths:
summary: Get one Users summary: Get one Users
tags: tags:
- Users - Users
/users/email-validation:
post:
description: API for Email Validation Users
parameters:
- 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: /users/forgot-password:
post: post:
description: API for ForgotPassword Users description: API for ForgotPassword Users
@ -6385,6 +6536,38 @@ paths:
summary: SavePassword Users summary: SavePassword Users
tags: tags:
- Users - Users
/users/setup-email:
post:
description: API for Setup Email Users
parameters:
- 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}: /users/username/{username}:
get: get:
description: API for getting one Users description: API for getting one Users

View File

@ -32,6 +32,7 @@ import (
"go-humas-be/config/logger" "go-humas-be/config/logger"
"go-humas-be/config/webserver" "go-humas-be/config/webserver"
"go.uber.org/fx" "go.uber.org/fx"
"time"
) )
func main() { func main() {
@ -90,5 +91,7 @@ func main() {
// define logger // define logger
fx.WithLogger(fxzerolog.Init()), fx.WithLogger(fxzerolog.Init()),
fx.StartTimeout(600*time.Second),
).Run() ).Run()
} }

View File

@ -29,3 +29,14 @@ func GetUserInfo(log zerolog.Logger, repo repository.UsersRepository, bearerToke
return user 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
}