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/structs" "jannex/admin-dashboard-backend/modules/utils" "os" "strconv" "strings" "github.com/gofiber/fiber/v2" "github.com/google/uuid" "github.com/rs/zerolog/log" ) const Base = "https://inv.ex.umbach.dev" const apiBase = Base + "/api" const ApiToken = "1367f15d21935e4eb540f897946fb5cd98485c3f" func InvexApiRequestClient(requestMethod string, url string) (statusCode int, body []byte, err error) { a := fiber.AcquireAgent() a.Add("Authorization", "Token "+ApiToken) req := a.Request() req.Header.SetMethod(requestMethod) req.SetRequestURI(url) if err := a.Parse(); err != nil { log.Error().Msgf("Failed to parse request, err: %s", err) return 0, nil, err } code, body, _ := a.Bytes() if code == 401 { log.Error().Msgf("invex not authorized, code: %d", code) logger.AddSystemLog(structs.LogMessage{ Id: 29, Type: utils.LogTypeInfo, Messages: []structs.LogData{ {Type: "error", Value: "invex not authorized"}, {Type: "statusCode", Value: strconv.Itoa(code)}, }, }) return code, nil, err } if code == 404 { log.Error().Msgf("Invex stock item not found, code: %d", code) logger.AddSystemLog(structs.LogMessage{ Id: 29, Type: utils.LogTypeInfo, Messages: []structs.LogData{ {Type: "error", Value: "invex stock item not found"}, {Type: "statusCode", Value: strconv.Itoa(code)}, }, }) return code, nil, err } return code, body, nil } 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(structs.LogMessage{ Id: 27, Type: utils.LogTypeInfo, Messages: []structs.LogData{ {Type: "userId", Value: userId}, {Type: "equipmentDocumentationId", Value: newEquipmentDocumentation.Id}, {Type: "stockItemId", Value: newEquipmentDocumentation.StockItemId}, {Type: "type", Value: strconv.Itoa(int(body.Type))}, {Type: "title", Value: body.Title}, {Type: "description", Value: 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, "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 = InvexApiRequestClient(fiber.MethodGet, apiBase+"/stock/"+stockItemId+"/") if err != nil { log.Error().Msgf("Invex api request error: %s", err) return c.SendStatus(fiber.StatusInternalServerError) } } else { totalPages = utils.GetTotalPages(&documentations, "stock_item_id = ?", stockItemId) } logger.AddSystemLog(structs.LogMessage{ Id: 30, Type: utils.LogTypeInfo, Messages: []structs.LogData{ {Type: "userId", Value: c.Locals("userId").(string)}, {Type: "stockItemId", Value: stockItemId}, {Type: "statusCode", Value: 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(structs.LogMessage{ Id: 28, Type: utils.LogTypeInfo, Messages: []structs.LogData{ {Type: "userId", Value: c.Locals("userId").(string)}, {Type: "equipmentDocumentationId", Value: body.DocumentationId}, {Type: "title", Value: body.Title}, {Type: "type", Value: strconv.Itoa(int(body.Type))}, {Type: "description", Value: 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 := InvexApiRequestClient(fiber.MethodGet, apiBase+"/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 = InvexApiRequestClient(fiber.MethodGet, 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) }