package equipment import ( "encoding/base64" "encoding/json" "jannex/admin-dashboard-backend/modules/config" "jannex/admin-dashboard-backend/modules/database" "jannex/admin-dashboard-backend/modules/logger" "jannex/admin-dashboard-backend/modules/requestclient" "jannex/admin-dashboard-backend/modules/structs" "jannex/admin-dashboard-backend/modules/utils" "os" "strconv" "strings" "github.com/gofiber/fiber/v2" "github.com/google/uuid" "github.com/rs/zerolog/log" ) type Notes struct { Image string Description string } func getImageType(data string) string { switch data { case "data:image/png;base64": return "png" default: return "jpeg" } } func CreateEquipmentDocumentation(c *fiber.Ctx, body structs.CreateEquipmentDocumentationRequest) error { var bodyNotes []map[string]string err := json.Unmarshal(body.Notes, &bodyNotes) if err != nil { log.Error().Msgf("Failed to unmarshal json, err: %s", err) return c.SendStatus(fiber.StatusInternalServerError) } userId := c.Locals("userId").(string) newEquipmentDocumentation := structs.EquipmentDocumentation{ Id: uuid.New().String(), StockItemId: body.StockItemId, Type: body.Type, Title: body.Title, CreatedByUserId: userId, } if err := os.Mkdir(config.Cfg.FolderPaths.PublicStatic+"/equipmentdocumentation/"+newEquipmentDocumentation.Id, os.ModePerm); err != nil { log.Error().Msgf("Failed to create folder, err: %s", err) return c.SendStatus(fiber.StatusInternalServerError) } var notes []Notes // loop throught bodyNotes and save image to static folder under random name for _, bodyNote := range bodyNotes { var newFileName string if bodyNote["Image"] != "" { // image is sent as base64 string // decode it and save it to the static folder // encoded base64 image structure is: data:image/jpeg;base64,/9j/4AA... splittedImageData := strings.Split(bodyNote["Image"], ",") decodedImage, err := base64.StdEncoding.DecodeString(splittedImageData[1]) if err != nil { log.Error().Msgf("Failed to decode base64 string, err: %s", err) return c.SendStatus(fiber.StatusInternalServerError) } newFileName = uuid.New().String() + "." + getImageType(splittedImageData[0]) err = os.WriteFile(config.Cfg.FolderPaths.PublicStatic+"/equipmentdocumentation/"+newEquipmentDocumentation.Id+"/"+newFileName, decodedImage, 0644) if err != nil { log.Error().Msgf("Failed to save image, err: %s", err) return c.SendStatus(fiber.StatusInternalServerError) } } notes = append(notes, Notes{ Image: newFileName, Description: bodyNote["Description"], }) } marshaledNotes, err := json.Marshal(notes) if err != nil { log.Error().Msgf("Failed to marshal notes, err: %s", err) return c.SendStatus(fiber.StatusInternalServerError) } newEquipmentDocumentation.Notes = string(marshaledNotes) database.DB.Create(&newEquipmentDocumentation) logger.AddSystemLog("User %s has created the equipment document %s for the stock item %s with the title %s type %s description %s", userId, newEquipmentDocumentation.Id, newEquipmentDocumentation.StockItemId, body.Title, body.Type, string(marshaledNotes)) return c.JSON(fiber.Map{"message": "ok"}) } func GetEquipmentDocumentations(stockItemId string, query structs.PageQuery, c *fiber.Ctx) error { var documentations []structs.EquipmentDocumentation utils.DbPageQuery(query, utils.EquipmentDocumentationsPaginationLimit, &documentations, "created_at DESC", "stock_item_id = ?", stockItemId) var err error totalPages := 0 statusCode := 200 if len(documentations) == 0 { // there are no documentations for this equipment on the our database // so there will be checked on invex if the stock item exists statusCode, _, err = requestclient.InvexApiRequestClient(fiber.MethodGet, config.Cfg.InvexAPI.Base+"/api/stock/"+stockItemId+"/") if err != nil { log.Error().Msgf("Invex api request error: %s", err) return c.SendStatus(fiber.StatusInternalServerError) } } else { totalPages = utils.GetTotalPages(utils.EquipmentDocumentationsPaginationLimit, &documentations, "stock_item_id = ?", stockItemId) } logger.AddSystemLog("User %s has viewed equipment documentation of the stock item %s. StatusCode: %s", c.Locals("userId").(string), stockItemId, strconv.Itoa(statusCode)) return c.JSON(structs.EquipmentDocumentationResponse{ Status: statusCode, Documentations: documentations, TotalPages: totalPages}) } func GetEquipmentDocumentation(stockItemId string, documentationId string, c *fiber.Ctx) error { var documentation structs.EquipmentDocumentation database.DB.Where("id = ? AND stock_item_id = ?", documentationId, stockItemId).Find(&documentation) return c.JSON(documentation) } func EditEquipmentDocumentation(c *fiber.Ctx, body structs.EditEquipmentDocumentationRequest) error { var bodyNotes []map[string]string err := json.Unmarshal(body.Notes, &bodyNotes) if err != nil { log.Error().Msgf("Failed to unmarshal json, err: %s", err) return c.SendStatus(fiber.StatusInternalServerError) } var notes []Notes // loop throught bodyNotes and save image to static folder under random name for _, bodyNote := range bodyNotes { newFileName := bodyNote["Image"] if bodyNote["Image"] != "" { // check if image starts with base64 string if strings.HasPrefix(bodyNote["Image"], "data:") { // image is sent as base64 string // decode it and save it to the static folder // encoded base64 image structure is: data:image/jpeg;base64,/9j/4AA... splittedImageData := strings.Split(bodyNote["Image"], ",") decodedImage, err := base64.StdEncoding.DecodeString(splittedImageData[1]) if err != nil { log.Error().Msgf("Failed to decode base64 string, err: %s", err) return c.SendStatus(fiber.StatusInternalServerError) } newFileName = uuid.New().String() + "." + getImageType(splittedImageData[0]) err = os.WriteFile(config.Cfg.FolderPaths.PublicStatic+"/equipmentdocumentation/"+body.DocumentationId+"/"+newFileName, decodedImage, 0644) if err != nil { log.Error().Msgf("Failed to save image, err: %s", err) return c.SendStatus(fiber.StatusInternalServerError) } } } notes = append(notes, Notes{ Image: newFileName, Description: bodyNote["Description"], }) } // read all images on the folder and delete images that are not longer used in notes files, err := os.ReadDir(config.Cfg.FolderPaths.PublicStatic + "/equipmentdocumentation/" + body.DocumentationId) if err != nil { log.Error().Msgf("Failed to read directory, err: %s", err) return c.SendStatus(fiber.StatusInternalServerError) } for _, file := range files { if file.IsDir() { continue } if !isInList(file.Name(), notes) { err := os.Remove(config.Cfg.FolderPaths.PublicStatic + "/equipmentdocumentation/" + body.DocumentationId + "/" + file.Name()) if err != nil { log.Error().Msgf("Failed to remove file, err: %s", err) return c.SendStatus(fiber.StatusInternalServerError) } } } // marshal notes to json marshaledNotes, err := json.Marshal(notes) if err != nil { log.Error().Msgf("Failed to marshal notes, err: %s", err) return c.SendStatus(fiber.StatusInternalServerError) } database.DB.Model(&structs.EquipmentDocumentation{}).Where("id = ?", body.DocumentationId).Updates(structs.EquipmentDocumentation{ Type: body.Type, Title: body.Title, Notes: string(marshaledNotes), }) logger.AddSystemLog("User %s has updated the equipment document %s to title %s type %s and description %s", c.Locals("userId").(string), body.DocumentationId, body.Title, strconv.Itoa(int(body.Type)), string(marshaledNotes)) return c.JSON(fiber.Map{"message": "ok"}) } func isInList(fileName string, notes []Notes) bool { for _, note := range notes { if note.Image == fileName { return true } } return false } // fetching the thumbnail from the invex server and sending it back to the client func GetEquipmentInvexThumbnail(c *fiber.Ctx, stockItemId string) error { // first request to /api/stock/:stockItemId/ to get the thumbnail url _, body, err := requestclient.InvexApiRequestClient(fiber.MethodGet, config.Cfg.InvexAPI.Base+"/api/stock/"+stockItemId+"/") if err != nil { log.Error().Msgf("Invex api request error: %s", err) return c.SendStatus(fiber.StatusInternalServerError) } // parse body as json var data map[string]interface{} if err := json.Unmarshal(body, &data); err != nil { log.Error().Msgf("Failed to unmarshal json, err: %s", err) return c.SendStatus(fiber.StatusInternalServerError) } partDetail := data["part_detail"].(map[string]interface{}) thumbnail := partDetail["thumbnail"].(string) // second request to /media/part_images/:thumbnail to get the thumbnail image _, body, err = requestclient.InvexApiRequestClient(fiber.MethodGet, config.Cfg.InvexAPI.Base+"/"+thumbnail) if err != nil { log.Error().Msgf("Invex api request error: %s", err) return c.SendStatus(fiber.StatusInternalServerError) } c.Set("Content-Type", "image/png") return c.Send(body) }