package controller import ( "context" "crypto/md5" "encoding/hex" "fmt" models "kemendagri/sipd/services/sipd_auth/model" "kemendagri/sipd/services/sipd_auth/model/form" "kemendagri/sipd/services/sipd_auth/utils" "log" "mime/multipart" "net/http" "os" "path/filepath" "regexp" "strings" "time" "github.com/gofiber/storage/redis/v3" "github.com/golang-jwt/jwt/v4" "github.com/minio/minio-go/v7" "golang.org/x/crypto/bcrypt" _ "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) type UserController struct { contextTimeout time.Duration pgxConn *pgxpool.Pool pgxConnPegawai *pgxpool.Pool pgxConnMstData *pgxpool.Pool dbConnMapsAnggaran map[string]*pgxpool.Pool minioClient *minio.Client redisCl *redis.Storage } func NewUserController(conn, connPeg, connMstData *pgxpool.Pool, mapsDbAnggaran map[string]*pgxpool.Pool, mc *minio.Client, tot time.Duration, redisClient *redis.Storage) (controller *UserController) { controller = &UserController{ pgxConn: conn, pgxConnPegawai: connPeg, pgxConnMstData: connMstData, dbConnMapsAnggaran: mapsDbAnggaran, minioClient: mc, contextTimeout: tot, redisCl: redisClient, } return } func (c *UserController) Logout(idPegawai string) error { var err error err = c.redisCl.Delete("peg:" + idPegawai) log.Println("peg:" + idPegawai) if err != nil { err = utils.RequestError{ Code: http.StatusInternalServerError, Message: "Failed to remove token data. - " + err.Error(), } return err } return err } func (c *UserController) GeneratePasswordHash(userId, idDaerah int64, pwd string) error { // periksa apakah sudah hashing atau belum, blokir jika sudah var uHashed bool q := `SELECT "hashed" FROM "user" WHERE "id_user"=$1 AND "id_daerah"=$2` err := c.pgxConn.QueryRow(context.Background(), q, userId, idDaerah).Scan(&uHashed) if err != nil { return err } if uHashed { return utils.RequestError{ Code: http.StatusForbidden, Message: "sudah hashing", } } // generate password hash pHash, err := bcrypt.GenerateFromPassword([]byte(pwd), 14) if err != nil { return err } q = `UPDATE "user" SET "pass_user"=$1, "hashed"=true WHERE "id_user"=$2 AND "id_daerah"=$3` _, err = c.pgxConn.Exec(context.Background(), q, pHash, userId, idDaerah) if err != nil { return err } return nil } /* Profile melihat detail profile user dengan id wilayah yang sama dengan si pembuat user baru route name auth-service/user/profile */ func (c *UserController) Profile(user *jwt.Token) (r models.UserDetail, err error) { claims := user.Claims.(jwt.MapClaims) // idDaerah := int64(claims["id_daerah"].(float64)) // kodeProv := claims["kode_provinsi"].(string) // tahun := int(claims["tahun"].(float64)) // idRole := int(claims["id_role"].(float64)) // skpdId := int64(claims["id_skpd"].(float64)) idPegawai := int64(claims["id_pegawai"].(float64)) userId := int64(claims["id_user"].(float64)) // log.Printf("Id User: %d, Id Daerah: %d\n", userId, idDaerah) // data user qStr := `SELECT id_user, nip_user, nama_user, nik_user, npwp_user, alamat FROM public."sipd_user" WHERE id_user=$1` err = c.pgxConn.QueryRow(context.Background(), qStr, userId). Scan(&r.IdUser, &r.Nip, &r.Nama, &r.Nik, &r.Npwp, &r.Alamat) if err != nil { err = utils.RequestError{ Code: http.StatusInternalServerError, Message: "gagal mengambil data user. - " + err.Error(), } return } // data pegawai var tahunPegawai int qStr = `SELECT id_daerah, id_skpd, id_role, tahun_pegawai, status from public.sipd_pegawai where id=$1` err = c.pgxConnPegawai.QueryRow(context.Background(), qStr, idPegawai). Scan(&r.IdDaerah, &r.IdSkpdLama, &r.IdRole, &tahunPegawai, &r.Status) if err != nil { err = utils.RequestError{ Code: http.StatusInternalServerError, Message: "gagal mengambil data pegawai. - " + err.Error(), } return } // data daerah var kodeDdn string qStr = `SELECT nama_daerah, kode_ddn FROM public.mst_daerah where id_daerah=$1 and deleted_by=0` err = c.pgxConnMstData.QueryRow(context.Background(), qStr, r.IdDaerah). Scan(&r.NamaDaerah, &kodeDdn) if err != nil { err = utils.RequestError{ Code: http.StatusInternalServerError, Message: "gagal mengambil data daerah. - " + err.Error(), } return } fmt.Print("kode_ddn=", kodeDdn) if r.IdSkpdLama != 0 { // data skpd var dbMapCode string dbMapCodeDraft := fmt.Sprintf("%s_%d", kodeDdn, tahunPegawai) fmt.Printf("%s_%d", kodeDdn, tahunPegawai) _, connExists := c.dbConnMapsAnggaran[dbMapCodeDraft] if connExists { dbMapCode = dbMapCodeDraft } else { dbMapCode = fmt.Sprintf("%s_%d", strings.Split(dbMapCodeDraft, ".")[0], tahunPegawai) } qStr = `select id_unik_skpd, kode_skpd, nama_skpd from public.mst_skpd where id_skpd_lama=$1` err = c.dbConnMapsAnggaran[dbMapCode].QueryRow(context.Background(), qStr, r.IdSkpdLama).Scan(&r.IdUnikSkpd, &r.KodeSkpd, &r.NamaSkpd) if err != nil { err = utils.RequestError{ Code: http.StatusInternalServerError, Message: "gagal mengambil informasi skpd. - " + err.Error(), } return r, err } } else { r.IdUnikSkpd = "00000000-0000-0000-0000-000000000000" r.KodeSkpd = "0.00.0.00.0.00.00.0000" r.NamaSkpd = "TIDAK ADA SKPD" } return } func (c *UserController) UploadAvatar(user *jwt.Token, file *multipart.FileHeader) error { var err error claims := user.Claims.(jwt.MapClaims) // sub := claims["sub"].(string) userId := int64(claims["id_user"].(float64)) /*daerahId := int64(claims["id_daerah"].(float64)) kodeProv := claims["kode_provinsi"].(string) skpdId := int64(claims["id_skpd"].(float64)) tahun := int(claims["tahun"].(float64)) idRole := int(claims["id_role"].(float64))*/ log.Println("userId: ", userId) if file == nil { return utils.RequestError{ Code: http.StatusUnprocessableEntity, Message: "file harus diunggah", } } // validasi file var fileExt string fileExt = filepath.Ext(file.Filename) if fileExt != ".jpg" && fileExt != ".jpeg" && fileExt != ".png" { return utils.RequestError{ Code: http.StatusUnprocessableEntity, Message: "jenis file tidak valid, hanya jpg, jpeg, png yang diizinkan.", } } // validasi ukuran file maxFsize := int64(1024) fSize := file.Size / 1000 // 3028/1000 = 3.028 byte if fSize > maxFsize { // max 1mb (1024 kb) err = utils.RequestError{ Code: http.StatusBadRequest, Message: fmt.Sprintf("lampiran ukuran nya terlalu besar (%d). maksimal %dkb", fSize, maxFsize), } return err } // Get Buffer from file buffer, err := file.Open() if err != nil { err = utils.RequestError{ Code: http.StatusInternalServerError, Message: "gagal open file to buffer. - " + err.Error(), } } defer buffer.Close() buckets, err := c.minioClient.ListBuckets(context.Background()) if err != nil { return err } for _, bucket := range buckets { fmt.Println("bucket: ", bucket) } // Check to see if we already own this bucket (which happens if you run this twice) bucketName := os.Getenv("MINIO_BUCKET_NAME") exists, errBucketExists := c.minioClient.BucketExists(context.Background(), bucketName) if errBucketExists != nil { err = utils.RequestError{ Code: http.StatusInternalServerError, Message: "gagal memeriksa ketersediaan bucket object storage. - " + err.Error(), } return err } if !exists { err = utils.RequestError{ Code: http.StatusInternalServerError, Message: "bucket object storage tidak tersedia. - (" + bucketName + ").", } return err } objectName := "avatar/" + file.Filename fileBuffer := buffer contentType := file.Header["Content-Type"][0] fileSize := file.Size log.Println("fileSize: ", fileSize) // Upload the file with PutObject info, err := c.minioClient.PutObject( context.Background(), bucketName, objectName, fileBuffer, fileSize, minio.PutObjectOptions{ContentType: contentType}, ) if err != nil { err = utils.RequestError{ Code: http.StatusInternalServerError, Message: "gagal upload file. - " + err.Error(), } return err } log.Printf("Successfully uploaded %s of size %d\n", objectName, info.Size) return err } /* UpdateProfile user update profile route name auth-service/user/update-profile */ func (c *UserController) UpdateProfile(user *jwt.Token, pl form.UpdateUserProfileForm) error { var err error claims := user.Claims.(jwt.MapClaims) // sub := claims["sub"].(string) daerahId := int64(claims["id_daerah"].(float64)) userId := int64(claims["id_user"].(float64)) //skpdId := int64(claims["id_skpd"].(float64)) //roleId := int64(claims["id_role"].(float64)) // log.Printf("userId: %d, daerahId: %d", userId, daerahId) // validasi tanggal lahir _, err = time.Parse("2006-01-02", pl.TglLahir) if err != nil { err = utils.RequestError{ Code: http.StatusUnprocessableEntity, Message: "Salah satu field tidak valid", Fields: []utils.DataValidationError{{Field: "tgl_lahir", Message: "Format tidak valid"}}, } return err } // validasi npwp if len(pl.Npwp) < 15 && len(pl.Npwp) > 18 { err = utils.RequestError{ Code: http.StatusUnprocessableEntity, Message: "Salah satu field tidak valid", Fields: []utils.DataValidationError{{Field: "npwp", Message: "NPWP tidak valid"}}, } return err } q := `UPDATE "user" SET "nama_user"=$1, "nik_user"=$2, "npwp_user"=$3, "alamat"=$4, "lahir_user"=$5, "id_pang_gol"=$6 WHERE "id_user"=$7 AND "id_daerah"=$8` _, err = c.pgxConn.Exec( context.Background(), q, pl.NamaUser, pl.Nik, pl.Npwp, pl.Alamat, pl.TglLahir, pl.IdPangGol, userId, daerahId, ) if err != nil { return err } return err } func (c *UserController) ChangePassword(userId, idDaerah int64, pl form.ChangePasswordForm) error { var err error var q string /*log.Println("idUser: ", userId) log.Println("idDaerah: ", idDaerah) log.Printf("%#v\\n", pl)*/ var passwordHash string q = `SELECT "pass_user" FROM "user" WHERE id_user=$1 AND id_daerah=$2` err = c.pgxConn.QueryRow(context.Background(), q, userId, idDaerah).Scan(&passwordHash) if err != nil { err = utils.RequestError{ Code: http.StatusUnprocessableEntity, Message: "Salah satu field tidak valid", Fields: []utils.DataValidationError{{Field: "old_password", Message: "kata sandi sebelumnya tidak valid"}}, } return err } // log.Println("passwordHash: ", passwordHash) if passwordHash == "" { err = utils.RequestError{ Code: http.StatusUnprocessableEntity, Message: "Salah satu field tidak valid", Fields: []utils.DataValidationError{{Field: "old_password", Message: ""}}, } return err } // validate oldPassword err = bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(pl.OldPassword)) if err != nil { err = utils.RequestError{ Code: http.StatusUnprocessableEntity, Message: "Salah satu field tidak valid", Fields: []utils.DataValidationError{{Field: "old_password", Message: "kata sandi sebelumnya tidak valid"}}, } return err } // validate password if !c.isValidPassword(pl.NewPassword) { err = utils.RequestError{ Code: http.StatusUnprocessableEntity, Message: "Salah satu field tidak valid", Fields: []utils.DataValidationError{ { Field: "new_password", Message: "kata sandi baru tidak valid. minimal 8 karakter mengandung angka, huruf besar, huruf kecil dan karakter spesial.", }, }, } return err } // generate new password hash var bytesPwd []byte bytesPwd, err = bcrypt.GenerateFromPassword([]byte(pl.NewPassword), 14) if err != nil { err = utils.RequestError{ Code: http.StatusInternalServerError, Message: "gagal enkripsi. - " + err.Error(), } return err } // log.Println("bytesPwd: ", bytesPwd) // update to new password q = `UPDATE "user" SET "pass_user"=$1 WHERE id_user=$2 AND id_daerah=$3` _, err = c.pgxConn.Exec(context.Background(), q, bytesPwd, userId, idDaerah) if err != nil { err = utils.RequestError{ Code: http.StatusInternalServerError, Message: "gagal set new password. - " + err.Error(), } return err } return nil } func (c *UserController) validatePassword(content, encrypted string) bool { return strings.EqualFold(c.encodePasswd(content), encrypted) } func (c *UserController) encodePasswd(data string) string { h := md5.New() h.Write([]byte(data)) return hex.EncodeToString(h.Sum(nil)) } func (c *UserController) isValidPassword(password string) bool { if len(password) < 8 { return false } reLower := regexp.MustCompile(`[a-z]`) reUpper := regexp.MustCompile(`[A-Z]`) reDigit := regexp.MustCompile(`\d`) reSpecial := regexp.MustCompile(`[@$!%*?&]`) return reLower.MatchString(password) && reUpper.MatchString(password) && reDigit.MatchString(password) && reSpecial.MatchString(password) }