first commit
This commit is contained in:
550
internal/modules/seriessetmaster/handler.go
Normal file
550
internal/modules/seriessetmaster/handler.go
Normal file
@@ -0,0 +1,550 @@
|
||||
package seriessetmaster
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
"image/png"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"cardverse/internal/pkg/response"
|
||||
"cardverse/internal/pkg/utils"
|
||||
"cardverse/internal/pkg/validator"
|
||||
|
||||
"github.com/chai2010/webp"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
service Service
|
||||
}
|
||||
|
||||
func NewHandler(service Service) *Handler {
|
||||
return &Handler{service: service}
|
||||
}
|
||||
|
||||
func (h *Handler) GetAllSeries(c *gin.Context) {
|
||||
var query ListSeriesQuery
|
||||
if err := c.ShouldBindQuery(&query); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "query parameter tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
|
||||
list, err := h.service.GetAllSeries(query)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusInternalServerError, "gagal mengambil data series", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "berhasil mengambil data series", ToSeriesSetMasterResponseList(list))
|
||||
}
|
||||
|
||||
func (h *Handler) GetAllSets(c *gin.Context) {
|
||||
var query ListSetQuery
|
||||
if err := c.ShouldBindQuery(&query); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "query parameter tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
|
||||
list, err := h.service.GetAllSets(query)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusInternalServerError, "gagal mengambil data set", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "berhasil mengambil data set", ToSeriesSetMasterResponseList(list))
|
||||
}
|
||||
|
||||
func (h *Handler) GetByID(c *gin.Context) {
|
||||
id, err := parseID(c)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "id tidak valid", []validator.FieldError{
|
||||
{Field: "id", Message: "id harus berupa angka"},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
m, err := h.service.GetByID(id)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusNotFound, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "berhasil mengambil data", ToSeriesSetMasterResponse(*m))
|
||||
}
|
||||
|
||||
func (h *Handler) GetSetByCode(c *gin.Context) {
|
||||
code := c.Param("code")
|
||||
if code == "" {
|
||||
response.Error(c, http.StatusBadRequest, "set_code tidak boleh kosong", nil)
|
||||
return
|
||||
}
|
||||
|
||||
m, err := h.service.GetSetByCode(code)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusNotFound, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "berhasil mengambil data set", ToSeriesSetMasterResponse(*m))
|
||||
}
|
||||
|
||||
func (h *Handler) CreateSeries(c *gin.Context) {
|
||||
var req CreateSeriesRequest
|
||||
contentType := c.Request.Header.Get("Content-Type")
|
||||
|
||||
if strings.Contains(contentType, "multipart/form-data") {
|
||||
req.Name = c.PostForm("name")
|
||||
if req.Name == "" {
|
||||
response.Error(c, http.StatusBadRequest, "name wajib diisi", nil)
|
||||
return
|
||||
}
|
||||
|
||||
fileHeader, err := c.FormFile("image")
|
||||
if err == nil && fileHeader != nil {
|
||||
if fileHeader.Size > 5*1024*1024 {
|
||||
response.Error(c, http.StatusBadRequest, "ukuran file gambar maksimal 5MB", nil)
|
||||
return
|
||||
}
|
||||
|
||||
srcFile, err := fileHeader.Open()
|
||||
if err == nil {
|
||||
defer srcFile.Close()
|
||||
img, _, err := image.Decode(srcFile)
|
||||
if err == nil {
|
||||
slug := utils.UnderscoreString(req.Name)
|
||||
uploadDir := "./public/images/series"
|
||||
_ = os.MkdirAll(uploadDir, os.ModePerm)
|
||||
filename := fmt.Sprintf("%s.webp", slug)
|
||||
filepathDst := filepath.Join(uploadDir, filename)
|
||||
|
||||
out, err := os.Create(filepathDst)
|
||||
if err == nil {
|
||||
defer out.Close()
|
||||
if err := webp.Encode(out, img, &webp.Options{Lossless: false, Quality: 90}); err == nil {
|
||||
req.Image = fmt.Sprintf("/images/series/%s", filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "input tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
created, err := h.service.CreateSeries(req)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusCreated, "series berhasil dibuat", ToSeriesSetMasterResponse(*created))
|
||||
}
|
||||
|
||||
func (h *Handler) CreateSet(c *gin.Context) {
|
||||
var req CreateSetRequest
|
||||
contentType := c.Request.Header.Get("Content-Type")
|
||||
|
||||
if strings.Contains(contentType, "multipart/form-data") {
|
||||
if parentIDStr := c.PostForm("parent_id"); parentIDStr != "" {
|
||||
if pID, err := strconv.ParseUint(parentIDStr, 10, 64); err == nil {
|
||||
req.ParentID = uint(pID)
|
||||
}
|
||||
}
|
||||
req.SetCode = c.PostForm("set_code")
|
||||
req.SetName = c.PostForm("set_name")
|
||||
|
||||
if req.ParentID == 0 || req.SetCode == "" || req.SetName == "" {
|
||||
response.Error(c, http.StatusBadRequest, "parent_id, set_code, dan set_name wajib diisi", nil)
|
||||
return
|
||||
}
|
||||
|
||||
parentSeries, err := h.service.GetByID(req.ParentID)
|
||||
if err != nil || parentSeries == nil || parentSeries.ParentID != nil {
|
||||
response.Error(c, http.StatusBadRequest, "parent_id tidak ditemukan atau bukan merupakan series", nil)
|
||||
return
|
||||
}
|
||||
|
||||
seriesSlug := utils.Slugify(parentSeries.SeriesName)
|
||||
uploadDir := fmt.Sprintf("./public/images/%s/%s", seriesSlug, req.SetCode)
|
||||
|
||||
// Handle file 'image'
|
||||
fileHeader, err := c.FormFile("image")
|
||||
if err == nil && fileHeader != nil {
|
||||
if fileHeader.Size <= 5*1024*1024 {
|
||||
srcFile, err := fileHeader.Open()
|
||||
if err == nil {
|
||||
defer srcFile.Close()
|
||||
img, _, err := image.Decode(srcFile)
|
||||
if err == nil {
|
||||
_ = os.MkdirAll(uploadDir, os.ModePerm)
|
||||
imgSlug := utils.UnderscoreString(req.SetName)
|
||||
filename := fmt.Sprintf("%s.webp", imgSlug)
|
||||
filepathDst := filepath.Join(uploadDir, filename)
|
||||
|
||||
out, err := os.Create(filepathDst)
|
||||
if err == nil {
|
||||
defer out.Close()
|
||||
if err := webp.Encode(out, img, &webp.Options{Lossless: false, Quality: 90}); err == nil {
|
||||
req.Image = fmt.Sprintf("/images/%s/%s/%s", seriesSlug, req.SetCode, filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle file 'logo'
|
||||
logoHeader, err := c.FormFile("logo")
|
||||
if err == nil && logoHeader != nil {
|
||||
if logoHeader.Size <= 5*1024*1024 {
|
||||
_ = os.MkdirAll(uploadDir, os.ModePerm)
|
||||
origFilename := logoHeader.Filename
|
||||
ext := filepath.Ext(origFilename)
|
||||
baseName := strings.TrimSuffix(origFilename, ext)
|
||||
if baseName == "" {
|
||||
baseName = req.SetCode
|
||||
}
|
||||
|
||||
var logoFilename string
|
||||
if (len(req.SetCode) > 5 || len(baseName) > 5) && (strings.Contains(req.SetCode, "-P") || strings.Contains(baseName, "-P")) {
|
||||
logoFilename = "PROMO.svg"
|
||||
} else {
|
||||
logoFilename = fmt.Sprintf("%s.svg", baseName)
|
||||
}
|
||||
|
||||
filepathDstLogo := filepath.Join(uploadDir, logoFilename)
|
||||
|
||||
srcLogo, err := logoHeader.Open()
|
||||
if err == nil {
|
||||
defer srcLogo.Close()
|
||||
logoBytes, err := io.ReadAll(srcLogo)
|
||||
if err == nil {
|
||||
checkLen := len(logoBytes)
|
||||
if checkLen > 512 {
|
||||
checkLen = 512
|
||||
}
|
||||
isSVG := strings.EqualFold(ext, ".svg") || strings.Contains(strings.ToLower(string(logoBytes[:checkLen])), "<svg")
|
||||
|
||||
if isSVG {
|
||||
if err := os.WriteFile(filepathDstLogo, logoBytes, 0644); err == nil {
|
||||
req.Logo = fmt.Sprintf("/images/%s/%s/%s", seriesSlug, req.SetCode, logoFilename)
|
||||
}
|
||||
} else {
|
||||
img, _, err := image.Decode(bytes.NewReader(logoBytes))
|
||||
if err == nil && img != nil {
|
||||
var buf bytes.Buffer
|
||||
if err := png.Encode(&buf, img); err == nil {
|
||||
b64 := base64.StdEncoding.EncodeToString(buf.Bytes())
|
||||
bounds := img.Bounds()
|
||||
w, h := bounds.Dx(), bounds.Dy()
|
||||
svgContent := fmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="%d" height="%d" viewBox="0 0 %d %d"><image width="%d" height="%d" xlink:href="data:image/png;base64,%s"/></svg>`, w, h, w, h, w, h, b64)
|
||||
|
||||
if err := os.WriteFile(filepathDstLogo, []byte(svgContent), 0644); err == nil {
|
||||
req.Logo = fmt.Sprintf("/images/%s/%s/%s", seriesSlug, req.SetCode, logoFilename)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := c.SaveUploadedFile(logoHeader, filepathDstLogo); err == nil {
|
||||
req.Logo = fmt.Sprintf("/images/%s/%s/%s", seriesSlug, req.SetCode, logoFilename)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "input tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
created, err := h.service.CreateSet(req)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusCreated, "set berhasil dibuat", ToSeriesSetMasterResponse(*created))
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateSeries(c *gin.Context) {
|
||||
id, err := parseID(c)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "id tidak valid", []validator.FieldError{
|
||||
{Field: "id", Message: "id harus berupa angka"},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateSeriesRequest
|
||||
contentType := c.Request.Header.Get("Content-Type")
|
||||
|
||||
if strings.Contains(contentType, "multipart/form-data") {
|
||||
req.Name = c.PostForm("name")
|
||||
|
||||
fileHeader, err := c.FormFile("image")
|
||||
if err == nil && fileHeader != nil {
|
||||
if fileHeader.Size > 5*1024*1024 {
|
||||
response.Error(c, http.StatusBadRequest, "ukuran file gambar maksimal 5MB", nil)
|
||||
return
|
||||
}
|
||||
|
||||
srcFile, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusInternalServerError, "gagal membaca file gambar series", err.Error())
|
||||
return
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
img, _, err := image.Decode(srcFile)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "file yang diunggah bukan format gambar yang valid", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
seriesName := req.Name
|
||||
if seriesName == "" {
|
||||
m, err := h.service.GetByID(id)
|
||||
if err == nil && m != nil {
|
||||
seriesName = m.SeriesName
|
||||
}
|
||||
}
|
||||
|
||||
slug := utils.UnderscoreString(seriesName)
|
||||
|
||||
uploadDir := "./public/images/series"
|
||||
if err := os.MkdirAll(uploadDir, os.ModePerm); err != nil {
|
||||
response.Error(c, http.StatusInternalServerError, "gagal membuat direktori penyimpanan series", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("%s.webp", slug)
|
||||
filepathDst := filepath.Join(uploadDir, filename)
|
||||
|
||||
out, err := os.Create(filepathDst)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusInternalServerError, "gagal membuat file gambar series", err.Error())
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
if err := webp.Encode(out, img, &webp.Options{Lossless: false, Quality: 90}); err != nil {
|
||||
response.Error(c, http.StatusInternalServerError, "gagal mengompresi gambar ke format WebP", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
req.Image = fmt.Sprintf("/images/series/%s", filename)
|
||||
}
|
||||
} else {
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "input tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
updated, err := h.service.UpdateSeries(id, req)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "series berhasil diupdate", ToSeriesSetMasterResponse(*updated))
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateSet(c *gin.Context) {
|
||||
id, err := parseID(c)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "id tidak valid", []validator.FieldError{
|
||||
{Field: "id", Message: "id harus berupa angka"},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
mOld, err := h.service.GetByID(id)
|
||||
if err != nil || mOld == nil {
|
||||
response.Error(c, http.StatusNotFound, "data set tidak ditemukan", nil)
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateSetRequest
|
||||
contentType := c.Request.Header.Get("Content-Type")
|
||||
|
||||
if strings.Contains(contentType, "multipart/form-data") {
|
||||
req.SetCode = c.PostForm("set_code")
|
||||
req.SetName = c.PostForm("set_name")
|
||||
|
||||
if parentIDStr := c.PostForm("parent_id"); parentIDStr != "" {
|
||||
if pID, err := strconv.ParseUint(parentIDStr, 10, 64); err == nil {
|
||||
uintPID := uint(pID)
|
||||
req.ParentID = &uintPID
|
||||
}
|
||||
}
|
||||
|
||||
setCode := req.SetCode
|
||||
if setCode == "" {
|
||||
setCode = mOld.SetCode
|
||||
}
|
||||
setName := req.SetName
|
||||
if setName == "" {
|
||||
setName = mOld.SetName
|
||||
}
|
||||
|
||||
seriesName := mOld.SeriesName
|
||||
if req.ParentID != nil {
|
||||
parentSeries, err := h.service.GetByID(*req.ParentID)
|
||||
if err == nil && parentSeries != nil && parentSeries.ParentID == nil {
|
||||
seriesName = parentSeries.SeriesName
|
||||
}
|
||||
}
|
||||
|
||||
seriesSlug := utils.Slugify(seriesName)
|
||||
uploadDir := fmt.Sprintf("./public/images/%s/%s", seriesSlug, setCode)
|
||||
|
||||
// 1. Handle file 'image' (WebP format, nama file = utils.UnderscoreString(setName) + ".webp")
|
||||
fileHeader, err := c.FormFile("image")
|
||||
if err == nil && fileHeader != nil {
|
||||
if fileHeader.Size > 5*1024*1024 {
|
||||
response.Error(c, http.StatusBadRequest, "ukuran file gambar maksimal 5MB", nil)
|
||||
return
|
||||
}
|
||||
|
||||
srcFile, err := fileHeader.Open()
|
||||
if err == nil {
|
||||
defer srcFile.Close()
|
||||
img, _, err := image.Decode(srcFile)
|
||||
if err == nil {
|
||||
_ = os.MkdirAll(uploadDir, os.ModePerm)
|
||||
imgSlug := utils.UnderscoreString(setName)
|
||||
filename := fmt.Sprintf("%s.webp", imgSlug)
|
||||
filepathDst := filepath.Join(uploadDir, filename)
|
||||
|
||||
out, err := os.Create(filepathDst)
|
||||
if err == nil {
|
||||
defer out.Close()
|
||||
if err := webp.Encode(out, img, &webp.Options{Lossless: false, Quality: 90}); err == nil {
|
||||
req.Image = fmt.Sprintf("/images/%s/%s/%s", seriesSlug, setCode, filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Handle file 'logo' (Format SVG: file SVG disimpan langsung, JPG/PNG/GIF/WebP dikonversi ke SVG XML)
|
||||
logoHeader, err := c.FormFile("logo")
|
||||
if err == nil && logoHeader != nil {
|
||||
if logoHeader.Size > 5*1024*1024 {
|
||||
response.Error(c, http.StatusBadRequest, "ukuran file logo maksimal 5MB", nil)
|
||||
return
|
||||
}
|
||||
|
||||
_ = os.MkdirAll(uploadDir, os.ModePerm)
|
||||
|
||||
origFilename := logoHeader.Filename
|
||||
ext := filepath.Ext(origFilename)
|
||||
baseName := strings.TrimSuffix(origFilename, ext)
|
||||
if baseName == "" {
|
||||
baseName = setCode
|
||||
}
|
||||
|
||||
var logoFilename string
|
||||
if (len(setCode) > 5 || len(baseName) > 5) && (strings.Contains(setCode, "-P") || strings.Contains(baseName, "-P")) {
|
||||
logoFilename = "PROMO.svg"
|
||||
} else {
|
||||
logoFilename = fmt.Sprintf("%s.svg", baseName)
|
||||
}
|
||||
|
||||
filepathDstLogo := filepath.Join(uploadDir, logoFilename)
|
||||
|
||||
srcLogo, err := logoHeader.Open()
|
||||
if err == nil {
|
||||
defer srcLogo.Close()
|
||||
logoBytes, err := io.ReadAll(srcLogo)
|
||||
if err == nil {
|
||||
checkLen := len(logoBytes)
|
||||
if checkLen > 512 {
|
||||
checkLen = 512
|
||||
}
|
||||
isSVG := strings.EqualFold(ext, ".svg") || strings.Contains(strings.ToLower(string(logoBytes[:checkLen])), "<svg")
|
||||
|
||||
if isSVG {
|
||||
if err := os.WriteFile(filepathDstLogo, logoBytes, 0644); err == nil {
|
||||
req.Logo = fmt.Sprintf("/images/%s/%s/%s", seriesSlug, setCode, logoFilename)
|
||||
}
|
||||
} else {
|
||||
img, _, err := image.Decode(bytes.NewReader(logoBytes))
|
||||
if err == nil && img != nil {
|
||||
var buf bytes.Buffer
|
||||
if err := png.Encode(&buf, img); err == nil {
|
||||
b64 := base64.StdEncoding.EncodeToString(buf.Bytes())
|
||||
bounds := img.Bounds()
|
||||
w, h := bounds.Dx(), bounds.Dy()
|
||||
svgContent := fmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="%d" height="%d" viewBox="0 0 %d %d"><image width="%d" height="%d" xlink:href="data:image/png;base64,%s"/></svg>`, w, h, w, h, w, h, b64)
|
||||
|
||||
if err := os.WriteFile(filepathDstLogo, []byte(svgContent), 0644); err == nil {
|
||||
req.Logo = fmt.Sprintf("/images/%s/%s/%s", seriesSlug, setCode, logoFilename)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := c.SaveUploadedFile(logoHeader, filepathDstLogo); err == nil {
|
||||
req.Logo = fmt.Sprintf("/images/%s/%s/%s", seriesSlug, setCode, logoFilename)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "input tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
updated, err := h.service.UpdateSet(id, req)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "set berhasil diupdate", ToSeriesSetMasterResponse(*updated))
|
||||
}
|
||||
|
||||
func (h *Handler) Delete(c *gin.Context) {
|
||||
id, err := parseID(c)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "id tidak valid", []validator.FieldError{
|
||||
{Field: "id", Message: "id harus berupa angka"},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.service.Delete(id); err != nil {
|
||||
response.Error(c, http.StatusNotFound, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "berhasil menghapus data series/set", nil)
|
||||
}
|
||||
|
||||
func parseID(c *gin.Context) (uint, error) {
|
||||
idParam := c.Param("id")
|
||||
id, err := strconv.ParseUint(idParam, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint(id), nil
|
||||
}
|
||||
Reference in New Issue
Block a user