equipment documentation

main
alex 2023-08-26 00:15:56 +02:00
parent 03e3822dac
commit c0c5b966fd
7 changed files with 277 additions and 208 deletions

BIN
main

Binary file not shown.

View File

@ -1,10 +1,16 @@
package equipment
import (
"encoding/base64"
"encoding/json"
"jannex/admin-dashboard-backend/modules/config"
"jannex/admin-dashboard-backend/modules/database"
"jannex/admin-dashboard-backend/modules/structs"
"os"
"strings"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
)
@ -12,38 +18,138 @@ const Base = "https://inv.ex.umbach.dev"
const apiBase = Base + "/api"
const ApiToken = "1367f15d21935e4eb540f897946fb5cd98485c3f"
func GetEquipmentDocumentation(stockItemId string, c *fiber.Ctx) error {
var documentations []structs.EquipmentDocumentation
database.DB.Where("stock_item_id = ?", stockItemId).Find(&documentations)
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
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(fiber.MethodGet)
req.SetRequestURI(apiBase + "/stock/" + stockItemId + "/")
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)
return code, nil, err
}
if code == 404 {
log.Error().Msgf("Invex stock item not found, statusCode: %d", statusCode)
return statusCode, 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.ApiCreateEquipmentDocumentationRequest) 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)
}
statusCode, _, _ = a.Bytes()
if statusCode == 401 {
log.Error().Msgf("invex not authorized, statusCode: %d", statusCode)
newEquipmentDocumentation := structs.EquipmentDocumentation{
Id: uuid.New().String(),
StockItemId: body.StockItemId,
Type: body.Type,
Title: body.Title,
}
if statusCode == 404 {
log.Error().Msgf("Invex stock item not found, statusCode: %d", statusCode)
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)
return c.JSON(fiber.Map{"message": "ok"})
}
func GetEquipmentDocumentations(stockItemId string, c *fiber.Ctx) error {
var documentations []structs.EquipmentDocumentation
database.DB.Where("stock_item_id = ?", stockItemId).Find(&documentations)
var err error
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)
}
}
@ -52,51 +158,88 @@ func GetEquipmentDocumentation(stockItemId string, c *fiber.Ctx) error {
Documentations: documentations})
}
/*
func GetEquipmentDocumentation(stockItemId string, c *fiber.Ctx) error {
equipment := structs.Equipment{Id: stockItemId}
func GetEquipmentDocumentation(stockItemId string, documentationId string, c *fiber.Ctx) error {
var documentation structs.EquipmentDocumentation
if err := database.DB.Where("id = ?", stockItemId).First(&equipment).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
log.Info().Msgf("a %s", err)
database.DB.Where("id = ? AND stock_item_id = ?", documentationId, stockItemId).Find(&documentation)
log.Error().Msgf("Failed to get equipment, err: %s", err)
return c.JSON([]structs.EquipmentDocumentation{})
return c.JSON(documentation)
}
}
func EditEquipmentDocumentation(c *fiber.Ctx, body structs.ApiEditEquipmentDocumentationRequest) error {
var bodyNotes []map[string]string
// if equipment already exists, return the documentation
if equipment.Name != "" {
var equipmentDocumentations []structs.EquipmentDocumentation
err := json.Unmarshal(body.Notes, &bodyNotes)
database.DB.Where("equipment_id = ?", stockItemId).Find(&equipmentDocumentations)
return c.JSON(equipmentDocumentations)
}
// create new equipment
a := fiber.AcquireAgent()
a.Add("Authorization", "Token "+ApiToken)
req := a.Request()
req.Header.SetMethod(fiber.MethodGet)
req.SetRequestURI(apiBase + "/stock/" + stockItemId + "/")
if err := a.Parse(); err != nil {
log.Error().Msgf("Failed to parse request, err: %s", err)
if err != nil {
log.Error().Msgf("Failed to unmarshal json, err: %s", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
code, body, _ := a.Bytes()
var notes []Notes
if code == 401 {
log.Error().Msgf("invex not authorized, code: %d", code)
// 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"],
})
}
marshaledNotes, err := json.Marshal(notes)
if err != nil {
log.Error().Msgf("Failed to marshal notes, err: %s", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
if code == 404 {
log.Error().Msgf("Inven stock item not found, code: %d", code)
return c.SendStatus(fiber.StatusNotFound)
database.DB.Model(&structs.EquipmentDocumentation{}).Where("id = ?", body.DocumentationId).Updates(structs.EquipmentDocumentation{
Type: body.Type,
Title: body.Title,
Notes: string(marshaledNotes),
})
return c.JSON(fiber.Map{"message": "ok"})
}
// 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
@ -108,32 +251,17 @@ func GetEquipmentDocumentation(stockItemId string, c *fiber.Ctx) error {
}
partDetail := data["part_detail"].(map[string]interface{})
thumbnail := partDetail["thumbnail"].(string)
database.DB.Create(&structs.Equipment{
Id: stockItemId,
Name: partDetail["name"].(string),
Thumbnail: partDetail["thumbnail"].(string),
})
// second request to /media/part_images/:thumbnail to get the thumbnail image
_, body, err = InvexApiRequestClient(fiber.MethodGet, Base+"/"+thumbnail)
return c.JSON([]structs.EquipmentDocumentation{})
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)
}
*/
/*
func GetEquipment() []structs.Equipment {
var equipments []structs.Equipment
database.DB.Find(&equipments)
return equipments
}*/
/*
// return whether the scanned equipment is existing in the database
func IsEquipmentExisting(stockItemId string) bool {
var equipment structs.Equipment
database.DB.Where("id = ?", stockItemId).FirstOrCreate(&equipment)
return equipment.Id != ""
} */

View File

@ -25,10 +25,15 @@ type EquipmentDocumentation struct {
UpdatedAt time.Time
}
type ApiEquipmentParamsRequest struct {
type ApiEquipmentRequest struct {
StockItemId string `json:"stockItemId"`
}
type ApiGetDocumentationEquipmentRequest struct {
StockItemId string `json:"stockItemId"`
DocumentationId string `json:"documentationId"`
}
type ApiCreateEquipmentDocumentationRequest struct {
StockItemId string `json:"stockItemId"`
Type uint8 `json:"type"`
@ -40,3 +45,10 @@ type ApiEquipmentDocumentationResponse struct {
Status int
Documentations []EquipmentDocumentation
}
type ApiEditEquipmentDocumentationRequest struct {
DocumentationId string `json:"documentationId"`
Type uint8 `json:"type"`
Title string `json:"title"`
Notes json.RawMessage `json:"notes"`
}

View File

@ -66,3 +66,31 @@ func GenerateSession() (string, error) {
return string(r), nil
}
func ParamsParserHelper(c *fiber.Ctx, params interface{}) error {
if err := c.ParamsParser(params); err != nil {
log.Error().Msgf("Failed to parse params, err: %s", err.Error())
return c.Status(fiber.StatusBadRequest).JSON(err)
}
if errValidation := ValidateStruct(params); errValidation != nil {
log.Error().Msgf("Failed to validate params, err: %v", errValidation)
return c.Status(fiber.StatusBadRequest).JSON(errValidation)
}
return c.Next()
}
func BodyParserHelper(c *fiber.Ctx, body interface{}) error {
if err := c.BodyParser(body); err != nil {
log.Error().Msgf("Failed to parse body, err: %s", err.Error())
return c.Status(fiber.StatusBadRequest).JSON(err)
}
if errValidation := ValidateStruct(body); errValidation != nil {
log.Error().Msgf("Failed to validate body, err: %v", errValidation)
return c.Status(fiber.StatusBadRequest).JSON(errValidation)
}
return c.Next()
}

View File

@ -38,5 +38,5 @@ func ValidatorInit() {
Validate.RegisterStructValidationMapRules(groupTaskRules,
structs.ApiGroupTaskRequest{})
Validate.RegisterStructValidationMapRules(equipmentRules, structs.ApiEquipmentParamsRequest{})
Validate.RegisterStructValidationMapRules(equipmentRules, structs.ApiEquipmentRequest{})
}

View File

@ -1,153 +1,52 @@
package equipment
import (
"encoding/base64"
"encoding/json"
"jannex/admin-dashboard-backend/modules/config"
"jannex/admin-dashboard-backend/modules/database"
"jannex/admin-dashboard-backend/modules/equipment"
"jannex/admin-dashboard-backend/modules/structs"
"jannex/admin-dashboard-backend/modules/utils"
"os"
"strings"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
)
func getImageType(data string) string {
switch data {
case "data:image/png;base64":
return "png"
default:
return "jpeg"
}
}
type Notes struct {
Image string
Description string
}
func CreateEquipmentDocumentation(c *fiber.Ctx) error {
var body structs.ApiCreateEquipmentDocumentationRequest
if err := c.BodyParser(&body); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(err)
}
utils.BodyParserHelper(c, &body)
var bodyNotes []map[string]string
log.Info().Msgf("body StockItemId: %+v", body.StockItemId)
err := json.Unmarshal(body.Notes, &bodyNotes)
return equipment.CreateEquipmentDocumentation(c, body)
}
if err != nil {
log.Error().Msgf("Failed to unmarshal json, err: %s", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
func GetEquipmentDocumentations(c *fiber.Ctx) error {
var params structs.ApiEquipmentRequest
newEquipmentDocumentation := structs.EquipmentDocumentation{
Id: uuid.New().String(),
StockItemId: body.StockItemId,
Type: body.Type,
Title: body.Title,
}
utils.ParamsParserHelper(c, &params)
// TODO: only create folder if there is an image
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)
return c.JSON(fiber.Map{"message": "ok"})
return equipment.GetEquipmentDocumentations(params.StockItemId, c)
}
func GetEquipmentDocumentation(c *fiber.Ctx) error {
var body structs.ApiEquipmentParamsRequest
var params structs.ApiGetDocumentationEquipmentRequest
if err := c.ParamsParser(&body); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(err)
}
utils.ParamsParserHelper(c, &params)
if errValidation := utils.ValidateStruct(body); errValidation != nil {
log.Error().Msgf("Failed to validate body, err: %v", errValidation)
return c.Status(fiber.StatusBadRequest).JSON(errValidation)
}
return equipment.GetEquipmentDocumentation(body.StockItemId, c)
return equipment.GetEquipmentDocumentation(params.StockItemId, params.DocumentationId, c)
}
func EditEquipmentDocumentation(c *fiber.Ctx) error {
var body structs.ApiEditEquipmentDocumentationRequest
utils.BodyParserHelper(c, &body)
return equipment.EditEquipmentDocumentation(c, body)
}
// fetching the thumbnail from the invex server and sending it back to the client
func GetEquipmentThumbnail(c *fiber.Ctx) error {
//resp, err := http.Get(equipment.Base + "/media/part_images/part_152_image.thumbnail.png")
var params structs.ApiEquipmentRequest
a := fiber.AcquireAgent()
a.Add("Authorization", "Token "+equipment.ApiToken)
req := a.Request()
req.Header.SetMethod(fiber.MethodGet)
req.SetRequestURI(equipment.Base + "/media/part_images/" + c.Params("stockItemThumbnail"))
if err := a.Parse(); err != nil {
log.Error().Msgf("Failed to parse request, err: %s", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
code, body, errs := a.Bytes()
log.Info().Msgf("code %d %s", code, errs)
c.Set("Content-Type", "image/png")
return c.Send(body)
utils.ParamsParserHelper(c, &params)
return equipment.GetEquipmentInvexThumbnail(c, params.StockItemId)
}

View File

@ -41,10 +41,12 @@ func SetupRoutes(app *fiber.App) {
// TODO: add user session validation
//e.Get("/scanned/:stockItemId", requestAccessValidation, equipment.EquipmentScanned)
//e.Get("/", requestAccessValidation, equipment.GetEquipment)
e.Get("/documentation/:stockItemId", requestAccessValidation, equipment.GetEquipmentDocumentation)
e.Get("/documentations/:stockItemId", requestAccessValidation, equipment.GetEquipmentDocumentations)
e.Post("/documentation/create", requestAccessValidation, equipment.CreateEquipmentDocumentation)
// access validation here implented as it would require to implement authorization on web client side on Avatar Component
e.Get("/thumbnail/media/part_images/:stockItemThumbnail", equipment.GetEquipmentThumbnail)
e.Get("/documentation/:stockItemId/:documentationId", requestAccessValidation, equipment.GetEquipmentDocumentation)
e.Post("/documentation/edit", requestAccessValidation, equipment.EditEquipmentDocumentation)
// access validation here implemented as it would require to implement authorization on web client side on Avatar Component
e.Get("/thumbnail/:stockItemId", equipment.GetEquipmentThumbnail)
app.Static("/", config.Cfg.FolderPaths.PublicStatic)
}