8 Commits

Author SHA1 Message Date
Daniel Goc
1bab7f642f typo 2026-04-03 11:44:15 +02:00
Daniel Goc
a988bbbc33 added copying and moving 2026-04-03 11:25:16 +02:00
Daniel Goc
395d670298 add storage to .gitignore 2026-04-02 14:00:58 +02:00
Daniel Goc
7d4242abb1 move path to params 2026-04-02 13:52:50 +02:00
Daniel Goc
9c7eb5ee4e Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into storage 2026-04-02 11:31:39 +02:00
Daniel Goc
833f4a5a07 deleting and uploading files 2026-04-02 11:26:58 +02:00
Daniel Goc
b9bc121d43 getting to upload 2026-04-02 10:27:14 +02:00
Daniel Goc
b2acb8c922 storage 2026-04-01 13:30:54 +02:00
70 changed files with 956 additions and 203 deletions

4
.env
View File

@@ -48,6 +48,10 @@ EMAIL_FROM=test@ma-al.com
EMAIL_FROM_NAME=Gitea Manager
EMAIL_ADMIN=goc_marek@ma-al.pl
# STORAGE
STORAGE_ROOT=./storage
I18N_LANGS=en,pl,cs
PDF_SERVER_URL=http://localhost:8000

4
.gitignore vendored
View File

@@ -5,4 +5,6 @@ bin/
i18n/*.json
*_templ.go
tmp/main
test.go
test.go
storage/*
!storage/.gitkeep

View File

@@ -2,8 +2,10 @@ package config
import (
"fmt"
"log"
"log/slog"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
@@ -24,7 +26,8 @@ type Config struct {
GoogleTranslate GoogleTranslateConfig
Image ImageConfig
Cors CorsConfig
MailiSearch MeiliSearchConfig
MeiliSearch MeiliSearchConfig
Storage StorageConfig
}
type I18n struct {
@@ -95,6 +98,10 @@ type EmailConfig struct {
Enabled bool `env:"EMAIL_ENABLED,false"`
}
type StorageConfig struct {
RootFolder string `env:"STORAGE_ROOT"`
}
type PdfPrinter struct {
ServerUrl string `env:"PDF_SERVER_URL,http://localhost:8000"`
}
@@ -155,7 +162,7 @@ func load() *Config {
err = loadEnv(&cfg.OAuth.Google)
if err != nil {
slog.Error("not possible to load env variables for outh google : ", err.Error(), "")
slog.Error("not possible to load env variables for oauth google : ", err.Error(), "")
}
err = loadEnv(&cfg.App)
@@ -170,12 +177,12 @@ func load() *Config {
err = loadEnv(&cfg.I18n)
if err != nil {
slog.Error("not possible to load env variables for email : ", err.Error(), "")
slog.Error("not possible to load env variables for i18n : ", err.Error(), "")
}
err = loadEnv(&cfg.Pdf)
if err != nil {
slog.Error("not possible to load env variables for email : ", err.Error(), "")
slog.Error("not possible to load env variables for pdf : ", err.Error(), "")
}
err = loadEnv(&cfg.GoogleTranslate)
@@ -185,19 +192,25 @@ func load() *Config {
err = loadEnv(&cfg.Image)
if err != nil {
slog.Error("not possible to load env variables for google translate : ", err.Error(), "")
slog.Error("not possible to load env variables for image : ", err.Error(), "")
}
err = loadEnv(&cfg.Cors)
if err != nil {
slog.Error("not possible to load env variables for google translate : ", err.Error(), "")
slog.Error("not possible to load env variables for cors : ", err.Error(), "")
}
err = loadEnv(&cfg.MailiSearch)
err = loadEnv(&cfg.MeiliSearch)
if err != nil {
slog.Error("not possible to load env variables for google translate : ", err.Error(), "")
slog.Error("not possible to load env variables for meili search : ", err.Error(), "")
}
err = loadEnv(&cfg.Storage)
if err != nil {
slog.Error("not possible to load env variables for storage : ", err.Error(), "")
}
cfg.Storage.RootFolder = ResolveRelativePath(cfg.Storage.RootFolder)
return cfg
}
@@ -308,6 +321,22 @@ func setValue(field reflect.Value, val string, key string) error {
return nil
}
func ResolveRelativePath(relativePath string) string {
// get working directory (where program was started)
wd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
// convert to absolute path
absPath := relativePath
if !filepath.IsAbs(absPath) {
absPath = filepath.Join(wd, absPath)
}
return filepath.Clean(absPath)
}
func parseEnvTag(tag string) (key string, def *string) {
if tag == "" {
return "", nil

View File

@@ -1,7 +1,6 @@
package middleware
import (
"strconv"
"strings"
"git.ma-al.com/goc_daniel/b2b/app/config"
@@ -61,52 +60,9 @@ func AuthMiddleware() fiber.Handler {
})
}
// Create locale. LangID is overwritten by auth Token
var userLocale model.UserLocale
userLocale.OriginalUser = user
// Check if target user is present
targetUserIDAttribute := c.Query("target_user_id")
if targetUserIDAttribute == "" {
userLocale.User = user
c.Locals(constdata.USER_LOCALE, &userLocale)
return c.Next()
}
// We now populate the target user
if user.Role != model.RoleAdmin {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "admin access required",
})
}
targetUserID, err := strconv.Atoi(targetUserIDAttribute)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "invalid target user id attribute",
})
}
// to verify target user, we use the same functionality as for verifying original user
// Get target user from database
user, err = authService.GetUserByID(uint(targetUserID))
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "target user not found",
})
}
// Check if target user is active
if !user.IsActive {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "target user account is inactive",
})
}
userLocale.User = user
c.Locals(constdata.USER_LOCALE, &userLocale)
// Set user in context
c.Locals(constdata.USER_LOCALES_NAME, user.ToSession())
c.Locals(constdata.USER_LOCALES_ID, user.ID)
return c.Next()
}
@@ -139,6 +95,24 @@ func RequireAdmin() fiber.Handler {
}
}
// GetUserID extracts user ID from context
func GetUserID(c fiber.Ctx) uint {
userID, ok := c.Locals("userID").(uint)
if !ok {
return 0
}
return userID
}
// GetUser extracts user from context
func GetUser(c fiber.Ctx) *model.UserSession {
user, ok := c.Locals("user").(*model.UserSession)
if !ok {
return nil
}
return user
}
// GetConfig returns the app config
func GetConfig() *config.Config {
return config.Get()

View File

@@ -4,9 +4,7 @@ import (
"strconv"
"strings"
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/service/langsService"
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
"github.com/gofiber/fiber/v3"
)
@@ -24,8 +22,12 @@ func LanguageMiddleware() fiber.Handler {
if id, err := strconv.ParseUint(langIDStr, 10, 32); err == nil {
langID = uint(id)
if langID > 0 {
c.Locals(constdata.USER_LOCALE, returnNewLocale(langID))
return c.Next()
lang, err := langService.GetLanguageById(langID)
if err == nil {
c.Locals("langID", langID)
c.Locals("lang", lang)
return c.Next()
}
}
}
}
@@ -36,8 +38,12 @@ func LanguageMiddleware() fiber.Handler {
if id, err := strconv.ParseUint(cookieLang, 10, 32); err == nil {
langID = uint(id)
if langID > 0 {
c.Locals(constdata.USER_LOCALE, returnNewLocale(langID))
return c.Next()
lang, err := langService.GetLanguageById(langID)
if err == nil {
c.Locals("langID", langID)
c.Locals("lang", lang)
return c.Next()
}
}
}
}
@@ -51,7 +57,8 @@ func LanguageMiddleware() fiber.Handler {
lang, err := langService.GetLanguageByISOCode(isoCode)
if err == nil && lang != nil {
langID = uint(lang.ID)
c.Locals(constdata.USER_LOCALE, returnNewLocale(langID))
c.Locals("langID", langID)
c.Locals("lang", lang)
return c.Next()
}
}
@@ -61,7 +68,8 @@ func LanguageMiddleware() fiber.Handler {
defaultLang, err := langService.GetDefaultLanguage()
if err == nil && defaultLang != nil {
langID = uint(defaultLang.ID)
c.Locals(constdata.USER_LOCALE, returnNewLocale(langID))
c.Locals("langID", langID)
c.Locals("lang", defaultLang)
}
return c.Next()
@@ -96,9 +104,11 @@ func parseAcceptLanguage(header string) string {
return strings.ToLower(first)
}
func returnNewLocale(lang_id uint) *model.UserLocale {
newLocale := model.UserLocale{}
newLocale.OriginalUser = &model.Customer{}
newLocale.OriginalUser.LangID = lang_id
return &newLocale
// GetLanguageID extracts language ID from context
func GetLanguageID(c fiber.Ctx) uint {
langID, ok := c.Locals("langID").(uint)
if !ok {
return 0
}
return langID
}

View File

@@ -268,15 +268,15 @@ func (h *AuthHandler) RefreshToken(c fiber.Ctx) error {
// Me returns the current user info
func (h *AuthHandler) Me(c fiber.Ctx) error {
userLocale := c.Locals(constdata.USER_LOCALE).(*model.UserLocale)
if userLocale.OriginalUser == nil {
user := c.Locals("user")
if user == nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": responseErrors.GetErrorCode(c, responseErrors.ErrNotAuthenticated),
})
}
return c.JSON(fiber.Map{
"user": *userLocale.OriginalUser,
"user": user,
})
}
@@ -351,12 +351,21 @@ func (h *AuthHandler) CompleteRegistration(c fiber.Ctx) error {
// Updates JWT Tokens. Requires authentication and updates access token only
func (h *AuthHandler) UpdateJWTToken(c fiber.Ctx) error {
userLocale, ok := c.Locals(constdata.USER_LOCALE).(*model.UserLocale)
userLocals, ok := c.Locals(constdata.USER_LOCALES_NAME).(*model.UserSession)
if !ok {
return c.Status(fiber.StatusUnauthorized).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrNotAuthenticated)))
}
user := model.Customer{
ID: userLocals.UserID,
Email: userLocals.Email,
Role: userLocals.Role,
LangID: userLocals.LangID,
CountryID: userLocals.CountryID,
IsActive: userLocals.IsActive,
}
// Parse language and country_id from query params
langIDStr := c.Query("lang_id")
@@ -366,7 +375,7 @@ func (h *AuthHandler) UpdateJWTToken(c fiber.Ctx) error {
return c.Status(fiber.StatusBadRequest).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadLangID)))
}
userLocale.OriginalUser.LangID = uint(parsedID)
user.LangID = uint(parsedID)
}
countryIDStr := c.Query("country_id")
@@ -377,10 +386,10 @@ func (h *AuthHandler) UpdateJWTToken(c fiber.Ctx) error {
return c.Status(fiber.StatusBadRequest).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadCountryID)))
}
userLocale.OriginalUser.CountryID = uint(parsedID)
user.CountryID = uint(parsedID)
}
newAccessToken, err := h.authService.UpdateJWTToken(userLocale.OriginalUser)
newAccessToken, err := h.authService.UpdateJWTToken(&user)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{

View File

@@ -3,7 +3,6 @@ package public
import (
"git.ma-al.com/goc_daniel/b2b/app/service/menuService"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
@@ -31,7 +30,7 @@ func RoutingHandlerRoutes(r fiber.Router) fiber.Router {
}
func (h *RoutingHandler) GetRouting(c fiber.Ctx) error {
lang_id, ok := localeExtractor.GetLangID(c)
lang_id, ok := c.Locals("langID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))

View File

@@ -5,7 +5,6 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/service/cartsService"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
@@ -38,7 +37,7 @@ func CartsHandlerRoutes(r fiber.Router) fiber.Router {
}
func (h *CartsHandler) AddNewCart(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
@@ -54,7 +53,7 @@ func (h *CartsHandler) AddNewCart(c fiber.Ctx) error {
}
func (h *CartsHandler) ChangeCartName(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
@@ -79,7 +78,7 @@ func (h *CartsHandler) ChangeCartName(c fiber.Ctx) error {
}
func (h *CartsHandler) RetrieveCartsInfo(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
@@ -95,7 +94,7 @@ func (h *CartsHandler) RetrieveCartsInfo(c fiber.Ctx) error {
}
func (h *CartsHandler) RetrieveCart(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
@@ -118,7 +117,7 @@ func (h *CartsHandler) RetrieveCart(c fiber.Ctx) error {
}
func (h *CartsHandler) AddProduct(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))

View File

@@ -5,7 +5,6 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/service/listService"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/query_params"
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
@@ -44,19 +43,19 @@ func (h *ListHandler) ListProducts(c fiber.Ctx) error {
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
id_lang, ok := localeExtractor.GetLangID(c)
id_lang, ok := c.Locals("langID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
}
list, err := h.listService.ListProducts(id_lang, paging, filters)
listing, err := h.listService.ListProducts(id_lang, paging, filters)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(&list.Items, int(list.Count), i18n.T_(c, response.Message_OK)))
return c.JSON(response.Make(&listing.Items, int(listing.Count), i18n.T_(c, response.Message_OK)))
}
var columnMappingListProducts map[string]string = map[string]string{
@@ -75,19 +74,19 @@ func (h *ListHandler) ListUsers(c fiber.Ctx) error {
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
id_lang, ok := localeExtractor.GetLangID(c)
id_lang, ok := c.Locals("langID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
}
list, err := h.listService.ListUsers(id_lang, paging, filters)
listing, err := h.listService.ListUsers(id_lang, paging, filters)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(&list.Items, int(list.Count), i18n.T_(c, response.Message_OK)))
return c.JSON(response.Make(&listing.Items, int(listing.Count), i18n.T_(c, response.Message_OK)))
}
var columnMappingListUsers map[string]string = map[string]string{

View File

@@ -5,7 +5,6 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/service/menuService"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
@@ -34,7 +33,7 @@ func MenuHandlerRoutes(r fiber.Router) fiber.Router {
}
func (h *MenuHandler) GetCategoryTree(c fiber.Ctx) error {
lang_id, ok := localeExtractor.GetLangID(c)
lang_id, ok := c.Locals("langID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
@@ -57,7 +56,7 @@ func (h *MenuHandler) GetCategoryTree(c fiber.Ctx) error {
}
func (h *MenuHandler) GetBreadcrumb(c fiber.Ctx) error {
lang_id, ok := localeExtractor.GetLangID(c)
lang_id, ok := c.Locals("langID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
@@ -87,7 +86,7 @@ func (h *MenuHandler) GetBreadcrumb(c fiber.Ctx) error {
}
func (h *MenuHandler) GetTopMenu(c fiber.Ctx) error {
lang_id, ok := localeExtractor.GetLangID(c)
lang_id, ok := c.Locals("langID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))

View File

@@ -6,7 +6,6 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/config"
"git.ma-al.com/goc_daniel/b2b/app/service/productTranslationService"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
@@ -42,7 +41,7 @@ func ProductTranslationHandlerRoutes(r fiber.Router) fiber.Router {
// GetProductDescription returns the product description for a given product ID
func (h *ProductTranslationHandler) GetProductDescription(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
@@ -73,7 +72,7 @@ func (h *ProductTranslationHandler) GetProductDescription(c fiber.Ctx) error {
// SaveProductDescription saves the description for a given product ID, in given language
func (h *ProductTranslationHandler) SaveProductDescription(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
@@ -110,7 +109,7 @@ func (h *ProductTranslationHandler) SaveProductDescription(c fiber.Ctx) error {
// TranslateProductDescription returns translated product description
func (h *ProductTranslationHandler) TranslateProductDescription(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))

View File

@@ -7,7 +7,6 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/service/meiliService"
searchservice "git.ma-al.com/goc_daniel/b2b/app/service/searchService"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
@@ -37,7 +36,7 @@ func MeiliSearchHandlerRoutes(r fiber.Router) fiber.Router {
}
func (h *MeiliSearchHandler) CreateIndex(c fiber.Ctx) error {
id_lang, ok := localeExtractor.GetLangID(c)
id_lang, ok := c.Locals("langID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
@@ -50,11 +49,12 @@ func (h *MeiliSearchHandler) CreateIndex(c fiber.Ctx) error {
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
nothing := ""
return c.JSON(response.Make(&nothing, 0, i18n.T_(c, response.Message_OK)))
}
func (h *MeiliSearchHandler) Search(c fiber.Ctx) error {
id_lang, ok := localeExtractor.GetLangID(c)
id_lang, ok := c.Locals("langID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
@@ -88,7 +88,7 @@ func (h *MeiliSearchHandler) Search(c fiber.Ctx) error {
}
func (h *MeiliSearchHandler) GetSettings(c fiber.Ctx) error {
id_lang, ok := localeExtractor.GetLangID(c)
id_lang, ok := c.Locals("langID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))

View File

@@ -0,0 +1,193 @@
package restricted
import (
"strconv"
"git.ma-al.com/goc_daniel/b2b/app/config"
"git.ma-al.com/goc_daniel/b2b/app/service/storageService"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
"github.com/gofiber/fiber/v3"
)
type StorageHandler struct {
storageService *storageService.StorageService
config *config.Config
}
func NewStorageHandler() *StorageHandler {
return &StorageHandler{
storageService: storageService.New(),
config: config.Get(),
}
}
func StorageHandlerRoutes(r fiber.Router) fiber.Router {
handler := NewStorageHandler()
r.Get("/list-content/*", handler.ListContent)
r.Get("/download-file/*", handler.DownloadFile)
r.Get("/move/*", handler.Move)
r.Get("/copy/*", handler.Copy)
r.Post("/upload-file/*", handler.UploadFile)
r.Get("/create-folder/*", handler.CreateFolder)
r.Delete("/delete-file/*", handler.DeleteFile)
r.Delete("/delete-folder/*", handler.DeleteFolder)
return r
}
func (h *StorageHandler) Move(c fiber.Ctx) error {
src_abs_path, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Params("*"))
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
dest_abs_path, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Query("dest_path"))
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
err = h.storageService.Move(src_abs_path, dest_abs_path)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
}
func (h *StorageHandler) Copy(c fiber.Ctx) error {
src_abs_path, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Params("*"))
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
dest_abs_path, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Query("dest_path"))
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
err = h.storageService.Copy(src_abs_path, dest_abs_path)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
}
// accepted path looks like e.g. "/folder1/" or "folder1"
func (h *StorageHandler) ListContent(c fiber.Ctx) error {
// relative path defaults to root directory
abs_path, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Params("*"))
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
entries_in_list, err := h.storageService.ListContent(abs_path)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(entries_in_list, 0, i18n.T_(c, response.Message_OK)))
}
func (h *StorageHandler) DownloadFile(c fiber.Ctx) error {
abs_path, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Params("*"))
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
f, filename, filesize, err := h.storageService.DownloadFilePrep(abs_path)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
c.Attachment(filename)
c.Set("Content-Length", strconv.FormatInt(filesize, 10))
c.Set("Content-Type", "application/octet-stream")
return c.SendStream(f, int(filesize))
}
func (h *StorageHandler) UploadFile(c fiber.Ctx) error {
abs_path, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Params("*"))
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
f, err := c.FormFile("document")
if err != nil {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrMissingFileFieldDocument)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrMissingFileFieldDocument)))
}
err = h.storageService.UploadFile(c, abs_path, f)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
}
func (h *StorageHandler) CreateFolder(c fiber.Ctx) error {
abs_path, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Params("*"))
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
err = h.storageService.CreateFolder(abs_path, c.Query("name"))
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
}
func (h *StorageHandler) DeleteFile(c fiber.Ctx) error {
abs_path, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Params("*"))
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
err = h.storageService.DeleteFile(abs_path)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
}
func (h *StorageHandler) DeleteFolder(c fiber.Ctx) error {
abs_path, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Params("*"))
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
err = h.storageService.DeleteFolder(abs_path)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
}

View File

@@ -115,6 +115,10 @@ func (s *Server) Setup() error {
carts := s.restricted.Group("/carts")
restricted.CartsHandlerRoutes(carts)
// storage (restricted)
storage := s.restricted.Group("/storage")
restricted.StorageHandlerRoutes(storage)
s.api.All("*", func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusNotFound)
})

View File

@@ -82,15 +82,6 @@ type UserSession struct {
IsActive bool `json:"is_active"`
}
type UserLocale struct {
// User is the Target user if present, otherwise same as Original.
// User ought to be used in applications
User *Customer
// Original user is the one associated with auth token
OriginalUser *Customer
// Importantly, lang_id used in application is stored as OriginalUser.LangID
}
// ToSession converts User to UserSession
func (u *Customer) ToSession() *UserSession {
return &UserSession{
@@ -107,7 +98,6 @@ func (u *Customer) ToSession() *UserSession {
type LoginRequest struct {
Email string `json:"email" form:"email"`
Password string `json:"password" form:"password"`
LangID *uint `json:"lang_id" form:"lang_id"`
}
// RegisterRequest represents the initial registration form data

6
app/model/entry.go Normal file
View File

@@ -0,0 +1,6 @@
package model
type EntryInList struct {
Name string
IsFolder bool
}

View File

@@ -18,10 +18,9 @@ type ProductDescription struct {
AvailableLater string `gorm:"column:available_later;type:varchar(255)" json:"available_later" form:"available_later"`
DeliveryInStock string `gorm:"column:delivery_in_stock;type:varchar(255)" json:"delivery_in_stock" form:"delivery_in_stock"`
DeliveryOutStock string `gorm:"column:delivery_out_stock;type:varchar(255)" json:"delivery_out_stock" form:"delivery_out_stock"`
Usage string `gorm:"column:_usage_;type:text" json:"usage" form:"usage"`
Usage string `gorm:"column:usage;type:text" json:"usage" form:"usage"`
ImageLink string `gorm:"column:image_link" json:"image_link"`
ExistsInDatabase bool `gorm:"-" json:"exists_in_database"`
ExistsInDatabase bool `gorm:"-" json:"exists_in_database"`
}
type ProductRow struct {

View File

@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"git.ma-al.com/goc_daniel/b2b/app/config"
"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/model/dbmodel"
@@ -37,27 +36,6 @@ func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productid
IDShop: int32(constdata.SHOP_ID),
IDLang: int32(productid_lang),
}).
Select(`
`+dbmodel.PsProductLangCols.IDProduct.TabCol()+` AS id_product,
`+dbmodel.PsProductLangCols.IDShop.TabCol()+` AS id_shop,
`+dbmodel.PsProductLangCols.IDLang.TabCol()+` AS id_lang,
`+dbmodel.PsProductLangCols.Description.TabCol()+` AS description,
`+dbmodel.PsProductLangCols.DescriptionShort.TabCol()+` AS description_short,
`+dbmodel.PsProductLangCols.LinkRewrite.TabCol()+` AS link_rewrite,
`+dbmodel.PsProductLangCols.MetaDescription.TabCol()+` AS meta_description,
`+dbmodel.PsProductLangCols.MetaKeywords.TabCol()+` AS meta_keywords,
`+dbmodel.PsProductLangCols.MetaTitle.TabCol()+` AS meta_title,
`+dbmodel.PsProductLangCols.Name.TabCol()+` AS name,
`+dbmodel.PsProductLangCols.AvailableNow.TabCol()+` AS available_now,
`+dbmodel.PsProductLangCols.AvailableLater.TabCol()+` AS available_later,
`+dbmodel.PsProductLangCols.DeliveryInStock.TabCol()+` AS delivery_in_stock,
`+dbmodel.PsProductLangCols.DeliveryOutStock.TabCol()+` AS delivery_out_stock,
`+dbmodel.PsProductLangCols.Usage.TabCol()+` AS _usage_,
CONCAT(?, '/', `+dbmodel.PsImageShopCols.IDImage.TabCol()+`, '-large_default/', `+dbmodel.PsProductLangCols.LinkRewrite.TabCol()+`, '.webp') AS image_link
`, config.Get().Image.ImagePrefix).
Joins("JOIN " + dbmodel.TableNamePsImageShop +
" ON " + dbmodel.PsImageShopCols.IDProduct.TabCol() + "=" + dbmodel.PsProductLangCols.IDProduct.TabCol() +
" AND " + dbmodel.PsImageShopCols.Cover.TabCol() + " = 1").
First(&ProductDescription).Error
if errors.Is(err, gorm.ErrRecordNotFound) {

View File

@@ -32,12 +32,12 @@ func New() UISearchRepo {
}
func (r *SearchRepo) Search(index string, body []byte) (*SearchProxyResponse, error) {
url := fmt.Sprintf("%s/indexes/%s/search", r.cfg.MailiSearch.ServerURL, index)
url := fmt.Sprintf("%s/indexes/%s/search", r.cfg.MeiliSearch.ServerURL, index)
return r.doRequest(http.MethodPost, url, body)
}
func (r *SearchRepo) GetIndexSettings(index string) (*SearchProxyResponse, error) {
url := fmt.Sprintf("%s/indexes/%s/settings", r.cfg.MailiSearch.ServerURL, index)
url := fmt.Sprintf("%s/indexes/%s/settings", r.cfg.MeiliSearch.ServerURL, index)
return r.doRequest(http.MethodGet, url, nil)
}
@@ -55,8 +55,8 @@ func (r *SearchRepo) doRequest(method, url string, body []byte) (*SearchProxyRes
}
req.Header.Set("Content-Type", "application/json")
if r.cfg.MailiSearch.ApiKey != "" {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", r.cfg.MailiSearch.ApiKey))
if r.cfg.MeiliSearch.ApiKey != "" {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", r.cfg.MeiliSearch.ApiKey))
}
client := &http.Client{}

View File

@@ -0,0 +1,100 @@
package storageRepo
import (
"io"
"mime/multipart"
"os"
"git.ma-al.com/goc_daniel/b2b/app/model"
"github.com/gofiber/fiber/v3"
)
type UIStorageRepo interface {
EntryInfo(abs_path string) (os.FileInfo, error)
ListContent(abs_path string) (*[]model.EntryInList, error)
Move(src_abs_path string, dest_abs_path string) error
Copy(src_abs_path string, dest_abs_path string) error
OpenFile(abs_path string) (*os.File, error)
UploadFile(c fiber.Ctx, abs_path string, f *multipart.FileHeader) error
CreateFolder(abs_path string) error
DeleteFile(abs_path string) error
DeleteFolder(abs_path string) error
}
type StorageRepo struct{}
func New() UIStorageRepo {
return &StorageRepo{}
}
func (r *StorageRepo) EntryInfo(abs_path string) (os.FileInfo, error) {
return os.Stat(abs_path)
}
func (r *StorageRepo) ListContent(abs_path string) (*[]model.EntryInList, error) {
entries, err := os.ReadDir(abs_path)
if err != nil {
return nil, err
}
var entries_in_list []model.EntryInList
for _, entry := range entries {
var next_entry_in_list model.EntryInList
next_entry_in_list.Name = entry.Name()
next_entry_in_list.IsFolder = entry.IsDir()
entries_in_list = append(entries_in_list, next_entry_in_list)
}
return &entries_in_list, nil
}
func (r *StorageRepo) Move(src_abs_path string, dest_abs_path string) error {
return os.Rename(src_abs_path, dest_abs_path)
}
func (r *StorageRepo) Copy(src_abs_path string, dest_abs_path string) error {
in, err := os.Open(src_abs_path)
if err != nil {
return err
}
defer in.Close()
info, err := in.Stat()
if err != nil {
return err
}
out, err := os.OpenFile(dest_abs_path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode())
if err != nil {
return err
}
defer out.Close()
if _, err := io.Copy(out, in); err != nil {
return err
}
return out.Sync()
}
func (r *StorageRepo) OpenFile(abs_path string) (*os.File, error) {
return os.Open(abs_path)
}
func (r *StorageRepo) UploadFile(c fiber.Ctx, abs_path string, f *multipart.FileHeader) error {
return c.SaveFile(f, abs_path)
}
func (r *StorageRepo) CreateFolder(abs_path string) error {
return os.Mkdir(abs_path, 0755)
}
func (r *StorageRepo) DeleteFile(abs_path string) error {
return os.Remove(abs_path)
}
func (r *StorageRepo) DeleteFolder(abs_path string) error {
return os.RemoveAll(abs_path)
}

View File

@@ -83,15 +83,6 @@ func (s *AuthService) Login(req *model.LoginRequest) (*model.AuthResponse, strin
// Update last login time
now := time.Now()
user.LastLoginAt = &now
if req.LangID != nil {
_, err := s.GetLangISOCode(*req.LangID)
if err != nil {
return nil, "", responseErrors.ErrBadLangID
}
user.LangID = *req.LangID
}
s.db.Save(&user)
// Generate access token (JWT)

View File

@@ -27,8 +27,8 @@ type MeiliService struct {
func New() *MeiliService {
client := meilisearch.New(
config.Get().MailiSearch.ServerURL,
meilisearch.WithAPIKey(config.Get().MailiSearch.ApiKey),
config.Get().MeiliSearch.ServerURL,
meilisearch.WithAPIKey(config.Get().MeiliSearch.ApiKey),
)
return &MeiliService{

View File

@@ -0,0 +1,163 @@
package storageService
import (
"mime/multipart"
"os"
"path/filepath"
"strings"
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/repos/storageRepo"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
"github.com/gofiber/fiber/v3"
)
type StorageService struct {
storageRepo storageRepo.UIStorageRepo
}
func New() *StorageService {
return &StorageService{
storageRepo: storageRepo.New(),
}
}
func (s *StorageService) ListContent(abs_path string) (*[]model.EntryInList, error) {
info, err := s.storageRepo.EntryInfo(abs_path)
if err != nil || !info.IsDir() {
return nil, responseErrors.ErrFolderDoesNotExist
}
entries_in_list, err := s.storageRepo.ListContent(abs_path)
return entries_in_list, err
}
func (s *StorageService) DownloadFilePrep(abs_path string) (*os.File, string, int64, error) {
info, err := s.storageRepo.EntryInfo(abs_path)
if err != nil || info.IsDir() {
return nil, "", 0, responseErrors.ErrFileDoesNotExist
}
f, err := s.storageRepo.OpenFile(abs_path)
if err != nil {
return nil, "", 0, err
}
return f, filepath.Base(abs_path), info.Size(), nil
}
func (s *StorageService) Move(src_abs_path string, dest_abs_path string) error {
_, err := s.storageRepo.EntryInfo(src_abs_path)
if err != nil {
return responseErrors.ErrFileDoesNotExist
}
_, err = s.storageRepo.EntryInfo(dest_abs_path)
if err == nil {
return responseErrors.ErrNameTaken
} else if os.IsNotExist(err) {
return s.storageRepo.Move(src_abs_path, dest_abs_path)
} else {
return err
}
}
func (s *StorageService) Copy(src_abs_path string, dest_abs_path string) error {
_, err := s.storageRepo.EntryInfo(src_abs_path)
if err != nil {
return responseErrors.ErrFileDoesNotExist
}
_, err = s.storageRepo.EntryInfo(dest_abs_path)
if err == nil {
return responseErrors.ErrNameTaken
} else if os.IsNotExist(err) {
return s.storageRepo.Copy(src_abs_path, dest_abs_path)
} else {
return err
}
}
func (s *StorageService) UploadFile(c fiber.Ctx, abs_path string, f *multipart.FileHeader) error {
info, err := s.storageRepo.EntryInfo(abs_path)
if err != nil || !info.IsDir() {
return responseErrors.ErrFolderDoesNotExist
}
name := f.Filename
if name == "" || name == "." || name == ".." || filepath.Base(name) != name {
return responseErrors.ErrBadAttribute
}
abs_file_path, err := s.AbsPath(abs_path, name)
if err != nil {
return err
}
if abs_file_path == abs_path {
return responseErrors.ErrBadAttribute
}
info, err = s.storageRepo.EntryInfo(abs_file_path)
if err == nil {
return responseErrors.ErrNameTaken
} else if os.IsNotExist(err) {
return s.storageRepo.UploadFile(c, abs_file_path, f)
} else {
return err
}
}
func (s *StorageService) CreateFolder(abs_path string, name string) error {
info, err := s.storageRepo.EntryInfo(abs_path)
if err != nil || !info.IsDir() {
return responseErrors.ErrFolderDoesNotExist
}
if name == "" || name == "." || name == ".." || filepath.Base(name) != name {
return responseErrors.ErrBadAttribute
}
abs_folder_path, err := s.AbsPath(abs_path, name)
if err != nil {
return err
}
if abs_folder_path == abs_path {
return responseErrors.ErrBadAttribute
}
info, err = s.storageRepo.EntryInfo(abs_folder_path)
if err == nil {
return responseErrors.ErrNameTaken
} else if os.IsNotExist(err) {
return s.storageRepo.CreateFolder(abs_folder_path)
} else {
return err
}
}
func (s *StorageService) DeleteFile(abs_path string) error {
info, err := s.storageRepo.EntryInfo(abs_path)
if err != nil || info.IsDir() {
return responseErrors.ErrFileDoesNotExist
}
return s.storageRepo.DeleteFile(abs_path)
}
func (s *StorageService) DeleteFolder(abs_path string) error {
info, err := s.storageRepo.EntryInfo(abs_path)
if err != nil || !info.IsDir() {
return responseErrors.ErrFolderDoesNotExist
}
return s.storageRepo.DeleteFolder(abs_path)
}
// AbsPath extracts an absolute path and validates it
func (s *StorageService) AbsPath(root string, relativePath string) (string, error) {
clean_name := filepath.Clean(relativePath)
full_path := filepath.Join(root, clean_name)
if full_path != root && !strings.HasPrefix(full_path, root+string(os.PathSeparator)) {
return "", responseErrors.ErrAccessDenied
}
return full_path, nil
}

View File

@@ -11,4 +11,5 @@ const CATEGORY_TREE_ROOT_ID = 2
const MAX_AMOUNT_OF_CARTS_PER_USER = 10
const DEFAULT_NEW_CART_NAME = "new cart"
const USER_LOCALE = "user"
const USER_LOCALES_NAME = "user"
const USER_LOCALES_ID = "userID"

View File

@@ -1,23 +0,0 @@
package localeExtractor
import (
"git.ma-al.com/goc_daniel/b2b/app/model"
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
"github.com/gofiber/fiber/v3"
)
func GetLangID(c fiber.Ctx) (uint, bool) {
user_locale, ok := c.Locals(constdata.USER_LOCALE).(*model.UserLocale)
if !ok || user_locale.OriginalUser == nil {
return 0, false
}
return user_locale.OriginalUser.LangID, true
}
func GetUserID(c fiber.Ctx) (uint, bool) {
user_locale, ok := c.Locals(constdata.USER_LOCALE).(*model.UserLocale)
if !ok || user_locale.User == nil {
return 0, false
}
return user_locale.User.ID, true
}

View File

@@ -59,6 +59,13 @@ var (
ErrMaxAmtOfCartsReached = errors.New("maximal amount of carts reached")
ErrUserHasNoSuchCart = errors.New("user does not have cart with given id")
ErrProductOrItsVariationDoesNotExist = errors.New("product or its variation with given ids does not exist")
// Typed errors for storage
ErrAccessDenied = errors.New("access denied!")
ErrFolderDoesNotExist = errors.New("folder does not exist")
ErrFileDoesNotExist = errors.New("file does not exist")
ErrNameTaken = errors.New("name taken")
ErrMissingFileFieldDocument = errors.New("missing file field 'document'")
)
// Error represents an error with HTTP status code
@@ -162,6 +169,17 @@ func GetErrorCode(c fiber.Ctx, err error) string {
case errors.Is(err, ErrProductOrItsVariationDoesNotExist):
return i18n.T_(c, "error.product_or_its_variation_does_not_exist")
case errors.Is(err, ErrAccessDenied):
return i18n.T_(c, "error.access_denied")
case errors.Is(err, ErrFolderDoesNotExist):
return i18n.T_(c, "error.folder_does_not_exist")
case errors.Is(err, ErrFileDoesNotExist):
return i18n.T_(c, "error.file_does_not_exist")
case errors.Is(err, ErrNameTaken):
return i18n.T_(c, "error.name_taken")
case errors.Is(err, ErrMissingFileFieldDocument):
return i18n.T_(c, "error.missing_file_field_document")
default:
return i18n.T_(c, "error.err_internal_server_error")
}
@@ -203,7 +221,12 @@ func GetErrorStatus(err error) int {
errors.Is(err, ErrRootNeverReached),
errors.Is(err, ErrMaxAmtOfCartsReached),
errors.Is(err, ErrUserHasNoSuchCart),
errors.Is(err, ErrProductOrItsVariationDoesNotExist):
errors.Is(err, ErrProductOrItsVariationDoesNotExist),
errors.Is(err, ErrAccessDenied),
errors.Is(err, ErrFolderDoesNotExist),
errors.Is(err, ErrFileDoesNotExist),
errors.Is(err, ErrNameTaken),
errors.Is(err, ErrMissingFileFieldDocument):
return fiber.StatusBadRequest
case errors.Is(err, ErrEmailExists):
return fiber.StatusConflict

View File

@@ -0,0 +1,28 @@
info:
name: save-product-description
type: http
seq: 19
http:
method: POST
url: http://localhost:3000/api/v1/restricted/product-translation/save-product-description?productID=1&productLangID=3
params:
- name: productID
value: "1"
type: query
- name: productLangID
value: "3"
type: query
body:
type: json
data: |-
{
"description": "<p>Der Einsatz von Rehabilitationsrollen in verschiedenen Übungen und Behandlungen wirkt sich positiv auf die Reduzierung von Verletzungen und die Genesungschancen aus. Sie werden in der Rehabilitation, bei Korrekturgymnastik sowie in der traditionellen und Sportmassage eingesetzt, da sie ideal zum Anheben und Spreizen von Gliedmaßen geeignet sind. Zudem können sie zur Unterstützung von Knien, Füßen, Armen und Schultern verwendet werden. Auch für Kinder sind Rehabilitationsrollen empfehlenswert; ihre spielerische Anwendung fördert die Entwicklung der Grobmotorik.</p><p> Dank der großen Auswahl an Farben und Größen lässt sich ein Übungsset zusammenstellen, das in jeder Physiotherapiepraxis, jedem Massageraum, jeder Schule oder jedem Kindergarten benötigt wird.</p><p> Die Rehabilitationsrolle ist ein Medizinprodukt, das den grundlegenden Anforderungen an Medizinprodukte und den Bestimmungen des Medizinproduktegesetzes entspricht, im Register für Medizinprodukte des Amtes für die Registrierung von Arzneimitteln, Medizinprodukten und Biozidprodukten eingetragen ist, mit der Konformitätserklärung des Herstellers versehen ist und das CE-Zeichen trägt. </p><p></p><p><img src=\"https://www.naluconcept.com/img/cms/Logotypy/images.jpg\" alt=\"Medizinprodukt\" style=\"margin-left:auto;margin-right:auto;\" width=\"253\" height=\"86\" /></p><h4> <strong>Empfohlene Verwendung:</strong></h4><ul style=\"list-style-type:circle;\"><li> in der Rehabilitation</li><li> während Massagen (traditionell, Sport)</li><li> in der Korrekturgymnastik (insbesondere für Kinder)</li><li> zur Linderung von Verletzungen einzelner Körperteile</li><li> Zur Unterstützung von: Knien, Knöcheln, Kopf des Patienten</li><li> bei Übungen zur Entwicklung der motorischen Fähigkeiten von Kindern</li><li> in Schönheitssalons</li><li> in Kinderspielzimmern</li></ul><p></p><h4> <strong>Materialspezifikationen:</strong></h4><p> <strong>Abdeckung:</strong> PVC-beschichtetes Material, das für medizinische Geräte vorgesehen ist und daher sehr leicht zu reinigen und zu desinfizieren ist:</p><ul style=\"list-style-type:circle;\"><li> Material gemäß REACH-Verordnung, zertifiziert mit dem STANDARD 100 Zertifikat von OEKO-TEX®.</li><li> Enthält keine Phthalate</li><li> feuerfest</li><li> resistent gegenüber physiologischen Flüssigkeiten (Blut, Urin, Schweiß) und Alkohol</li><li> UV-beständig, daher auch für den Einsatz im Freien geeignet.</li><li> kratzfest</li><li> ölbeständig </li></ul><p><img src=\"https://www.naluconcept.com/img/cms/Logotypy/reach.jpg\" alt=\"ERREICHEN\" width=\"115\" height=\"115\" /><img src=\"https://www.naluconcept.com/img/cms/Logotypy/oeko-tex.jpg\" alt=\"Öko-Tex Standard 100 Zertifikat\" width=\"116\" height=\"114\" /><img src=\"https://www.naluconcept.com/img/cms/Logotypy/phthalate-free.jpg\" alt=\"Enthält keine Phthalate\" width=\"112\" height=\"111\" /><img src=\"https://www.naluconcept.com/img/cms/Logotypy/fireresistant.jpg\" alt=\"Feuerfest\" width=\"114\" height=\"113\" /><img src=\"https://www.naluconcept.com/img/cms/Logotypy/odporny-na-alkohol.jpg\" alt=\"Alkoholbeständig\" width=\"114\" height=\"114\" /><img src=\"https://www.naluconcept.com/img/cms/Logotypy/odporny-na-uv.jpg\" alt=\"UV-beständig\" width=\"117\" height=\"116\" /><img src=\"https://www.naluconcept.com/img/cms/Logotypy/outdoor.jpg\" alt=\"Für den Einsatz im Freien konzipiert\" width=\"116\" height=\"116\" /><img src=\"https://www.naluconcept.com/img/cms/Logotypy/odporny-na-zadrapania.jpg\" alt=\"Kratzfest\" width=\"97\" height=\"96\" /><img src=\"https://www.naluconcept.com/img/cms/Logotypy/olejoodporny.jpg\" alt=\"Ölbeständig\" width=\"99\" height=\"98\" /></p><p> <strong>Füllung:</strong> mittelharter Polyurethanschaum mit erhöhter Verformungsbeständigkeit:</p><ul style=\"list-style-type:circle;\"><li> besitzt ein Hygienezertifikat, ausgestellt vom Institut für Maritime und Tropenmedizin in Gdynia</li><li> zertifiziert mit dem STANDARD 100 by OEKO-TEX® Zertifikat Produktklasse I, ausgestellt vom Textilforschungsinstitut in Łódź</li><li> Hergestellt aus hochwertigen Rohstoffen, die die Ozonschicht nicht schädigen. </li></ul><p><img src=\"https://www.naluconcept.com/img/cms/Logotypy/oeko-tex.jpg\" alt=\"Öko-Tex Standard 100 Zertifikat\" width=\"95\" height=\"95\" /><img src=\"https://www.naluconcept.com/img/cms/Logotypy/Logo_GUMed_kolor-180x180.jpg\" alt=\"Hygienezertifikat\" width=\"94\" height=\"94\" /><img src=\"https://www.naluconcept.com/img/cms/Logotypy/atest_higieniczny_kolor.jpg\" alt=\"Hygienezertifikat\" width=\"79\" height=\"94\" /></p><p></p><p></p>"
}
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -0,0 +1,28 @@
info:
name: translate-product-description
type: http
seq: 21
http:
method: GET
url: http://localhost:3000/api/v1/restricted/product-translation/translate-product-description?productID=51&productFromLangID=1&productToLangID=3&model=Google
params:
- name: productID
value: "51"
type: query
- name: productFromLangID
value: "1"
type: query
- name: productToLangID
value: "3"
type: query
- name: model
value: Google
type: query
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -0,0 +1,7 @@
info:
name: auth
type: folder
seq: 1
request:
auth: inherit

View File

@@ -1,7 +1,7 @@
info:
name: update-choice
type: http
seq: 3
seq: 1
http:
method: POST

View File

@@ -1,7 +1,7 @@
info:
name: add-new-cart
type: http
seq: 11
seq: 1
http:
method: GET

View File

@@ -1,7 +1,7 @@
info:
name: add-product-to-cart (1)
type: http
seq: 16
seq: 1
http:
method: GET

View File

@@ -1,7 +1,7 @@
info:
name: add-product-to-cart
type: http
seq: 15
seq: 14
http:
method: GET

View File

@@ -1,7 +1,7 @@
info:
name: change-cart-name
type: http
seq: 12
seq: 1
http:
method: GET

View File

@@ -0,0 +1,7 @@
info:
name: carts
type: folder
seq: 7
request:
auth: inherit

View File

@@ -1,7 +1,7 @@
info:
name: retrieve-cart
type: http
seq: 14
seq: 1
http:
method: GET

View File

@@ -1,7 +1,7 @@
info:
name: retrieve-carts-info
type: http
seq: 13
seq: 1
http:
method: GET

View File

@@ -0,0 +1,7 @@
info:
name: langs-and-countries
type: folder
seq: 4
request:
auth: inherit

View File

@@ -1,7 +1,7 @@
info:
name: get_countries
type: http
seq: 4
seq: 1
http:
method: GET

View File

@@ -0,0 +1,7 @@
info:
name: list
type: folder
seq: 3
request:
auth: inherit

View File

@@ -5,7 +5,7 @@ info:
http:
method: GET
url: http://localhost:3000/api/v1/restricted/list/list-products?p=1&elems=10&target_user_id=2
url: http://localhost:3000/api/v1/restricted/list/list-products?p=1&elems=10
params:
- name: p
value: "1"
@@ -13,9 +13,6 @@ http:
- name: elems
value: "10"
type: query
- name: target_user_id
value: "2"
type: query
settings:
encodeUrl: true

View File

@@ -1,7 +1,7 @@
info:
name: list-users
type: http
seq: 2
seq: 1
http:
method: GET

View File

@@ -0,0 +1,7 @@
info:
name: menu
type: folder
seq: 5
request:
auth: inherit

View File

@@ -1,7 +1,7 @@
info:
name: get-breadcrumb
type: http
seq: 18
seq: 1
http:
method: GET

View File

@@ -1,7 +1,7 @@
info:
name: get-category-tree
type: http
seq: 5
seq: 1
http:
method: GET

View File

@@ -0,0 +1,7 @@
info:
name: product-translation
type: folder
seq: 2
request:
auth: inherit

View File

@@ -1,7 +1,7 @@
info:
name: get-product-description
type: http
seq: 17
seq: 1
http:
method: GET

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,28 @@
info:
name: translate-product-description
type: http
seq: 24
http:
method: GET
url: http://localhost:3000/api/v1/restricted/product-translation/translate-product-description?productID=51&productFromLangID=2&productToLangID=3&model=Google
params:
- name: productID
value: "51"
type: query
- name: productFromLangID
value: "2"
type: query
- name: productToLangID
value: "3"
type: query
- name: model
value: Google
type: query
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -1,11 +1,11 @@
info:
name: create-index
type: http
seq: 7
seq: 1
http:
method: GET
url: http://localhost:3000/api/v1/restricted/meili-search/create-index
url: http://localhost:3000/api/v1/restricted/search/create-index
auth: inherit
settings:

View File

@@ -0,0 +1,7 @@
info:
name: search
type: folder
seq: 6
request:
auth: inherit

View File

@@ -1,7 +1,7 @@
info:
name: get-indexes
type: http
seq: 9
seq: 1
http:
method: GET

View File

@@ -1,7 +1,7 @@
info:
name: remove-index
type: http
seq: 8
seq: 1
http:
method: DELETE

View File

@@ -1,11 +1,11 @@
info:
name: search
type: http
seq: 10
seq: 1
http:
method: GET
url: http://localhost:3000/api/v1/restricted/meili-search/search?query=w&limit=4&id_category=0&price_lower_bound=60.0&price_upper_bound=70.0
url: http://localhost:3000/api/v1/restricted/search/search?query=w&limit=4&id_category=0&price_lower_bound=60.0&price_upper_bound=70.0
params:
- name: query
value: w

View File

@@ -1,11 +1,11 @@
info:
name: test
type: http
seq: 6
seq: 1
http:
method: GET
url: http://localhost:3000/api/v1/restricted/meili-search/test
url: http://localhost:3000/api/v1/restricted/search/test
auth: inherit
settings:

View File

@@ -0,0 +1,19 @@
info:
name: copy
type: http
seq: 7
http:
method: GET
url: http://localhost:3000/api/v1/restricted/storage/copy/folder1/test.txt?dest_path=/folder/a.txt
params:
- name: dest_path
value: /folder/a.txt
type: query
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -0,0 +1,19 @@
info:
name: create-folder
type: http
seq: 1
http:
method: GET
url: http://localhost:3000/api/v1/restricted/storage/create-folder?name=folder
params:
- name: name
value: folder
type: query
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -0,0 +1,15 @@
info:
name: delete-file
type: http
seq: 1
http:
method: DELETE
url: http://localhost:3000/api/v1/restricted/storage/delete-file/folder1/TODO.txt
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -0,0 +1,15 @@
info:
name: delete-folder
type: http
seq: 1
http:
method: DELETE
url: http://localhost:3000/api/v1/restricted/storage/delete-folder/folder/
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -0,0 +1,15 @@
info:
name: download-file
type: http
seq: 1
http:
method: GET
url: http://localhost:3000/api/v1/restricted/storage/download-file/folder1/test.xlsx
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -0,0 +1,7 @@
info:
name: storage
type: folder
seq: 1
request:
auth: inherit

View File

@@ -0,0 +1,15 @@
info:
name: list-content
type: http
seq: 1
http:
method: GET
url: http://localhost:3000/api/v1/restricted/storage/list-content/folder1
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -0,0 +1,19 @@
info:
name: move
type: http
seq: 8
http:
method: GET
url: http://localhost:3000/api/v1/restricted/storage/move/folder?dest_path=/folder1/test.txt
params:
- name: dest_path
value: /folder1/test.txt
type: query
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -0,0 +1,22 @@
info:
name: upload-file
type: http
seq: 1
http:
method: POST
url: http://localhost:3000/api/v1/restricted/storage/upload-file/folder1/
body:
type: multipart-form
data:
- name: document
type: file
value:
- /home/daniel/TODO.txt
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

0
storage/.gitkeep Normal file
View File

0
storage/folder1/test Normal file
View File

1
storage/test.txt Normal file
View File

@@ -0,0 +1 @@
This is a test.