diff --git a/app/database/entity/advertisement.entity.go b/app/database/entity/advertisement.entity.go index 6353d48..4f497f4 100644 --- a/app/database/entity/advertisement.entity.go +++ b/app/database/entity/advertisement.entity.go @@ -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()"` diff --git a/app/database/entity/articles.entity.go b/app/database/entity/articles.entity.go index b2f9ae1..6fe3f4f 100644 --- a/app/database/entity/articles.entity.go +++ b/app/database/entity/articles.entity.go @@ -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"` diff --git a/app/database/entity/audit_trails.entity.go b/app/database/entity/audit_trails.entity.go new file mode 100644 index 0000000..acb37f1 --- /dev/null +++ b/app/database/entity/audit_trails.entity.go @@ -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 +} diff --git a/app/database/entity/registration_otps.entity.go b/app/database/entity/one_time_passwords.entity.go similarity index 84% rename from app/database/entity/registration_otps.entity.go rename to app/database/entity/one_time_passwords.entity.go index efff593..aba4be7 100644 --- a/app/database/entity/registration_otps.entity.go +++ b/app/database/entity/one_time_passwords.entity.go @@ -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"` diff --git a/app/database/entity/users/users.entity.go b/app/database/entity/users/users.entity.go index ffe6b62..95b128a 100644 --- a/app/database/entity/users/users.entity.go +++ b/app/database/entity/users/users.entity.go @@ -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()"` diff --git a/app/database/index.database.go b/app/database/index.database.go index ac2882d..c869254 100644 --- a/app/database/index.database.go +++ b/app/database/index.database.go @@ -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{}, diff --git a/app/middleware/register.middleware.go b/app/middleware/register.middleware.go index ed96257..fedb801 100644 --- a/app/middleware/register.middleware.go +++ b/app/middleware/register.middleware.go @@ -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), diff --git a/app/module/advertisement/advertisement.module.go b/app/module/advertisement/advertisement.module.go index 34be7dd..eae08b4 100644 --- a/app/module/advertisement/advertisement.module.go +++ b/app/module/advertisement/advertisement.module.go @@ -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) }) } diff --git a/app/module/advertisement/controller/advertisement.controller.go b/app/module/advertisement/controller/advertisement.controller.go index e55e043..8a7e2c2 100644 --- a/app/module/advertisement/controller/advertisement.controller.go +++ b/app/module/advertisement/controller/advertisement.controller.go @@ -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) +} diff --git a/app/module/advertisement/repository/advertisement.repository.go b/app/module/advertisement/repository/advertisement.repository.go index b655e60..81ea9e7 100644 --- a/app/module/advertisement/repository/advertisement.repository.go +++ b/app/module/advertisement/repository/advertisement.repository.go @@ -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 diff --git a/app/module/advertisement/request/advertisement.request.go b/app/module/advertisement/request/advertisement.request.go index cd29b47..f3e25c5 100644 --- a/app/module/advertisement/request/advertisement.request.go +++ b/app/module/advertisement/request/advertisement.request.go @@ -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 { diff --git a/app/module/advertisement/service/advertisement.service.go b/app/module/advertisement/service/advertisement.service.go index 158ae20..ff90a34 100644 --- a/app/module/advertisement/service/advertisement.service.go +++ b/app/module/advertisement/service/advertisement.service.go @@ -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] +} diff --git a/app/module/article_files/controller/article_files.controller.go b/app/module/article_files/controller/article_files.controller.go index 6540a22..14d0c16 100644 --- a/app/module/article_files/controller/article_files.controller.go +++ b/app/module/article_files/controller/article_files.controller.go @@ -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" diff --git a/app/module/articles/articles.module.go b/app/module/articles/articles.module.go index 785b976..0ccb93e 100644 --- a/app/module/articles/articles.module.go +++ b/app/module/articles/articles.module.go @@ -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) diff --git a/app/module/articles/controller/articles.controller.go b/app/module/articles/controller/articles.controller.go index c4b0e32..869e80b 100644 --- a/app/module/articles/controller/articles.controller.go +++ b/app/module/articles/controller/articles.controller.go @@ -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 diff --git a/app/module/articles/service/articles.service.go b/app/module/articles/service/articles.service.go index 6676125..1e82c0c 100644 --- a/app/module/articles/service/articles.service.go +++ b/app/module/articles/service/articles.service.go @@ -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) } diff --git a/app/module/users/controller/users.controller.go b/app/module/users/controller/users.controller.go index 14e7de1..d8d02c4 100644 --- a/app/module/users/controller/users.controller.go +++ b/app/module/users/controller/users.controller.go @@ -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}, + }) +} diff --git a/app/module/users/repository/users.repository.go b/app/module/users/repository/users.repository.go index 208da65..1c3c95b 100644 --- a/app/module/users/repository/users.repository.go +++ b/app/module/users/repository/users.repository.go @@ -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(®istrationOtps).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 } diff --git a/app/module/users/request/users.request.go b/app/module/users/request/users.request.go index 6d6dc8b..02ef8f4 100644 --- a/app/module/users/request/users.request.go +++ b/app/module/users/request/users.request.go @@ -129,14 +129,22 @@ 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"` - OtpCode string `json:"otpCode"` + Email *string `json:"email"` + Username *string `json:"username"` + OtpCode string `json:"otpCode"` } type UsersQueryRequestContext struct { diff --git a/app/module/users/service/users.service.go b/app/module/users/service/users.service.go index b7e2951..02473d2 100644 --- a/app/module/users/service/users.service.go +++ b/app/module/users/service/users.service.go @@ -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("

Hai %s !

Berikut kode OTP yang digunakan untuk verifikasi.

", *req.Name) - htmlBody += fmt.Sprintf("

%s

", codeRequest) - htmlBody += "

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

" - htmlBody += "

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

" - 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) - if err != nil { - return fmt.Errorf("OTP is not valid") + 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("

Hai %s !

Berikut kode OTP yang digunakan untuk Login.

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

%s

", otp) + htmlBody += "

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

" + htmlBody += "

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

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

Hai %s !

Berikut kode OTP yang digunakan untuk Verifikasi Registrasi.

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

%s

", otp) + htmlBody += "

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

" + htmlBody += "

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

" + err := _i.Smtp.SendEmail(subject, email, name, htmlBody) + + return err +} diff --git a/app/module/users/users.module.go b/app/module/users/users.module.go index 70dbd1d..26673fb 100644 --- a/app/module/users/users.module.go +++ b/app/module/users/users.module.go @@ -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) }) } diff --git a/config/config/index.config.go b/config/config/index.config.go index c20cd45..68252f0 100644 --- a/config/config/index.config.go +++ b/config/config/index.config.go @@ -72,6 +72,10 @@ type middleware = struct { Max int Expiration time.Duration `toml:"expiration_seconds"` } + + AuditTrails struct { + Enable bool + } } // minio struct config diff --git a/config/middleware/audit_trails.middleware.go b/config/middleware/audit_trails.middleware.go new file mode 100644 index 0000000..67b3eb5 --- /dev/null +++ b/config/middleware/audit_trails.middleware.go @@ -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 + } +} diff --git a/config/toml/config.toml b/config/toml/config.toml index 0a8939f..c10f132 100644 --- a/config/toml/config.toml +++ b/config/toml/config.toml @@ -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" # ://:@:/ 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" diff --git a/config/webserver/webserver.config.go b/config/webserver/webserver.config.go index 9640007..30eaa76 100644 --- a/config/webserver/webserver.config.go +++ b/config/webserver/webserver.config.go @@ -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() diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go index 0844479..9d312d7 100644 --- a/docs/swagger/docs.go +++ b/docs/swagger/docs.go @@ -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" } } }, diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index a66ed62..0f7dff6 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -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" } } }, diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index cd1006c..5a464cc 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -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 diff --git a/main.go b/main.go index 177822a..ed6a7ee 100644 --- a/main.go +++ b/main.go @@ -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() } diff --git a/utils/service/user_utils.service.go b/utils/service/user_utils.service.go index bdb1b79..13e39d1 100644 --- a/utils/service/user_utils.service.go +++ b/utils/service/user_utils.service.go @@ -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 +}