diff --git a/app/delivery/web/api/restricted/productDescription.go b/app/delivery/web/api/restricted/productDescription.go index 857ae5e..d787573 100644 --- a/app/delivery/web/api/restricted/productDescription.go +++ b/app/delivery/web/api/restricted/productDescription.go @@ -32,8 +32,7 @@ func ProductDescriptionHandlerRoutes(r fiber.Router) fiber.Router { r.Get("/get-product-description", handler.GetProductDescription) r.Post("/save-product-description", handler.SaveProductDescription) - // r.Get("/get-quarters", handler.GetQuarters) - // r.Get("/get-issues", handler.GetIssues) + r.Get("/translate-product-description", handler.TranslateProductDescription) return r } @@ -132,3 +131,54 @@ func (h *ProductDescriptionHandler) SaveProductDescription(c fiber.Ctx) error { "message": i18n.T_(c, "product_description.successfully_updated_fields"), }) } + +// GetProductDescription returns the product description for a given product ID +func (h *ProductDescriptionHandler) TranslateProductDescription(c fiber.Ctx) error { + userID, ok := c.Locals("userID").(uint) + if !ok { + return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).JSON(fiber.Map{ + "error": responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody), // possibly could return a different error + }) + } + + productID_attribute := c.Query("productID") + productID, err := strconv.Atoi(productID_attribute) + if err != nil { + return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).JSON(fiber.Map{ + "error": responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute), + }) + } + + productShopID_attribute := c.Query("productShopID") + productShopID, err := strconv.Atoi(productShopID_attribute) + if err != nil { + return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).JSON(fiber.Map{ + "error": responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute), + }) + } + + productFromLangID_attribute := c.Query("productFromLangID") + productFromLangID, err := strconv.Atoi(productFromLangID_attribute) + if err != nil { + return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).JSON(fiber.Map{ + "error": responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute), + }) + } + + productToLangID_attribute := c.Query("productToLangID") + productToLangID, err := strconv.Atoi(productToLangID_attribute) + if err != nil { + return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).JSON(fiber.Map{ + "error": responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute), + }) + } + + response, err := h.productDescriptionService.TranslateProductDescription(userID, uint(productID), uint(productShopID), uint(productFromLangID), uint(productToLangID)) + if err != nil { + return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{ + "error": responseErrors.GetErrorCode(c, err), + }) + } + + return c.JSON(response) +} diff --git a/app/service/langsService/service.go b/app/service/langsService/service.go index 78e2fbd..5d8ca88 100644 --- a/app/service/langsService/service.go +++ b/app/service/langsService/service.go @@ -85,7 +85,7 @@ func (s *LangService) GetLanguageByISOCode(isoCode string) (*view.Language, erro return s.repo.GetByISOCode(isoCode) } -// GetLanguageByISOCode returns a language by its ISO code +// GetLanguageByISOCode returns a language by its id func (s *LangService) GetLanguageById(id uint) (*view.Language, error) { return s.repo.GetById(id) } diff --git a/app/service/productDescriptionService/productDescriptionService.go b/app/service/productDescriptionService/productDescriptionService.go index 3c24c03..37358a0 100644 --- a/app/service/productDescriptionService/productDescriptionService.go +++ b/app/service/productDescriptionService/productDescriptionService.go @@ -1,6 +1,7 @@ package productDescriptionService import ( + "context" "encoding/xml" "fmt" "io" @@ -9,17 +10,24 @@ import ( "git.ma-al.com/goc_daniel/b2b/app/db" "git.ma-al.com/goc_daniel/b2b/app/model" + "git.ma-al.com/goc_daniel/b2b/app/service/langsService" "git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors" "gorm.io/gorm" + + "github.com/openai/openai-go/v3" + "github.com/openai/openai-go/v3/option" + "github.com/openai/openai-go/v3/responses" ) type ProductDescriptionService struct { - db *gorm.DB + db *gorm.DB + client openai.Client } func New() *ProductDescriptionService { return &ProductDescriptionService{ - db: db.Get(), + db: db.Get(), + client: openai.NewClient(option.WithAPIKey("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")), } } @@ -110,185 +118,148 @@ func (s *ProductDescriptionService) SaveProductDescription(userID uint, productI return nil } -// func (s *ProductDescriptionService) GetRepositoriesForUser(userID uint) ([]uint, error) { -// var repoIDs []uint +// Updates relevant fields with the "updates" map +func (s *ProductDescriptionService) TranslateProductDescription(userID uint, productID uint, productShopID uint, productFromLangID uint, productToLangID uint) (*model.ProductDescription, error) { + var ProductDescription model.ProductDescription -// err := s.db. -// Table("customer_repo_accesses"). -// Where("user_id = ?", userID). -// Pluck("repo_id", &repoIDs).Error -// if err != nil { -// return nil, fmt.Errorf("database error: %w", err) -// } + err := s.db. + Table("ps_product_lang"). + Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, productShopID, productFromLangID). + First(&ProductDescription).Error + if err != nil { + return nil, fmt.Errorf("database error: %w", err) + } -// return repoIDs, nil -// } + // we translate all changeable fields, and we keep the exact same HTML structure in relevant fields. + lang, err := langsService.LangSrv.GetLanguageById(productToLangID) + if err != nil { + return nil, err + } -// func (s *RepoService) UserHasAccessToRepo(userID uint, repoID uint) (bool, error) { -// var repositories []uint -// var err error + request := "Translate to " + lang.ISOCode + " without changing the html structure. You must only translate text visible on website.\n\n" + request += "\n" + request += "" + request += ProductDescription.Description + request += "" + request += "\n" + request += "Remember: translate to " + lang.ISOCode + " without changing the html structure. You must only translate text visible on website." + request += "\n" + request += "" + request += ProductDescription.DescriptionShort + request += "" + request += "\n" + request += "Remember: translate to " + lang.ISOCode + " without changing the html structure. You must only translate text visible on website." + request += "\n" + request += "" + request += ProductDescription.MetaDescription + request += "" + request += "\n" + request += "Remember: translate to " + lang.ISOCode + " without changing the html structure. You must only translate text visible on website." + request += "\n" + request += "" + request += ProductDescription.MetaTitle + request += "" + request += "\n" + request += "Remember: translate to " + lang.ISOCode + " without changing the html structure. You must only translate text visible on website." + request += "\n" + request += "" + request += ProductDescription.Name + request += "" + request += "\n" + request += "Remember: translate to " + lang.ISOCode + " without changing the html structure. You must only translate text visible on website." + request += "\n" + request += "" + request += ProductDescription.AvailableNow + request += "" + request += "\n" + request += "Remember: translate to " + lang.ISOCode + " without changing the html structure. You must only translate text visible on website." + request += "\n" + request += "" + request += ProductDescription.AvailableLater + request += "" + request += "\n" + request += "Remember: translate to " + lang.ISOCode + " without changing the html structure. You must only translate text visible on website." + request += "\n" + request += "" + request += ProductDescription.Usage + request += "" -// if repositories, err = s.GetRepositoriesForUser(userID); err != nil { -// return false, err -// } + openai_response, err := s.client.Responses.New(context.Background(), responses.ResponseNewParams{ + Input: responses.ResponseNewParamsInputUnion{OfString: openai.String(request)}, + Model: openai.ChatModelGPT4_1Mini, + // Model: openai.ChatModelGPT4_1Nano, + }) + if openai_response.Status != "completed" { + return nil, responseErrors.ErrOpenAIResponseFail + } + output := openai_response.OutputText() -// if !slices.Contains(repositories, repoID) { -// return false, responseErrors.ErrInvalidRepoID -// } + // for debugging purposes + // fi, err := os.ReadFile("/home/daniel/coding/work/b2b/app/service/productDescriptionService/test.txt") // just pass the file name + // output := string(fi) -// return true, nil -// } + success, match := GetStringInBetween(output, "", "") + if !success { + return nil, responseErrors.ErrOpenAIBadOutput + } + ProductDescription.Description = match -// // Extract all repositories assigned to user with specific id -// func (s *RepoService) GetYearsForUser(userID uint, repoID uint) ([]uint, error) { -// if ok, err := s.UserHasAccessToRepo(userID, repoID); !ok { -// return nil, err -// } + success, match = GetStringInBetween(output, "", "") + if !success { + return nil, responseErrors.ErrOpenAIBadOutput + } + ProductDescription.DescriptionShort = match -// years, err := s.GetYears(repoID) -// if err != nil { -// return nil, fmt.Errorf("database error: %w", err) -// } + success, match = GetStringInBetween(output, "", "") + if !success { + return nil, responseErrors.ErrOpenAIBadOutput + } + ProductDescription.MetaDescription = match -// return years, nil -// } + success, match = GetStringInBetween(output, "", "") + if !success { + return nil, responseErrors.ErrOpenAIBadOutput + } + ProductDescription.MetaTitle = match -// func (s *RepoService) GetYears(repo uint) ([]uint, error) { + success, match = GetStringInBetween(output, "", "") + if !success { + return nil, responseErrors.ErrOpenAIBadOutput + } + ProductDescription.Name = match -// var years []uint + success, match = GetStringInBetween(output, "", "") + if !success { + return nil, responseErrors.ErrOpenAIBadOutput + } + ProductDescription.AvailableNow = match -// query := ` -// WITH bounds AS ( -// SELECT -// MIN(to_timestamp(tt.created_unix)) AS min_ts, -// MAX(to_timestamp(tt.created_unix)) AS max_ts -// FROM tracked_time tt -// JOIN issue i ON i.id = tt.issue_id -// WHERE i.repo_id = ? -// AND tt.deleted = false -// ) -// SELECT -// EXTRACT(YEAR FROM y.year_start)::int AS year -// FROM bounds -// CROSS JOIN LATERAL generate_series( -// date_trunc('year', min_ts), -// date_trunc('year', max_ts), -// interval '1 year' -// ) AS y(year_start) -// ORDER BY year -// ` + success, match = GetStringInBetween(output, "", "") + if !success { + return nil, responseErrors.ErrOpenAIBadOutput + } + ProductDescription.AvailableLater = match -// err := db.Get().Raw(query, repo).Find(&years).Error -// if err != nil { -// return nil, err -// } -// return years, nil -// } + success, match = GetStringInBetween(output, "", "") + if !success { + return nil, responseErrors.ErrOpenAIBadOutput + } + ProductDescription.Usage = match -// // Extract all repositories assigned to user with specific id -// func (s *RepoService) GetQuartersForUser(userID uint, repoID uint, year uint) ([]model.QuarterData, error) { -// if ok, err := s.UserHasAccessToRepo(userID, repoID); !ok { -// return nil, err -// } + return &ProductDescription, nil +} -// response, err := s.GetQuarters(repoID, year) -// if err != nil { -// return nil, fmt.Errorf("database error: %w", err) -// } +// GetStringInBetween returns empty string if no start or end string found +func GetStringInBetween(str string, start string, end string) (success bool, result string) { + s := strings.Index(str, start) + if s == -1 { + return false, "" + } + s += len(start) + e := strings.Index(str[s:], end) + if e == -1 { + return false, "" + } -// return response, nil -// } - -// func (s *RepoService) GetQuarters(repo uint, year uint) ([]model.QuarterData, error) { -// var quarters []model.QuarterData - -// query := ` -// WITH quarters AS ( -// SELECT -// make_date(?::int, 1, 1) + (q * interval '3 months') AS quarter_start, -// q + 1 AS quarter -// FROM generate_series(0, 3) AS q -// ), -// data AS ( -// SELECT -// EXTRACT(QUARTER FROM to_timestamp(tt.created_unix)) AS quarter, -// SUM(tt.time) / 3600 AS time -// FROM tracked_time tt -// JOIN issue i ON i.id = tt.issue_id -// JOIN repository r ON i.repo_id = r.id -// WHERE -// EXTRACT(YEAR FROM to_timestamp(tt.created_unix)) = ? -// AND r.id = ? -// AND tt.deleted = false -// GROUP BY EXTRACT(QUARTER FROM to_timestamp(tt.created_unix)) -// ) -// SELECT -// COALESCE(d.time, 0) AS time, -// CONCAT(EXTRACT(YEAR FROM q.quarter_start)::int, '_Q', q.quarter) AS quarter -// FROM quarters q -// LEFT JOIN data d ON d.quarter = q.quarter -// ORDER BY q.quarter -// ` - -// err := db.Get(). -// Raw(query, year, year, repo). -// Find(&quarters). -// Error -// if err != nil { -// return nil, err -// } - -// return quarters, nil -// } - -// func (s *RepoService) GetIssuesForUser( -// userID uint, -// repoID uint, -// year uint, -// quarter uint, -// p pagination.Paging, -// ) (*pagination.Found[model.IssueTimeSummary], error) { -// if ok, err := s.UserHasAccessToRepo(userID, repoID); !ok { -// return nil, err -// } - -// return s.GetIssues(repoID, year, quarter, p) -// } - -// func (s *RepoService) GetIssues( -// repoId uint, -// year uint, -// quarter uint, -// p pagination.Paging, -// ) (*pagination.Found[model.IssueTimeSummary], error) { - -// query := db.Get(). -// Table("issue i"). -// Select(` -// i.id AS issue_id, -// i.name AS issue_name, -// to_timestamp(i.created_unix) AS issue_created_at, -// ROUND(SUM(tt.time) / 3600.0, 2) AS total_hours_spent -// `). -// Joins(`JOIN tracked_time tt ON tt.issue_id = i.id`). -// Joins(`JOIN "user" u ON u.id = tt.user_id`). -// Where("i.repo_id = ?", repoId). -// Where(` -// EXTRACT(YEAR FROM to_timestamp(tt.created_unix)) = ? -// AND EXTRACT(QUARTER FROM to_timestamp(tt.created_unix)) = ? -// `, year, quarter). -// Group(` -// i.id, -// i.name, -// u.id, -// u.full_name -// `). -// Order("i.created_unix") - -// result, err := pagination.Paginate[model.IssueTimeSummary](p, query) -// if err != nil { -// return nil, err -// } - -// return &result, nil -// } + return true, str[s : s+e] +} diff --git a/app/service/productDescriptionService/test.txt b/app/service/productDescriptionService/test.txt new file mode 100644 index 0000000..350eff5 --- /dev/null +++ b/app/service/productDescriptionService/test.txt @@ -0,0 +1,158 @@ +

The use of rehabilitation rollers in various exercises and treatments positively affects the alleviation of injuries and increases the chances of the patient returning to full physical fitness. They are used in motor rehabilitation, during corrective gymnastics, traditional and sports massages, as they are ideal for lifting and separating limbs. They can also be used to support knees, feet, arms, and the patient’s shoulders. Rehabilitation rollers are also recommended for children; using them during play greatly supports the development of gross motor skills.

+

Thanks to a wide color range and varied sizes, it is possible to compose an exercise set necessary in every physiotherapy office, massage room, as well as in schools and kindergartens.

+

The rehabilitation roller is a medical device in accordance with the essential requirements for medical devices and the understanding of the Medical Devices Act, registered in the Medical Devices Register maintained by the Office for Registration of Medicinal Products, Medical Devices and Biocidal Products, equipped with the manufacturer's declaration of conformity and marked with the CE mark.

+

+

Medical device

+

Recommended uses:

+ +

+

Material specification:

+

Cover: material with a PVC coating intended for medical devices, making it very easy to clean and disinfect:

+ +

REACHOEKO-TEX Standard 100 CertificatePhthalate-freeFire-resistantAlcohol-resistantUV-resistantSuitable for outdoor useScratch-resistantOil-resistant

+

Filling: medium-hard polyurethane foam with increased resistance to deformation:

+ +

OEKO-TEX Standard 100 CertificateHygienic certificateHygienic certificate

+

+

+

Rehabilitation rollers are used in various types of exercises. They are used in motor rehabilitation, during corrective gymnastics, traditional and sports massages, as they are ideal for lifting and separating limbs. They can also be used to support knees, feet, arms, and the patient’s shoulders. Rehabilitation rollers are also recommended for children; using them during play significantly supports the development of gross motor skills. The product is certified as a medical device.

+ + +Rehabilitation roller 10 x 30 cm +available +on order +

I. Cleaning and maintenance

+

The upholstery should be cleaned superficially using permitted agents:

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Type of dirt

+
+

Permitted agents

+
+

Procedure

+
+

Everyday dirt

+

+
+

Mild detergent, preferably a solution of gray soap

+
+

Clean regularly using a sponge or soft brush. Finally, wipe the cleaned area with a damp cloth and then dry it (to remove detergent residues).

+
+

Local, stronger dirt

+
+

25% ethyl alcohol solution

+
+

Gently wipe with a gauze tampon soaked with the solution. Finally, wipe the cleaned area with a damp cloth and then dry it (to remove detergent residues).

+
+

Disinfection

+
+

Commercially available disinfectants containing:

+

- active chlorine – sodium dichloroisocyanurate, max concentration 10000 ppm

+

- active chlorine - chlorine dioxide in a solution up to 20000 ppm

+

- isopropyl alcohol max concentration 70%

+

+
+

Disinfect according to the recommendations of the product manufacturer.

+
+

Before using any agent other than a mild detergent, test the effect in an inconspicuous area, and perform cleaning very carefully.

+
+


II. Information

+

+ + + + + + + + + + + + + + + + + + + + + + + +
+

Shampoo using a sponge

+
+

+
+

Do not wash!!! (delicate products)

+
+

+
+

Do not bleach!!! (do not use bleaching agents that release free chlorine)

+
+

+
+

Do not iron!!! (avoid contact with hot surfaces such as radiators)

+
+

+
+

Do not dry clean!!!

+
+

+

III. Warranty conditions

+

The warranty does not cover:

+
diff --git a/app/service/productDescriptionService/test2.txt b/app/service/productDescriptionService/test2.txt new file mode 100644 index 0000000..d3bd4a7 --- /dev/null +++ b/app/service/productDescriptionService/test2.txt @@ -0,0 +1,17 @@ +{ + "product_id": 51, + "shop_id": 1, + "lang_id": 1, + "description": "

The use of rehabilitation rollers in various types of exercises and treatments positively affects the alleviation of injuries and increases the chances of the patient returning to full physical fitness. They are used in motor rehabilitation, during corrective gymnastics, traditional and sports massages, as they are perfectly suited for lifting and separating limbs. They can also be used to support the knees, feet, arms, and shoulders of the patient. Rehabilitation rollers are also recommended for children; their use during play greatly supports the development of gross motor skills.

\n

Thanks to a wide range of colors and various sizes, it is possible to compose a set of exercises essential in every physiotherapy office, massage parlor, as well as school and kindergarten.

\n

The rehabilitation roller is a medical device compliant with essential requirements for medical devices and within the meaning of the Medical Devices Act, registered in the Register of Medical Devices maintained by the Office for Registration of Medicinal Products, Medical Devices and Biocidal Products, equipped with the manufacturer's declaration of conformity and marked with the CE sign.

\n

\n

\"Medical

\n

Recommended use:

\n\n

\n

Material specification:

\n

Cover: fabric with a PVC coating designed for medical products, which makes it very easy to clean and disinfect:

\n\n

\"REACH\"\"OEKO-TEX\"Phthalate-free\"\"Fire-resistant\"\"Alcohol\"UV\"Designed\"Scratch\"Oil

\n

Filling: medium-hard polyurethane foam with increased resistance to deformation:

\n\n

\"OEKO-TEX\"Hygienic\"Hygienic

\n

\n

", + "description_short": "

Rehabilitation rollers are used in various types of exercises. They are applied in motor rehabilitation, during corrective gymnastics, traditional and sports massages, as they are perfectly suited for lifting and separating limbs. They can also be used to support the knees, feet, arms, and shoulders of the patient. Rehabilitation rollers are also recommended for children; their use during play greatly supports the development of gross motor skills. The product is certified as a medical device.

", + "link_rewrite": "walek-rehabilitacyjny-10-x-30-cm", + "meta_description": "", + "meta_keywords": "", + "meta_title": "", + "name": "Rehabilitation roller 10 x 30 cm", + "available_now": "available", + "available_later": "on order", + "delivery_in_stock": "Czas realizacji 6-10 dni roboczych", + "delivery_out_stock": "Czas realizacji 6-10 dni roboczych", + "usage": "

I. Cleaning and maintenance

\n

The upholstery should be surface cleaned using allowed agents:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n

Type of dirt

\n
\n

Allowed agents

\n
\n

Procedure

\n
\n

Daily dirt

\n

\n
\n

Mild detergent, preferably gray soap solution

\n
\n

Clean regularly using a sponge or soft brush. Finally, wipe the cleaned area with a damp cloth and then dry it (to remove detergent residues).

\n
\n

Local, stronger dirt

\n
\n

25% ethyl alcohol solution

\n
\n

Gently wipe with a gauze soaked tampon. Finally, wipe the cleaned area with a damp cloth and then dry it (to remove detergent residues).

\n
\n

Disinfection

\n
\n

Commonly available disinfectants containing:

\n

- active chlorine – sodium dichloroisocyanurate, max concentration 10,000 ppm

\n

- active chlorine - chlorine dioxide solution up to 20,000 ppm

\n

- isopropyl alcohol max concentration 70 %

\n

\n
\n

Disinfect according to the instructions of the used agent.

\n
\n

Before using any agent other than a mild detergent, test the effect in an inconspicuous area and perform cleaning very carefully.

\n
\n


II. Information

\n

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\"\"\n

Shampoo using a sponge

\n
\n

\"\"

\n
\n

Do not wash!!! (delicate products)

\n
\n

\n
\n

Do not bleach!!! (do not use bleaching agents that release free chlorine)

\n
\n

\"\"

\n
\n

Do not iron!!! (avoid contact with hot surfaces such as radiators)

\n
\n

\"\"

\n
\n

Do not dry clean!!!

\n
\n

\n

III. Warranty conditions

\n

Warranty does not cover:

\n" +} \ No newline at end of file diff --git a/app/utils/responseErrors/response_errors.go b/app/utils/responseErrors/response_errors.go index a9300ed..0ce00bf 100644 --- a/app/utils/responseErrors/response_errors.go +++ b/app/utils/responseErrors/response_errors.go @@ -38,9 +38,11 @@ var ( ErrVerificationTokenExpired = errors.New("verification token has expired") // Typed errors for product description handler - ErrBadAttribute = errors.New("bad attribute") - ErrBadField = errors.New("this field can not be updated") - ErrInvalidHTML = errors.New("text is not in html format") + ErrBadAttribute = errors.New("bad attribute") + ErrBadField = errors.New("this field can not be updated") + ErrInvalidHTML = errors.New("text is not in html format") + ErrOpenAIResponseFail = errors.New("OpenAI responded with failure") + ErrOpenAIBadOutput = errors.New("OpenAI response does not obey the format") ) // Error represents an error with HTTP status code @@ -116,6 +118,10 @@ func GetErrorCode(c fiber.Ctx, err error) string { return i18n.T_(c, "error.err_bad_field") case errors.Is(err, ErrInvalidHTML): return i18n.T_(c, "error.err_invalid_html") + case errors.Is(err, ErrOpenAIResponseFail): + return i18n.T_(c, "error.err_openai_response_fail") + case errors.Is(err, ErrOpenAIBadOutput): + return i18n.T_(c, "error.err_openai_bad_output") default: return i18n.T_(c, "error.err_internal_server_error") @@ -152,6 +158,9 @@ func GetErrorStatus(err error) int { return fiber.StatusBadRequest case errors.Is(err, ErrEmailExists): return fiber.StatusConflict + case errors.Is(err, ErrOpenAIResponseFail), + errors.Is(err, ErrOpenAIBadOutput): + return fiber.StatusServiceUnavailable default: return fiber.StatusInternalServerError } diff --git a/go.mod b/go.mod index 5c79d37..d2dc011 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,13 @@ require ( gorm.io/gorm v1.31.1 ) +require ( + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect +) + require ( cloud.google.com/go/compute/metadata v0.9.0 // indirect dario.cat/mergo v1.0.2 // indirect @@ -46,6 +53,7 @@ require ( github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/onsi/gomega v1.39.1 // indirect + github.com/openai/openai-go/v3 v3.26.0 github.com/philhofer/fwd v1.2.0 // indirect github.com/pjbgf/sha1cd v0.5.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect diff --git a/go.sum b/go.sum index 04c290f..2102cc1 100644 --- a/go.sum +++ b/go.sum @@ -94,6 +94,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/openai/openai-go/v3 v3.26.0 h1:bRt6H/ozMNt/dDkN4gobnLqaEGrRGBzmbVs0xxJEnQE= +github.com/openai/openai-go/v3 v3.26.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= @@ -119,6 +121,16 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s= github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= diff --git a/tmp/build-errors.log b/tmp/build-errors.log index bad11c8..989d320 100644 --- a/tmp/build-errors.log +++ b/tmp/build-errors.log @@ -1 +1 @@ -exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file +exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file diff --git a/tmp/main b/tmp/main index 8c9caa1..376144c 100755 Binary files a/tmp/main and b/tmp/main differ