adding more complex response and more readable also adding some feature like rbac
This commit is contained in:
parent
84d20145cd
commit
d5f611e390
|
|
@ -2,8 +2,10 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DbConfig struct {
|
type DbConfig struct {
|
||||||
|
|
@ -17,6 +19,7 @@ type LoginConfig struct {
|
||||||
LoginAPI string
|
LoginAPI string
|
||||||
AuthMeAPI string
|
AuthMeAPI string
|
||||||
LogoutAPI string
|
LogoutAPI string
|
||||||
|
SkipSSLVerification bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type TokenConfig struct {
|
type TokenConfig struct {
|
||||||
|
|
@ -67,10 +70,17 @@ func (c *Config) readConfig() error {
|
||||||
TokenApiKey: os.Getenv("TOKEN_API_KEY"),
|
TokenApiKey: os.Getenv("TOKEN_API_KEY"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
skipSSL, err := strconv.ParseBool(os.Getenv("SKIP_SSL_VERIFICATION"))
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to read SKIP_SSL_VERIFICATION environment variable")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
c.LoginConfig = LoginConfig{
|
c.LoginConfig = LoginConfig{
|
||||||
LoginAPI: os.Getenv("API_LOGIN_URL"),
|
LoginAPI: os.Getenv("API_LOGIN_URL"),
|
||||||
AuthMeAPI: os.Getenv("API_ME_URL"),
|
AuthMeAPI: os.Getenv("API_ME_URL"),
|
||||||
LogoutAPI: os.Getenv("API_LOGOUT_URL"),
|
LogoutAPI: os.Getenv("API_LOGOUT_URL"),
|
||||||
|
SkipSSLVerification: skipSSL,
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.ApiConfig.ApiPort == "" {
|
if c.ApiConfig.ApiPort == "" {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"users_management/m/middleware"
|
||||||
|
"users_management/m/usecase"
|
||||||
|
"users_management/m/utils/common"
|
||||||
|
"users_management/m/utils/helper"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActivityLogController struct {
|
||||||
|
activityLogUC usecase.ActivityLogUseCase
|
||||||
|
rg *gin.RouterGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActivityLogController(activityLogUC usecase.ActivityLogUseCase, rg *gin.RouterGroup) *ActivityLogController {
|
||||||
|
return &ActivityLogController{
|
||||||
|
activityLogUC: activityLogUC,
|
||||||
|
rg: rg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ActivityLogController) Route() {
|
||||||
|
logs := c.rg.Group("/logs")
|
||||||
|
{
|
||||||
|
// Users can see their own logs (simplified response)
|
||||||
|
logs.GET("/my-logs", c.getMyLogs)
|
||||||
|
|
||||||
|
// Only admins and superadmins can see all logs (detailed response)
|
||||||
|
logs.GET("/all", middleware.RequireAdminRole(), c.getAllLogs)
|
||||||
|
|
||||||
|
// Admins and superadmins can see teknisi logs specifically (detailed response)
|
||||||
|
logs.GET("/teknisi", middleware.RequireAdminRole(), c.getTeknisinLogs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ActivityLogController) getMyLogs(ctx *gin.Context) {
|
||||||
|
userID, exists := ctx.Get("userID")
|
||||||
|
if !exists {
|
||||||
|
common.ErrorResponses(ctx, http.StatusUnauthorized, "User ID not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, ok := userID.(uuid.UUID)
|
||||||
|
if !ok {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid user ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
|
||||||
|
limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "10"))
|
||||||
|
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if limit < 1 || limit > 100 {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, total, err := c.activityLogUC.GetUserLogs(uid, page, limit)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to simplified response using helper
|
||||||
|
simplifiedLogs := helper.ConvertToActivityLogResponses(logs)
|
||||||
|
|
||||||
|
response := gin.H{
|
||||||
|
"logs": simplifiedLogs,
|
||||||
|
"total": total,
|
||||||
|
"page": page,
|
||||||
|
"limit": limit,
|
||||||
|
"total_pages": (total + int64(limit) - 1) / int64(limit),
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "User logs retrieved successfully", response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ActivityLogController) getAllLogs(ctx *gin.Context) {
|
||||||
|
page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
|
||||||
|
limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "10"))
|
||||||
|
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if limit < 1 || limit > 100 {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, total, err := c.activityLogUC.GetAllLogs(page, limit)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to detailed response for admins using helper
|
||||||
|
detailedLogs := helper.ConvertToActivityLogDetailResponses(logs)
|
||||||
|
|
||||||
|
response := gin.H{
|
||||||
|
"logs": detailedLogs,
|
||||||
|
"total": total,
|
||||||
|
"page": page,
|
||||||
|
"limit": limit,
|
||||||
|
"total_pages": (total + int64(limit) - 1) / int64(limit),
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "All logs retrieved successfully", response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ActivityLogController) getTeknisinLogs(ctx *gin.Context) {
|
||||||
|
page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
|
||||||
|
limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "10"))
|
||||||
|
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if limit < 1 || limit > 100 {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, total, err := c.activityLogUC.GetTeknisinLogs(page, limit)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to detailed response for admins using helper
|
||||||
|
detailedLogs := helper.ConvertToActivityLogDetailResponses(logs)
|
||||||
|
|
||||||
|
response := gin.H{
|
||||||
|
"logs": detailedLogs,
|
||||||
|
"total": total,
|
||||||
|
"page": page,
|
||||||
|
"limit": limit,
|
||||||
|
"total_pages": (total + int64(limit) - 1) / int64(limit),
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "Teknisi logs retrieved successfully", response)
|
||||||
|
}
|
||||||
|
|
@ -18,15 +18,13 @@ type BackboneController struct {
|
||||||
|
|
||||||
func (bc *BackboneController) Route() {
|
func (bc *BackboneController) Route() {
|
||||||
rg := bc.rg.Group("/backbone")
|
rg := bc.rg.Group("/backbone")
|
||||||
|
|
||||||
rg.Use(middleware.AuthMiddleware())
|
|
||||||
rg.Use(middleware.CORSMiddleware())
|
|
||||||
rg.Use(middleware.RateLimitMiddleware())
|
|
||||||
{
|
{
|
||||||
rg.GET("", bc.GetBackbone())
|
rg.GET("", bc.GetBackbone())
|
||||||
rg.POST("", bc.CreateBackbone())
|
|
||||||
rg.GET("/:uuid", bc.GetBackboneByID())
|
rg.GET("/:uuid", bc.GetBackboneByID())
|
||||||
rg.PUT("/:uuid", bc.UpdateBackbone())
|
|
||||||
|
rg.POST("", middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin"), bc.CreateBackbone())
|
||||||
|
rg.PUT("/:uuid", middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin"), bc.UpdateBackbone())
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"users_management/m/usecase"
|
||||||
|
"users_management/m/utils/common"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CountAssetsController struct {
|
||||||
|
countAssetsUC usecase.CountAssetsUseCase
|
||||||
|
rg *gin.RouterGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCountAssetsController(countAssetsUC usecase.CountAssetsUseCase, rg *gin.RouterGroup) *CountAssetsController {
|
||||||
|
return &CountAssetsController{
|
||||||
|
countAssetsUC: countAssetsUC,
|
||||||
|
rg: rg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CountAssetsController) Route() {
|
||||||
|
c.rg.GET("/count-assets", c.getCounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CountAssetsController) getCounts(ctx *gin.Context) {
|
||||||
|
counts, err := c.countAssetsUC.GetCounts()
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "Count assets retrieved successfully", counts)
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,6 @@ package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"users_management/m/middleware"
|
|
||||||
"users_management/m/model/dto/req"
|
"users_management/m/model/dto/req"
|
||||||
"users_management/m/usecase"
|
"users_management/m/usecase"
|
||||||
"users_management/m/utils/common"
|
"users_management/m/utils/common"
|
||||||
|
|
@ -18,9 +17,6 @@ type DevicePortController struct {
|
||||||
|
|
||||||
func (dc *DevicePortController) Route() {
|
func (dc *DevicePortController) Route() {
|
||||||
rg := dc.rg.Group("/device-port")
|
rg := dc.rg.Group("/device-port")
|
||||||
rg.Use(middleware.AuthMiddleware())
|
|
||||||
rg.Use(middleware.RateLimitMiddleware())
|
|
||||||
rg.Use(middleware.CORSMiddleware())
|
|
||||||
{
|
{
|
||||||
rg.GET("", dc.GetDevicePort())
|
rg.GET("", dc.GetDevicePort())
|
||||||
rg.POST("", dc.CreateDevicePort())
|
rg.POST("", dc.CreateDevicePort())
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,7 @@ type DeviceController struct {
|
||||||
|
|
||||||
func (dc *DeviceController) Route() {
|
func (dc *DeviceController) Route() {
|
||||||
rg := dc.rg.Group("/devices")
|
rg := dc.rg.Group("/devices")
|
||||||
rg.Use(middleware.AuthMiddleware())
|
rg.Use(middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin"))
|
||||||
rg.Use(middleware.RateLimitMiddleware())
|
|
||||||
rg.Use(middleware.CORSMiddleware())
|
|
||||||
{
|
{
|
||||||
rg.POST("", dc.CreateDevice())
|
rg.POST("", dc.CreateDevice())
|
||||||
rg.GET("", dc.GetAllDevices())
|
rg.GET("", dc.GetAllDevices())
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"users_management/m/middleware"
|
"users_management/m/middleware"
|
||||||
"users_management/m/model/dto/req"
|
"users_management/m/model/dto/req"
|
||||||
|
|
@ -18,9 +19,8 @@ type FishboneController struct {
|
||||||
|
|
||||||
func (fc *FishboneController) Route() {
|
func (fc *FishboneController) Route() {
|
||||||
rg := fc.rg.Group("/fishbone")
|
rg := fc.rg.Group("/fishbone")
|
||||||
rg.Use(middleware.AuthMiddleware())
|
rg.Use(middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin"))
|
||||||
rg.Use(middleware.RateLimitMiddleware())
|
// Apply middleware to all routes
|
||||||
rg.Use(middleware.CORSMiddleware())
|
|
||||||
{
|
{
|
||||||
rg.GET("", fc.GetFishbone())
|
rg.GET("", fc.GetFishbone())
|
||||||
rg.POST("", fc.CreateFishbone())
|
rg.POST("", fc.CreateFishbone())
|
||||||
|
|
@ -37,82 +37,126 @@ func NewFishboneController(fu usecase.FishboneUseCase, rg *gin.RouterGroup) *Fis
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fc *FishboneController) GetFishbone() gin.HandlerFunc {
|
func (fc *FishboneController) GetFishbone() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
fishbones, err := fc.fu.GetAllFishbone()
|
log.Println("Fetching all fishbones")
|
||||||
|
fishbones, err := fc.fu.GetAllFishbone()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
common.SingleResponses(c, "Success", fishbones)
|
common.SingleResponses(c, "Success", fishbones)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fc *FishboneController) CreateFishbone() gin.HandlerFunc {
|
func (fc *FishboneController) CreateFishbone() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
var fishboneDTO req.FishboneDTO
|
var fishboneDTO req.FishboneDTO
|
||||||
err := c.ShouldBindJSON(&fishboneDTO)
|
err := c.ShouldBindJSON(&fishboneDTO)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = fc.fu.CreateFishbone(fishboneDTO)
|
err = fc.fu.CreateFishbone(fishboneDTO)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
common.SingleResponses(c, "Fishbone has been created", nil)
|
common.SingleResponses(c, "Fishbone has been created successfully", nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fc *FishboneController) GetFishboneByID() gin.HandlerFunc {
|
func (fc *FishboneController) GetFishboneByID() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
id := c.Param("uuid")
|
id := c.Param("uuid")
|
||||||
uuid, err := uuid.Parse(id)
|
fishboneUUID, err := uuid.Parse(id)
|
||||||
if err != nil{
|
if err != nil {
|
||||||
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
common.ErrorResponses(c, http.StatusBadRequest, "Invalid UUID format")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fishbone, err := fc.fu.GetByID(uuid)
|
|
||||||
|
|
||||||
if err != nil {
|
fishbone, err := fc.fu.GetByID(fishboneUUID)
|
||||||
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
if err != nil {
|
||||||
return
|
if err.Error() == "fishbone not found" {
|
||||||
}
|
common.ErrorResponses(c, http.StatusNotFound, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.ErrorResponses(c, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
common.SingleResponses(c, "Success", fishbone)
|
common.SingleResponses(c, "Success", fishbone)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fc *FishboneController) UpdateFishbone() gin.HandlerFunc {
|
func (fc *FishboneController) UpdateFishbone() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
id := c.Param("uuid")
|
id := c.Param("uuid")
|
||||||
uuid, err := uuid.Parse(id)
|
fishboneUUID, err := uuid.Parse(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
common.ErrorResponses(c, http.StatusBadRequest, "Invalid UUID format")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var fishboneDTO req.UpdateFishboneDTO
|
var fishboneDTO req.UpdateFishboneDTO
|
||||||
err = c.ShouldBindJSON(&fishboneDTO)
|
err = c.ShouldBindJSON(&fishboneDTO)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = fc.fu.UpdateFishbone(uuid, fishboneDTO)
|
err = fc.fu.UpdateFishbone(fishboneUUID, fishboneDTO)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
if err.Error() == "fishbone not found" {
|
||||||
return
|
common.ErrorResponses(c, http.StatusNotFound, err.Error())
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
common.SingleResponses(c, "Fishbone has been updated", nil)
|
common.SingleResponses(c, "Fishbone has been updated successfully", nil)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fc *FishboneController) DeleteFishbone() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
id := c.Param("uuid")
|
||||||
|
fishboneUUID, err := uuid.Parse(id)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, "Invalid UUID format")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fc.fu.DeleteFishbone(fishboneUUID)
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() == "fishbone not found" {
|
||||||
|
common.ErrorResponses(c, http.StatusNotFound, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.ErrorResponses(c, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(c, "Fishbone has been deleted successfully", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fc *FishboneController) GetFishboneStats() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
stats, err := fc.fu.GetFishboneStats()
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(c, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(c, "Success", stats)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"users_management/m/middleware"
|
"users_management/m/middleware"
|
||||||
"users_management/m/model/dto/req"
|
"users_management/m/model/dto/req"
|
||||||
"users_management/m/usecase"
|
"users_management/m/usecase"
|
||||||
|
|
@ -18,12 +19,11 @@ type TowerController struct {
|
||||||
|
|
||||||
func (tc *TowerController) Route() {
|
func (tc *TowerController) Route() {
|
||||||
rg := tc.rg.Group("/tower")
|
rg := tc.rg.Group("/tower")
|
||||||
rg.Use(middleware.AuthMiddleware())
|
rg.Use(middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin")) // Apply middleware to all routes
|
||||||
rg.Use(middleware.RateLimitMiddleware())
|
|
||||||
rg.Use(middleware.CORSMiddleware())
|
|
||||||
{
|
{
|
||||||
rg.GET("", tc.GetTower())
|
rg.GET("", tc.GetTower())
|
||||||
rg.POST("", tc.CreateTower())
|
rg.POST("", tc.CreateTower())
|
||||||
|
|
||||||
rg.GET("/:uuid", tc.GetTowerByID())
|
rg.GET("/:uuid", tc.GetTowerByID())
|
||||||
rg.PUT("/:uuid", tc.UpdateTower())
|
rg.PUT("/:uuid", tc.UpdateTower())
|
||||||
}
|
}
|
||||||
|
|
@ -50,24 +50,65 @@ func (tc *TowerController) GetTower() gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *TowerController) CreateTower() gin.HandlerFunc {
|
func (tc *TowerController) CreateTower() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
var towerDTO req.TowerDTO
|
// Parse multipart form
|
||||||
err := c.ShouldBindJSON(&towerDTO)
|
err := c.Request.ParseMultipartForm(10 << 20) // 10MB max
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, "Failed to parse multipart form")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
// Extract form data
|
||||||
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
deviceIDStr := c.PostForm("dev_id")
|
||||||
return
|
towerCode := c.PostForm("tower_code")
|
||||||
}
|
longitudeStr := c.PostForm("longitude")
|
||||||
|
latitudeStr := c.PostForm("latitude")
|
||||||
|
|
||||||
err = tc.tu.Post(towerDTO)
|
// Validate required fields
|
||||||
|
if deviceIDStr == "" || towerCode == "" || longitudeStr == "" || latitudeStr == "" {
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, "Missing required fields")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
// Parse UUID
|
||||||
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
deviceID, err := uuid.Parse(deviceIDStr)
|
||||||
return
|
if err != nil {
|
||||||
}
|
common.ErrorResponses(c, http.StatusBadRequest, "Invalid device ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
common.SingleResponses(c, "Tower has been created", nil)
|
// Parse coordinates
|
||||||
}
|
longitude, err := strconv.ParseFloat(longitudeStr, 64)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, "Invalid longitude")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
latitude, err := strconv.ParseFloat(latitudeStr, 64)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, "Invalid latitude")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create DTO
|
||||||
|
towerDTO := req.TowerDTO{
|
||||||
|
DeviceID: deviceID,
|
||||||
|
TowerCode: towerCode,
|
||||||
|
Longitude: longitude,
|
||||||
|
Latitude: latitude,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get image file (optional)
|
||||||
|
imageFile, _ := c.FormFile("image")
|
||||||
|
|
||||||
|
err = tc.tu.Post(towerDTO, imageFile)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(c, "Tower has been created", nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *TowerController) GetTowerByID() gin.HandlerFunc {
|
func (tc *TowerController) GetTowerByID() gin.HandlerFunc {
|
||||||
|
|
@ -90,27 +131,57 @@ func (tc *TowerController) GetTowerByID() gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *TowerController) UpdateTower() gin.HandlerFunc {
|
func (tc *TowerController) UpdateTower() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
id := c.Param("uuid")
|
id := c.Param("uuid")
|
||||||
uuid, err := uuid.Parse(id)
|
|
||||||
if err != nil {
|
|
||||||
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
|
||||||
}
|
|
||||||
var towerUpdateDTO req.UpdateTowerDTO
|
|
||||||
err = c.ShouldBindJSON(&towerUpdateDTO)
|
|
||||||
|
|
||||||
if err != nil {
|
tower_uuid, err := uuid.Parse(id)
|
||||||
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
if err != nil {
|
||||||
return
|
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err = tc.tu.UpdateTower(uuid, towerUpdateDTO)
|
// Parse multipart form
|
||||||
|
err = c.Request.ParseMultipartForm(10 << 20) // 10MB max
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, "Failed to parse multipart form")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
// Create update DTO
|
||||||
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
towerUpdateDTO := req.UpdateTowerDTO{}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
common.SingleResponses(c, "Tower has been updated", nil)
|
// Optional fields
|
||||||
}
|
if deviceIDStr := c.PostForm("device_id"); deviceIDStr != "" {
|
||||||
|
if deviceID, err := uuid.Parse(deviceIDStr); err == nil {
|
||||||
|
towerUpdateDTO.DeviceID = &deviceID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if towerCode := c.PostForm("tower_code"); towerCode != "" {
|
||||||
|
towerUpdateDTO.TowerCode = &towerCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if longitudeStr := c.PostForm("longitude"); longitudeStr != "" {
|
||||||
|
if longitude, err := strconv.ParseFloat(longitudeStr, 64); err == nil {
|
||||||
|
towerUpdateDTO.Longitude = &longitude
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if latitudeStr := c.PostForm("latitude"); latitudeStr != "" {
|
||||||
|
if latitude, err := strconv.ParseFloat(latitudeStr, 64); err == nil {
|
||||||
|
towerUpdateDTO.Latitude = &latitude
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get image file (optional)
|
||||||
|
imageFile, _ := c.FormFile("image")
|
||||||
|
|
||||||
|
err = tc.tu.UpdateTower(tower_uuid, towerUpdateDTO, imageFile)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(c, "Tower has been updated", nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"users_management/m/middleware"
|
||||||
|
"users_management/m/usecase"
|
||||||
|
"users_management/m/utils/common"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserManagementController struct {
|
||||||
|
userUC usecase.UsersUsecase
|
||||||
|
rg *gin.RouterGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserManagementController(userUC usecase.UsersUsecase, rg *gin.RouterGroup) *UserManagementController {
|
||||||
|
return &UserManagementController{
|
||||||
|
userUC: userUC,
|
||||||
|
rg: rg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserManagementController) Route() {
|
||||||
|
users := c.rg.Group("/user-management")
|
||||||
|
{
|
||||||
|
// Only superadmin can manage user roles
|
||||||
|
users.PUT("/role", middleware.RequireSuperAdminRole(), c.updateUserRole)
|
||||||
|
|
||||||
|
// Admins and superadmins can view all users
|
||||||
|
users.GET("/users", middleware.RequireAdminRole(), c.getAllUsers)
|
||||||
|
|
||||||
|
// Users can view their own profile
|
||||||
|
users.GET("/profile", c.getMyProfile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateRoleRequest struct {
|
||||||
|
NomorInduk string `json:"nomor_induk" binding:"required"`
|
||||||
|
RoleName string `json:"role_name" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserManagementController) updateUserRole(ctx *gin.Context) {
|
||||||
|
var req UpdateRoleRequest
|
||||||
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.userUC.UpdateUserRole(req.NomorInduk, req.RoleName)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "User role updated successfully", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserManagementController) getAllUsers(ctx *gin.Context) {
|
||||||
|
users, err := c.userUC.GetAllUsers()
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "Users retrieved successfully", users)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserManagementController) getMyProfile(ctx *gin.Context) {
|
||||||
|
userID, exists := ctx.Get("userID")
|
||||||
|
if !exists {
|
||||||
|
common.ErrorResponses(ctx, http.StatusUnauthorized, "User ID not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nomorInduk, ok := userID.(string)
|
||||||
|
if !ok {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid user ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := c.userUC.GetUserByNomorInduk(nomorInduk)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusNotFound, "User not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "User profile retrieved successfully", user)
|
||||||
|
}
|
||||||
|
|
@ -42,18 +42,29 @@ func NewServer() *Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) setupController() {
|
func (s *Server) setupController() {
|
||||||
|
|
||||||
|
s.engine.Static("/uploads", "./uploads")
|
||||||
|
s.engine.Use(middleware.CORSMiddleware())
|
||||||
|
|
||||||
|
|
||||||
rg := s.engine.Group("/api/v1")
|
rg := s.engine.Group("/api/v1")
|
||||||
controller.NewUsersController(s.ucManager.NewUserUsecase(), s.ucManager.NewAuthUsecase(),rg).Route()
|
controller.NewUsersController(s.ucManager.NewUserUsecase(), s.ucManager.NewAuthUsecase(),rg).Route()
|
||||||
rg.Use(middleware.AuthMiddleware())
|
rg.Use(middleware.AuthMiddleware(s.ucManager.NewUserUsecase()))
|
||||||
|
rg.Use(middleware.ActivityLoggingMiddleware(s.ucManager.NewActivityLogUsecase()))
|
||||||
|
rg.Use(middleware.RateLimitMiddleware())
|
||||||
{
|
{
|
||||||
controller.NewDeviceController(s.ucManager.NewDeviceUsecase(), rg).Route()
|
controller.NewDeviceController(s.ucManager.NewDeviceUsecase(), rg).Route()
|
||||||
controller.NewBackboneController(s.ucManager.NewBackboneUsecase(), rg).Route()
|
controller.NewBackboneController(s.ucManager.NewBackboneUsecase(), rg).Route()
|
||||||
controller.NewFishboneController(s.ucManager.NewFishboneUsecase(), rg).Route()
|
controller.NewFishboneController(s.ucManager.NewFishboneUsecase(), rg).Route()
|
||||||
controller.NewTowerController(s.ucManager.NewTowerUsecase(), rg).Route()
|
controller.NewTowerController(s.ucManager.NewTowerUsecase(), rg).Route()
|
||||||
controller.NewDevicePortController(s.ucManager.NewDevicePortUsecase(), rg).Route()
|
controller.NewDevicePortController(s.ucManager.NewDevicePortUsecase(), rg).Route()
|
||||||
|
controller.NewCountAssetsController(s.ucManager.NewCountAssetsUsecase(), rg).Route()
|
||||||
|
controller.NewActivityLogController(s.ucManager.NewActivityLogUsecase(), rg).Route()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func (s *Server) Run() {
|
func (s *Server) Run() {
|
||||||
s.setupController()
|
s.setupController()
|
||||||
if err := s.engine.Run(s.host); err != nil {
|
if err := s.engine.Run(s.host); err != nil {
|
||||||
|
|
|
||||||
5
go.mod
5
go.mod
|
|
@ -7,6 +7,8 @@ require (
|
||||||
github.com/go-playground/validator/v10 v10.24.0
|
github.com/go-playground/validator/v10 v10.24.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
|
golang.org/x/crypto v0.33.0
|
||||||
|
golang.org/x/time v0.10.0
|
||||||
gorm.io/driver/postgres v1.5.11
|
gorm.io/driver/postgres v1.5.11
|
||||||
gorm.io/gorm v1.25.12
|
gorm.io/gorm v1.25.12
|
||||||
)
|
)
|
||||||
|
|
@ -15,7 +17,6 @@ require (
|
||||||
github.com/bytedance/sonic v1.12.8 // indirect
|
github.com/bytedance/sonic v1.12.8 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
|
@ -39,12 +40,10 @@ require (
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
golang.org/x/arch v0.14.0 // indirect
|
golang.org/x/arch v0.14.0 // indirect
|
||||||
golang.org/x/crypto v0.33.0 // indirect
|
|
||||||
golang.org/x/net v0.35.0 // indirect
|
golang.org/x/net v0.35.0 // indirect
|
||||||
golang.org/x/sync v0.11.0 // indirect
|
golang.org/x/sync v0.11.0 // indirect
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/text v0.22.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
golang.org/x/time v0.10.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.36.5 // indirect
|
google.golang.org/protobuf v1.36.5 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -10,8 +10,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package manager
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"users_management/m/config"
|
"users_management/m/config"
|
||||||
|
"users_management/m/model/entity"
|
||||||
|
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
@ -24,10 +26,30 @@ func (im *infraManager) openConn() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = im.autoMigrate(db)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to migrate database schema: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
im.db = db
|
im.db = db
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (im *infraManager) autoMigrate(db *gorm.DB) error {
|
||||||
|
return db.AutoMigrate(
|
||||||
|
&entity.Role{},
|
||||||
|
&entity.User{},
|
||||||
|
&entity.Device{},
|
||||||
|
&entity.Backbone{},
|
||||||
|
&entity.Fishbone{},
|
||||||
|
&entity.Tower{},
|
||||||
|
&entity.DevicePort{},
|
||||||
|
&entity.CountAssets{},
|
||||||
|
&entity.ActivityLog{},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func NewInfraManager(cfg *config.Config) (InfraManager, error) {
|
func NewInfraManager(cfg *config.Config) (InfraManager, error) {
|
||||||
im := &infraManager{
|
im := &infraManager{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ type RepositoryManager interface {
|
||||||
NewTowerRepository() repository.TowerRepo
|
NewTowerRepository() repository.TowerRepo
|
||||||
|
|
||||||
NewDevicePortRepository() repository.DevicePortRepo
|
NewDevicePortRepository() repository.DevicePortRepo
|
||||||
|
NewCountAssetsRepository() repository.CountAssetsRepo
|
||||||
|
NewActivityLogRepository() repository.ActivityLogRepo
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type repositoryManager struct {
|
type repositoryManager struct {
|
||||||
|
|
@ -45,3 +48,11 @@ func (rm *repositoryManager) NewTowerRepository() repository.TowerRepo {
|
||||||
func (rm *repositoryManager) NewDevicePortRepository() repository.DevicePortRepo {
|
func (rm *repositoryManager) NewDevicePortRepository() repository.DevicePortRepo {
|
||||||
return repository.NewDevicePortRepo(rm.infra.Conn())
|
return repository.NewDevicePortRepo(rm.infra.Conn())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rm *repositoryManager) NewCountAssetsRepository() repository.CountAssetsRepo {
|
||||||
|
return repository.NewCountAssetsRepo(rm.infra.Conn())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm *repositoryManager) NewActivityLogRepository() repository.ActivityLogRepo {
|
||||||
|
return repository.NewActivityLogRepo(rm.infra.Conn())
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,10 @@ type UsecaseManager interface {
|
||||||
NewTowerUsecase() usecase.TowerUseCase
|
NewTowerUsecase() usecase.TowerUseCase
|
||||||
|
|
||||||
NewDevicePortUsecase() usecase.DevicePortUseCase
|
NewDevicePortUsecase() usecase.DevicePortUseCase
|
||||||
|
NewCountAssetsUsecase() usecase.CountAssetsUseCase
|
||||||
|
|
||||||
|
NewActivityLogUsecase() usecase.ActivityLogUseCase
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type usecaseManager struct {
|
type usecaseManager struct {
|
||||||
|
|
@ -57,3 +61,12 @@ func (um *usecaseManager) NewTowerUsecase() usecase.TowerUseCase {
|
||||||
func (um *usecaseManager) NewDevicePortUsecase() usecase.DevicePortUseCase {
|
func (um *usecaseManager) NewDevicePortUsecase() usecase.DevicePortUseCase {
|
||||||
return usecase.NewDevicePortUseCase(um.repo.NewDevicePortRepository())
|
return usecase.NewDevicePortUseCase(um.repo.NewDevicePortRepository())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (um *usecaseManager) NewCountAssetsUsecase() usecase.CountAssetsUseCase {
|
||||||
|
return usecase.NewCountAssetsUseCase(um.repo.NewCountAssetsRepository())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (um *usecaseManager) NewActivityLogUsecase() usecase.ActivityLogUseCase {
|
||||||
|
return usecase.NewActivityLogUseCase(um.repo.NewActivityLogRepository())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"users_management/m/usecase"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type responseWriter struct {
|
||||||
|
gin.ResponseWriter
|
||||||
|
body *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r responseWriter) Write(b []byte) (int, error) {
|
||||||
|
r.body.Write(b)
|
||||||
|
return r.ResponseWriter.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ActivityLoggingMiddleware(activityLogUC usecase.ActivityLogUseCase) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// Skip logging for certain endpoints
|
||||||
|
if shouldSkipLogging(c.Request.URL.Path) {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user info from context (set by auth middleware)
|
||||||
|
userID, userExists := c.Get("userID")
|
||||||
|
if !userExists {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert userID to UUID
|
||||||
|
uid, ok := userID.(uuid.UUID)
|
||||||
|
if !ok {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read request body
|
||||||
|
var requestBody []byte
|
||||||
|
if c.Request.Body != nil {
|
||||||
|
requestBody, _ = io.ReadAll(c.Request.Body)
|
||||||
|
c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap response writer to capture response
|
||||||
|
w := &responseWriter{body: &bytes.Buffer{}, ResponseWriter: c.Writer}
|
||||||
|
c.Writer = w
|
||||||
|
|
||||||
|
// Process request
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
// Log the activity in background
|
||||||
|
go func() {
|
||||||
|
action := getActionFromMethod(c.Request.Method)
|
||||||
|
resource := getResourceFromPath(c.Request.URL.Path)
|
||||||
|
resourceID := getResourceIDFromPath(c.Request.URL.Path)
|
||||||
|
|
||||||
|
var oldData, newData interface{}
|
||||||
|
|
||||||
|
// For updates, you might want to fetch old data before the operation
|
||||||
|
// This is a simplified version
|
||||||
|
if len(requestBody) > 0 {
|
||||||
|
json.Unmarshal(requestBody, &newData)
|
||||||
|
}
|
||||||
|
|
||||||
|
details := ""
|
||||||
|
if c.Writer.Status() >= 400 {
|
||||||
|
details = fmt.Sprintf("Request failed with status: %d", c.Writer.Status())
|
||||||
|
}
|
||||||
|
|
||||||
|
activityLogUC.LogActivity(
|
||||||
|
uid,
|
||||||
|
action,
|
||||||
|
resource,
|
||||||
|
resourceID,
|
||||||
|
oldData,
|
||||||
|
newData,
|
||||||
|
c.ClientIP(),
|
||||||
|
c.Request.UserAgent(),
|
||||||
|
details,
|
||||||
|
)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldSkipLogging(path string) bool {
|
||||||
|
skipPaths := []string{
|
||||||
|
"/api/v1/logs",
|
||||||
|
"/api/v1/users/login",
|
||||||
|
"/api/v1/users/logout",
|
||||||
|
"/uploads",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, skipPath := range skipPaths {
|
||||||
|
if strings.HasPrefix(path, skipPath) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getActionFromMethod(method string) string {
|
||||||
|
switch method {
|
||||||
|
case "POST":
|
||||||
|
return "CREATE"
|
||||||
|
case "PUT", "PATCH":
|
||||||
|
return "UPDATE"
|
||||||
|
case "DELETE":
|
||||||
|
return "DELETE"
|
||||||
|
case "GET":
|
||||||
|
return "READ"
|
||||||
|
default:
|
||||||
|
return method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getResourceFromPath(path string) string {
|
||||||
|
parts := strings.Split(path, "/")
|
||||||
|
if len(parts) >= 4 {
|
||||||
|
return parts[3] // /api/v1/devices -> devices
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
func getResourceIDFromPath(path string) *string {
|
||||||
|
parts := strings.Split(path, "/")
|
||||||
|
if len(parts) >= 5 {
|
||||||
|
id := parts[4]
|
||||||
|
return &id
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -1,67 +1,96 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"users_management/m/model/dto/res"
|
"users_management/m/model/dto/res"
|
||||||
|
"users_management/m/model/entity"
|
||||||
|
"users_management/m/usecase"
|
||||||
"users_management/m/utils/common"
|
"users_management/m/utils/common"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AuthMiddleware() gin.HandlerFunc {
|
func AuthMiddleware(userUC usecase.UsersUsecase) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
token := c.GetHeader("Authorization")
|
token := c.GetHeader("Authorization")
|
||||||
|
|
||||||
if token == "" {
|
if token == "" {
|
||||||
common.ErrorResponses(c, http.StatusUnauthorized, "authorization token required")
|
common.ErrorResponses(c, http.StatusUnauthorized, "authorization token required")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token = strings.TrimPrefix(token, "Bearer ")
|
token = strings.TrimPrefix(token, "Bearer ")
|
||||||
|
|
||||||
c.Set("token", token)
|
c.Set("token", token)
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", "https://demo.api-hrm.winteraccess.id/api/v2/auth/me", nil)
|
req, err := http.NewRequest("POST", "https://demo.api-hrm.winteraccess.id/api/v2/auth/me", nil)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResponses(c, http.StatusInternalServerError, err.Error())
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
req.Header.Set("Accept", "application/json")
|
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
common.ErrorResponses(c, http.StatusInternalServerError, err.Error())
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
common.ErrorResponses(c, http.StatusUnauthorized, "Unauthroized")
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var authResponse res.AuthMeResponse
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&authResponse); err != nil {
|
|
||||||
common.ErrorResponses(c, http.StatusInternalServerError, err.Error())
|
common.ErrorResponses(c, http.StatusInternalServerError, err.Error())
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Set("userID", authResponse.Data.NomorInduk)
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
c.Next()
|
|
||||||
|
|
||||||
}
|
var client *http.Client
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
client = &http.Client{
|
||||||
|
Transport: tr,
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(c, http.StatusInternalServerError, err.Error())
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
common.ErrorResponses(c, http.StatusUnauthorized, "Unauthorized")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var authResponse res.AuthMeResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&authResponse); err != nil {
|
||||||
|
common.ErrorResponses(c, http.StatusInternalServerError, err.Error())
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set basic user info from external API
|
||||||
|
var user entity.User
|
||||||
|
|
||||||
|
|
||||||
|
// Check if user exists in local database
|
||||||
|
user, err = userUC.GetUserByUsername(strings.ToLower(authResponse.Data.Username))
|
||||||
|
c.Set("userID", user.ID)
|
||||||
|
c.Set("userName", user.Username)
|
||||||
|
log.Println("User data from local DB:", user.ID)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
defaultRole := "Teknisi"
|
||||||
|
c.Set("userRole", defaultRole)
|
||||||
|
} else {
|
||||||
|
// User exists in local DB, use their assigned role
|
||||||
|
c.Set("userRole", user.Role.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,23 +1,61 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CORSMiddleware() gin.HandlerFunc {
|
func CORSMiddleware() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*") // Change to specific domains if needed
|
origin := c.Request.Header.Get("Origin")
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE")
|
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
|
|
||||||
|
|
||||||
// Allow OPTIONS method to pass through
|
// Define allowed origins (add your React app's URL)
|
||||||
if c.Request.Method == http.MethodOptions {
|
allowedOrigins := []string{
|
||||||
c.AbortWithStatus(http.StatusNoContent)
|
"http://localhost:3000",
|
||||||
return
|
"http://localhost:3001",
|
||||||
}
|
"http://127.0.0.1:3000",
|
||||||
|
"http://127.0.0.1:5173", // Add production URL
|
||||||
|
}
|
||||||
|
|
||||||
c.Next()
|
// Check if origin is in allowed list
|
||||||
}
|
isAllowed := false
|
||||||
|
for _, allowed := range allowedOrigins {
|
||||||
|
if origin == allowed {
|
||||||
|
isAllowed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For development, also allow localhost variations
|
||||||
|
if strings.Contains(origin, "localhost") || strings.Contains(origin, "127.0.0.1") {
|
||||||
|
isAllowed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if origin != "" && isAllowed {
|
||||||
|
// For allowed origins, set specific origin and enable credentials
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||||
|
} else if origin == "" {
|
||||||
|
// For requests without origin (direct API calls, mobile apps, etc.)
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
// Don't set credentials for wildcard
|
||||||
|
} else {
|
||||||
|
// For disallowed origins, still set basic CORS but no credentials
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE, PATCH")
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, accept, origin, Cache-Control, X-Requested-With")
|
||||||
|
c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length, Content-Type")
|
||||||
|
c.Writer.Header().Set("Access-Control-Max-Age", "86400")
|
||||||
|
|
||||||
|
// Handle preflight requests
|
||||||
|
if c.Request.Method == http.MethodOptions {
|
||||||
|
c.AbortWithStatus(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"log"
|
||||||
"sync"
|
"net/http"
|
||||||
"time"
|
"sync"
|
||||||
"users_management/m/utils/common"
|
"time"
|
||||||
|
"users_management/m/utils/common"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"golang.org/x/time/rate"
|
"github.com/google/uuid"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
rateLimiters = make(map[string]*rate.Limiter)
|
rateLimiters = make(map[string]*rate.Limiter)
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
)
|
)
|
||||||
|
|
||||||
func getRateLimiter(userID string) *rate.Limiter {
|
func getRateLimiter(userID string) *rate.Limiter {
|
||||||
|
|
@ -21,7 +23,7 @@ func getRateLimiter(userID string) *rate.Limiter {
|
||||||
|
|
||||||
limiter, exists := rateLimiters[userID]
|
limiter, exists := rateLimiters[userID]
|
||||||
if !exists {
|
if !exists {
|
||||||
limiter = rate.NewLimiter(rate.Every(1*time.Minute), 50) // 1 request per second with a burst of 2 requests
|
limiter = rate.NewLimiter(rate.Every(1*time.Minute), 50) // 50 requests per minute
|
||||||
rateLimiters[userID] = limiter
|
rateLimiters[userID] = limiter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,32 +31,49 @@ func getRateLimiter(userID string) *rate.Limiter {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLoginLimiter() *rate.Limiter {
|
func getLoginLimiter() *rate.Limiter {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
limiter, exists := rateLimiters["login"]
|
limiter, exists := rateLimiters["login"]
|
||||||
if !exists {
|
if !exists {
|
||||||
limiter = rate.NewLimiter(rate.Every(1*time.Minute), 10) // 5 request per second with a burst of 10 requests
|
limiter = rate.NewLimiter(rate.Every(1*time.Minute), 10) // 10 requests per minute for login
|
||||||
rateLimiters["login"] = limiter
|
rateLimiters["login"] = limiter
|
||||||
}
|
}
|
||||||
|
|
||||||
return limiter
|
return limiter
|
||||||
}
|
}
|
||||||
|
|
||||||
func RateLimitMiddleware() gin.HandlerFunc{
|
func RateLimitMiddleware() gin.HandlerFunc{
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
if c.Request.Method == http.MethodOptions {
|
if c.Request.Method == http.MethodOptions {
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userID, exists := c.Get("userID")
|
|
||||||
|
userID, exists := c.Get("userID")
|
||||||
if !exists {
|
if !exists {
|
||||||
common.ErrorResponses(c, http.StatusUnauthorized, "Unauthorized: No user ID found")
|
common.ErrorResponses(c, http.StatusUnauthorized, "Unauthorized: No user ID found")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
limiter := getRateLimiter(userID.(string))
|
// Convert UUID to string for rate limiting
|
||||||
|
var userIDStr string
|
||||||
|
switch v := userID.(type) {
|
||||||
|
case uuid.UUID:
|
||||||
|
userIDStr = v.String()
|
||||||
|
case string:
|
||||||
|
userIDStr = v
|
||||||
|
default:
|
||||||
|
log.Printf("Unexpected userID type: %T", userID)
|
||||||
|
common.ErrorResponses(c, http.StatusInternalServerError, "Invalid user ID type")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("User ID for rate limiting: %s", userIDStr)
|
||||||
|
|
||||||
|
limiter := getRateLimiter(userIDStr)
|
||||||
|
|
||||||
if !limiter.Allow() {
|
if !limiter.Allow() {
|
||||||
common.ErrorResponses(c, http.StatusTooManyRequests, "Too many requests")
|
common.ErrorResponses(c, http.StatusTooManyRequests, "Too many requests")
|
||||||
|
|
@ -62,24 +81,23 @@ func RateLimitMiddleware() gin.HandlerFunc{
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func RateLoginMiddleware() gin.HandlerFunc{
|
func RateLoginMiddleware() gin.HandlerFunc{
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
if c.Request.Method == http.MethodOptions {
|
if c.Request.Method == http.MethodOptions {
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
limiter := getLoginLimiter()
|
limiter := getLoginLimiter()
|
||||||
|
|
||||||
if !limiter.Allow() {
|
if !limiter.Allow() {
|
||||||
common.ErrorResponses(c, http.StatusTooManyRequests, "Too many requests")
|
common.ErrorResponses(c, http.StatusTooManyRequests, "Too many requests")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,161 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"users_management/m/utils/common"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequireRole middleware to check if user has required role
|
||||||
|
func RequireRole(allowedRoles ...string) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
userRole, exists := c.Get("userRole")
|
||||||
|
if !exists {
|
||||||
|
common.ErrorResponses(c, http.StatusUnauthorized, "User role not found")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
role, ok := userRole.(string)
|
||||||
|
if !ok {
|
||||||
|
common.ErrorResponses(c, http.StatusUnauthorized, "Invalid user role")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user role is in allowed roles
|
||||||
|
for _, allowedRole := range allowedRoles {
|
||||||
|
if role == allowedRole {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
common.ErrorResponses(c, http.StatusForbidden, "Insufficient permissions")
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireAnyRole middleware - user needs at least one of the specified roles
|
||||||
|
func RequireAnyRole(roles ...string) gin.HandlerFunc {
|
||||||
|
return RequireRole(roles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireAdminRole middleware for admin-only access
|
||||||
|
func RequireAdminRole() gin.HandlerFunc {
|
||||||
|
return RequireRole("Admin", "Superadmin")
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireSuperAdminRole middleware for superadmin-only access
|
||||||
|
func RequireSuperAdminRole() gin.HandlerFunc {
|
||||||
|
return RequireRole("Superadmin")
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireTeknisiRole middleware for teknisi access
|
||||||
|
func RequireTeknisiRole() gin.HandlerFunc {
|
||||||
|
return RequireRole("Teknisi")
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireNonTeknisiRole middleware to block teknisi users
|
||||||
|
func RequireNonTeknisiRole() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
userRole, exists := c.Get("userRole")
|
||||||
|
if !exists {
|
||||||
|
common.ErrorResponses(c, http.StatusUnauthorized, "User role not found")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
role, ok := userRole.(string)
|
||||||
|
if !ok {
|
||||||
|
common.ErrorResponses(c, http.StatusUnauthorized, "Invalid user role")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if role == "Teknisi" {
|
||||||
|
common.ErrorResponses(c, http.StatusForbidden, "Teknisi users are not allowed to access this resource")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PermissionMiddleware for more granular permissions
|
||||||
|
func PermissionMiddleware(resource, action string) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
userRole, exists := c.Get("userRole")
|
||||||
|
if !exists {
|
||||||
|
common.ErrorResponses(c, http.StatusUnauthorized, "User role not found")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
role, ok := userRole.(string)
|
||||||
|
if !ok {
|
||||||
|
common.ErrorResponses(c, http.StatusUnauthorized, "Invalid user role")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasPermission(role, resource, action) {
|
||||||
|
common.ErrorResponses(c, http.StatusForbidden, "Insufficient permissions for this action")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permission checker function
|
||||||
|
func hasPermission(role, resource, action string) bool {
|
||||||
|
permissions := getPermissions()
|
||||||
|
|
||||||
|
rolePermissions, exists := permissions[role]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
resourcePermissions, exists := rolePermissions[resource]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, allowedAction := range resourcePermissions {
|
||||||
|
if allowedAction == action || allowedAction == "*" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define permissions for each role
|
||||||
|
func getPermissions() map[string]map[string][]string {
|
||||||
|
return map[string]map[string][]string{
|
||||||
|
"Superadmin": {
|
||||||
|
"*": {"*"}, // Full access to everything
|
||||||
|
},
|
||||||
|
"Admin": {
|
||||||
|
"backbone": {"CREATE", "READ", "UPDATE", "DELETE"},
|
||||||
|
"fishbone": {"CREATE", "READ", "UPDATE", "DELETE"},
|
||||||
|
"devices": {"CREATE", "READ", "UPDATE", "DELETE"},
|
||||||
|
"towers": {"CREATE", "READ", "UPDATE", "DELETE"},
|
||||||
|
"ports": {"CREATE", "READ", "UPDATE", "DELETE"},
|
||||||
|
"logs": {"READ"},
|
||||||
|
"users": {"READ"},
|
||||||
|
},
|
||||||
|
"Teknisi": {
|
||||||
|
"backbone": {"CREATE", "READ", "UPDATE"},
|
||||||
|
"fishbone": {"CREATE", "READ", "UPDATE"},
|
||||||
|
"devices": {"CREATE", "READ", "UPDATE"},
|
||||||
|
"towers": {"CREATE", "READ", "UPDATE"},
|
||||||
|
"ports": {"CREATE", "READ", "UPDATE"},
|
||||||
|
"logs": {"READ"}, // Only own logs
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,10 @@ type DeviceDTO struct {
|
||||||
Latitude float64 `json:"latitude" validate:"required"`
|
Latitude float64 `json:"latitude" validate:"required"`
|
||||||
PortAmount int `json:"port_amount" validate:"required"`
|
PortAmount int `json:"port_amount" validate:"required"`
|
||||||
Status string `json:"status" validate:"required,oneof=active inactive maintenance"`
|
Status string `json:"status" validate:"required,oneof=active inactive maintenance"`
|
||||||
|
Region string `json:"region" validate:"required"`
|
||||||
|
Province string `json:"province" validate:"required"`
|
||||||
|
City string `json:"city" validate:"required"`
|
||||||
|
District string `json:"district" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateDeviceDTO struct {
|
type UpdateDeviceDTO struct {
|
||||||
|
|
@ -17,4 +21,8 @@ type UpdateDeviceDTO struct {
|
||||||
Latitude *float64 `json:"latitude,omitempty" validate:"omitempty,latitude"`
|
Latitude *float64 `json:"latitude,omitempty" validate:"omitempty,latitude"`
|
||||||
PortAmount *int `json:"port_amount,omitempty" validate:"omitempty,min=1"`
|
PortAmount *int `json:"port_amount,omitempty" validate:"omitempty,min=1"`
|
||||||
Status *string `json:"status,omitempty" validate:"omitempty,oneof=active inactive maintenance"`
|
Status *string `json:"status,omitempty" validate:"omitempty,oneof=active inactive maintenance"`
|
||||||
|
Region *string `json:"region,omitempty" validate:"omitempty,min=3"`
|
||||||
|
Province *string `json:"province,omitempty" validate:"omitempty,min=3"`
|
||||||
|
City *string `json:"city,omitempty" validate:"omitempty,min=3"`
|
||||||
|
District *string `json:"district,omitempty" validate:"omitempty,min=3"`
|
||||||
}
|
}
|
||||||
|
|
@ -3,15 +3,17 @@ package req
|
||||||
import "github.com/google/uuid"
|
import "github.com/google/uuid"
|
||||||
|
|
||||||
type FishboneDTO struct {
|
type FishboneDTO struct {
|
||||||
BackboneID uuid.UUID `json:"bb_id"`
|
FishboneCode string `json:"fishbone_code" validate:"required,min=3"`
|
||||||
DeviceStartID uuid.UUID `json:"dev_start_id"`
|
BackboneID uuid.UUID `json:"bb_id" validate:"required"`
|
||||||
DeviceEndID uuid.UUID `json:"dev_end_id"`
|
DeviceStartID uuid.UUID `json:"dev_start_id" validate:"required"`
|
||||||
CoreAmount int `json:"core_amount"`
|
DeviceEndID uuid.UUID `json:"dev_end_id" validate:"required"`
|
||||||
|
CoreAmount int `json:"core_amount" validate:"required,min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateFishboneDTO struct {
|
type UpdateFishboneDTO struct {
|
||||||
BackboneID *uuid.UUID `json:"bb_id,omitempty" validate:"omitempty,min=3"`
|
FishboneCode *string `json:"fishbone_code,omitempty" validate:"omitempty,min=3"`
|
||||||
DeviceStartID *uuid.UUID `json:"dev_start_id,omitempty" validate:"omitempty,min=3"`
|
BackboneID *uuid.UUID `json:"bb_id,omitempty"`
|
||||||
DeviceEndID *uuid.UUID `json:"dev_end_id,omitempty" validate:"omitempty,min=3"`
|
DeviceStartID *uuid.UUID `json:"dev_start_id,omitempty"`
|
||||||
CoreAmount *int `json:"core_amount,omitempty" validate:"omitempty,min=1"`
|
DeviceEndID *uuid.UUID `json:"dev_end_id,omitempty"`
|
||||||
|
CoreAmount *int `json:"core_amount,omitempty" validate:"omitempty,min=1"`
|
||||||
}
|
}
|
||||||
|
|
@ -4,14 +4,17 @@ import "github.com/google/uuid"
|
||||||
|
|
||||||
type TowerDTO struct {
|
type TowerDTO struct {
|
||||||
DeviceID uuid.UUID `json:"dev_id"`
|
DeviceID uuid.UUID `json:"dev_id"`
|
||||||
|
DeviceName string `json:"device_name"`
|
||||||
TowerCode string `json:"tower_code"`
|
TowerCode string `json:"tower_code"`
|
||||||
Longitude float64 `json:"longitude"`
|
Longitude float64 `json:"longitude"`
|
||||||
Latitude float64 `json:"latitude"`
|
Latitude float64 `json:"latitude"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateTowerDTO struct {
|
type UpdateTowerDTO struct {
|
||||||
DeviceID *string `json:"device_id,omitempty" validate:"omitempty,min=3"`
|
DeviceID *uuid.UUID `json:"device_id,omitempty" validate:"omitempty,min=3"`
|
||||||
|
DeviceName *string `json:"device_name,omitempty" validate:"omitempty,min=3"`
|
||||||
TowerCode *string `json:"tower_code,omitempty" validate:"omitempty"`
|
TowerCode *string `json:"tower_code,omitempty" validate:"omitempty"`
|
||||||
Longitude *float64 `json:"longitude,omitempty" validate:"omitempty,longitude"`
|
Longitude *float64 `json:"longitude,omitempty" validate:"omitempty,longitude"`
|
||||||
Latitude *float64 `json:"latitude,omitempty" validate:"omitempty,latitude"`
|
Latitude *float64 `json:"latitude,omitempty" validate:"omitempty,latitude"`
|
||||||
|
ImageURL *string `json:"image_url,omitempty" validate:"omitempty,url"`
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package res
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ActivityLogResponse - simplified response for users
|
||||||
|
type ActivityLogResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
Resource string `json:"resource"`
|
||||||
|
IPAddress string `json:"ip_address"`
|
||||||
|
UserAgent string `json:"user_agent"`
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActivityLogDetailResponse - detailed response for admins (if needed)
|
||||||
|
type ActivityLogDetailResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
UserID uuid.UUID `json:"user_id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
Resource string `json:"resource"`
|
||||||
|
ResourceID *string `json:"resource_id,omitempty"`
|
||||||
|
OldData *string `json:"old_data,omitempty"`
|
||||||
|
NewData *string `json:"new_data,omitempty"`
|
||||||
|
IPAddress string `json:"ip_address"`
|
||||||
|
UserAgent string `json:"user_agent"`
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,33 @@
|
||||||
package res
|
package res
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FishboneResponse struct {
|
type FishboneResponse struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
DeviceName string `json:"device_name"`
|
FishboneCode string `json:"fishbone_code"`
|
||||||
TowerCode string `json:"tower_code"`
|
BackboneCode string `json:"backbone_code"`
|
||||||
Longtitude float64 `json:"longtitude"`
|
DeviceStart string `json:"device_start"`
|
||||||
Latitude float64 `json:"latitude"`
|
DeviceEnd string `json:"device_end"`
|
||||||
Address string `json:"address"`
|
CoreAmount int `json:"core_amount"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
TotalFishbone int `json:"total_fishbone"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FishboneDetailResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
FishboneCode string `json:"fishbone_code"`
|
||||||
|
Backbone BackboneInfo `json:"backbone"`
|
||||||
|
CoreAmount int `json:"core_amount"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BackboneInfo struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
BackboneCode string `json:"backbone_code"`
|
||||||
}
|
}
|
||||||
|
|
@ -13,5 +13,6 @@ type TowerResponse struct {
|
||||||
Longitude float64 `json:"longtitude"`
|
Longitude float64 `json:"longtitude"`
|
||||||
Latitude float64 `json:"latitude"`
|
Latitude float64 `json:"latitude"`
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
|
ImageURL string `json:"image_url"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActivityLog struct {
|
||||||
|
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
|
||||||
|
UserID uuid.UUID `json:"user_id" gorm:"type:uuid;not null"`
|
||||||
|
User User `json:"user" gorm:"foreignKey:UserID"`
|
||||||
|
Action string `json:"action" gorm:"not null"` // CREATE, UPDATE, DELETE, LOGIN, LOGOUT
|
||||||
|
Resource string `json:"resource" gorm:"not null"` // devices, backbone, fishbone, towers, etc.
|
||||||
|
ResourceID *string `json:"resource_id,omitempty"` // ID of the affected resource
|
||||||
|
OldData *string `json:"old_data,omitempty" gorm:"type:text"` // JSON of old data for updates
|
||||||
|
NewData *string `json:"new_data,omitempty" gorm:"type:text"` // JSON of new data
|
||||||
|
IPAddress string `json:"ip_address"`
|
||||||
|
UserAgent string `json:"user_agent"`
|
||||||
|
Timestamp time.Time `json:"timestamp" gorm:"autoCreateTime"`
|
||||||
|
Details *string `json:"details,omitempty"` // Additional details
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ActivityLog) TableName() string {
|
||||||
|
return "activity_logs"
|
||||||
|
}
|
||||||
|
|
@ -1,22 +1,21 @@
|
||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Backbone struct {
|
type Backbone struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
|
||||||
BackboneCode string `json:"backbone_code"`
|
BackboneCode string `json:"backbone_code" gorm:"unique"`
|
||||||
DeviceStartID uuid.UUID `json:"dev_start_id" gorm:"column:dev_start_id"`
|
DeviceStartID uuid.UUID `json:"dev_start_id" gorm:"type:uuid;column:dev_start_id"`
|
||||||
DeviceEndID uuid.UUID `json:"dev_end_id" gorm:"column:dev_end_id"`
|
DeviceEndID uuid.UUID `json:"dev_end_id" gorm:"type:uuid;column:dev_end_id"`
|
||||||
CoreAmount int `json:"core_amount"`
|
CoreAmount int `json:"core_amount"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
DeviceStart Device `gorm:"foreignKey:DeviceStartID"`
|
DeviceStart Device `gorm:"foreignKey:DeviceStartID"`
|
||||||
DeviceEnd Device `gorm:"foreignKey:DeviceEndID"`
|
DeviceEnd Device `gorm:"foreignKey:DeviceEndID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Backbone) TableName() string {
|
func (Backbone) TableName() string {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CountAssets struct {
|
||||||
|
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
|
||||||
|
FishboneAmount int `json:"fishbone_amount" gorm:"column:fishbone_amount"`
|
||||||
|
BackboneAmount int `json:"backbone_amount" gorm:"column:backbone_amount"`
|
||||||
|
DeviceAmount int `json:"device_amount" gorm:"column:device_amount"`
|
||||||
|
TowerAmount int `json:"tower_amount" gorm:"column:tower_amount"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"column:created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (CountAssets) TableName() string {
|
||||||
|
return "count_assets"
|
||||||
|
}
|
||||||
|
|
@ -1,21 +1,20 @@
|
||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DevicePort struct {
|
type DevicePort struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
|
||||||
DeviceID uuid.UUID `json:"device_id" gorm:"column:device_id"`
|
DeviceID uuid.UUID `json:"device_id" gorm:"type:uuid;column:device_id"`
|
||||||
PortNumber int `json:"port_number"`
|
PortNumber int `json:"port_number"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
Device Device `gorm:"foreignKey:DeviceID"`
|
Device Device `gorm:"foreignKey:DeviceID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (DevicePort) TableName() string {
|
func (DevicePort) TableName() string {
|
||||||
return "device_ports"
|
return "device_ports"
|
||||||
}
|
}
|
||||||
|
|
@ -1,29 +1,36 @@
|
||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeviceType string
|
type DeviceType string
|
||||||
type DeviceStatus string
|
type DeviceStatus string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Odp DeviceType = "ODP"
|
Odp DeviceType = "ODP"
|
||||||
activeDev DeviceStatus = "active"
|
activeDev DeviceStatus = "active"
|
||||||
inactiveDev DeviceStatus = "inactive"
|
inactiveDev DeviceStatus = "inactive"
|
||||||
maintenanceDev DeviceStatus = "maintenance"
|
maintenanceDev DeviceStatus = "maintenance"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
|
||||||
DeviceCode string `json:"device_code"`
|
DeviceCode string `json:"device_code" gorm:"unique"`
|
||||||
DeviceType DeviceType `json:"device_type"`
|
DeviceType DeviceType `json:"device_type"`
|
||||||
Longitude float64 `json:"longitude"`
|
Longitude float64 `json:"longitude"`
|
||||||
Latitude float64 `json:"latitude"`
|
Latitude float64 `json:"latitude"`
|
||||||
PortAmount int `json:"port_amount"`
|
PortAmount int `json:"port_amount"`
|
||||||
Status DeviceStatus `json:"status"`
|
Status DeviceStatus `json:"status"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
Region string `json:"region"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
Province string `json:"province"`
|
||||||
|
City string `json:"city"`
|
||||||
|
District string `json:"district"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Device) TableName() string {
|
||||||
|
return "devices"
|
||||||
}
|
}
|
||||||
|
|
@ -1,27 +1,26 @@
|
||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Fishbone struct {
|
type Fishbone struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
|
||||||
BackboneID uuid.UUID `json:"backbone_id" gorm:"column:bb_id"`
|
FishboneCode string `json:"fishbone_code" gorm:"unique"`
|
||||||
DeviceStartID uuid.UUID `json:"dev_start_id" gorm:"column:dev_start_id"`
|
BackboneID uuid.UUID `json:"bb_id" gorm:"type:uuid;column:bb_id"`
|
||||||
DeviceEndID uuid.UUID `json:"dev_end_id" gorm:"column:dev_end_id"`
|
DeviceStartID uuid.UUID `json:"dev_start_id" gorm:"type:uuid;column:dev_start_id"`
|
||||||
CoreAmount int `json:"core_amount"`
|
DeviceEndID uuid.UUID `json:"dev_end_id" gorm:"type:uuid;column:dev_end_id"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CoreAmount int `json:"core_amount" gorm:"column:core_amount"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
DeviceStart Device `gorm:"foreignKey:DeviceStartID"`
|
|
||||||
DeviceEnd Device `gorm:"foreignKey:DeviceEndID"`
|
|
||||||
|
|
||||||
Backbone Backbone `gorm:"foreignKey:BackboneID"`
|
|
||||||
|
|
||||||
|
// Relationships
|
||||||
|
Backbone Backbone `json:"backbone" gorm:"foreignKey:BackboneID"`
|
||||||
|
DeviceStart Device `json:"device_start" gorm:"foreignKey:DeviceStartID"`
|
||||||
|
DeviceEnd Device `json:"device_end" gorm:"foreignKey:DeviceEndID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Fishbone) TableName() string {
|
func (Fishbone) TableName() string {
|
||||||
return "fishbone"
|
return "fishbone"
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,18 @@
|
||||||
package entity
|
package entity
|
||||||
|
|
||||||
import "github.com/google/uuid"
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
type Role struct {
|
type Role struct {
|
||||||
Id uuid.UUID `json:"id"`
|
Id uuid.UUID `json:"id" gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name" gorm:"type:varchar(100);not null"`
|
||||||
CreatedAt string `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt string `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Role) TableName() string {
|
||||||
|
return "roles"
|
||||||
}
|
}
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Tower struct {
|
type Tower struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
|
||||||
DeviceID uuid.UUID`json:"dev_id" gorm:"column:dev_id"`
|
DeviceID uuid.UUID `json:"dev_id" gorm:"type:uuid;column:dev_id"`
|
||||||
TowerCode string `json:"tower_code"`
|
TowerCode string `json:"tower_code" gorm:"unique"`
|
||||||
Longitude float64 `json:"longitude"`
|
Longitude float64 `json:"longitude"`
|
||||||
Latitude float64 `json:"latitude"`
|
Latitude float64 `json:"latitude"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
ImageURL string `json:"image_url" gorm:"column:image_url"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
Device Device `gorm:"foreignKey:DeviceID"`
|
Device Device `gorm:"foreignKey:DeviceID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Tower) TableName() string {
|
func (Tower) TableName() string {
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,21 @@ package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
|
||||||
RoleID uuid.UUID `json:"role_id"`
|
NomorInduk *string `json:"nomor_induk,omitempty" gorm:"unique"` // Add this field
|
||||||
Role Role `json:"role"`
|
RoleID uuid.UUID `json:"role_id" gorm:"type:uuid"`
|
||||||
Name string `json:"name"`
|
Role Role `json:"role" gorm:"foreignKey:RoleID"`
|
||||||
Username string `json:"username"`
|
Name string `json:"name"`
|
||||||
Password string `json:"password"`
|
Username string `json:"username" gorm:"unique"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
Password string `json:"password"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (User) TableName() string {
|
||||||
|
return "users"
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"users_management/m/model/entity"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActivityLogRepo interface {
|
||||||
|
Create(log entity.ActivityLog) error
|
||||||
|
GetByUserID(userID uuid.UUID, limit, offset int) ([]entity.ActivityLog, error)
|
||||||
|
GetAllForAdmins(limit, offset int) ([]entity.ActivityLog, error)
|
||||||
|
GetByUserRole(role string, limit, offset int) ([]entity.ActivityLog, error)
|
||||||
|
CountByUserID(userID uuid.UUID) (int64, error)
|
||||||
|
CountAll() (int64, error)
|
||||||
|
CountByUserRole(role string) (int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type activityLogRepo struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActivityLogRepo(db *gorm.DB) ActivityLogRepo {
|
||||||
|
return &activityLogRepo{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *activityLogRepo) Create(log entity.ActivityLog) error {
|
||||||
|
return r.db.Create(&log).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *activityLogRepo) GetByUserID(userID uuid.UUID, limit, offset int) ([]entity.ActivityLog, error) {
|
||||||
|
var logs []entity.ActivityLog
|
||||||
|
err := r.db.Where("user_id = ?", userID).
|
||||||
|
Preload("User").
|
||||||
|
Preload("User.Role").
|
||||||
|
Order("timestamp DESC").
|
||||||
|
Limit(limit).
|
||||||
|
Offset(offset).
|
||||||
|
Find(&logs).Error
|
||||||
|
return logs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *activityLogRepo) GetAllForAdmins(limit, offset int) ([]entity.ActivityLog, error) {
|
||||||
|
var logs []entity.ActivityLog
|
||||||
|
err := r.db.Preload("User").
|
||||||
|
Preload("User.Role").
|
||||||
|
Order("timestamp DESC").
|
||||||
|
Limit(limit).
|
||||||
|
Offset(offset).
|
||||||
|
Find(&logs).Error
|
||||||
|
return logs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *activityLogRepo) GetByUserRole(role string, limit, offset int) ([]entity.ActivityLog, error) {
|
||||||
|
var logs []entity.ActivityLog
|
||||||
|
err := r.db.Joins("JOIN users ON users.id = activity_logs.user_id").
|
||||||
|
Joins("JOIN roles ON roles.id = users.role_id").
|
||||||
|
Where("roles.name = ?", role).
|
||||||
|
Preload("User").
|
||||||
|
Preload("User.Role").
|
||||||
|
Order("activity_logs.timestamp DESC").
|
||||||
|
Limit(limit).
|
||||||
|
Offset(offset).
|
||||||
|
Find(&logs).Error
|
||||||
|
return logs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *activityLogRepo) CountByUserID(userID uuid.UUID) (int64, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.db.Model(&entity.ActivityLog{}).Where("user_id = ?", userID).Count(&count).Error
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *activityLogRepo) CountAll() (int64, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.db.Model(&entity.ActivityLog{}).Count(&count).Error
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *activityLogRepo) CountByUserRole(role string) (int64, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.db.Model(&entity.ActivityLog{}).
|
||||||
|
Joins("JOIN users ON users.id = activity_logs.user_id").
|
||||||
|
Joins("JOIN roles ON roles.id = users.role_id").
|
||||||
|
Where("roles.name = ?", role).
|
||||||
|
Count(&count).Error
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"users_management/m/model/entity"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CountAssetsRepo interface {
|
||||||
|
Get() (entity.CountAssets, error)
|
||||||
|
UpdateCounts() error
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type countAssetsRepo struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func NewCountAssetsRepo(db *gorm.DB) CountAssetsRepo {
|
||||||
|
return &countAssetsRepo{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *countAssetsRepo) Get() (entity.CountAssets, error) {
|
||||||
|
var countAssets entity.CountAssets
|
||||||
|
|
||||||
|
// Try to get existing record, if not found create a new one
|
||||||
|
err := r.db.First(&countAssets).Error
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
// Create new record with current counts
|
||||||
|
return r.createNewCountRecord()
|
||||||
|
}
|
||||||
|
return countAssets, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return countAssets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *countAssetsRepo) UpdateCounts() error {
|
||||||
|
var countAssets entity.CountAssets
|
||||||
|
|
||||||
|
// Get current counts from each table
|
||||||
|
fishboneCount, err := r.countFishbones()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
backboneCount, err := r.countBackbones()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceCount, err := r.countDevices()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
towerCount, err := r.countTowers()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if record exists
|
||||||
|
err = r.db.First(&countAssets).Error
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
// Create new record
|
||||||
|
countAssets = entity.CountAssets{
|
||||||
|
ID: uuid.New(),
|
||||||
|
FishboneAmount: fishboneCount,
|
||||||
|
BackboneAmount: backboneCount,
|
||||||
|
DeviceAmount: deviceCount,
|
||||||
|
TowerAmount: towerCount,
|
||||||
|
}
|
||||||
|
return r.db.Create(&countAssets).Error
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update existing record
|
||||||
|
updates := map[string]interface{}{
|
||||||
|
"fishbone_amount": fishboneCount,
|
||||||
|
"backbone_amount": backboneCount,
|
||||||
|
"device_amount": deviceCount,
|
||||||
|
"tower_amount": towerCount,
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.db.Model(&countAssets).Updates(updates).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *countAssetsRepo) createNewCountRecord() (entity.CountAssets, error) {
|
||||||
|
fishboneCount, _ := r.countFishbones()
|
||||||
|
backboneCount, _ := r.countBackbones()
|
||||||
|
deviceCount, _ := r.countDevices()
|
||||||
|
towerCount, _ := r.countTowers()
|
||||||
|
|
||||||
|
countAssets := entity.CountAssets{
|
||||||
|
ID: uuid.New(),
|
||||||
|
FishboneAmount: fishboneCount,
|
||||||
|
BackboneAmount: backboneCount,
|
||||||
|
DeviceAmount: deviceCount,
|
||||||
|
TowerAmount: towerCount,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.db.Create(&countAssets).Error
|
||||||
|
return countAssets, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper methods to count each entity
|
||||||
|
func (r *countAssetsRepo) countFishbones() (int, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.db.Model(&entity.Fishbone{}).Count(&count).Error
|
||||||
|
return int(count), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *countAssetsRepo) countBackbones() (int, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.db.Model(&entity.Backbone{}).Count(&count).Error
|
||||||
|
return int(count), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *countAssetsRepo) countDevices() (int, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.db.Model(&entity.Device{}).Count(&count).Error
|
||||||
|
return int(count), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *countAssetsRepo) countTowers() (int, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.db.Model(&entity.Tower{}).Count(&count).Error
|
||||||
|
return int(count), err
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"users_management/m/model/entity"
|
"users_management/m/model/entity"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -16,6 +17,12 @@ type FishboneRepo interface {
|
||||||
CountFishbone() (map[uuid.UUID]int, error)
|
CountFishbone() (map[uuid.UUID]int, error)
|
||||||
|
|
||||||
CountFishboneByBackboneID(backboneID uuid.UUID) (int, error)
|
CountFishboneByBackboneID(backboneID uuid.UUID) (int, error)
|
||||||
|
GetAllWithRelations() ([]entity.Fishbone, error)
|
||||||
|
GetByIDWithRelations(id uuid.UUID) (entity.Fishbone, error)
|
||||||
|
CheckFishboneExists(id uuid.UUID) (bool, error)
|
||||||
|
CheckBackboneExists(id uuid.UUID) (bool, error)
|
||||||
|
CheckDeviceExists(id uuid.UUID) (bool, error)
|
||||||
|
Delete(id uuid.UUID) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type fishboneRepo struct {
|
type fishboneRepo struct {
|
||||||
|
|
@ -37,11 +44,14 @@ func (r *fishboneRepo) Post(fishbone entity.Fishbone) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *fishboneRepo) GetAll() ([]entity.Fishbone, error) {
|
func (r *fishboneRepo) GetAll() ([]entity.Fishbone, error) {
|
||||||
|
log.Print("Fetching all fishbones with relations")
|
||||||
var fishbones []entity.Fishbone
|
var fishbones []entity.Fishbone
|
||||||
err := r.db.Preload("DeviceStart").Preload("DeviceEnd").Preload("Backbone").Preload("Backbone.DeviceStart").Preload("Backbone.DeviceEnd").Find(&fishbones).Error
|
err := r.db.Preload("DeviceStart").Preload("DeviceEnd").Preload("Backbone").Find(&fishbones).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fishbones, err
|
return fishbones, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Print(fishbones)
|
||||||
return fishbones, nil
|
return fishbones, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,3 +104,37 @@ func (r *fishboneRepo) CountFishboneByBackboneID(backboneID uuid.UUID) (int, err
|
||||||
}
|
}
|
||||||
return int(count), nil
|
return int(count), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *fishboneRepo) GetAllWithRelations() ([]entity.Fishbone, error) {
|
||||||
|
var fishbones []entity.Fishbone
|
||||||
|
err := r.db.Preload("Backbone").Preload("DeviceStart").Preload("DeviceEnd").Find(&fishbones).Error
|
||||||
|
return fishbones, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fishboneRepo) GetByIDWithRelations(id uuid.UUID) (entity.Fishbone, error) {
|
||||||
|
var fishbone entity.Fishbone
|
||||||
|
err := r.db.Preload("Backbone").Preload("DeviceStart").Preload("DeviceEnd").Where("id = ?", id).First(&fishbone).Error
|
||||||
|
return fishbone, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fishboneRepo) CheckFishboneExists(id uuid.UUID) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.db.Model(&entity.Fishbone{}).Where("id = ?", id).Count(&count).Error
|
||||||
|
return count > 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fishboneRepo) CheckBackboneExists(id uuid.UUID) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.db.Model(&entity.Backbone{}).Where("id = ?", id).Count(&count).Error
|
||||||
|
return count > 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fishboneRepo) CheckDeviceExists(id uuid.UUID) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.db.Model(&entity.Device{}).Where("id = ?", id).Count(&count).Error
|
||||||
|
return count > 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fishboneRepo) Delete(id uuid.UUID) error {
|
||||||
|
return r.db.Where("id = ?", id).Delete(&entity.Fishbone{}).Error
|
||||||
|
}
|
||||||
|
|
@ -1,50 +1,75 @@
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"users_management/m/model/entity"
|
"users_management/m/model/entity"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UsersRepo interface {
|
type UsersRepo interface {
|
||||||
Post(user entity.User) error
|
Post(user entity.User) error
|
||||||
GetRoleByDepartment(departmentName string) (entity.Role, error)
|
GetRoleByDepartment(departmentName string) (entity.Role, error)
|
||||||
GetUserByUsername(username string) (entity.User, error)
|
GetUserByUsername(username string) (entity.User, error)
|
||||||
|
GetUserByNomorInduk(nomorInduk string) (entity.User, error) // Add this
|
||||||
|
CreateUserFromExternal(user entity.User) error // Add this
|
||||||
|
UpdateUserRole(nomorInduk string, roleID uuid.UUID) error // Add this
|
||||||
|
GetAllUsers() ([]entity.User, error) // Add this
|
||||||
}
|
}
|
||||||
|
|
||||||
type usersRepo struct {
|
type usersRepo struct {
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUsersRepo(db *gorm.DB) UsersRepo {
|
func NewUsersRepo(db *gorm.DB) UsersRepo {
|
||||||
return &usersRepo{
|
return &usersRepo{
|
||||||
db: db,
|
db: db,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *usersRepo) Post(user entity.User) error {
|
func (r *usersRepo) Post(user entity.User) error {
|
||||||
err := r.db.Create(&user).Error
|
err := r.db.Create(&user).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *usersRepo) GetRoleByDepartment(departmentName string) (entity.Role, error) {
|
func (r *usersRepo) GetRoleByDepartment(departmentName string) (entity.Role, error) {
|
||||||
var role entity.Role
|
var role entity.Role
|
||||||
err := r.db.Where("name = ?", departmentName).First(&role).Error
|
err := r.db.Where("name = ?", departmentName).First(&role).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return role, err
|
return role, err
|
||||||
}
|
}
|
||||||
return role, nil
|
return role, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *usersRepo) GetUserByUsername(username string) (entity.User, error) {
|
func (r *usersRepo) GetUserByUsername(username string) (entity.User, error) {
|
||||||
var users entity.User
|
var users entity.User
|
||||||
err := r.db.Where("username = ?", username).First(&users).Error
|
err := r.db.Where("username = ?", username).Preload("Role").First(&users).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return users, err
|
return users, err
|
||||||
}
|
}
|
||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New methods for RBAC
|
||||||
|
func (r *usersRepo) GetUserByNomorInduk(nomorInduk string) (entity.User, error) {
|
||||||
|
var user entity.User
|
||||||
|
err := r.db.Where("nomor_induk = ?", nomorInduk).Preload("Role").First(&user).Error
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *usersRepo) CreateUserFromExternal(user entity.User) error {
|
||||||
|
return r.db.Create(&user).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *usersRepo) UpdateUserRole(nomorInduk string, roleID uuid.UUID) error {
|
||||||
|
return r.db.Model(&entity.User{}).Where("nomor_induk = ?", nomorInduk).Update("role_id", roleID).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *usersRepo) GetAllUsers() ([]entity.User, error) {
|
||||||
|
var users []entity.User
|
||||||
|
err := r.db.Preload("Role").Find(&users).Error
|
||||||
|
return users, err
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"users_management/m/model/entity"
|
||||||
|
"users_management/m/repository"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActivityLogUseCase interface {
|
||||||
|
LogActivity(userID uuid.UUID, action, resource string, resourceID *string, oldData, newData interface{}, ipAddress, userAgent, details string) error
|
||||||
|
GetUserLogs(userID uuid.UUID, page, limit int) ([]entity.ActivityLog, int64, error)
|
||||||
|
GetAllLogs(page, limit int) ([]entity.ActivityLog, int64, error)
|
||||||
|
GetTeknisinLogs(page, limit int) ([]entity.ActivityLog, int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type activityLogUseCase struct {
|
||||||
|
activityLogRepo repository.ActivityLogRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActivityLogUseCase(activityLogRepo repository.ActivityLogRepo) ActivityLogUseCase {
|
||||||
|
return &activityLogUseCase{
|
||||||
|
activityLogRepo: activityLogRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *activityLogUseCase) LogActivity(userID uuid.UUID, action, resource string, resourceID *string, oldData, newData interface{}, ipAddress, userAgent, details string) error {
|
||||||
|
var oldDataJSON, newDataJSON *string
|
||||||
|
|
||||||
|
if oldData != nil {
|
||||||
|
oldBytes, err := json.Marshal(oldData)
|
||||||
|
if err == nil {
|
||||||
|
oldStr := string(oldBytes)
|
||||||
|
oldDataJSON = &oldStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if newData != nil {
|
||||||
|
newBytes, err := json.Marshal(newData)
|
||||||
|
if err == nil {
|
||||||
|
newStr := string(newBytes)
|
||||||
|
newDataJSON = &newStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var detailsPtr *string
|
||||||
|
if details != "" {
|
||||||
|
detailsPtr = &details
|
||||||
|
}
|
||||||
|
|
||||||
|
log := entity.ActivityLog{
|
||||||
|
ID: uuid.New(),
|
||||||
|
UserID: userID,
|
||||||
|
Action: action,
|
||||||
|
Resource: resource,
|
||||||
|
ResourceID: resourceID,
|
||||||
|
OldData: oldDataJSON,
|
||||||
|
NewData: newDataJSON,
|
||||||
|
IPAddress: ipAddress,
|
||||||
|
UserAgent: userAgent,
|
||||||
|
Details: detailsPtr,
|
||||||
|
}
|
||||||
|
|
||||||
|
return uc.activityLogRepo.Create(log)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *activityLogUseCase) GetUserLogs(userID uuid.UUID, page, limit int) ([]entity.ActivityLog, int64, error) {
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
log.Printf("Fetching logs for user %s, page: %d, limit: %d, offset: %d", userID, page, limit, offset)
|
||||||
|
logs, err := uc.activityLogRepo.GetByUserID(userID, limit, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
total, err := uc.activityLogRepo.CountByUserID(userID)
|
||||||
|
return logs, total, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *activityLogUseCase) GetAllLogs(page, limit int) ([]entity.ActivityLog, int64, error) {
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
logs, err := uc.activityLogRepo.GetAllForAdmins(limit, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
total, err := uc.activityLogRepo.CountAll()
|
||||||
|
return logs, total, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *activityLogUseCase) GetTeknisinLogs(page, limit int) ([]entity.ActivityLog, int64, error) {
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
logs, err := uc.activityLogRepo.GetByUserRole("Teknisi", limit, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
total, err := uc.activityLogRepo.CountByUserRole("Teknisi")
|
||||||
|
return logs, total, err
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package usecase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
@ -28,13 +29,32 @@ type authUsecase struct {
|
||||||
userRepo repository.UsersRepo
|
userRepo repository.UsersRepo
|
||||||
validate *validator.Validate
|
validate *validator.Validate
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
|
client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthUsecase(userRepo repository.UsersRepo, cfg *config.Config) AuthUsecase {
|
func NewAuthUsecase(userRepo repository.UsersRepo, cfg *config.Config) AuthUsecase {
|
||||||
|
var client *http.Client
|
||||||
|
|
||||||
|
if cfg.LoginConfig.SkipSSLVerification {
|
||||||
|
// Create HTTP client with SSL verification disabled
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
client = &http.Client{
|
||||||
|
Transport: tr,
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use standard HTTP client with SSL verification enabled
|
||||||
|
client = &http.Client{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
}
|
||||||
|
}
|
||||||
return &authUsecase{
|
return &authUsecase{
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
validate: validator.New(),
|
validate: validator.New(),
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
|
client: client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,14 +74,16 @@ func (u *authUsecase) Login(login dto.UserLoginDTO) (string, string, string, err
|
||||||
return "","","", err
|
return "","","", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
client := &http.Client{}
|
// client := &http.Client{}
|
||||||
resp, err := client.Do(req)
|
resp, err := u.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "","","", err
|
return "","","", err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
|
@ -88,7 +110,8 @@ func (u *authUsecase) Login(login dto.UserLoginDTO) (string, string, string, err
|
||||||
meReq.Header.Set("Authorization", "Bearer "+token)
|
meReq.Header.Set("Authorization", "Bearer "+token)
|
||||||
meReq.Header.Set("Accept", "application/json")
|
meReq.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
meResp, err := client.Do(meReq)
|
meResp, err := u.client.Do(meReq)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "","","", err
|
return "","","", err
|
||||||
}
|
}
|
||||||
|
|
@ -109,15 +132,25 @@ func (u *authUsecase) Login(login dto.UserLoginDTO) (string, string, string, err
|
||||||
return "","","", err
|
return "","","", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
departemen := meResponse.Data.Departemen
|
departemen := meResponse.Data.Departemen
|
||||||
if departemen != "Teknisi" {
|
if departemen != "TECHNICAL PRORGAMMER" {
|
||||||
return "","","", errors.New("user is not a technician")
|
return "","","", errors.New("user is not a technician")
|
||||||
}
|
}
|
||||||
|
|
||||||
role , err := u.userRepo.GetRoleByDepartment(departemen)
|
isUserExist, err := u.userRepo.GetUserByUsername(login.Username)
|
||||||
|
if isUserExist.ID != uuid.Nil {
|
||||||
|
// Validate the password
|
||||||
|
if !utils.CheckPasswordHash(login.Password, isUserExist.Password) {
|
||||||
|
return "","","", errors.New("incorrect password")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "","","", err
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
return token, isUserExist.Role.Name, isUserExist.Name, nil
|
||||||
return "","","", err
|
}else if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
|
return "ERROR WHILE SEARCHING USERNAME","", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
password, err := utils.HashPassword(login.Password)
|
password, err := utils.HashPassword(login.Password)
|
||||||
|
|
@ -125,19 +158,11 @@ func (u *authUsecase) Login(login dto.UserLoginDTO) (string, string, string, err
|
||||||
return "error while hasing password: ","", "", err
|
return "error while hasing password: ","", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
isUserExist, err := u.userRepo.GetUserByUsername(login.Username)
|
default_role := "Teknisi"
|
||||||
|
role, err := u.userRepo.GetRoleByDepartment(default_role)
|
||||||
if isUserExist.ID != uuid.Nil {
|
if err != nil {
|
||||||
// Validate the password
|
return "","","", err
|
||||||
if !utils.CheckPasswordHash(login.Password, isUserExist.Password) {
|
}
|
||||||
return "","","", errors.New("incorrect password")
|
|
||||||
}
|
|
||||||
|
|
||||||
return token, role.Name, isUserExist.Name, nil
|
|
||||||
}else if err != nil && err != gorm.ErrRecordNotFound {
|
|
||||||
return "ERROR WHILE SEARCHING USERNAME","", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
user := entity.User{
|
user := entity.User{
|
||||||
ID: uuid.New(),
|
ID: uuid.New(),
|
||||||
|
|
@ -148,7 +173,6 @@ func (u *authUsecase) Login(login dto.UserLoginDTO) (string, string, string, err
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = u.userRepo.Post(user)
|
err = u.userRepo.Post(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "","","", err
|
return "","","", err
|
||||||
|
|
@ -166,8 +190,8 @@ func (u *authUsecase) Logout(token string) error {
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
client := &http.Client{}
|
// client := &http.Client{}
|
||||||
resp, err := client.Do(req)
|
resp, err := u.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"users_management/m/model/entity"
|
||||||
|
"users_management/m/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CountAssetsUseCase interface {
|
||||||
|
GetCounts() (entity.CountAssets, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type countAssetsUseCase struct {
|
||||||
|
countAssetsRepo repository.CountAssetsRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCountAssetsUseCase(countAssetsRepo repository.CountAssetsRepo) CountAssetsUseCase {
|
||||||
|
return &countAssetsUseCase{
|
||||||
|
countAssetsRepo: countAssetsRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *countAssetsUseCase) GetCounts() (entity.CountAssets, error) {
|
||||||
|
// Update counts before returning
|
||||||
|
err := uc.countAssetsRepo.UpdateCounts()
|
||||||
|
if err != nil {
|
||||||
|
return entity.CountAssets{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the updated counts
|
||||||
|
return uc.countAssetsRepo.Get()
|
||||||
|
}
|
||||||
|
|
@ -51,6 +51,10 @@ func (u *deviceUseCase) CreateDevice(device req.DeviceDTO) error {
|
||||||
Latitude: device.Latitude,
|
Latitude: device.Latitude,
|
||||||
PortAmount: device.PortAmount,
|
PortAmount: device.PortAmount,
|
||||||
Status: entity.DeviceStatus(device.Status),
|
Status: entity.DeviceStatus(device.Status),
|
||||||
|
Region: device.Region,
|
||||||
|
Province: device.Province,
|
||||||
|
City: device.City,
|
||||||
|
District: device.District,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
@ -106,6 +110,21 @@ func (u *deviceUseCase) UpdateDevice(id uuid.UUID, device req.UpdateDeviceDTO) e
|
||||||
if device.PortAmount != nil {
|
if device.PortAmount != nil {
|
||||||
updates["PortAmount"] = *device.PortAmount
|
updates["PortAmount"] = *device.PortAmount
|
||||||
}
|
}
|
||||||
|
if device.Status != nil {
|
||||||
|
updates["Status"] = *device.Status
|
||||||
|
}
|
||||||
|
if device.Region != nil {
|
||||||
|
updates["Region"] = *device.Region
|
||||||
|
}
|
||||||
|
if device.Province != nil {
|
||||||
|
updates["Province"] = *device.Province
|
||||||
|
}
|
||||||
|
if device.City != nil {
|
||||||
|
updates["City"] = *device.City
|
||||||
|
}
|
||||||
|
if device.District != nil {
|
||||||
|
updates["District"] = *device.District
|
||||||
|
}
|
||||||
|
|
||||||
if len(updates) == 0 {
|
if len(updates) == 0 {
|
||||||
return fmt.Errorf("no update data")
|
return fmt.Errorf("no update data")
|
||||||
|
|
|
||||||
|
|
@ -1,97 +1,188 @@
|
||||||
package usecase
|
package usecase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
"users_management/m/model/dto/req"
|
"users_management/m/model/dto/req"
|
||||||
"users_management/m/model/entity"
|
"users_management/m/model/dto/res"
|
||||||
"users_management/m/repository"
|
"users_management/m/model/entity"
|
||||||
|
"users_management/m/repository"
|
||||||
|
"users_management/m/utils/helper"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FishboneUseCase interface {
|
type FishboneUseCase interface {
|
||||||
CreateFishbone(fishbone req.FishboneDTO) error
|
CreateFishbone(fishbone req.FishboneDTO) error
|
||||||
GetAllFishbone() ([]entity.Fishbone, error)
|
GetAllFishbone() ([]res.FishboneResponse, error)
|
||||||
GetByID(id uuid.UUID) (entity.Fishbone, error)
|
GetByID(id uuid.UUID) (res.FishboneDetailResponse, error)
|
||||||
|
UpdateFishbone(id uuid.UUID, fishbone req.UpdateFishboneDTO) error
|
||||||
UpdateFishbone(id uuid.UUID, fishbone req.UpdateFishboneDTO) error
|
DeleteFishbone(id uuid.UUID) error
|
||||||
|
GetFishboneStats() (map[string]interface{}, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type fishboneUsecase struct {
|
type fishboneUsecase struct {
|
||||||
fishboneRepo repository.FishboneRepo
|
fishboneRepo repository.FishboneRepo
|
||||||
validate *validator.Validate
|
validate *validator.Validate
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFishboneUseCase(fishboneRepo repository.FishboneRepo) FishboneUseCase {
|
func NewFishboneUseCase(fishboneRepo repository.FishboneRepo) FishboneUseCase {
|
||||||
return &fishboneUsecase{
|
return &fishboneUsecase{
|
||||||
fishboneRepo: fishboneRepo,
|
fishboneRepo: fishboneRepo,
|
||||||
validate: validator.New(),
|
validate: validator.New(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *fishboneUsecase) CreateFishbone(fishbone req.FishboneDTO) error {
|
func (u *fishboneUsecase) CreateFishbone(fishbone req.FishboneDTO) error {
|
||||||
err := u.validate.Struct(fishbone)
|
err := u.validate.Struct(fishbone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
newFishbone := entity.Fishbone{
|
// Check if backbone exists
|
||||||
ID: uuid.New(),
|
backboneExists, err := u.fishboneRepo.CheckBackboneExists(fishbone.BackboneID)
|
||||||
BackboneID: fishbone.BackboneID,
|
if err != nil {
|
||||||
DeviceStartID: fishbone.DeviceStartID,
|
return err
|
||||||
DeviceEndID: fishbone.DeviceEndID,
|
}
|
||||||
CoreAmount: fishbone.CoreAmount,
|
if !backboneExists {
|
||||||
CreatedAt: time.Now(),
|
return errors.New("backbone not found")
|
||||||
UpdatedAt: time.Now(),
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return u.fishboneRepo.Post(newFishbone)
|
// Check if devices exist
|
||||||
|
deviceStartExists, err := u.fishboneRepo.CheckDeviceExists(fishbone.DeviceStartID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !deviceStartExists {
|
||||||
|
return errors.New("start device not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceEndExists, err := u.fishboneRepo.CheckDeviceExists(fishbone.DeviceEndID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !deviceEndExists {
|
||||||
|
return errors.New("end device not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
newFishbone := entity.Fishbone{
|
||||||
|
ID: uuid.New(),
|
||||||
|
FishboneCode: fishbone.FishboneCode,
|
||||||
|
BackboneID: fishbone.BackboneID,
|
||||||
|
DeviceStartID: fishbone.DeviceStartID,
|
||||||
|
DeviceEndID: fishbone.DeviceEndID,
|
||||||
|
CoreAmount: fishbone.CoreAmount,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.fishboneRepo.Post(newFishbone)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *fishboneUsecase) GetAllFishbone() ([]entity.Fishbone, error) {
|
func (u *fishboneUsecase) GetAllFishbone() ([]res.FishboneResponse, error) {
|
||||||
fishbones, err := u.fishboneRepo.GetAll()
|
fishbones, err := u.fishboneRepo.GetAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fishbones, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return fishbones, nil
|
|
||||||
|
return helper.ConvertToSimpleFishboneResponses(fishbones), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *fishboneUsecase) GetByID(id uuid.UUID) (entity.Fishbone, error) {
|
func (u *fishboneUsecase) GetByID(id uuid.UUID) (res.FishboneDetailResponse, error) {
|
||||||
fishbone, err := u.fishboneRepo.GetByID(id)
|
fishbone, err := u.fishboneRepo.GetByIDWithRelations(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fishbone, err
|
return res.FishboneDetailResponse{}, err
|
||||||
}
|
}
|
||||||
return fishbone, nil
|
|
||||||
|
// Convert to detailed response using helper
|
||||||
|
return helper.ConvertToFishboneDetailResponse(fishbone), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *fishboneUsecase) UpdateFishbone(id uuid.UUID, fishbone req.UpdateFishboneDTO) error {
|
func (u *fishboneUsecase) UpdateFishbone(id uuid.UUID, fishbone req.UpdateFishboneDTO) error {
|
||||||
err := u.validate.Struct(fishbone)
|
err := u.validate.Struct(fishbone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
updates := make(map[string]interface{})
|
// Check if fishbone exists
|
||||||
|
exists, err := u.fishboneRepo.CheckFishboneExists(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return errors.New("fishbone not found")
|
||||||
|
}
|
||||||
|
|
||||||
if fishbone.BackboneID != nil {
|
updates := make(map[string]interface{})
|
||||||
updates["BackboneID"] = *fishbone.BackboneID
|
|
||||||
}
|
|
||||||
if fishbone.DeviceStartID != nil {
|
|
||||||
updates["DeviceStartID"] = *fishbone.DeviceStartID
|
|
||||||
}
|
|
||||||
if fishbone.DeviceEndID != nil {
|
|
||||||
updates["DeviceEndID"] = *fishbone.DeviceEndID
|
|
||||||
}
|
|
||||||
if fishbone.CoreAmount != nil {
|
|
||||||
updates["CoreAmount"] = *fishbone.CoreAmount
|
|
||||||
}
|
|
||||||
if len(updates) == 0 {
|
|
||||||
return errors.New("no fields to update")
|
|
||||||
}
|
|
||||||
|
|
||||||
updates["UpdatedAt"] = time.Now()
|
if fishbone.FishboneCode != nil {
|
||||||
|
updates["fishbone_code"] = *fishbone.FishboneCode
|
||||||
|
}
|
||||||
|
if fishbone.BackboneID != nil {
|
||||||
|
// Validate backbone exists
|
||||||
|
backboneExists, err := u.fishboneRepo.CheckBackboneExists(*fishbone.BackboneID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !backboneExists {
|
||||||
|
return errors.New("backbone not found")
|
||||||
|
}
|
||||||
|
updates["bb_id"] = *fishbone.BackboneID
|
||||||
|
}
|
||||||
|
if fishbone.DeviceStartID != nil {
|
||||||
|
// Validate device exists
|
||||||
|
deviceExists, err := u.fishboneRepo.CheckDeviceExists(*fishbone.DeviceStartID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !deviceExists {
|
||||||
|
return errors.New("start device not found")
|
||||||
|
}
|
||||||
|
updates["dev_start_id"] = *fishbone.DeviceStartID
|
||||||
|
}
|
||||||
|
if fishbone.DeviceEndID != nil {
|
||||||
|
// Validate device exists
|
||||||
|
deviceExists, err := u.fishboneRepo.CheckDeviceExists(*fishbone.DeviceEndID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !deviceExists {
|
||||||
|
return errors.New("end device not found")
|
||||||
|
}
|
||||||
|
updates["dev_end_id"] = *fishbone.DeviceEndID
|
||||||
|
}
|
||||||
|
if fishbone.CoreAmount != nil {
|
||||||
|
updates["core_amount"] = *fishbone.CoreAmount
|
||||||
|
}
|
||||||
|
|
||||||
return u.fishboneRepo.Update(id, updates)
|
if len(updates) == 0 {
|
||||||
|
return errors.New("no fields to update")
|
||||||
|
}
|
||||||
|
|
||||||
|
updates["updated_at"] = time.Now()
|
||||||
|
|
||||||
|
return u.fishboneRepo.Update(id, updates)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *fishboneUsecase) DeleteFishbone(id uuid.UUID) error {
|
||||||
|
// Check if fishbone exists
|
||||||
|
exists, err := u.fishboneRepo.CheckFishboneExists(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return errors.New("fishbone not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.fishboneRepo.Delete(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *fishboneUsecase) GetFishboneStats() (map[string]interface{}, error) {
|
||||||
|
fishbones, err := u.fishboneRepo.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return helper.GetFishboneStats(fishbones), nil
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package usecase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"mime/multipart"
|
||||||
"time"
|
"time"
|
||||||
"users_management/m/model/dto/req"
|
"users_management/m/model/dto/req"
|
||||||
"users_management/m/model/dto/res"
|
"users_management/m/model/dto/res"
|
||||||
|
|
@ -15,10 +16,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type TowerUseCase interface {
|
type TowerUseCase interface {
|
||||||
Post(tower req.TowerDTO) error
|
Post(tower req.TowerDTO, imageFile *multipart.FileHeader) error
|
||||||
GetAll() ([]res.TowerResponse, error)
|
GetAll() ([]res.TowerResponse, error)
|
||||||
GetByID(id uuid.UUID) (res.TowerResponse, error)
|
GetByID(id uuid.UUID) (res.TowerResponse, error)
|
||||||
UpdateTower(id uuid.UUID, tower req.UpdateTowerDTO) error
|
UpdateTower(id uuid.UUID, tower req.UpdateTowerDTO, imageFile *multipart.FileHeader) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type towerUsecase struct {
|
type towerUsecase struct {
|
||||||
|
|
@ -35,18 +36,27 @@ func NewTowerUseCase(towerRepo repository.TowerRepo, geocoder service.GeocodingS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *towerUsecase) Post(tower req.TowerDTO) error {
|
func (u *towerUsecase) Post(tower req.TowerDTO, imageFile *multipart.FileHeader) error {
|
||||||
err := u.validate.Struct(tower)
|
err := u.validate.Struct(tower)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var imageURL string
|
||||||
|
if imageFile != nil {
|
||||||
|
imageURL, err = helper.SaveTowerImage(imageFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newTower := entity.Tower{
|
newTower := entity.Tower{
|
||||||
ID: uuid.New(),
|
ID: uuid.New(),
|
||||||
DeviceID: tower.DeviceID,
|
DeviceID: tower.DeviceID,
|
||||||
TowerCode: tower.TowerCode,
|
TowerCode: tower.TowerCode,
|
||||||
Longitude: tower.Longitude,
|
Longitude: tower.Longitude,
|
||||||
Latitude: tower.Latitude,
|
Latitude: tower.Latitude,
|
||||||
|
ImageURL: imageURL,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +90,7 @@ func (u *towerUsecase) GetByID(id uuid.UUID) (res.TowerResponse, error) {
|
||||||
return towerResp, nil
|
return towerResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *towerUsecase) UpdateTower(id uuid.UUID, tower req.UpdateTowerDTO) error {
|
func (u *towerUsecase) UpdateTower(id uuid.UUID, tower req.UpdateTowerDTO, imageFile *multipart.FileHeader) error {
|
||||||
err := u.validate.Struct(tower)
|
err := u.validate.Struct(tower)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err // Return validation error
|
return err // Return validation error
|
||||||
|
|
@ -100,6 +110,23 @@ func (u *towerUsecase) UpdateTower(id uuid.UUID, tower req.UpdateTowerDTO) error
|
||||||
updates["Latitude"] = *tower.Latitude
|
updates["Latitude"] = *tower.Latitude
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle image upload
|
||||||
|
if imageFile != nil {
|
||||||
|
// Get current tower to delete old image
|
||||||
|
currentTower, err := u.towerRepo.GetByID(id)
|
||||||
|
if err == nil && currentTower.ImageURL != "" {
|
||||||
|
// Delete old image
|
||||||
|
helper.DeleteTowerImage(currentTower.ImageURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save new image
|
||||||
|
imageURL, err := helper.SaveTowerImage(imageFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
updates["ImageURL"] = imageURL
|
||||||
|
}
|
||||||
|
|
||||||
// If no fields are updated, return an error
|
// If no fields are updated, return an error
|
||||||
if len(updates) == 0 {
|
if len(updates) == 0 {
|
||||||
return errors.New("no fields to update")
|
return errors.New("no fields to update")
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,33 @@
|
||||||
package usecase
|
package usecase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"users_management/m/model/entity"
|
"time"
|
||||||
"users_management/m/repository"
|
"users_management/m/model/entity"
|
||||||
|
"users_management/m/repository"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UsersUsecase interface {
|
type UsersUsecase interface {
|
||||||
GetRoleByDepartment(departmentName string) (uuid.UUID, error)
|
GetRoleByDepartment(departmentName string) (uuid.UUID, error)
|
||||||
GetUserByUsername(username string) (entity.User, error)
|
GetUserByUsername(username string) (entity.User, error)
|
||||||
|
GetUserByNomorInduk(nomorInduk string) (entity.User, error) // Add this
|
||||||
|
CreateUserFromExternal(nomorInduk, name, roleName string) error // Add this
|
||||||
|
UpdateUserRole(nomorInduk, roleName string) error // Add this
|
||||||
|
GetAllUsers() ([]entity.User, error) // Add this
|
||||||
}
|
}
|
||||||
|
|
||||||
type usersUsecase struct {
|
type usersUsecase struct {
|
||||||
userRepo repository.UsersRepo
|
userRepo repository.UsersRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUsersUsecase(userRepo repository.UsersRepo) UsersUsecase {
|
func NewUsersUsecase(userRepo repository.UsersRepo) UsersUsecase {
|
||||||
return &usersUsecase{
|
return &usersUsecase{
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (u *usersUsecase) GetRoleByDepartment(departmentName string) (uuid.UUID, error) {
|
func (u *usersUsecase) GetRoleByDepartment(departmentName string) (uuid.UUID, error) {
|
||||||
|
|
||||||
role, err := u.userRepo.GetRoleByDepartment(departmentName)
|
role, err := u.userRepo.GetRoleByDepartment(departmentName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return uuid.Nil, err
|
return uuid.Nil, err
|
||||||
|
|
@ -33,10 +36,49 @@ func (u *usersUsecase) GetRoleByDepartment(departmentName string) (uuid.UUID, er
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *usersUsecase) GetUserByUsername(username string) (entity.User, error) {
|
func (u *usersUsecase) GetUserByUsername(username string) (entity.User, error) {
|
||||||
users, err := u.userRepo.GetUserByUsername(username)
|
users, err := u.userRepo.GetUserByUsername(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return users, err
|
return users, err
|
||||||
}
|
}
|
||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New methods for RBAC
|
||||||
|
func (u *usersUsecase) GetUserByNomorInduk(nomorInduk string) (entity.User, error) {
|
||||||
|
return u.userRepo.GetUserByNomorInduk(nomorInduk)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *usersUsecase) CreateUserFromExternal(nomorInduk, name, roleName string) error {
|
||||||
|
// Get role ID
|
||||||
|
roleID, err := u.GetRoleByDepartment(roleName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create user
|
||||||
|
user := entity.User{
|
||||||
|
ID: uuid.New(),
|
||||||
|
NomorInduk: &nomorInduk,
|
||||||
|
RoleID: roleID,
|
||||||
|
Name: name,
|
||||||
|
Username: nomorInduk, // Use nomor_induk as username
|
||||||
|
Password: "", // External auth, no local password needed
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.userRepo.CreateUserFromExternal(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *usersUsecase) UpdateUserRole(nomorInduk, roleName string) error {
|
||||||
|
roleID, err := u.GetRoleByDepartment(roleName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.userRepo.UpdateUserRole(nomorInduk, roleID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *usersUsecase) GetAllUsers() ([]entity.User, error) {
|
||||||
|
return u.userRepo.GetAllUsers()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
package helper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"users_management/m/model/dto/res"
|
||||||
|
"users_management/m/model/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConvertToActivityLogResponses converts activity log entities to simple response format for users
|
||||||
|
func ConvertToActivityLogResponses(logs []entity.ActivityLog) []res.ActivityLogResponse {
|
||||||
|
var responses []res.ActivityLogResponse
|
||||||
|
|
||||||
|
for _, log := range logs {
|
||||||
|
response := res.ActivityLogResponse{
|
||||||
|
ID: log.ID,
|
||||||
|
Username: log.User.Username,
|
||||||
|
Role: log.User.Role.Name,
|
||||||
|
Action: log.Action,
|
||||||
|
Resource: log.Resource,
|
||||||
|
IPAddress: log.IPAddress,
|
||||||
|
UserAgent: log.UserAgent,
|
||||||
|
Timestamp: log.Timestamp,
|
||||||
|
}
|
||||||
|
responses = append(responses, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertToActivityLogDetailResponses converts activity log entities to detailed response format for admins
|
||||||
|
func ConvertToActivityLogDetailResponses(logs []entity.ActivityLog) []res.ActivityLogDetailResponse {
|
||||||
|
var responses []res.ActivityLogDetailResponse
|
||||||
|
|
||||||
|
for _, log := range logs {
|
||||||
|
response := res.ActivityLogDetailResponse{
|
||||||
|
ID: log.ID,
|
||||||
|
UserID: log.UserID,
|
||||||
|
Username: log.User.Username,
|
||||||
|
Role: log.User.Role.Name,
|
||||||
|
Action: log.Action,
|
||||||
|
Resource: log.Resource,
|
||||||
|
ResourceID: log.ResourceID,
|
||||||
|
OldData: log.OldData,
|
||||||
|
NewData: log.NewData,
|
||||||
|
IPAddress: log.IPAddress,
|
||||||
|
UserAgent: log.UserAgent,
|
||||||
|
Timestamp: log.Timestamp,
|
||||||
|
}
|
||||||
|
responses = append(responses, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertToSingleActivityLogResponse converts single activity log entity to simple response
|
||||||
|
func ConvertToSingleActivityLogResponse(log entity.ActivityLog) res.ActivityLogResponse {
|
||||||
|
return res.ActivityLogResponse{
|
||||||
|
ID: log.ID,
|
||||||
|
Username: log.User.Username,
|
||||||
|
Role: log.User.Role.Name,
|
||||||
|
Action: log.Action,
|
||||||
|
Resource: log.Resource,
|
||||||
|
IPAddress: log.IPAddress,
|
||||||
|
UserAgent: log.UserAgent,
|
||||||
|
Timestamp: log.Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertToSingleActivityLogDetailResponse converts single activity log entity to detailed response
|
||||||
|
func ConvertToSingleActivityLogDetailResponse(log entity.ActivityLog) res.ActivityLogDetailResponse {
|
||||||
|
return res.ActivityLogDetailResponse{
|
||||||
|
ID: log.ID,
|
||||||
|
UserID: log.UserID,
|
||||||
|
Username: log.User.Username,
|
||||||
|
Role: log.User.Role.Name,
|
||||||
|
Action: log.Action,
|
||||||
|
Resource: log.Resource,
|
||||||
|
ResourceID: log.ResourceID,
|
||||||
|
OldData: log.OldData,
|
||||||
|
NewData: log.NewData,
|
||||||
|
IPAddress: log.IPAddress,
|
||||||
|
UserAgent: log.UserAgent,
|
||||||
|
Timestamp: log.Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
package helper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"users_management/m/model/dto/res"
|
||||||
|
"users_management/m/model/entity"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConvertToFishboneResponses converts fishbone entities to response DTOs
|
||||||
|
func ConvertToFishboneResponses(fishbones []entity.Fishbone, totalFishbone map[uuid.UUID]int) ([]res.FishboneResponse, error) {
|
||||||
|
var responses []res.FishboneResponse
|
||||||
|
|
||||||
|
for _, fishbone := range fishbones {
|
||||||
|
count := 0
|
||||||
|
if fishboneCount, exist := totalFishbone[fishbone.ID]; exist {
|
||||||
|
count = fishboneCount
|
||||||
|
}
|
||||||
|
|
||||||
|
fishboneResp := res.FishboneResponse{
|
||||||
|
ID: fishbone.ID,
|
||||||
|
FishboneCode: fishbone.FishboneCode,
|
||||||
|
BackboneCode: fishbone.Backbone.BackboneCode,
|
||||||
|
DeviceStart: fishbone.DeviceStart.DeviceCode,
|
||||||
|
DeviceEnd: fishbone.DeviceEnd.DeviceCode,
|
||||||
|
CoreAmount: fishbone.CoreAmount,
|
||||||
|
TotalFishbone: count,
|
||||||
|
CreatedAt: fishbone.CreatedAt,
|
||||||
|
UpdatedAt: fishbone.UpdatedAt,
|
||||||
|
}
|
||||||
|
responses = append(responses, fishboneResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertToFishboneDetailResponse converts a single fishbone entity to detailed response
|
||||||
|
func ConvertToFishboneDetailResponse(fishbone entity.Fishbone) res.FishboneDetailResponse {
|
||||||
|
return res.FishboneDetailResponse{
|
||||||
|
ID: fishbone.ID,
|
||||||
|
FishboneCode: fishbone.FishboneCode,
|
||||||
|
Backbone: res.BackboneInfo{
|
||||||
|
BackboneCode: fishbone.Backbone.BackboneCode,// Fixed missing field
|
||||||
|
},
|
||||||
|
CoreAmount: fishbone.CoreAmount,
|
||||||
|
CreatedAt: fishbone.CreatedAt,
|
||||||
|
UpdatedAt: fishbone.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertToSimpleFishboneResponses converts fishbone entities to simple response format
|
||||||
|
func ConvertToSimpleFishboneResponses(fishbones []entity.Fishbone) []res.FishboneResponse {
|
||||||
|
var responses []res.FishboneResponse
|
||||||
|
|
||||||
|
for _, fishbone := range fishbones {
|
||||||
|
fishboneResp := res.FishboneResponse{
|
||||||
|
ID: fishbone.ID,
|
||||||
|
FishboneCode: fishbone.FishboneCode,
|
||||||
|
BackboneCode: fishbone.Backbone.BackboneCode,
|
||||||
|
DeviceStart: fishbone.DeviceStart.DeviceCode,
|
||||||
|
DeviceEnd: fishbone.DeviceEnd.DeviceCode,
|
||||||
|
CoreAmount: fishbone.CoreAmount,
|
||||||
|
CreatedAt: fishbone.CreatedAt,
|
||||||
|
UpdatedAt: fishbone.UpdatedAt,
|
||||||
|
}
|
||||||
|
responses = append(responses, fishboneResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFishboneStats calculates statistics for fishbones
|
||||||
|
func GetFishboneStats(fishbones []entity.Fishbone) map[string]interface{} {
|
||||||
|
totalFishbones := len(fishbones)
|
||||||
|
totalCores := 0
|
||||||
|
backboneCount := make(map[uuid.UUID]int)
|
||||||
|
|
||||||
|
for _, fishbone := range fishbones {
|
||||||
|
totalCores += fishbone.CoreAmount
|
||||||
|
backboneCount[fishbone.BackboneID]++
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"total_fishbones": totalFishbones,
|
||||||
|
"total_cores": totalCores,
|
||||||
|
"average_cores": float64(totalCores) / float64(totalFishbones),
|
||||||
|
"unique_backbones": len(backboneCount),
|
||||||
|
"backbone_usage": backboneCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
package helper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MaxFileSize = 5 << 20 // 5MB
|
||||||
|
UploadDir = "./uploads/towers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SaveTowerImage saves uploaded image and returns the URL
|
||||||
|
func SaveTowerImage(file *multipart.FileHeader) (string, error) {
|
||||||
|
// Validate file size
|
||||||
|
if file.Size > MaxFileSize {
|
||||||
|
return "", fmt.Errorf("file size exceeds 5MB limit")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate file type
|
||||||
|
allowedTypes := []string{".jpg", ".jpeg", ".png", ".webp"}
|
||||||
|
ext := strings.ToLower(filepath.Ext(file.Filename))
|
||||||
|
isAllowed := false
|
||||||
|
for _, allowedType := range allowedTypes {
|
||||||
|
if ext == allowedType {
|
||||||
|
isAllowed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isAllowed {
|
||||||
|
return "", fmt.Errorf("file type not allowed. Only jpg, jpeg, png, webp are allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create upload directory if it doesn't exist
|
||||||
|
if err := os.MkdirAll(UploadDir, 0755); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create upload directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate unique filename
|
||||||
|
filename := fmt.Sprintf("%s_%d%s", uuid.New().String(), time.Now().Unix(), ext)
|
||||||
|
filePath := filepath.Join(UploadDir, filename)
|
||||||
|
|
||||||
|
// Open uploaded file
|
||||||
|
src, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to open uploaded file: %v", err)
|
||||||
|
}
|
||||||
|
defer src.Close()
|
||||||
|
|
||||||
|
// Create destination file
|
||||||
|
dst, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create destination file: %v", err)
|
||||||
|
}
|
||||||
|
defer dst.Close()
|
||||||
|
|
||||||
|
// Copy file
|
||||||
|
if _, err := io.Copy(dst, src); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to copy file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return URL (adjust based on your server setup)
|
||||||
|
return fmt.Sprintf("/uploads/towers/%s", filename), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTowerImage deletes an image file
|
||||||
|
func DeleteTowerImage(imageURL string) error {
|
||||||
|
if imageURL == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract filename from URL
|
||||||
|
filename := filepath.Base(imageURL)
|
||||||
|
filePath := filepath.Join(UploadDir, filename)
|
||||||
|
|
||||||
|
// Check if file exists
|
||||||
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||||
|
return nil // File doesn't exist, nothing to delete
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete file
|
||||||
|
return os.Remove(filePath)
|
||||||
|
}
|
||||||
|
|
@ -33,6 +33,7 @@ func ConvertToTowerResponses(towers []entity.Tower, geocoder service.GeocodingSe
|
||||||
Longitude: tower.Longitude,
|
Longitude: tower.Longitude,
|
||||||
Latitude: tower.Latitude,
|
Latitude: tower.Latitude,
|
||||||
Address: address,
|
Address: address,
|
||||||
|
ImageURL: tower.ImageURL,
|
||||||
CreatedAt: tower.CreatedAt,
|
CreatedAt: tower.CreatedAt,
|
||||||
}
|
}
|
||||||
responses = append(responses, towerResp)
|
responses = append(responses, towerResp)
|
||||||
|
|
@ -62,6 +63,7 @@ func ConvertToTowerIDResponses(tower entity.Tower, geocoder service.GeocodingSer
|
||||||
Longitude: tower.Longitude,
|
Longitude: tower.Longitude,
|
||||||
Latitude: tower.Latitude,
|
Latitude: tower.Latitude,
|
||||||
Address: address,
|
Address: address,
|
||||||
|
ImageURL: tower.ImageURL,
|
||||||
CreatedAt: tower.CreatedAt,
|
CreatedAt: tower.CreatedAt,
|
||||||
}
|
}
|
||||||
return towerResp, nil
|
return towerResp, nil
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateInsecureHTTPClient creates an HTTP client that skips SSL verification
|
||||||
|
func CreateInsecureHTTPClient() *http.Client {
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &http.Client{
|
||||||
|
Transport: tr,
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSecureHTTPClient creates a standard HTTP client with SSL verification
|
||||||
|
func CreateSecureHTTPClient() *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHTTPClient returns appropriate client based on environment
|
||||||
|
func GetHTTPClient(skipSSL bool) *http.Client {
|
||||||
|
if skipSSL {
|
||||||
|
return CreateInsecureHTTPClient()
|
||||||
|
}
|
||||||
|
return CreateSecureHTTPClient()
|
||||||
|
}
|
||||||
|
|
@ -17,7 +17,7 @@ type nominatimGeocoder struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGeocodingService creates a new geocoding service using Nominatim (OpenStreetMap)
|
|
||||||
func NewGeocodingService() GeocodingService {
|
func NewGeocodingService() GeocodingService {
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: 10 * time.Second,
|
Timeout: 10 * time.Second,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue