feat: update advertisement, users, articles, etc

This commit is contained in:
hanif salafi 2025-04-07 08:12:02 +07:00
parent de618efe3a
commit 74da9d3e90
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"`
Placement string `json:"placement" gorm:"type:varchar"`
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"`
CreatedAt time.Time `json:"created_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"`
HasApprovedBy *string `json:"has_approved_by" gorm:"type:varchar"`
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"`

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"
type RegistrationOtps struct {
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')"`
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"`
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"`
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()"`

View File

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

View File

@ -1,6 +1,7 @@
package middleware
import (
"go-humas-be/app/database"
"go-humas-be/config/config"
"go-humas-be/utils"
"time"
@ -12,6 +13,8 @@ import (
"github.com/gofiber/fiber/v2/middleware/monitor"
"github.com/gofiber/fiber/v2/middleware/pprof"
"github.com/gofiber/fiber/v2/middleware/recover"
auditTrails "go-humas-be/config/middleware"
)
// 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
func (m *Middleware) Register() {
func (m *Middleware) Register(db *database.Database) {
// Add Extra Middlewares
m.App.Use(limiter.New(limiter.Config{
@ -60,6 +63,8 @@ func (m *Middleware) Register() {
MaxAge: 12,
}))
m.App.Use(auditTrails.AuditTrailsMiddleware(db.DB))
//m.App.Use(filesystem.New(filesystem.Config{
// Next: utils.IsEnabled(m.Cfg.Middleware.FileSystem.Enable),
// Root: http.Dir(m.Cfg.Middleware.FileSystem.Root),

View File

@ -48,7 +48,9 @@ func (_i *AdvertisementRouter) RegisterAdvertisementRoutes() {
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)
})
}

View File

@ -23,7 +23,9 @@ type AdvertisementController interface {
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 {
@ -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
// @Summary 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"},
})
}
// 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 {
GetAll(req request.AdvertisementQueryRequest) (advertisements []*entity.Advertisement, paging paginator.Pagination, 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)
Update(id uint, advertisement *entity.Advertisement) (err error)
Delete(id uint) (err error)
@ -88,6 +89,15 @@ func (_i *advertisementRepository) FindOne(id uint) (advertisement *entity.Adver
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) {
result := _i.DB.DB.Create(advertisement)
return advertisement, result.Error

View File

@ -16,6 +16,7 @@ type AdvertisementQueryRequest struct {
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"`
}
@ -34,6 +35,8 @@ func (req AdvertisementCreateRequest) ToEntity() *entity.Advertisement {
RedirectLink: req.RedirectLink,
Placement: req.Placement,
StatusId: 1,
IsPublish: true,
IsActive: true,
}
}
@ -62,6 +65,7 @@ type AdvertisementQueryRequestContext struct {
RedirectLink string `json:"redirectLink"`
Placement string `json:"placement"`
StatusId string `json:"statusId"`
IsPublish string `json:"isPublish"`
}
func (req AdvertisementQueryRequestContext) ToParamRequest() AdvertisementQueryRequest {
@ -79,6 +83,12 @@ func (req AdvertisementQueryRequestContext) ToParamRequest() AdvertisementQueryR
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 {

View File

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

View File

@ -48,6 +48,7 @@ func (_i *ArticlesRouter) RegisterArticlesRoutes() {
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)

View File

@ -21,6 +21,7 @@ type ArticlesController interface {
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
@ -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
// @Summary Delete Articles
// @Description API for delete Articles

View File

@ -55,6 +55,7 @@ type ArticlesService interface {
Delete(id uint) error
UpdateActivityCount(id uint, activityTypeId 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
SummaryStats(authToken string) (summaryStats *response.ArticleSummaryStats, 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
}
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)
}

View File

@ -31,6 +31,8 @@ type UsersController interface {
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 {
@ -469,3 +471,59 @@ func (_i *usersController) OtpValidation(c *fiber.Ctx) error {
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)
UpdateForgotPassword(id uint, forgotPasswords *entity.ForgotPasswords) (err error)
FindForgotPassword(keycloakId string, code string) (forgotPasswords *entity.ForgotPasswords, err error)
CreateRegistrationOtps(registrationOtps *entity.RegistrationOtps) (err error)
FindRegistrationOtps(email string, code string) (registrationOtps *entity.RegistrationOtps, 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 {
@ -168,15 +169,23 @@ func (_i *usersRepository) FindForgotPassword(keycloakId string, code string) (f
return forgotPasswords, nil
}
func (_i *usersRepository) CreateRegistrationOtps(registrationOtps *entity.RegistrationOtps) (err error) {
result := _i.DB.DB.Create(registrationOtps)
func (_i *usersRepository) CreateOtp(otp *entity.OneTimePasswords) (err error) {
result := _i.DB.DB.Create(otp)
return result.Error
}
func (_i *usersRepository) FindRegistrationOtps(email string, code string) (registrationOtps *entity.RegistrationOtps, err error) {
if err := _i.DB.DB.Where("email = ?", email).Where("otp_code = ?", code).First(&registrationOtps).Error; err != nil {
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 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,13 +129,21 @@ type UserResetPassword struct {
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"`
Email *string `json:"email"`
Username *string `json:"username"`
OtpCode string `json:"otpCode"`
}

View File

@ -44,8 +44,12 @@ type UsersService interface {
SavePassword(req request.UserSavePassword, authToken string) (err error)
ResetPassword(req request.UserResetPassword) (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)
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
@ -343,47 +347,50 @@ 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.RegistrationOtps{
otpReq := entity.OneTimePasswords{
Email: req.Email,
Name: req.Name,
OtpCode: codeRequest,
IsActive: true,
}
subject := "[HUMAS POLRI] Permintaan OTP"
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)
err = _i.Repo.CreateOtp(&otpReq)
if err != nil {
return err
}
err = _i.Repo.CreateRegistrationOtps(&otpReq)
err = _i.SendRegistrationOtp(*req.Name, req.Email, codeRequest)
if err != nil {
return err
}
// send otp to email
return nil
}
func (_i *usersService) OtpValidation(req request.UserOtpValidation) (err error) {
_i.Log.Info().Interface("data", req).Msg("")
registrationOtp, err := _i.Repo.FindRegistrationOtps(req.Email, req.OtpCode)
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 registrationOtp != nil {
if registrationOtp.ValidUntil.Before(time.Now()) {
if otp != nil {
if otp.ValidUntil.Before(time.Now()) {
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) {
// Pisahkan JWT menjadi 3 bagian: header, payload, dan signature
parts := strings.Split(token, ".")
@ -414,3 +523,25 @@ func ParseJWTToken(token string) (map[string]interface{}, error) {
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("/otp-request", usersController.OtpRequest)
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
Expiration time.Duration `toml:"expiration_seconds"`
}
AuditTrails struct {
Enable bool
}
}
// 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]
dsn = "postgresql://humas_user:HumasDB@2024@38.47.180.165:5432/humas_db" # <driver>://<username>:<password>@<host>:<port>/<database>
log-mode = "NONE"
migrate = true
migrate = false
seed = false
[logger]
@ -39,7 +39,7 @@ level = 1
enable = true
[middleware.monitor]
enable = false
enable = true
path = "/monitor"
[middleware.pprof]
@ -49,10 +49,13 @@ enable = true
enable = true
[middleware.limiter]
enable = false
enable = true
max = 20
expiration_seconds = 60
[middleware.audittrails]
enable = true
[keycloak]
endpoint = "http://38.47.180.165:8008"
realm = "humas"

View File

@ -48,8 +48,14 @@ func Start(lifecycle fx.Lifecycle, cfg *config.Config, fiber *fiber.App, router
lifecycle.Append(
fx.Hook{
OnStart: func(ctx context.Context) error {
// Register middlewares & routes
middlewares.Register()
// Connect database
db.ConnectDatabase()
// Register middlewares
middlewares.Register(db)
// Register routes
router.Register()
// 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")
seedFlag := flag.Bool("seed", db.Cfg.DB.Postgres.Seed, "seed the database")
flag.Parse()

View File

@ -343,6 +343,11 @@ const docTemplate = `{
"name": "description",
"in": "query"
},
{
"type": "boolean",
"name": "isPublish",
"in": "query"
},
{
"type": "string",
"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}": {
"post": {
"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}": {
"get": {
"security": [
@ -2332,11 +2442,11 @@ const docTemplate = `{
"Bearer": []
}
],
"description": "API for create ArticleFiles",
"description": "API for Viewer ArticleFiles",
"tags": [
"Article Files"
],
"summary": "Create ArticleFiles",
"summary": "Viewer ArticleFiles",
"parameters": [
{
"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": {
"post": {
"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": {
"post": {
"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}": {
"get": {
"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": {
"type": "object",
"properties": {
@ -9677,6 +9962,9 @@ const docTemplate = `{
},
"otpCode": {
"type": "string"
},
"username": {
"type": "string"
}
}
},

View File

@ -332,6 +332,11 @@
"name": "description",
"in": "query"
},
{
"type": "boolean",
"name": "isPublish",
"in": "query"
},
{
"type": "string",
"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}": {
"post": {
"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}": {
"get": {
"security": [
@ -2321,11 +2431,11 @@
"Bearer": []
}
],
"description": "API for create ArticleFiles",
"description": "API for Viewer ArticleFiles",
"tags": [
"Article Files"
],
"summary": "Create ArticleFiles",
"summary": "Viewer ArticleFiles",
"parameters": [
{
"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": {
"post": {
"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": {
"post": {
"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}": {
"get": {
"security": [
@ -9557,6 +9825,23 @@
}
}
},
"request.UserEmailValidationRequest": {
"type": "object",
"properties": {
"newEmail": {
"type": "string"
},
"oldEmail": {
"type": "string"
},
"password": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"request.UserForgotPassword": {
"type": "object",
"properties": {
@ -9666,6 +9951,9 @@
},
"otpCode": {
"type": "string"
},
"username": {
"type": "string"
}
}
},

View File

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

View File

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

View File

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